Compare commits

..

28 Commits

Author SHA1 Message Date
nolash
eefb3ec4ff Merge branch 'master' into bvander/move-scripts-to-e2e-folder 2021-05-19 11:58:40 +02:00
Louis Holbrook
a5ca898532 Merge branch 'lash/update-contracts-in-migration-2' into 'master'
imports: Fix stale dep in sovereign import users script

See merge request grassrootseconomics/cic-internal-integration!149
2021-05-19 09:57:03 +00:00
Louis Holbrook
6d8508aebf imports: Fix stale dep in sovereign import users script 2021-05-19 09:57:02 +00:00
Louis Holbrook
f8f66984d2 Merge branch 'lash/no-ussd-contamination' into 'master'
Isolate ussd-related data files in imports

See merge request grassrootseconomics/cic-internal-integration!150
2021-05-19 09:55:24 +00:00
Louis Holbrook
0f02dd1b7c Isolate ussd-related data files in imports 2021-05-19 09:55:24 +00:00
63a4a82ab0 Merge branch 'philip/replicate-wills-changes' into 'master'
Replicates changes in broken MR by will.

See merge request grassrootseconomics/cic-internal-integration!158
2021-05-19 08:25:19 +00:00
949c1070a9 Replicates changes in broken MR by will. 2021-05-19 11:19:29 +03:00
5d9fbe9b64 Merge branch 'willruddick-master-patch-28332' into 'master'
shortened and changed service code

See merge request grassrootseconomics/cic-internal-integration!152
2021-05-19 07:47:09 +00:00
873a3f082a shortened and changed service code 2021-05-19 07:47:09 +00:00
7b408cf564 Merge branch 'willruddick-master-patch-28450' into 'master'
small changes, note the 'other' gender

See merge request grassrootseconomics/cic-internal-integration!151
2021-05-19 07:33:37 +00:00
nolash
3ac3c3c0af Update dependencies in sccripts 2021-05-19 09:17:13 +02:00
Louis Holbrook
9dfbd7034c Merge branch 'lash/decimals-in-api' into 'master'
cic-eth-tasker: Add decimals and token name to default token api call return struct

Closes cic-eth#123

See merge request grassrootseconomics/cic-internal-integration!148
2021-05-19 06:59:43 +00:00
Louis Holbrook
235f5cede8 cic-eth-tasker: Add decimals and token name to default token api call return struct 2021-05-19 06:59:42 +00:00
Geoff Turk
0a59539f9a Merge branch 'lash/cache-data-api' into 'master'
cic-cache: Add data API

Closes cic-cache#11

See merge request grassrootseconomics/cic-internal-integration!157
2021-05-18 17:13:57 +00:00
Louis Holbrook
60b36945df cic-cache: Add data API 2021-05-18 17:13:57 +00:00
d4bf43204b Update apps/data-seeding/.gitlab-ci.yml 2021-05-18 14:53:36 +00:00
a753f09177 Merge branch 'master' into bvander/move-scripts-to-e2e-folder 2021-05-18 07:29:00 -07:00
dae6526677 Merge branch 'philip/remove-notify-config-defaults' into 'master'
Refactors at configs.

See merge request grassrootseconomics/cic-internal-integration!156
2021-05-17 16:47:30 +00:00
1e94a516c2 Refactors at configs. 2021-05-17 19:41:07 +03:00
552ac600ed build for data-seeding 2021-05-17 07:58:11 -07:00
f2c955c60b small changes, note the 'other' gender 2021-05-17 11:06:10 +00:00
db2e32e511 merge in data seeding changes 2021-05-16 17:03:42 -07:00
17b3b27d81 Merge branch 'philip/import-pins-script' into 'master'
Philip/import pins script

See merge request grassrootseconomics/cic-internal-integration!109
2021-05-15 07:40:34 +00:00
1cb172b8bf Philip/import pins script 2021-05-15 07:40:34 +00:00
Louis Holbrook
9d47e4c764 Merge branch 'lash/descriptive-documentation' into 'master'
cic-eth system documentation

See merge request grassrootseconomics/cic-internal-integration!122
2021-05-15 04:36:55 +00:00
Louis Holbrook
c68cc318ab cic-eth system documentation 2021-05-15 04:36:54 +00:00
46e089e63c fix extra index ref and remove scripts folder 2021-05-03 10:33:02 -07:00
b8ad1ba49f move files 2021-05-03 09:01:01 -07:00
65 changed files with 820 additions and 225 deletions

View File

@@ -6,6 +6,7 @@ include:
- local: 'apps/cic-notify/.gitlab-ci.yml' - local: 'apps/cic-notify/.gitlab-ci.yml'
- local: 'apps/cic-meta/.gitlab-ci.yml' - local: 'apps/cic-meta/.gitlab-ci.yml'
- local: 'apps/cic-cache/.gitlab-ci.yml' - local: 'apps/cic-cache/.gitlab-ci.yml'
- local: 'apps/data-seeding/.gitlab-ci.yml'
stages: stages:
- build - build

View File

@@ -1,22 +1,28 @@
# standard imports # standard imports
import logging import logging
import datetime
# third-party imports # external imports
import moolb import moolb
# local imports # local imports
from cic_cache.db import list_transactions_mined from cic_cache.db.list import (
from cic_cache.db import list_transactions_account_mined list_transactions_mined,
list_transactions_account_mined,
list_transactions_mined_with_data,
)
logg = logging.getLogger() logg = logging.getLogger()
class BloomCache: class Cache:
def __init__(self, session): def __init__(self, session):
self.session = session self.session = session
class BloomCache(Cache):
@staticmethod @staticmethod
def __get_filter_size(n): def __get_filter_size(n):
n = 8192 * 8 n = 8192 * 8
@@ -87,3 +93,43 @@ class BloomCache:
f_blocktx.add(block + tx) f_blocktx.add(block + tx)
logg.debug('added block {} tx {} lo {} hi {}'.format(r[0], r[1], lowest_block, highest_block)) logg.debug('added block {} tx {} lo {} hi {}'.format(r[0], r[1], lowest_block, highest_block))
return (lowest_block, highest_block, f_block.to_bytes(), f_blocktx.to_bytes(),) return (lowest_block, highest_block, f_block.to_bytes(), f_blocktx.to_bytes(),)
class DataCache(Cache):
def load_transactions_with_data(self, offset, end):
rows = list_transactions_mined_with_data(self.session, offset, end)
tx_cache = []
highest_block = -1;
lowest_block = -1;
date_is_str = None # stick this in startup
for r in rows:
if highest_block == -1:
highest_block = r['block_number']
lowest_block = r['block_number']
tx_type = 'unknown'
if r['value'] != None:
tx_type = '{}.{}'.format(r['domain'], r['value'])
if date_is_str == None:
date_is_str = type(r['date_block']).__name__ == 'str'
o = {
'block_number': r['block_number'],
'tx_hash': r['tx_hash'],
'date_block': r['date_block'],
'sender': r['sender'],
'recipient': r['recipient'],
'from_value': int(r['from_value']),
'to_value': int(r['to_value']),
'source_token': r['source_token'],
'destination_token': r['destination_token'],
'tx_type': tx_type,
}
if date_is_str:
o['date_block'] = datetime.datetime.fromisoformat(r['date_block'])
tx_cache.append(o)
return (lowest_block, highest_block, tx_cache)

View File

@@ -28,6 +28,26 @@ def list_transactions_mined(
return r return r
def list_transactions_mined_with_data(
session,
offset,
end,
):
"""Executes db query to return all confirmed transactions according to the specified offset and limit.
:param offset: Offset in data set to return transactions from
:type offset: int
:param limit: Max number of transactions to retrieve
:type limit: int
:result: Result set
:rtype: SQLAlchemy.ResultProxy
"""
s = "SELECT tx_hash, block_number, date_block, sender, recipient, from_value, to_value, source_token, destination_token, domain, value FROM tx LEFT JOIN tag_tx_link ON tx.id = tag_tx_link.tx_id LEFT JOIN tag ON tag_tx_link.tag_id = tag.id WHERE block_number >= {} AND block_number <= {} ORDER BY block_number ASC, tx_index ASC".format(offset, end)
r = session.execute(s)
return r
def list_transactions_account_mined( def list_transactions_account_mined(
session, session,
address, address,

View File

@@ -0,0 +1,110 @@
# standard imports
import logging
import json
import re
import base64
# local imports
from cic_cache.cache import (
BloomCache,
DataCache,
)
logg = logging.getLogger(__name__)
re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?'
re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)/?(\d+)?/?(\d+)/?'
re_transactions_all_data = r'/txa/(\d+)/(\d+)/?'
DEFAULT_LIMIT = 100
def process_transactions_account_bloom(session, env):
r = re.match(re_transactions_account_bloom, env.get('PATH_INFO'))
if not r:
return None
address = r[1]
if r[2] == None:
address = '0x' + address
offset = DEFAULT_LIMIT
if r.lastindex > 2:
offset = r[3]
limit = 0
if r.lastindex > 3:
limit = r[4]
c = BloomCache(session)
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit)
o = {
'alg': 'sha256',
'low': lowest_block,
'high': highest_block,
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
'filter_rounds': 3,
}
j = json.dumps(o)
return ('application/json', j.encode('utf-8'),)
def process_transactions_all_bloom(session, env):
r = re.match(re_transactions_all_bloom, env.get('PATH_INFO'))
if not r:
return None
offset = DEFAULT_LIMIT
if r.lastindex > 0:
offset = r[1]
limit = 0
if r.lastindex > 1:
limit = r[2]
c = BloomCache(session)
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions(offset, limit)
o = {
'alg': 'sha256',
'low': lowest_block,
'high': highest_block,
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
'filter_rounds': 3,
}
j = json.dumps(o)
return ('application/json', j.encode('utf-8'),)
def process_transactions_all_data(session, env):
r = re.match(re_transactions_all_data, env.get('PATH_INFO'))
if not r:
return None
if env.get('HTTP_X_CIC_CACHE_MODE') != 'all':
return None
offset = r[1]
end = r[2]
if r[2] < r[1]:
raise ValueError('cart before the horse, dude')
c = DataCache(session)
(lowest_block, highest_block, tx_cache) = c.load_transactions_with_data(offset, end)
for r in tx_cache:
r['date_block'] = r['date_block'].timestamp()
o = {
'low': lowest_block,
'high': highest_block,
'data': tx_cache,
}
j = json.dumps(o)
return ('application/json', j.encode('utf-8'),)

View File

@@ -1,18 +1,20 @@
# standard imports # standard imports
import os import os
import re
import logging import logging
import argparse import argparse
import json
import base64 import base64
# third-party imports # external imports
import confini import confini
# local imports # local imports
from cic_cache import BloomCache
from cic_cache.db import dsn_from_config from cic_cache.db import dsn_from_config
from cic_cache.db.models.base import SessionBase from cic_cache.db.models.base import SessionBase
from cic_cache.runnable.daemons.query import (
process_transactions_account_bloom,
process_transactions_all_bloom,
process_transactions_all_data,
)
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger() logg = logging.getLogger()
@@ -44,72 +46,6 @@ logg.debug('config:\n{}'.format(config))
dsn = dsn_from_config(config) dsn = dsn_from_config(config)
SessionBase.connect(dsn, config.true('DATABASE_DEBUG')) SessionBase.connect(dsn, config.true('DATABASE_DEBUG'))
re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?'
re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)/?(\d+)?/?(\d+)/?'
DEFAULT_LIMIT = 100
def process_transactions_account_bloom(session, env):
r = re.match(re_transactions_account_bloom, env.get('PATH_INFO'))
if not r:
return None
address = r[1]
if r[2] == None:
address = '0x' + address
offset = DEFAULT_LIMIT
if r.lastindex > 2:
offset = r[3]
limit = 0
if r.lastindex > 3:
limit = r[4]
c = BloomCache(session)
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit)
o = {
'alg': 'sha256',
'low': lowest_block,
'high': highest_block,
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
'filter_rounds': 3,
}
j = json.dumps(o)
return ('application/json', j.encode('utf-8'),)
def process_transactions_all_bloom(session, env):
r = re.match(re_transactions_all_bloom, env.get('PATH_INFO'))
if not r:
return None
offset = DEFAULT_LIMIT
if r.lastindex > 0:
offset = r[1]
limit = 0
if r.lastindex > 1:
limit = r[2]
c = BloomCache(session)
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions(offset, limit)
o = {
'alg': 'sha256',
'low': lowest_block,
'high': highest_block,
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
'filter_rounds': 3,
}
j = json.dumps(o)
return ('application/json', j.encode('utf-8'),)
# uwsgi application # uwsgi application
def application(env, start_response): def application(env, start_response):
@@ -119,10 +55,16 @@ def application(env, start_response):
session = SessionBase.create_session() session = SessionBase.create_session()
for handler in [ for handler in [
process_transactions_all_data,
process_transactions_all_bloom, process_transactions_all_bloom,
process_transactions_account_bloom, process_transactions_account_bloom,
]: ]:
r = handler(session, env) r = None
try:
r = handler(session, env)
except ValueError as e:
start_response('400 {}'.format(str(e)))
return []
if r != None: if r != None:
(mime_type, content) = r (mime_type, content) = r
break break

View File

@@ -88,3 +88,16 @@ def txs(
tx_hash_first, tx_hash_first,
tx_hash_second, tx_hash_second,
] ]
@pytest.fixture(scope='function')
def tag_txs(
init_database,
txs,
):
db.add_tag(init_database, 'taag', domain='test')
init_database.commit()
db.tag_transaction(init_database, txs[1], 'taag', domain='test')

View File

@@ -0,0 +1,31 @@
# standard imports
import json
# external imports
import pytest
# local imports
from cic_cache.runnable.daemons.query import process_transactions_all_data
def test_api_all_data(
init_database,
txs,
):
env = {
'PATH_INFO': '/txa/410000/420000',
'HTTP_X_CIC_CACHE_MODE': 'all',
}
j = process_transactions_all_data(init_database, env)
o = json.loads(j[1])
assert len(o['data']) == 2
env = {
'PATH_INFO': '/txa/420000/410000',
'HTTP_X_CIC_CACHE_MODE': 'all',
}
with pytest.raises(ValueError):
j = process_transactions_all_data(init_database, env)

View File

@@ -9,6 +9,7 @@ import pytest
# local imports # local imports
from cic_cache import BloomCache from cic_cache import BloomCache
from cic_cache.cache import DataCache
logg = logging.getLogger() logg = logging.getLogger()
@@ -33,3 +34,23 @@ def test_cache(
assert b[0] == list_defaults['block'] - 1 assert b[0] == list_defaults['block'] - 1
def test_cache_data(
init_database,
list_defaults,
list_actors,
list_tokens,
txs,
tag_txs,
):
session = init_database
c = DataCache(session)
b = c.load_transactions_with_data(410000, 420000)
assert len(b[2]) == 2
assert b[2][0]['tx_hash'] == txs[1]
assert b[2][1]['tx_type'] == 'unknown'
assert b[2][0]['tx_type'] == 'test.taag'

View File

@@ -16,4 +16,6 @@ def default_token(self):
return { return {
'symbol': self.default_token_symbol, 'symbol': self.default_token_symbol,
'address': self.default_token_address, 'address': self.default_token_address,
'name': self.default_token_name,
'decimals': self.default_token_decimals,
} }

View File

@@ -22,6 +22,7 @@ from chainlib.eth.connection import (
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainqueue.db.models.otx import Otx from chainqueue.db.models.otx import Otx
from cic_eth_registry.error import UnknownContractError from cic_eth_registry.error import UnknownContractError
from cic_eth_registry.erc20 import ERC20Token
import liveness.linux import liveness.linux
@@ -207,6 +208,11 @@ def main():
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL') BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol) BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
default_token = ERC20Token(chain_spec, rpc, BaseTask.default_token_address)
default_token.load(rpc)
BaseTask.default_token_decimals = default_token.decimals
BaseTask.default_token_name = default_token.name
BaseTask.run_dir = config.get('CIC_RUN_DIR') BaseTask.run_dir = config.get('CIC_RUN_DIR')
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address)) logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))

View File

@@ -67,6 +67,8 @@ def main():
token_info = t.get() token_info = t.get()
print('Default token symbol: {}'.format(token_info['symbol'])) print('Default token symbol: {}'.format(token_info['symbol']))
print('Default token address: {}'.format(token_info['address'])) print('Default token address: {}'.format(token_info['address']))
logg.debug('Default token name: {}'.format(token_info['name']))
logg.debug('Default token decimals: {}'.format(token_info['decimals']))
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -33,6 +33,8 @@ class BaseTask(celery.Task):
create_gas_oracle = RPCGasOracle create_gas_oracle = RPCGasOracle
default_token_address = None default_token_address = None
default_token_symbol = None default_token_symbol = None
default_token_name = None
default_token_decimals = None
run_dir = '/run' run_dir = '/run'
def create_session(self): def create_session(self):

View File

@@ -1,4 +1,4 @@
[AFRICASTALKING] [AFRICASTALKING]
api_username = foo api_username =
api_key = bar api_key =
api_sender_id = baz api_sender_id =

View File

@@ -120,7 +120,7 @@ class MetadataRequestsHandler(Metadata):
data = json.loads(response_data.decode('utf-8')) data = json.loads(response_data.decode('utf-8'))
if result.status_code == 200 and self.cic_type == ':cic.person': if result.status_code == 200 and self.cic_type == ':cic.person':
person = Person() person = Person()
deserialized_person = person.deserialize(person_data=json.loads(data)) deserialized_person = person.deserialize(person_data=data)
data = json.dumps(deserialized_person.serialize()) data = json.dumps(deserialized_person.serialize())
cache_data(self.metadata_pointer, data=data) cache_data(self.metadata_pointer, data=data)
logg.debug(f'caching: {data} with key: {self.metadata_pointer}') logg.debug(f'caching: {data} with key: {self.metadata_pointer}')

View File

@@ -325,6 +325,14 @@ def process_menu_interaction_requests(chain_str: str,
# get user # get user
user = Account.session.query(Account).filter_by(phone_number=phone_number).first() user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
# retrieve and cache user's metadata
blockchain_address = user.blockchain_address
s_query_person_metadata = celery.signature(
'cic_ussd.tasks.metadata.query_person_metadata',
[blockchain_address]
)
s_query_person_metadata.apply_async(queue='cic-ussd')
# find any existing ussd session # find any existing ussd session
existing_ussd_session = UssdSession.session.query(UssdSession).filter_by( existing_ussd_session = UssdSession.session.query(UssdSession).filter_by(
external_session_id=external_session_id).first() external_session_id=external_session_id).first()

View File

@@ -371,13 +371,6 @@ def process_start_menu(display_key: str, user: Account):
# get operational balance # get operational balance
operational_balance = compute_operational_balance(balances=balances_data) operational_balance = compute_operational_balance(balances=balances_data)
# retrieve and cache account's metadata
s_query_person_metadata = celery.signature(
'cic_ussd.tasks.metadata.query_person_metadata',
[blockchain_address]
)
s_query_person_metadata.apply_async(queue='cic-ussd')
# retrieve and cache account's statement # retrieve and cache account's statement
retrieve_account_statement(blockchain_address=blockchain_address) retrieve_account_statement(blockchain_address=blockchain_address)

View File

@@ -1,7 +1,7 @@
en: en:
account_successfully_created: |- account_successfully_created: |-
Hello, you have been registered on Sarafu Network! Your balance is %{balance} %{token_symbol}. To use dial *483*46#. For help 0757628885. You have been registered on Sarafu Network! To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}.
received_tokens: |- received_tokens: |-
Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp}. New balance is %{balance} %{token_symbol}. Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp}. New balance is %{balance} %{token_symbol}.
terms: |- terms: |-
By using the service, you agree to the terms and conditions at https://www.grassrootseconomics.org/terms-and-conditions. By using the service, you agree to the terms and conditions at http://grassecon.org/tos

View File

@@ -1,7 +1,7 @@
sw: sw:
account_successfully_created: |- account_successfully_created: |-
Habari, umesajiliwa kwa huduma ya sarafu! Salio lako ni %{token_symbol} %{balance}. Kutumia bonyeza *483*46#. Kwa Usaidizi 0757628885. Umesajiliwa kwa huduma ya Sarafu! Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}.
received_tokens: |- received_tokens: |-
Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp}. Salio la %{token_symbol} ni %{balance}. Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp}. Salio la %{token_symbol} ni %{balance}.
terms: |- terms: |-
Kwa kutumia hii huduma, umekubali sheria na masharti yafuatayo https://www.grassrootseconomics.org/terms-and-conditions. Kwa kutumia hii huduma, umekubali sheria na masharti yafuatayo http://grassecon.org/tos

View File

@@ -1,29 +1,30 @@
en: en:
kenya: kenya:
initial_language_selection: |- initial_language_selection: |-
CON Welcome to Sarafu CON Welcome to Sarafu Network
1. English 1. English
2. Kiswahili 2. Kiswahili
3. Help 3. Help
initial_pin_entry: |- initial_pin_entry: |-
CON Please enter a PIN to manage your account. CON Please enter a new four number PIN for your account.
0. Back 0. Back
initial_pin_confirmation: |- initial_pin_confirmation: |-
CON Enter your PIN again CON Enter your four number PIN again
0. Back 0. Back
enter_given_name: |- enter_given_name: |-
CON Enter first name CON Enter first name
0. Back 0. Back
enter_family_name: |- enter_family_name: |-
CON Enter last name CON Enter family name
0. Back 0. Back
enter_gender: |- enter_gender: |-
CON Enter gender CON Enter gender
1. Male 1. Male
2. Female 2. Female
3. Other
0. Back 0. Back
enter_location: |- enter_location: |-
CON Enter location CON Enter your location
0. Back 0. Back
enter_products: |- enter_products: |-
CON Please enter a product or service you offer CON Please enter a product or service you offer
@@ -83,34 +84,34 @@ en:
Please enter your PIN to confirm. Please enter your PIN to confirm.
0. Back 0. Back
retry: |- retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining. CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back 0. Back
display_metadata_pin_authorization: display_metadata_pin_authorization:
first: |- first: |-
CON Please enter your PIN. CON Please enter your PIN
0. Back 0. Back
retry: |- retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining. CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back 0. Back
account_balances_pin_authorization: account_balances_pin_authorization:
first: |- first: |-
CON Please enter your PIN to view balances. CON Please enter your PIN to view balances
0. Back 0. Back
retry: |- retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining. CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back 0. Back
account_statement_pin_authorization: account_statement_pin_authorization:
first: |- first: |-
CON Please enter your PIN to view statement. CON Please enter your PIN to view statement
0. Back 0. Back
retry: |- retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining. CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back 0. Back
account_balances: |- account_balances: |-
CON Your balances are as follows: CON Your balances are as follows:
balance: %{operational_balance} %{token_symbol} balance: %{operational_balance} %{token_symbol}
taxes: %{tax} %{token_symbol} fees: %{tax} %{token_symbol}
bonsuses: %{bonus} %{token_symbol} rewards: %{bonus} %{token_symbol}
0. Back 0. Back
first_transaction_set: |- first_transaction_set: |-
CON %{first_transaction_set} CON %{first_transaction_set}
@@ -140,9 +141,9 @@ en:
exit_pin_blocked: |- exit_pin_blocked: |-
END Your PIN has been blocked. For help, please call %{support_phone}. END Your PIN has been blocked. For help, please call %{support_phone}.
exit_invalid_pin: |- exit_invalid_pin: |-
END The PIN you have entered is Invalid. PIN must consist of 4 digits. For help, call %{support_phone}. END The PIN you have entered is invalid. PIN must consist of 4 digits. For help, call %{support_phone}.
exit_invalid_new_pin: |- exit_invalid_new_pin: |-
END The PIN you have entered is Invalid. PIN must be different from your current PIN. For help, call %{support_phone}. END The PIN you have entered is invalid. PIN must be different from your current PIN. For help, call %{support_phone}.
exit_pin_mismatch: |- exit_pin_mismatch: |-
END The new PIN does not match the one you entered. Please try again. For help, call %{support_phone}. END The new PIN does not match the one you entered. Please try again. For help, call %{support_phone}.
exit_invalid_recipient: |- exit_invalid_recipient: |-
@@ -169,4 +170,4 @@ en:
00. Back 00. Back
99. Exit 99. Exit
account_creation_prompt: |- account_creation_prompt: |-
Your account is being created. You will receive an SMS when your account is ready. Your account is being created. You will receive an SMS when your account is ready.

View File

@@ -87,7 +87,6 @@ COPY contract-migration/testdata/pgp testdata/pgp
COPY contract-migration/sarafu_declaration.json sarafu_declaration.json COPY contract-migration/sarafu_declaration.json sarafu_declaration.json
COPY contract-migration/keystore keystore COPY contract-migration/keystore keystore
COPY contract-migration/envlist . COPY contract-migration/envlist .
COPY contract-migration/scripts scripts/
# A shared output dir for environment configs # A shared output dir for environment configs
RUN mkdir -p /tmp/cic/config RUN mkdir -p /tmp/cic/config

View File

@@ -0,0 +1,21 @@
.data_seeding_variables:
variables:
APP_NAME: data-seeding
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
.data_seeding_changes_target:
rules:
- changes:
- $CONTEXT/$APP_NAME/*
build-mr-contract-migration:
extends:
- .data_seeding_changes_target
- .py_build_merge_request
- .data_seeding_variables
build-push-contract-migration:
extends:
- .py_build_push
- .data_seeding_variables

View File

@@ -89,7 +89,12 @@ After this step is run, you can find top-level ethereum addresses (like the cic
#### Custodial provisions #### Custodial provisions
response_data = send_ussd_request(address, self.data_dir)
state = response_data[:3]
out = response_data[4:]
m = '{} {}'.format(state, out[:7])
if m != 'CON Welcome':
raise VerifierError(response_data, 'ussd')
This step is _only_ needed if you are importing using `cic_eth` or `cic_ussd` This step is _only_ needed if you are importing using `cic_eth` or `cic_ussd`
`RUN_MASK=2 docker-compose up contract-migration` `RUN_MASK=2 docker-compose up contract-migration`
@@ -104,8 +109,8 @@ If importing using `cic_eth` or `cic_ussd` also run:
* cic-eth-retrier * cic-eth-retrier
If importing using `cic_ussd` also run: If importing using `cic_ussd` also run:
* cic-ussd-tasker * cic-user-tasker
* cic-ussd-server * cic-user-ussd-server
* cic-notify-tasker * cic-notify-tasker
If metadata is to be imported, also run: If metadata is to be imported, also run:
@@ -169,6 +174,26 @@ In second terminal:
`python cic_ussd/import_users.py -v -c config out` `python cic_ussd/import_users.py -v -c config out`
##### Importing pins and ussd data (optional)
Once the user imports are complete the next step should be importing the user's pins and auxiliary ussd data. This can be done in 3 steps:
In one terminal run:
`python create_import_pins.py -c config -v --userdir <path to the users export dir tree> pinsdir <path to pin export dir tree>`
This script will recursively walk through all the directories defining user data in the users export directory and generate a csv file containing phone numbers and password hashes generated using fernet in a manner reflecting the nature of said hashes in the old system.
This csv file will be stored in the pins export dir defined as the positional argument.
Once the creation of the pins file is complete, proceed to import the pins and ussd data as follows:
- To import the pins:
`python cic_ussd/import_pins.py -c config -v pinsdir <path to pin export dir tree>`
- To import ussd data:
`python cic_ussd/import_ussd_data.py -c config -v userdir <path to the users export dir tree>`
The balance script is a celery task worker, and will not exit by itself in its current version. However, after it's done doing its job, you will find "reached nonce ... exiting" among the last lines of the log. The balance script is a celery task worker, and will not exit by itself in its current version. However, after it's done doing its job, you will find "reached nonce ... exiting" among the last lines of the log.
The connection parameters for the `cic-ussd-server` is currently _hardcoded_ in the `import_users.py` script file. The connection parameters for the `cic-ussd-server` is currently _hardcoded_ in the `import_users.py` script file.

View File

@@ -171,6 +171,7 @@ if __name__ == '__main__':
logg.error('load error for {}: {}'.format(y, e)) logg.error('load error for {}: {}'.format(y, e))
continue continue
f.close() f.close()
logg.debug('deserializing {} {}'.format(filepath, o))
u = Person.deserialize(o) u = Person.deserialize(o)
new_address = register_eth(i, u) new_address = register_eth(i, u)

View File

@@ -0,0 +1,70 @@
# standard import
import argparse
import csv
import logging
import os
# third-party imports
import celery
import confini
# local imports
from import_task import *
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_config_dir = './config'
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
arg_parser.add_argument('--env-prefix',
default=os.environ.get('CONFINI_ENV_PREFIX'),
dest='env_prefix',
type=str,
help='environment prefix for variables to overwrite configuration')
arg_parser.add_argument('-q', type=str, default='cic-import-ussd', help='celery queue to submit transaction tasks to')
arg_parser.add_argument('-v', help='be verbose', action='store_true')
arg_parser.add_argument('-vv', help='be more verbose', action='store_true')
arg_parser.add_argument('pins_dir', default='out', type=str, help='user export directory')
args = arg_parser.parse_args()
# set log levels
if args.v:
logg.setLevel(logging.INFO)
elif args.vv:
logg.setLevel(logging.DEBUG)
# process configs
config_dir = args.c
config = confini.Config(config_dir, os.environ.get('CONFINI_ENV_PREFIX'))
config.process()
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
db_configs = {
'database': config.get('DATABASE_NAME'),
'host': config.get('DATABASE_HOST'),
'port': config.get('DATABASE_PORT'),
'user': config.get('DATABASE_USER'),
'password': config.get('DATABASE_PASSWORD')
}
def main():
with open(f'{args.pins_dir}/pins.csv') as pins_file:
phone_to_pins = [tuple(row) for row in csv.reader(pins_file)]
s_import_pins = celery.signature(
'import_task.set_pins',
(db_configs, phone_to_pins),
queue=args.q
)
s_import_pins.apply_async()
argv = ['worker', '-Q', 'cic-import-ussd', '--loglevel=DEBUG']
celery_app.worker_main(argv)
if __name__ == '__main__':
main()

View File

@@ -8,6 +8,8 @@ import json
# external imports # external imports
import celery import celery
import psycopg2
from psycopg2 import extras
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
add_0x, add_0x,
@@ -53,7 +55,7 @@ class MetadataTask(ImportTask):
def meta_url(self): def meta_url(self):
scheme = 'http' scheme = 'http'
if self.meta_ssl: if self.meta_ssl:
scheme += s scheme += 's'
url = urllib.parse.urlparse('{}://{}:{}/{}'.format(scheme, self.meta_host, self.meta_port, self.meta_path)) url = urllib.parse.urlparse('{}://{}:{}/{}'.format(scheme, self.meta_host, self.meta_port, self.meta_path))
return urllib.parse.urlunparse(url) return urllib.parse.urlunparse(url)
@@ -91,7 +93,6 @@ def resolve_phone(self, phone):
def generate_metadata(self, address, phone): def generate_metadata(self, address, phone):
old_address = old_address_from_phone(self.import_dir, phone) old_address = old_address_from_phone(self.import_dir, phone)
logg.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> foo')
logg.debug('address {}'.format(address)) logg.debug('address {}'.format(address))
old_address_upper = strip_0x(old_address).upper() old_address_upper = strip_0x(old_address).upper()
metadata_path = '{}/old/{}/{}/{}.json'.format( metadata_path = '{}/old/{}/{}/{}.json'.format(
@@ -216,3 +217,60 @@ def send_txs(self, nonce):
return nonce return nonce
@celery_app.task
def set_pins(config: dict, phone_to_pins: list):
# define db connection
db_conn = psycopg2.connect(
database=config.get('database'),
host=config.get('host'),
port=config.get('port'),
user=config.get('user'),
password=config.get('password')
)
db_cursor = db_conn.cursor()
# update db
for element in phone_to_pins:
sql = 'UPDATE account SET password_hash = %s WHERE phone_number = %s'
db_cursor.execute(sql, (element[1], element[0]))
logg.debug(f'Updating: {element[0]} with: {element[1]}')
# commit changes
db_conn.commit()
# close connections
db_cursor.close()
db_conn.close()
@celery_app.task
def set_ussd_data(config: dict, ussd_data: dict):
# define db connection
db_conn = psycopg2.connect(
database=config.get('database'),
host=config.get('host'),
port=config.get('port'),
user=config.get('user'),
password=config.get('password')
)
db_cursor = db_conn.cursor()
# process ussd_data
account_status = 1
if ussd_data['is_activated'] == 1:
account_status = 2
preferred_language = ussd_data['preferred_language']
phone_number = ussd_data['phone']
sql = 'UPDATE account SET account_status = %s, preferred_language = %s WHERE phone_number = %s'
db_cursor.execute(sql, (account_status, preferred_language, phone_number))
# commit changes
db_conn.commit()
# close connections
db_cursor.close()
db_conn.close()

View File

@@ -87,6 +87,13 @@ chain_str = str(chain_spec)
batch_size = args.batch_size batch_size = args.batch_size
batch_delay = args.batch_delay batch_delay = args.batch_delay
db_configs = {
'database': config.get('DATABASE_NAME'),
'host': config.get('DATABASE_HOST'),
'port': config.get('DATABASE_PORT'),
'user': config.get('DATABASE_USER'),
'password': config.get('DATABASE_PASSWORD')
}
def build_ussd_request(phone, host, port, service_code, username, password, ssl=False): def build_ussd_request(phone, host, port, service_code, username, password, ssl=False):
@@ -135,57 +142,60 @@ if __name__ == '__main__':
for y in x[2]: for y in x[2]:
if y[len(y)-5:] != '.json': if y[len(y)-5:] != '.json':
continue continue
filepath = os.path.join(x[0], y) # handle json containing person object
f = open(filepath, 'r') filepath = None
try: if y[:15] != '_ussd_data.json':
o = json.load(f) filepath = os.path.join(x[0], y)
except json.decoder.JSONDecodeError as e: f = open(filepath, 'r')
try:
o = json.load(f)
except json.decoder.JSONDecodeError as e:
f.close()
logg.error('load error for {}: {}'.format(y, e))
continue
f.close() f.close()
logg.error('load error for {}: {}'.format(y, e)) u = Person.deserialize(o)
continue
f.close()
u = Person.deserialize(o)
new_address = register_ussd(i, u) new_address = register_ussd(i, u)
phone_object = phonenumbers.parse(u.tel) phone_object = phonenumbers.parse(u.tel)
phone = phonenumbers.format_number(phone_object, phonenumbers.PhoneNumberFormat.E164) phone = phonenumbers.format_number(phone_object, phonenumbers.PhoneNumberFormat.E164)
s_phone = celery.signature( s_phone = celery.signature(
'import_task.resolve_phone', 'import_task.resolve_phone',
[ [
phone, phone,
], ],
queue='cic-import-ussd', queue='cic-import-ussd',
) )
s_meta = celery.signature( s_meta = celery.signature(
'import_task.generate_metadata', 'import_task.generate_metadata',
[ [
phone, phone,
], ],
queue='cic-import-ussd', queue='cic-import-ussd',
) )
s_balance = celery.signature( s_balance = celery.signature(
'import_task.opening_balance_tx', 'import_task.opening_balance_tx',
[ [
phone, phone,
i, i,
], ],
queue='cic-import-ussd', queue='cic-import-ussd',
) )
s_meta.link(s_balance) s_meta.link(s_balance)
s_phone.link(s_meta) s_phone.link(s_meta)
s_phone.apply_async(countdown=7) # block time plus a bit of time for ussd processing # block time plus a bit of time for ussd processing
s_phone.apply_async(countdown=7)
i += 1 i += 1
sys.stdout.write('imported {} {}'.format(i, u).ljust(200) + "\r") sys.stdout.write('imported {} {}'.format(i, u).ljust(200) + "\r")
j += 1 j += 1
if j == batch_size: if j == batch_size:
time.sleep(batch_delay) time.sleep(batch_delay)
j = 0 j = 0
#fi.close()

View File

@@ -0,0 +1,70 @@
# standard imports
import argparse
import json
import logging
import os
# external imports
import celery
from confini import Config
# local imports
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_config_dir = '/usr/local/etc/cic'
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-c', type=str, default=default_config_dir, help='config file')
arg_parser.add_argument('-q', type=str, default='cic-eth', help='Task queue')
arg_parser.add_argument('-v', action='store_true', help='Be verbose')
arg_parser.add_argument('-vv', action='store_true', help='Be more verbose')
arg_parser.add_argument('user_dir', type=str, help='path to users export dir tree')
args = arg_parser.parse_args()
if args.v:
logg.setLevel(logging.INFO)
elif args.vv:
logg.setLevel(logging.DEBUG)
config_dir = args.c
config = Config(config_dir, os.environ.get('CONFINI_ENV_PREFIX'))
config.process()
user_old_dir = os.path.join(args.user_dir, 'old')
os.stat(user_old_dir)
db_configs = {
'database': config.get('DATABASE_NAME'),
'host': config.get('DATABASE_HOST'),
'port': config.get('DATABASE_PORT'),
'user': config.get('DATABASE_USER'),
'password': config.get('DATABASE_PASSWORD')
}
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
if __name__ == '__main__':
for x in os.walk(user_old_dir):
for y in x[2]:
if y[len(y) - 5:] != '.json':
continue
# handle ussd_data json object
if y[:15] == '_ussd_data.json':
filepath = os.path.join(x[0], y)
f = open(filepath, 'r')
try:
ussd_data = json.load(f)
except json.decoder.JSONDecodeError as e:
f.close()
logg.error('load error for {}: {}'.format(y, e))
continue
f.close()
s_set_ussd_data = celery.signature(
'import_task.set_ussd_data',
[db_configs, ussd_data]
)
s_set_ussd_data.apply_async(queue='cic-import-ussd')

View File

@@ -0,0 +1,90 @@
# standard imports
import argparse
import json
import logging
import os
# third-party imports
import bcrypt
import celery
import confini
import phonenumbers
import random
from cic_types.models.person import Person
from cryptography.fernet import Fernet
# local imports
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.realpath(os.path.dirname(__file__))
default_config_dir = os.environ.get('CONFINI_DIR', os.path.join(script_dir, 'config'))
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-c', type=str, default=default_config_dir, help='Config dir')
arg_parser.add_argument('-v', action='store_true', help='Be verbose')
arg_parser.add_argument('-vv', action='store_true', help='Be more verbose')
arg_parser.add_argument('--userdir', type=str, help='path to users export dir tree')
arg_parser.add_argument('pins_dir', type=str, help='path to pin export dir tree')
args = arg_parser.parse_args()
if args.v:
logg.setLevel(logging.INFO)
elif args.vv:
logg.setLevel(logging.DEBUG)
config = confini.Config(args.c, os.environ.get('CONFINI_ENV_PREFIX'))
config.process()
logg.info('loaded config\n{}'.format(config))
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
user_dir = args.userdir
pins_dir = args.pins_dir
def generate_password_hash():
key = Fernet.generate_key()
fnt = Fernet(key)
pin = str(random.randint(1000, 9999))
return fnt.encrypt(bcrypt.hashpw(pin.encode('utf-8'), bcrypt.gensalt())).decode()
user_old_dir = os.path.join(user_dir, 'old')
logg.debug(f'reading user data from: {user_old_dir}')
pins_file = open(f'{pins_dir}/pins.csv', 'w')
if __name__ == '__main__':
for x in os.walk(user_old_dir):
for y in x[2]:
# skip non-json files
if y[len(y) - 5:] != '.json':
continue
# define file path for
filepath = None
if y[:15] != '_ussd_data.json':
filepath = os.path.join(x[0], y)
f = open(filepath, 'r')
try:
o = json.load(f)
except json.decoder.JSONDecodeError as e:
f.close()
logg.error('load error for {}: {}'.format(y, e))
continue
f.close()
u = Person.deserialize(o)
phone_object = phonenumbers.parse(u.tel)
phone = phonenumbers.format_number(phone_object, phonenumbers.PhoneNumberFormat.E164)
password_hash = generate_password_hash()
pins_file.write(f'{phone},{password_hash}\n')
logg.info(f'Writing phone: {phone}, password_hash: {password_hash}')
pins_file.close()

View File

@@ -105,7 +105,7 @@ def genId(addr, typ):
def genDate(): def genDate():
ts = random.randint(ts_then, ts_now) ts = random.randint(ts_then, ts_now)
return datetime.datetime.fromtimestamp(ts).timestamp() return int(datetime.datetime.fromtimestamp(ts).timestamp())
def genPhone(): def genPhone():
@@ -130,6 +130,7 @@ def genCats():
def genAmount(): def genAmount():
return random.randint(0, gift_max) * gift_factor return random.randint(0, gift_max) * gift_factor
def genDob(): def genDob():
dob_src = fake.date_of_birth(minimum_age=15) dob_src = fake.date_of_birth(minimum_age=15)
dob = {} dob = {}
@@ -168,8 +169,9 @@ def gen():
} }
p.location['area_name'] = city p.location['area_name'] = city
if random.randint(0, 1): if random.randint(0, 1):
p.identities['latitude'] = (random.random() + 180) - 90 #fake.local_latitude() p.location['latitude'] = (random.random() + 180) - 90 #fake.local_latitude()
p.identities['longitude'] = (random.random() + 360) - 180 #fake.local_latitude() p.location['longitude'] = (random.random() + 360) - 180 #fake.local_latitude()
return (old_blockchain_checksum_address, phone, p) return (old_blockchain_checksum_address, phone, p)
@@ -191,6 +193,7 @@ def prepareLocalFilePath(datadir, address):
if __name__ == '__main__': if __name__ == '__main__':
base_dir = os.path.join(user_dir, 'old') base_dir = os.path.join(user_dir, 'old')
ussd_dir = os.path.join(user_dir, 'ussd')
os.makedirs(base_dir, exist_ok=True) os.makedirs(base_dir, exist_ok=True)
fa = open(os.path.join(user_dir, 'balances.csv'), 'w') fa = open(os.path.join(user_dir, 'balances.csv'), 'w')
@@ -210,11 +213,23 @@ if __name__ == '__main__':
print(o) print(o)
ussd_data = {
'phone': phone,
'is_activated': 1,
'preferred_language': random.sample(['en', 'sw'], 1)[0],
'is_disabled': False
}
d = prepareLocalFilePath(base_dir, uid) d = prepareLocalFilePath(base_dir, uid)
f = open('{}/{}'.format(d, uid + '.json'), 'w') f = open('{}/{}'.format(d, uid + '.json'), 'w')
json.dump(o.serialize(), f) json.dump(o.serialize(), f)
f.close() f.close()
d = prepareLocalFilePath(ussd_dir, uid)
x = open('{}/{}'.format(d, uid + '_ussd_data.json'), 'w')
json.dump(ussd_data, x)
x.close()
pidx = genPhoneIndex(phone) pidx = genPhoneIndex(phone)
d = prepareLocalFilePath(os.path.join(user_dir, 'phone'), pidx) d = prepareLocalFilePath(os.path.join(user_dir, 'phone'), pidx)
f = open('{}/{}'.format(d, pidx), 'w') f = open('{}/{}'.format(d, pidx), 'w')

View File

@@ -0,0 +1,43 @@
# syntax = docker/dockerfile:1.2
FROM python:3.8.6-slim-buster as compile-image
RUN apt-get update
RUN apt-get install -y --no-install-recommends git gcc g++ libpq-dev gawk jq telnet wget openssl iputils-ping gnupg socat bash procps make python2 cargo
WORKDIR /root
RUN mkdir -vp /usr/local/etc/cic
COPY data-seeding/requirements.txt .
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
RUN pip install --extra-index-url $EXTRA_INDEX_URL -r requirements.txt
# -------------- begin runtime container ----------------
FROM python:3.8.6-slim-buster as runtime-image
RUN apt-get update
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
RUN apt-get install -y jq bash iputils-ping socat telnet dnsutils
COPY --from=compile-image /usr/local/bin/ /usr/local/bin/
COPY --from=compile-image /usr/local/etc/cic/ /usr/local/etc/cic/
COPY --from=compile-image /usr/local/lib/python3.8/site-packages/ \
/usr/local/lib/python3.8/site-packages/
WORKDIR root/
ENV EXTRA_INDEX_URL https://pip.grassrootseconomics.net:8433
# RUN useradd -u 1001 --create-home grassroots
# RUN adduser grassroots sudo && \
# echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# WORKDIR /home/grassroots
COPY data-seeding/ .
# we copied these from the root build container.
# this is dumb though...I guess the compile image should have the same user
# RUN chown grassroots:grassroots -R /usr/local/lib/python3.8/site-packages/
# USER grassroots
ENTRYPOINT [ ]

View File

@@ -23,7 +23,7 @@ from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.gas import RPCGasOracle from chainlib.eth.gas import RPCGasOracle
from chainlib.eth.nonce import RPCNonceOracle from chainlib.eth.nonce import RPCNonceOracle
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
from eth_accounts_index import AccountRegistry from eth_accounts_index.registry import AccountRegistry
from eth_contract_registry import Registry from eth_contract_registry import Registry
from crypto_dev_signer.keystore.dict import DictKeystore from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner as EIP155Signer from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner as EIP155Signer

View File

@@ -1,5 +1,5 @@
cic-base[full_graph]==0.1.2b9 cic-base[full_graph]==0.1.2b11
sarafu-faucet==0.0.3a3 sarafu-faucet==0.0.3a3
cic-eth==0.11.0b13 cic-eth==0.11.0b14
cic-types==0.1.0a11 cic-types==0.1.0a11
crypto-dev-signer==0.4.14b3 crypto-dev-signer==0.4.14b3

View File

@@ -1,52 +1,37 @@
# standard imports # standard imports
import argparse
import copy
import hashlib
import json
import logging
import os import os
import sys import sys
import logging
import time
import argparse
import sys
import re
import hashlib
import csv
import json
import urllib import urllib
import copy
import uuid
import urllib.request import urllib.request
import uuid
# external imports # external imports
import celery import celery
import eth_abi
import confini import confini
from hexathon import ( import eth_abi
strip_0x,
add_0x,
)
from chainsyncer.backend.memory import MemBackend
from chainsyncer.driver import HeadSyncer
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainlib.eth.address import to_checksum_address
from chainlib.eth.connection import EthHTTPConnection from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.block import (
block_latest,
block_by_number,
Block,
)
from chainlib.hash import keccak256_string_to_hex
from chainlib.eth.address import to_checksum_address
from chainlib.eth.gas import ( from chainlib.eth.gas import (
OverrideGasOracle, OverrideGasOracle,
balance, balance,
) )
from chainlib.eth.tx import TxFactory from chainlib.eth.tx import TxFactory
from chainlib.hash import keccak256_string_to_hex
from chainlib.jsonrpc import jsonrpc_template from chainlib.jsonrpc import jsonrpc_template
from chainlib.eth.error import EthException
from cic_types.models.person import ( from cic_types.models.person import (
Person, Person,
generate_metadata_pointer, generate_metadata_pointer,
) )
from erc20_faucet import Faucet from erc20_faucet import Faucet
from eth_erc20 import ERC20 from eth_erc20 import ERC20
from hexathon.parse import strip_0x, add_0x
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger() logg = logging.getLogger()
@@ -72,6 +57,7 @@ eth_tests = [
phone_tests = [ phone_tests = [
'ussd', 'ussd',
'ussd_pins'
] ]
all_tests = eth_tests + custodial_tests + metadata_tests + phone_tests all_tests = eth_tests + custodial_tests + metadata_tests + phone_tests
@@ -171,6 +157,39 @@ if logg.isEnabledFor(logging.DEBUG):
outfunc = logg.debug outfunc = logg.debug
def send_ussd_request(address, data_dir):
upper_address = strip_0x(address).upper()
f = open(os.path.join(
data_dir,
'new',
upper_address[:2],
upper_address[2:4],
upper_address + '.json',
), 'r'
)
o = json.load(f)
f.close()
p = Person.deserialize(o)
phone = p.tel
session = uuid.uuid4().hex
data = {
'sessionId': session,
'serviceCode': config.get('APP_SERVICE_CODE'),
'phoneNumber': phone,
'text': '',
}
req = urllib.request.Request(config.get('_USSD_PROVIDER'))
data_str = json.dumps(data)
data_bytes = data_str.encode('utf-8')
req.add_header('Content-Type', 'application/json')
req.data = data_bytes
response = urllib.request.urlopen(req)
return response.read().decode('utf-8')
class VerifierState: class VerifierState:
def __init__(self, item_keys, active_tests=None): def __init__(self, item_keys, active_tests=None):
@@ -354,42 +373,18 @@ class Verifier:
def verify_ussd(self, address, balance=None): def verify_ussd(self, address, balance=None):
upper_address = strip_0x(address).upper() response_data = send_ussd_request(address, self.data_dir)
f = open(os.path.join(
self.data_dir,
'new',
upper_address[:2],
upper_address[2:4],
upper_address + '.json',
), 'r'
)
o = json.load(f)
f.close()
p = Person.deserialize(o)
phone = p.tel
session = uuid.uuid4().hex
data = {
'sessionId': session,
'serviceCode': config.get('APP_SERVICE_CODE'),
'phoneNumber': phone,
'text': config.get('APP_SERVICE_CODE'),
}
req = urllib.request.Request(config.get('_USSD_PROVIDER'))
data_str = json.dumps(data)
data_bytes = data_str.encode('utf-8')
req.add_header('Content-Type', 'application/json')
req.data = data_bytes
response = urllib.request.urlopen(req)
response_data = response.read().decode('utf-8')
state = response_data[:3] state = response_data[:3]
out = response_data[4:] out = response_data[4:]
m = '{} {}'.format(state, out[:7]) m = '{} {}'.format(state, out[:7])
if m != 'CON Welcome': if m != 'CON Welcome':
raise VerifierError(response_data, 'ussd') raise VerifierError(response_data, 'ussd')
def verify_ussd_pins(self, address, balance):
response_data = send_ussd_request(address, self.data_dir)
if response_data[:11] != 'CON Balance':
raise VerifierError(response_data, 'pins')
def verify(self, address, balance, debug_stem=None): def verify(self, address, balance, debug_stem=None):