Merge remote-tracking branch 'origin/master' into lash/health-util
This commit is contained in:
commit
d7bc76ac28
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
service-configs/*
|
||||
!service-configs/.gitkeep
|
||||
node_modules
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.o
|
@ -17,7 +17,7 @@ RUN apt-get update && \
|
||||
|
||||
# Copy shared requirements from top of mono-repo
|
||||
RUN echo "copying root req file ${root_requirement_file}"
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
|
||||
|
||||
COPY cic-cache/requirements.txt ./
|
||||
COPY cic-cache/setup.cfg \
|
||||
|
@ -1,6 +1,10 @@
|
||||
# extended imports
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.status import Status as TxStatus
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
|
||||
# local imports
|
||||
from cic_eth.ext.address import translate_address
|
||||
|
||||
|
||||
class ExtendedTx:
|
||||
@ -27,12 +31,12 @@ class ExtendedTx:
|
||||
self.status_code = TxStatus.PENDING.value
|
||||
|
||||
|
||||
def set_actors(self, sender, recipient, trusted_declarator_addresses=None):
|
||||
def set_actors(self, sender, recipient, trusted_declarator_addresses=None, caller_address=ZERO_ADDRESS):
|
||||
self.sender = sender
|
||||
self.recipient = recipient
|
||||
if trusted_declarator_addresses != None:
|
||||
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec)
|
||||
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec)
|
||||
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
|
||||
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
|
||||
|
||||
|
||||
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
||||
@ -40,8 +44,8 @@ class ExtendedTx:
|
||||
destination = source
|
||||
if destination_value == None:
|
||||
destination_value = source_value
|
||||
st = ERC20Token(self.rpc, source)
|
||||
dt = ERC20Token(self.rpc, destination)
|
||||
st = ERC20Token(self.chain_spec, self.rpc, source)
|
||||
dt = ERC20Token(self.chain_spec, self.rpc, destination)
|
||||
self.source_token = source
|
||||
self.source_token_symbol = st.symbol
|
||||
self.source_token_name = st.name
|
||||
@ -62,10 +66,10 @@ class ExtendedTx:
|
||||
self.status_code = n
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
def asdict(self):
|
||||
o = {}
|
||||
for attr in dir(self):
|
||||
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'to_dict']:
|
||||
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'asdict', 'rpc']:
|
||||
continue
|
||||
o[attr] = getattr(self, attr)
|
||||
return o
|
||||
|
@ -1,7 +1,7 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
import celery
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from chainlib.status import Status as TxStatus
|
||||
@ -9,7 +9,13 @@ from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.error import RequestMismatchException
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from hexathon import strip_0x
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
)
|
||||
# TODO: use sarafu_Faucet for both when inheritance has been implemented
|
||||
from erc20_single_shot_faucet import SingleShotFaucet
|
||||
from sarafu_faucet import MinterFaucet as Faucet
|
||||
|
||||
# local imports
|
||||
from .base import SyncFilter
|
||||
@ -18,65 +24,73 @@ from cic_eth.eth.meta import ExtendedTx
|
||||
logg = logging.getLogger().getChild(__name__)
|
||||
|
||||
|
||||
def parse_transfer(tx):
|
||||
r = ERC20.parse_transfer_request(tx.payload)
|
||||
transfer_data = {}
|
||||
transfer_data['to'] = r[0]
|
||||
transfer_data['value'] = r[1]
|
||||
transfer_data['from'] = tx['from']
|
||||
transfer_data['token_address'] = tx['to']
|
||||
return ('transfer', transfer_data)
|
||||
|
||||
|
||||
def parse_transferfrom(tx):
|
||||
r = ERC20.parse_transfer_request(tx.payload)
|
||||
transfer_data = unpack_transferfrom(tx.payload)
|
||||
transfer_data['from'] = r[0]
|
||||
transfer_data['to'] = r[1]
|
||||
transfer_data['value'] = r[2]
|
||||
transfer_data['token_address'] = tx['to']
|
||||
return ('transferfrom', transfer_data)
|
||||
|
||||
|
||||
def parse_giftto(tx):
|
||||
# TODO: broken
|
||||
logg.error('broken')
|
||||
return
|
||||
transfer_data = unpack_gift(tx.payload)
|
||||
transfer_data['from'] = tx.inputs[0]
|
||||
transfer_data['value'] = 0
|
||||
transfer_data['token_address'] = ZERO_ADDRESS
|
||||
# TODO: would be better to query the gift amount from the block state
|
||||
for l in tx.logs:
|
||||
topics = l['topics']
|
||||
logg.debug('topixx {}'.format(topics))
|
||||
if strip_0x(topics[0]) == '45c201a59ac545000ead84f30b2db67da23353aa1d58ac522c48505412143ffa':
|
||||
#transfer_data['value'] = web3.Web3.toInt(hexstr=strip_0x(l['data']))
|
||||
transfer_data['value'] = int.from_bytes(bytes.fromhex(strip_0x(l_data)))
|
||||
#token_address_bytes = topics[2][32-20:]
|
||||
token_address = strip_0x(topics[2])[64-40:]
|
||||
transfer_data['token_address'] = to_checksum_address(token_address)
|
||||
return ('tokengift', transfer_data)
|
||||
|
||||
|
||||
class CallbackFilter(SyncFilter):
|
||||
|
||||
trusted_addresses = []
|
||||
|
||||
def __init__(self, chain_spec, method, queue):
|
||||
def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS):
|
||||
self.queue = queue
|
||||
self.method = method
|
||||
self.chain_spec = chain_spec
|
||||
self.caller_address = caller_address
|
||||
|
||||
|
||||
def parse_transfer(self, tx, conn):
|
||||
if not tx.payload:
|
||||
return (None, None)
|
||||
r = ERC20.parse_transfer_request(tx.payload)
|
||||
transfer_data = {}
|
||||
transfer_data['to'] = r[0]
|
||||
transfer_data['value'] = r[1]
|
||||
transfer_data['from'] = tx.outputs[0]
|
||||
transfer_data['token_address'] = tx.inputs[0]
|
||||
return ('transfer', transfer_data)
|
||||
|
||||
|
||||
def parse_transferfrom(self, tx, conn):
|
||||
if not tx.payload:
|
||||
return (None, None)
|
||||
r = ERC20.parse_transfer_from_request(tx.payload)
|
||||
transfer_data = {}
|
||||
transfer_data['from'] = r[0]
|
||||
transfer_data['to'] = r[1]
|
||||
transfer_data['value'] = r[2]
|
||||
transfer_data['token_address'] = tx.inputs[0]
|
||||
return ('transferfrom', transfer_data)
|
||||
|
||||
|
||||
def parse_giftto(self, tx, conn):
|
||||
if not tx.payload:
|
||||
return (None, None)
|
||||
r = Faucet.parse_give_to_request(tx.payload)
|
||||
transfer_data = {}
|
||||
transfer_data['to'] = r[0]
|
||||
transfer_data['value'] = tx.value
|
||||
transfer_data['from'] = tx.outputs[0]
|
||||
#transfer_data['token_address'] = tx.inputs[0]
|
||||
faucet_contract = tx.inputs[0]
|
||||
|
||||
c = SingleShotFaucet(self.chain_spec)
|
||||
o = c.token(faucet_contract, sender_address=self.caller_address)
|
||||
r = conn.do(o)
|
||||
transfer_data['token_address'] = add_0x(c.parse_token(r))
|
||||
|
||||
o = c.amount(faucet_contract, sender_address=self.caller_address)
|
||||
r = conn.do(o)
|
||||
transfer_data['value'] = c.parse_amount(r)
|
||||
|
||||
return ('tokengift', transfer_data)
|
||||
|
||||
|
||||
def call_back(self, transfer_type, result):
|
||||
logg.debug('result {}'.format(result))
|
||||
result['chain_spec'] = result['chain_spec'].asdict()
|
||||
s = celery.signature(
|
||||
self.method,
|
||||
[
|
||||
result,
|
||||
transfer_type,
|
||||
int(result['status_code'] == 0),
|
||||
int(result['status_code'] != 0),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
@ -92,26 +106,29 @@ class CallbackFilter(SyncFilter):
|
||||
# s_translate.link(s)
|
||||
# s_translate.apply_async()
|
||||
t = s.apply_async()
|
||||
return s
|
||||
return t
|
||||
|
||||
|
||||
def parse_data(self, tx):
|
||||
def parse_data(self, tx, conn):
|
||||
transfer_type = None
|
||||
transfer_data = None
|
||||
# TODO: what's with the mix of attributes and dict keys
|
||||
logg.debug('have payload {}'.format(tx.payload))
|
||||
method_signature = tx.payload[:8]
|
||||
|
||||
logg.debug('tx status {}'.format(tx.status))
|
||||
|
||||
for parser in [
|
||||
parse_transfer,
|
||||
parse_transferfrom,
|
||||
parse_giftto,
|
||||
self.parse_transfer,
|
||||
self.parse_transferfrom,
|
||||
self.parse_giftto,
|
||||
]:
|
||||
try:
|
||||
(transfer_type, transfer_data) = parser(tx)
|
||||
break
|
||||
if tx:
|
||||
(transfer_type, transfer_data) = parser(tx, conn)
|
||||
if transfer_type == None:
|
||||
continue
|
||||
else:
|
||||
pass
|
||||
except RequestMismatchException:
|
||||
continue
|
||||
|
||||
@ -128,7 +145,7 @@ class CallbackFilter(SyncFilter):
|
||||
transfer_data = None
|
||||
transfer_type = None
|
||||
try:
|
||||
(transfer_type, transfer_data) = self.parse_data(tx)
|
||||
(transfer_type, transfer_data) = self.parse_data(tx, conn)
|
||||
except TypeError:
|
||||
logg.debug('invalid method data length for tx {}'.format(tx.hash))
|
||||
return
|
||||
@ -144,16 +161,17 @@ class CallbackFilter(SyncFilter):
|
||||
result = None
|
||||
try:
|
||||
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
|
||||
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses)
|
||||
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses, caller_address=self.caller_address)
|
||||
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
|
||||
if transfer_data['status'] == 0:
|
||||
tokentx.set_status(1)
|
||||
else:
|
||||
tokentx.set_status(0)
|
||||
t = self.call_back(transfer_type, tokentx.to_dict())
|
||||
logg.info('callback success task id {} tx {}'.format(t, tx.hash))
|
||||
result = tokentx.asdict()
|
||||
t = self.call_back(transfer_type, result)
|
||||
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
|
||||
except UnknownContractError:
|
||||
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tc.queue, tc.method, transfer_data['to'], tx.hash))
|
||||
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tx.queue, tx.method, transfer_data['to'], tx.hash))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
@ -39,6 +39,7 @@ from cic_eth.queue import (
|
||||
from cic_eth.callbacks import (
|
||||
Callback,
|
||||
http,
|
||||
noop,
|
||||
#tcp,
|
||||
redis,
|
||||
)
|
||||
|
@ -15,7 +15,6 @@ import cic_base.config
|
||||
import cic_base.log
|
||||
import cic_base.argparse
|
||||
import cic_base.rpc
|
||||
from cic_eth_registry import CICRegistry
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
@ -43,6 +42,12 @@ from cic_eth.runnable.daemons.filters import (
|
||||
TransferAuthFilter,
|
||||
)
|
||||
from cic_eth.stat import init_chain_stat
|
||||
from cic_eth.registry import (
|
||||
connect as connect_registry,
|
||||
connect_declarator,
|
||||
connect_token_registry,
|
||||
)
|
||||
|
||||
|
||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||
|
||||
@ -109,6 +114,8 @@ def main():
|
||||
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
||||
syncers.append(HeadSyncer(syncer_backend))
|
||||
|
||||
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
||||
|
||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||
if trusted_addresses_src == None:
|
||||
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
||||
@ -116,6 +123,8 @@ def main():
|
||||
trusted_addresses = trusted_addresses_src.split(',')
|
||||
for address in trusted_addresses:
|
||||
logg.info('using trusted address {}'.format(address))
|
||||
connect_declarator(rpc, chain_spec, trusted_addresses)
|
||||
connect_token_registry(rpc, chain_spec)
|
||||
CallbackFilter.trusted_addresses = trusted_addresses
|
||||
|
||||
callback_filters = []
|
||||
|
@ -10,7 +10,7 @@ version = (
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
'beta.3',
|
||||
'beta.6',
|
||||
)
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
|
@ -29,8 +29,7 @@ RUN /usr/local/bin/python -m pip install --upgrade pip
|
||||
# python merge_requirements.py | tee merged_requirements.txt
|
||||
#RUN cd cic-base && \
|
||||
# pip install $pip_extra_index_url_flag -r ./merged_requirements.txt
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
|
||||
RUN pip install $pip_extra_index_url_flag chainsyncer~=0.0.2a2
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a77
|
||||
|
||||
COPY cic-eth/scripts/ scripts/
|
||||
COPY cic-eth/setup.cfg cic-eth/setup.py ./
|
||||
|
@ -1,25 +1,25 @@
|
||||
cic-base~=0.1.2a67
|
||||
cic-base~=0.1.2a76
|
||||
celery==4.4.7
|
||||
crypto-dev-signer~=0.4.14a17
|
||||
crypto-dev-signer~=0.4.14b2
|
||||
confini~=0.3.6rc3
|
||||
cic-eth-registry~=0.5.4a13
|
||||
cic-eth-registry~=0.5.4a16
|
||||
#cic-bancor~=0.0.6
|
||||
redis==3.5.3
|
||||
alembic==1.4.2
|
||||
websockets==8.1
|
||||
requests~=2.24.0
|
||||
eth_accounts_index~=0.0.11a7
|
||||
erc20-transfer-authorization~=0.3.1a3
|
||||
#simple-rlp==0.1.2
|
||||
eth_accounts_index~=0.0.11a9
|
||||
erc20-transfer-authorization~=0.3.1a5
|
||||
uWSGI==2.0.19.1
|
||||
semver==2.13.0
|
||||
websocket-client==0.57.0
|
||||
moolb~=0.1.1b2
|
||||
eth-address-index~=0.1.1a7
|
||||
chainlib~=0.0.2a5
|
||||
eth-address-index~=0.1.1a9
|
||||
chainlib~=0.0.2a13
|
||||
hexathon~=0.0.1a7
|
||||
chainsyncer[sql]~=0.0.2a2
|
||||
chainqueue~=0.0.1a7
|
||||
pysha3==1.0.2
|
||||
coincurve==15.0.0
|
||||
sarafu-faucet~=0.0.2a19
|
||||
sarafu-faucet==0.0.2a28
|
||||
potaahto~=0.0.1a1
|
||||
|
@ -4,4 +4,4 @@ pytest-mock==3.3.1
|
||||
pytest-cov==2.10.1
|
||||
eth-tester==0.5.0b3
|
||||
py-evm==0.3.0a20
|
||||
giftable-erc20-token==0.0.8a4
|
||||
giftable-erc20-token==0.0.8a9
|
||||
|
223
apps/cic-eth/tests/filters/test_callback_filter.py
Normal file
223
apps/cic-eth/tests/filters/test_callback_filter.py
Normal file
@ -0,0 +1,223 @@
|
||||
# standard import
|
||||
import logging
|
||||
import datetime
|
||||
import os
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
transaction,
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.block import Block
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from sarafu_faucet import MinterFaucet
|
||||
from eth_accounts_index import AccountRegistry
|
||||
from potaahto.symbols import snake_and_camel
|
||||
from hexathon import add_0x
|
||||
|
||||
# local imports
|
||||
from cic_eth.runnable.daemons.filters.callback import CallbackFilter
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transfer_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
foo_token,
|
||||
agent_roles,
|
||||
token_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
|
||||
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
|
||||
|
||||
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
|
||||
r = rpc.do(o)
|
||||
|
||||
o = transaction(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
logg.debug(r)
|
||||
tx_src = snake_and_camel(r)
|
||||
tx = Tx(tx_src)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
rcpt = snake_and_camel(r)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
(transfer_type, transfer_data) = fltr.parse_transfer(tx, eth_rpc)
|
||||
|
||||
assert transfer_type == 'transfer'
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transfer_from_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
foo_token,
|
||||
agent_roles,
|
||||
token_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
|
||||
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
|
||||
|
||||
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
|
||||
(tx_hash_hex, o) = txf.approve(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
|
||||
r = rpc.do(o)
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
|
||||
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = txf.transfer_from(foo_token, agent_roles['ALICE'], token_roles['FOO_TOKEN_OWNER'], agent_roles['BOB'], 1024)
|
||||
r = rpc.do(o)
|
||||
|
||||
o = transaction(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
tx_src = snake_and_camel(r)
|
||||
tx = Tx(tx_src)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
rcpt = snake_and_camel(r)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
(transfer_type, transfer_data) = fltr.parse_transferfrom(tx, eth_rpc)
|
||||
|
||||
assert transfer_type == 'transferfrom'
|
||||
|
||||
|
||||
def test_faucet_gift_to_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
foo_token,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
faucet,
|
||||
account_registry,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
gas_oracle = OverrideGasOracle(conn=rpc, limit=800000)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(contract_roles['ACCOUNT_REGISTRY_WRITER'], rpc)
|
||||
txf = AccountRegistry(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = txf.add(account_registry, contract_roles['ACCOUNT_REGISTRY_WRITER'], agent_roles['ALICE'])
|
||||
r = rpc.do(o)
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
|
||||
txf = MinterFaucet(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = txf.give_to(faucet, agent_roles['ALICE'], agent_roles['ALICE'])
|
||||
r = rpc.do(o)
|
||||
|
||||
o = transaction(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
tx_src = snake_and_camel(r)
|
||||
tx = Tx(tx_src)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
rcpt = snake_and_camel(r)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
(transfer_type, transfer_data) = fltr.parse_giftto(tx, eth_rpc)
|
||||
|
||||
assert transfer_type == 'tokengift'
|
||||
assert transfer_data['token_address'] == foo_token
|
||||
|
||||
|
||||
def test_callback_filter(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
foo_token,
|
||||
token_roles,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
register_lookups,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
|
||||
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
|
||||
|
||||
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
|
||||
r = rpc.do(o)
|
||||
|
||||
o = transaction(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
logg.debug(r)
|
||||
|
||||
mockblock_src = {
|
||||
'hash': add_0x(os.urandom(32).hex()),
|
||||
'number': '0x2a',
|
||||
'transactions': [tx_hash_hex],
|
||||
'timestamp': datetime.datetime.utcnow().timestamp(),
|
||||
}
|
||||
mockblock = Block(mockblock_src)
|
||||
|
||||
tx_src = snake_and_camel(r)
|
||||
tx = Tx(tx_src, block=mockblock)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
rcpt = snake_and_camel(r)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
|
||||
class CallbackMock:
|
||||
|
||||
def __init__(self):
|
||||
self.results = {}
|
||||
|
||||
def call_back(self, transfer_type, result):
|
||||
self.results[transfer_type] = result
|
||||
|
||||
mock = CallbackMock()
|
||||
fltr.call_back = mock.call_back
|
||||
|
||||
fltr.filter(eth_rpc, mockblock, tx, init_database)
|
||||
|
||||
assert mock.results.get('transfer') != None
|
||||
assert mock.results['transfer']['destination_token'] == foo_token
|
@ -33,7 +33,9 @@ elif args.v:
|
||||
|
||||
config = confini.Config(args.c, args.env_prefix)
|
||||
config.process()
|
||||
config.add(args.q, '_CELERY_QUEUE', True)
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||
|
||||
# connect to database
|
||||
dsn = dsn_from_config(config)
|
||||
|
@ -6,11 +6,10 @@ import time
|
||||
import semver
|
||||
|
||||
# local imports
|
||||
from cic_notify.error import PleaseCommitFirstError
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
version = (0, 4, 0, 'alpha.3')
|
||||
version = (0, 4, 0, 'alpha.4')
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
major=version[0],
|
||||
@ -18,27 +17,4 @@ version_object = semver.VersionInfo(
|
||||
patch=version[2],
|
||||
prerelease=version[3],
|
||||
)
|
||||
|
||||
version_string = str(version_object)
|
||||
|
||||
|
||||
def git_hash():
|
||||
import subprocess
|
||||
|
||||
git_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True)
|
||||
git_hash_brief = git_hash.stdout.decode('utf-8')[:8]
|
||||
return git_hash_brief
|
||||
|
||||
|
||||
try:
|
||||
version_git = git_hash()
|
||||
version_string += '+build.{}'.format(version_git)
|
||||
except FileNotFoundError:
|
||||
time_string_pair = str(time.time()).split('.')
|
||||
version_string += '+build.{}{:<09d}'.format(
|
||||
time_string_pair[0],
|
||||
int(time_string_pair[1]),
|
||||
)
|
||||
logg.info(f'Final version string will be {version_string}')
|
||||
|
||||
__version_string__ = version_string
|
||||
|
@ -1,6 +1,5 @@
|
||||
[metadata]
|
||||
name = cic-notify
|
||||
version= 0.4.0a3
|
||||
description = CIC notifications service
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
|
@ -1,9 +1,31 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
from setuptools import setup
|
||||
|
||||
# third-party imports
|
||||
|
||||
# local imports
|
||||
from cic_notify.version import version_string
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def git_hash():
|
||||
git_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True)
|
||||
git_hash_brief = git_hash.stdout.decode('utf-8')[:8]
|
||||
return git_hash_brief
|
||||
|
||||
|
||||
try:
|
||||
version_git = git_hash()
|
||||
version_string += '+build.{}'.format(version_git)
|
||||
except FileNotFoundError:
|
||||
time_string_pair = str(time.time()).split('.')
|
||||
version_string += '+build.{}{:<09d}'.format(
|
||||
time_string_pair[0],
|
||||
int(time_string_pair[1]),
|
||||
)
|
||||
logg.info(f'Final version string will be {version_string}')
|
||||
|
||||
|
||||
requirements = []
|
||||
@ -25,6 +47,6 @@ while True:
|
||||
test_requirements_file.close()
|
||||
|
||||
setup(
|
||||
version=version_string,
|
||||
install_requires=requirements,
|
||||
tests_require=test_requirements,
|
||||
)
|
||||
tests_require=test_requirements)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[celery]
|
||||
BROKER_URL=redis://redis:6379
|
||||
RESULT_URL=redis://redis:6379
|
||||
BROKER_URL=redis://
|
||||
RESULT_URL=redis://
|
||||
|
||||
[redis]
|
||||
HOSTNAME=redis
|
||||
|
@ -8,12 +8,12 @@ from cic_types.processor import generate_metadata_pointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.chain import Chain
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||
from cic_ussd.redis import get_cached_data
|
||||
|
||||
|
||||
def define_account_tx_metadata(user: User):
|
||||
def define_account_tx_metadata(user: Account):
|
||||
# get sender metadata
|
||||
identifier = blockchain_address_to_metadata_pointer(
|
||||
blockchain_address=user.blockchain_address
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Create user table
|
||||
"""Create account table
|
||||
|
||||
Revision ID: f289e8510444
|
||||
Revises:
|
||||
@ -17,7 +17,7 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('user',
|
||||
op.create_table('account',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('blockchain_address', sa.String(), nullable=False),
|
||||
sa.Column('phone_number', sa.String(), nullable=False),
|
||||
@ -29,11 +29,11 @@ def upgrade():
|
||||
sa.Column('updated', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True)
|
||||
op.create_index(op.f('ix_user_blockchain_address'), 'user', ['blockchain_address'], unique=True)
|
||||
op.create_index(op.f('ix_account_phone_number'), 'account', ['phone_number'], unique=True)
|
||||
op.create_index(op.f('ix_account_blockchain_address'), 'account', ['blockchain_address'], unique=True)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index(op.f('ix_user_blockchain_address'), table_name='user')
|
||||
op.drop_index(op.f('ix_user_phone_number'), table_name='user')
|
||||
op.drop_table('user')
|
||||
op.drop_index(op.f('ix_account_blockchain_address'), table_name='account')
|
||||
op.drop_index(op.f('ix_account_phone_number'), table_name='account')
|
||||
op.drop_table('account')
|
||||
|
@ -16,12 +16,12 @@ class AccountStatus(IntEnum):
|
||||
RESET = 4
|
||||
|
||||
|
||||
class User(SessionBase):
|
||||
class Account(SessionBase):
|
||||
"""
|
||||
This class defines a user record along with functions responsible for hashing the user's corresponding password and
|
||||
subsequently verifying a password's validity given an input to compare against the persisted hash.
|
||||
"""
|
||||
__tablename__ = 'user'
|
||||
__tablename__ = 'account'
|
||||
|
||||
blockchain_address = Column(String)
|
||||
phone_number = Column(String)
|
||||
@ -38,7 +38,7 @@ class User(SessionBase):
|
||||
self.account_status = AccountStatus.PENDING.value
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User: {self.blockchain_address}>'
|
||||
return f'<Account: {self.blockchain_address}>'
|
||||
|
||||
def create_password(self, password):
|
||||
"""This method takes a password value and hashes the value before assigning it to the corresponding
|
@ -10,7 +10,7 @@ from tinydb.table import Document
|
||||
from typing import Optional
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.db.models.ussd_session import UssdSession
|
||||
from cic_ussd.db.models.task_tracker import TaskTracker
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
@ -143,10 +143,10 @@ def get_account_status(phone_number) -> str:
|
||||
:return: The user account status.
|
||||
:rtype: str
|
||||
"""
|
||||
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||
status = user.get_account_status()
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
return status
|
||||
|
||||
@ -269,12 +269,12 @@ def cache_account_creation_task_id(phone_number: str, task_id: str):
|
||||
redis_cache.persist(name=task_id)
|
||||
|
||||
|
||||
def process_current_menu(ussd_session: Optional[dict], user: User, user_input: str) -> Document:
|
||||
def process_current_menu(ussd_session: Optional[dict], user: Account, user_input: str) -> Document:
|
||||
"""This function checks user input and returns a corresponding ussd menu
|
||||
:param ussd_session: An in db ussd session object.
|
||||
:type ussd_session: UssdSession
|
||||
:param user: A user object.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param user_input: The user's input.
|
||||
:type user_input: str
|
||||
:return: An in memory ussd menu object.
|
||||
@ -324,7 +324,7 @@ def process_menu_interaction_requests(chain_str: str,
|
||||
|
||||
else:
|
||||
# get user
|
||||
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||
|
||||
# find any existing ussd session
|
||||
existing_ussd_session = UssdSession.session.query(UssdSession).filter_by(
|
||||
@ -390,10 +390,10 @@ def reset_pin(phone_number: str) -> str:
|
||||
:return: The status of the pin reset.
|
||||
:rtype: str
|
||||
"""
|
||||
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||
user.reset_account_pin()
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
response = f'Pin reset for user {phone_number} is successful!'
|
||||
return response
|
||||
|
@ -5,7 +5,7 @@ from typing import Optional
|
||||
import phonenumbers
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
|
||||
def process_phone_number(phone_number: str, region: str):
|
||||
@ -30,14 +30,14 @@ def process_phone_number(phone_number: str, region: str):
|
||||
return parsed_phone_number
|
||||
|
||||
|
||||
def get_user_by_phone_number(phone_number: str) -> Optional[User]:
|
||||
def get_user_by_phone_number(phone_number: str) -> Optional[Account]:
|
||||
"""This function queries the database for a user based on the provided phone number.
|
||||
:param phone_number: A valid phone number.
|
||||
:type phone_number: str
|
||||
:return: A user object matching a given phone number
|
||||
:rtype: User|None
|
||||
:rtype: Account|None
|
||||
"""
|
||||
# consider adding region to user's metadata
|
||||
phone_number = process_phone_number(phone_number=phone_number, region='KE')
|
||||
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||
return user
|
||||
|
@ -13,7 +13,7 @@ from tinydb.table import Document
|
||||
from cic_ussd.account import define_account_tx_metadata, retrieve_account_statement
|
||||
from cic_ussd.balance import BalanceManager, compute_operational_balance, get_cached_operational_balance
|
||||
from cic_ussd.chain import Chain
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.db.models.ussd_session import UssdSession
|
||||
from cic_ussd.error import MetadataNotFoundError
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
@ -28,13 +28,13 @@ from cic_types.models.person import generate_metadata_pointer, get_contact_data_
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def process_pin_authorization(display_key: str, user: User, **kwargs) -> str:
|
||||
def process_pin_authorization(display_key: str, user: Account, **kwargs) -> str:
|
||||
"""
|
||||
This method provides translation for all ussd menu entries that follow the pin authorization pattern.
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
:type display_key: str
|
||||
:param user: The user in a running USSD session.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param kwargs: Any additional information required by the text values in the internationalization files.
|
||||
:type kwargs
|
||||
:return: A string value corresponding the ussd menu's text value.
|
||||
@ -55,13 +55,13 @@ def process_pin_authorization(display_key: str, user: User, **kwargs) -> str:
|
||||
)
|
||||
|
||||
|
||||
def process_exit_insufficient_balance(display_key: str, user: User, ussd_session: dict):
|
||||
def process_exit_insufficient_balance(display_key: str, user: Account, ussd_session: dict):
|
||||
"""This function processes the exit menu letting users their account balance is insufficient to perform a specific
|
||||
transaction.
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
:type display_key: str
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||
:type ussd_session: dict
|
||||
:return: Corresponding translation text response
|
||||
@ -90,12 +90,12 @@ def process_exit_insufficient_balance(display_key: str, user: User, ussd_session
|
||||
)
|
||||
|
||||
|
||||
def process_exit_successful_transaction(display_key: str, user: User, ussd_session: dict):
|
||||
def process_exit_successful_transaction(display_key: str, user: Account, ussd_session: dict):
|
||||
"""This function processes the exit menu after a successful initiation for a transfer of tokens.
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
:type display_key: str
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||
:type ussd_session: dict
|
||||
:return: Corresponding translation text response
|
||||
@ -118,11 +118,11 @@ def process_exit_successful_transaction(display_key: str, user: User, ussd_sessi
|
||||
)
|
||||
|
||||
|
||||
def process_transaction_pin_authorization(user: User, display_key: str, ussd_session: dict):
|
||||
def process_transaction_pin_authorization(user: Account, display_key: str, ussd_session: dict):
|
||||
"""This function processes pin authorization where making a transaction is concerned. It constructs a
|
||||
pre-transaction response menu that shows the details of the transaction.
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
:type display_key: str
|
||||
:param ussd_session: The USSD session determining what user data needs to be extracted and added to the menu's
|
||||
@ -151,7 +151,7 @@ def process_transaction_pin_authorization(user: User, display_key: str, ussd_ses
|
||||
)
|
||||
|
||||
|
||||
def process_account_balances(user: User, display_key: str, ussd_session: dict):
|
||||
def process_account_balances(user: Account, display_key: str, ussd_session: dict):
|
||||
"""
|
||||
:param user:
|
||||
:type user:
|
||||
@ -205,7 +205,7 @@ def format_transactions(transactions: list, preferred_language: str):
|
||||
return formatted_transactions
|
||||
|
||||
|
||||
def process_display_user_metadata(user: User, display_key: str):
|
||||
def process_display_user_metadata(user: Account, display_key: str):
|
||||
"""
|
||||
:param user:
|
||||
:type user:
|
||||
@ -238,7 +238,7 @@ def process_display_user_metadata(user: User, display_key: str):
|
||||
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
|
||||
|
||||
|
||||
def process_account_statement(user: User, display_key: str, ussd_session: dict):
|
||||
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
|
||||
"""
|
||||
:param user:
|
||||
:type user:
|
||||
@ -301,12 +301,12 @@ def process_account_statement(user: User, display_key: str, ussd_session: dict):
|
||||
)
|
||||
|
||||
|
||||
def process_start_menu(display_key: str, user: User):
|
||||
def process_start_menu(display_key: str, user: Account):
|
||||
"""This function gets data on an account's balance and token in order to append it to the start of the start menu's
|
||||
title. It passes said arguments to the translation function and returns the appropriate corresponding text from the
|
||||
translation files.
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
:type display_key: str
|
||||
:return: Corresponding translation text response
|
||||
@ -361,13 +361,13 @@ def retrieve_most_recent_ussd_session(phone_number: str) -> UssdSession:
|
||||
return last_ussd_session
|
||||
|
||||
|
||||
def process_request(user_input: str, user: User, ussd_session: Optional[dict] = None) -> Document:
|
||||
def process_request(user_input: str, user: Account, ussd_session: Optional[dict] = None) -> Document:
|
||||
"""This function assesses a request based on the user from the request comes, the session_id and the user's
|
||||
input. It determines whether the request translates to a return to an existing session by checking whether the
|
||||
provided session id exists in the database or whether the creation of a new ussd session object is warranted.
|
||||
It then returns the appropriate ussd menu text values.
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param user_input: The value a user enters in the ussd menu.
|
||||
:type user_input: str
|
||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||
@ -415,14 +415,14 @@ def process_request(user_input: str, user: User, ussd_session: Optional[dict] =
|
||||
return UssdMenu.find_by_name(name='initial_pin_entry')
|
||||
|
||||
|
||||
def next_state(ussd_session: dict, user: User, user_input: str) -> str:
|
||||
def next_state(ussd_session: dict, user: Account, user_input: str) -> str:
|
||||
"""This function navigates the state machine based on the ussd session object and user inputs it receives.
|
||||
It checks the user input and provides the successive state in the state machine. It then updates the session's
|
||||
state attribute with the new state.
|
||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||
:type ussd_session: dict
|
||||
:param user: The user requesting access to the ussd menu.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param user_input: The value a user enters in the ussd menu.
|
||||
:type user_input: str
|
||||
:return: A string value corresponding the successive give a specific state in the state machine.
|
||||
@ -438,7 +438,7 @@ def custom_display_text(
|
||||
display_key: str,
|
||||
menu_name: str,
|
||||
ussd_session: dict,
|
||||
user: User) -> str:
|
||||
user: Account) -> str:
|
||||
"""This function extracts the appropriate session data based on the current menu name. It then inserts them as
|
||||
keywords in the i18n function.
|
||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||
@ -446,7 +446,7 @@ def custom_display_text(
|
||||
:param menu_name: The name by which a specific menu can be identified.
|
||||
:type menu_name: str
|
||||
:param user: The user in a running USSD session.
|
||||
:type user: User
|
||||
:type user: Account
|
||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||
:type ussd_session: dict
|
||||
:return: A string value corresponding the ussd menu's text value.
|
||||
|
@ -10,7 +10,7 @@ from urllib.parse import urlparse, parse_qs
|
||||
from sqlalchemy import desc
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.operations import get_account_status, reset_pin
|
||||
from cic_ussd.validator import check_known_user
|
||||
|
||||
@ -123,9 +123,9 @@ def process_locked_accounts_requests(env: dict) -> tuple:
|
||||
else:
|
||||
limit = r[1]
|
||||
|
||||
locked_accounts = User.session.query(User.blockchain_address).filter(
|
||||
User.account_status == AccountStatus.LOCKED.value,
|
||||
User.failed_pin_attempts >= 3).order_by(desc(User.updated)).offset(offset).limit(limit).all()
|
||||
locked_accounts = Account.session.query(Account.blockchain_address).filter(
|
||||
Account.account_status == AccountStatus.LOCKED.value,
|
||||
Account.failed_pin_attempts >= 3).order_by(desc(Account.updated)).offset(offset).limit(limit).all()
|
||||
|
||||
# convert lists to scalar blockchain addresses
|
||||
locked_accounts = [blockchain_address for (blockchain_address, ) in locked_accounts]
|
||||
|
@ -57,19 +57,17 @@ arg_parser.add_argument('--env-prefix',
|
||||
help='environment prefix for variables to overwrite configuration')
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
# parse config
|
||||
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
|
||||
config.process()
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
|
||||
# define log levels
|
||||
if args.vv:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
# log config vars
|
||||
logg.debug(config)
|
||||
# parse config
|
||||
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
|
||||
config.process()
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||
|
||||
# initialize elements
|
||||
# set up translations
|
||||
|
@ -6,6 +6,7 @@ import tempfile
|
||||
|
||||
# third party imports
|
||||
import celery
|
||||
import i18n
|
||||
import redis
|
||||
from confini import Config
|
||||
|
||||
@ -33,18 +34,18 @@ arg_parser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||
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')
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
# parse config
|
||||
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
|
||||
config.process()
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
|
||||
# define log levels
|
||||
if args.vv:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
logg.debug(config)
|
||||
# parse config
|
||||
config = Config(args.c, args.env_prefix)
|
||||
config.process()
|
||||
config.add(args.q, '_CELERY_QUEUE', True)
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||
|
||||
# connect to database
|
||||
data_source_name = dsn_from_config(config)
|
||||
@ -77,6 +78,10 @@ if key_file_path:
|
||||
validate_presence(path=key_file_path)
|
||||
Signer.key_file_path = key_file_path
|
||||
|
||||
# set up translations
|
||||
i18n.load_path.append(config.get('APP_LOCALE_PATH'))
|
||||
i18n.set('fallback', config.get('APP_LOCALE_FALLBACK'))
|
||||
|
||||
# set up celery
|
||||
current_app = celery.Celery(__name__)
|
||||
|
||||
|
@ -5,12 +5,12 @@ from typing import Tuple
|
||||
# third-party imports
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
|
||||
def process_mini_statement_request(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function compiles a brief statement of a user's last three inbound and outbound transactions and send the
|
||||
same as a message on their selected avenue for notification.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
|
@ -6,10 +6,10 @@ ussd menu facilitating the return of appropriate menu responses based on said us
|
||||
from typing import Tuple
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
|
||||
def menu_one_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_one_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that user input matches a string with value '1'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
@ -20,7 +20,7 @@ def menu_one_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '1'
|
||||
|
||||
|
||||
def menu_two_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_two_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that user input matches a string with value '2'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -31,7 +31,7 @@ def menu_two_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '2'
|
||||
|
||||
|
||||
def menu_three_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_three_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that user input matches a string with value '3'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -42,7 +42,7 @@ def menu_three_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '3'
|
||||
|
||||
|
||||
def menu_four_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_four_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
This function checks that user input matches a string with value '4'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -54,7 +54,7 @@ def menu_four_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '4'
|
||||
|
||||
|
||||
def menu_five_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_five_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
This function checks that user input matches a string with value '5'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -66,7 +66,7 @@ def menu_five_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '5'
|
||||
|
||||
|
||||
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
This function checks that user input matches a string with value '00'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -78,7 +78,7 @@ def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user_input == '00'
|
||||
|
||||
|
||||
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
This function checks that user input matches a string with value '99'
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
|
@ -12,7 +12,7 @@ from typing import Tuple
|
||||
import bcrypt
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.encoder import PasswordEncoder, create_password_hash
|
||||
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
|
||||
from cic_ussd.redis import InMemoryStore
|
||||
@ -21,7 +21,7 @@ from cic_ussd.redis import InMemoryStore
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def is_valid_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_valid_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks a pin's validity by ensuring it has a length of for characters and the characters are
|
||||
numeric.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -37,7 +37,7 @@ def is_valid_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return pin_is_valid
|
||||
|
||||
|
||||
def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -48,7 +48,7 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user.verify_password(password=user_input)
|
||||
|
||||
|
||||
def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks whether a user's account is locked due to too many failed attempts.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -59,7 +59,7 @@ def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user.get_account_status() == AccountStatus.LOCKED.name
|
||||
|
||||
|
||||
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function hashes a pin and stores it in session data.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -94,7 +94,7 @@ def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, User])
|
||||
persist_session_to_db_task(external_session_id=external_session_id, queue='cic-ussd')
|
||||
|
||||
|
||||
def pins_match(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def pins_match(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -108,7 +108,7 @@ def pins_match(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return bcrypt.checkpw(user_input.encode(), initial_pin)
|
||||
|
||||
|
||||
def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
|
||||
def complete_pin_change(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function persists the user's pin to the database
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -116,11 +116,11 @@ def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
password_hash = ussd_session.get('session_data').get('initial_pin')
|
||||
user.password_hash = password_hash
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
|
||||
def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_blocked_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -131,7 +131,7 @@ def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return user.get_account_status() == AccountStatus.LOCKED.name
|
||||
|
||||
|
||||
def is_valid_new_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_valid_new_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks whether the user's new pin is a valid pin and that it isn't the same as the old one.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
|
@ -3,21 +3,21 @@ import logging
|
||||
from typing import Tuple
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, User]):
|
||||
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, Account]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
logg.debug('Requires integration to cic-notify.')
|
||||
|
||||
|
||||
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
|
||||
def process_mini_statement_request(state_machine_data: Tuple[str, dict, Account]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
logg.debug('Requires integration to cic-notify.')
|
||||
|
||||
|
||||
def upsell_unregistered_recipient(state_machine_data: Tuple[str, dict, User]):
|
||||
def upsell_unregistered_recipient(state_machine_data: Tuple[str, dict, Account]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
logg.debug('Requires integration to cic-notify.')
|
@ -9,7 +9,7 @@ import celery
|
||||
# local imports
|
||||
from cic_ussd.balance import BalanceManager, compute_operational_balance
|
||||
from cic_ussd.chain import Chain
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.operations import save_to_in_memory_ussd_session_data
|
||||
from cic_ussd.phone_number import get_user_by_phone_number
|
||||
from cic_ussd.redis import create_cached_data_key, get_cached_data
|
||||
@ -19,7 +19,7 @@ from cic_ussd.transactions import OutgoingTransactionProcessor
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_valid_recipient(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that a user exists, is not the initiator of the transaction, has an active account status
|
||||
and is authorized to perform standard transactions.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -34,7 +34,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return is_not_initiator and has_active_account_status and recipient is not None
|
||||
|
||||
|
||||
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
|
||||
being attempted.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -49,7 +49,7 @@ def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> b
|
||||
return False
|
||||
|
||||
|
||||
def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
def has_sufficient_balance(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
|
||||
being attempted.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@ -72,7 +72,7 @@ def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> bool:
|
||||
return int(user_input) <= operational_balance
|
||||
|
||||
|
||||
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function saves the phone number corresponding the intended recipients blockchain account.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
@ -85,7 +85,7 @@ def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Us
|
||||
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
|
||||
|
||||
|
||||
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
@ -104,7 +104,7 @@ def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
s_query_person_metadata.apply_async(queue='cic-ussd')
|
||||
|
||||
|
||||
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function saves the phone number corresponding the intended recipients blockchain account.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
@ -117,7 +117,7 @@ def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict,
|
||||
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
|
||||
|
||||
|
||||
def process_transaction_request(state_machine_data: Tuple[str, dict, User]):
|
||||
def process_transaction_request(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function saves the phone number corresponding the intended recipients blockchain account.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
|
@ -10,7 +10,7 @@ from cic_types.models.person import generate_vcard_from_contact_data, manage_ide
|
||||
|
||||
# local imports
|
||||
from cic_ussd.chain import Chain
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.error import MetadataNotFoundError
|
||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||
from cic_ussd.operations import save_to_in_memory_ussd_session_data
|
||||
@ -19,40 +19,40 @@ from cic_ussd.redis import get_cached_data
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, User]):
|
||||
def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function changes the user's preferred language to english.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
"""
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
user.preferred_language = 'en'
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
|
||||
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, User]):
|
||||
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function changes the user's preferred language to swahili.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
"""
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
user.preferred_language = 'sw'
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
|
||||
def update_account_status_to_active(state_machine_data: Tuple[str, dict, User]):
|
||||
def update_account_status_to_active(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function sets user's account to active.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
"""
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
user.activate_account()
|
||||
User.session.add(user)
|
||||
User.session.commit()
|
||||
Account.session.add(user)
|
||||
Account.session.commit()
|
||||
|
||||
|
||||
def process_gender_user_input(user: User, user_input: str):
|
||||
def process_gender_user_input(user: Account, user_input: str):
|
||||
"""
|
||||
:param user:
|
||||
:type user:
|
||||
@ -74,7 +74,7 @@ def process_gender_user_input(user: User, user_input: str):
|
||||
return gender
|
||||
|
||||
|
||||
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function saves first name data to the ussd session in the redis cache.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -109,7 +109,7 @@ def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict,
|
||||
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
|
||||
|
||||
|
||||
def format_user_metadata(metadata: dict, user: User):
|
||||
def format_user_metadata(metadata: dict, user: Account):
|
||||
"""
|
||||
:param metadata:
|
||||
:type metadata:
|
||||
@ -150,7 +150,7 @@ def format_user_metadata(metadata: dict, user: User):
|
||||
}
|
||||
|
||||
|
||||
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function persists elements of the user metadata stored in session data
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: tuple
|
||||
@ -171,7 +171,7 @@ def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
s_create_person_metadata.apply_async(queue='cic-ussd')
|
||||
|
||||
|
||||
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
|
||||
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
blockchain_address = user.blockchain_address
|
||||
key = generate_metadata_pointer(
|
||||
@ -218,7 +218,7 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
|
||||
s_edit_person_metadata.apply_async(queue='cic-ussd')
|
||||
|
||||
|
||||
def get_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
def get_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
blockchain_address = user.blockchain_address
|
||||
s_get_user_metadata = celery.signature(
|
||||
|
@ -7,14 +7,14 @@ from typing import Tuple
|
||||
from cic_types.models.person import generate_metadata_pointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||
from cic_ussd.redis import get_cached_data
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function checks whether the attributes of the user's metadata constituting a profile are filled out.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
@ -29,7 +29,7 @@ def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||
return user_metadata is not None
|
||||
|
||||
|
||||
def is_valid_name(state_machine_data: Tuple[str, dict, User]):
|
||||
def is_valid_name(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""This function checks that a user provided name is valid
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
:type state_machine_data: str
|
||||
@ -43,7 +43,7 @@ def is_valid_name(state_machine_data: Tuple[str, dict, User]):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, User]):
|
||||
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, Account]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
|
@ -13,7 +13,7 @@ class UssdStateMachine(Machine):
|
||||
"""This class describes a finite state machine responsible for maintaining all the states that describe the ussd
|
||||
menu as well as providing a means for navigating through these states based on different user inputs.
|
||||
It defines different helper functions that co-ordinate with the stakeholder components of the ussd menu: i.e the
|
||||
User, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
|
||||
Account, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
|
||||
:cvar states: A list of pre-defined states.
|
||||
:type states: list
|
||||
:cvar transitions: A list of pre-defined transitions.
|
||||
|
@ -9,7 +9,7 @@ import celery
|
||||
# local imports
|
||||
from cic_ussd.conversions import from_wei
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.account import define_account_tx_metadata
|
||||
from cic_ussd.error import ActionDataNotFoundError
|
||||
from cic_ussd.redis import InMemoryStore, cache_data, create_cached_data_key
|
||||
@ -49,7 +49,7 @@ def process_account_creation_callback(self, result: str, url: str, status_code:
|
||||
phone_number = account_creation_data.get('phone_number')
|
||||
|
||||
# create user
|
||||
user = User(blockchain_address=result, phone_number=phone_number)
|
||||
user = Account(blockchain_address=result, phone_number=phone_number)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.close()
|
||||
@ -83,12 +83,12 @@ def process_incoming_transfer_callback(result: dict, param: str, status_code: in
|
||||
# collect result data
|
||||
recipient_blockchain_address = result.get('recipient')
|
||||
sender_blockchain_address = result.get('sender')
|
||||
token_symbol = result.get('token_symbol')
|
||||
value = result.get('destination_value')
|
||||
token_symbol = result.get('destination_token_symbol')
|
||||
value = result.get('destination_token_value')
|
||||
|
||||
# try to find users in system
|
||||
recipient_user = session.query(User).filter_by(blockchain_address=recipient_blockchain_address).first()
|
||||
sender_user = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||
recipient_user = session.query(Account).filter_by(blockchain_address=recipient_blockchain_address).first()
|
||||
sender_user = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||
|
||||
# check whether recipient is in the system
|
||||
if not recipient_user:
|
||||
@ -188,8 +188,8 @@ def process_statement_callback(result, param: str, status_code: int):
|
||||
processed_transaction = {}
|
||||
|
||||
# check if sender is in the system
|
||||
sender: User = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||
owner: User = session.query(User).filter_by(blockchain_address=param).first()
|
||||
sender: Account = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||
owner: Account = session.query(Account).filter_by(blockchain_address=param).first()
|
||||
if sender:
|
||||
processed_transaction['sender_phone_number'] = sender.phone_number
|
||||
|
||||
@ -205,7 +205,7 @@ def process_statement_callback(result, param: str, status_code: int):
|
||||
processed_transaction['sender_phone_number'] = 'GRASSROOTS ECONOMICS'
|
||||
|
||||
# check if recipient is in the system
|
||||
recipient: User = session.query(User).filter_by(blockchain_address=recipient_address).first()
|
||||
recipient: Account = session.query(Account).filter_by(blockchain_address=recipient_address).first()
|
||||
if recipient:
|
||||
processed_transaction['recipient_phone_number'] = recipient.phone_number
|
||||
|
||||
|
@ -8,7 +8,7 @@ from typing import Optional
|
||||
def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs) -> str:
|
||||
"""
|
||||
Translates text mapped to a specific YAML key into the user's set preferred language.
|
||||
:param preferred_language: User's preferred language in which to view the ussd menu.
|
||||
:param preferred_language: Account's preferred language in which to view the ussd menu.
|
||||
:type preferred_language str
|
||||
:param key: Key to a specific YAML test entry
|
||||
:type key: str
|
||||
|
@ -8,7 +8,7 @@ import ipaddress
|
||||
from confini import Config
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
@ -68,7 +68,7 @@ def check_known_user(phone: str):
|
||||
:return: Is known phone number
|
||||
:rtype: boolean
|
||||
"""
|
||||
user = User.session.query(User).filter_by(phone_number=phone).first()
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone).first()
|
||||
return user is not None
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# standard imports
|
||||
import semver
|
||||
|
||||
version = (0, 3, 0, 'alpha.8')
|
||||
version = (0, 3, 0, 'alpha.9')
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
major=version[0],
|
||||
|
@ -1,4 +1,4 @@
|
||||
cic_base[full_graph]~=0.1.2a68
|
||||
cic-eth~=0.11.0b3
|
||||
cic-notify~=0.4.0a3
|
||||
cic-notify~=0.4.0a4
|
||||
cic-types~=0.1.0a10
|
||||
|
@ -4,19 +4,19 @@
|
||||
import pytest
|
||||
|
||||
# platform imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
|
||||
|
||||
def test_user(init_database, set_fernet_key):
|
||||
user = User(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
|
||||
phone_number='+254700000000')
|
||||
user = Account(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
|
||||
phone_number='+254700000000')
|
||||
user.create_password('0000')
|
||||
|
||||
session = User.session
|
||||
session = Account.session
|
||||
session.add(user)
|
||||
session.commit()
|
||||
|
||||
queried_user = session.query(User).get(1)
|
||||
queried_user = session.query(Account).get(1)
|
||||
assert queried_user.blockchain_address == '0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51'
|
||||
assert queried_user.phone_number == '+254700000000'
|
||||
assert queried_user.failed_pin_attempts == 0
|
||||
@ -25,7 +25,7 @@ def test_user(init_database, set_fernet_key):
|
||||
|
||||
def test_user_state_transition(create_pending_user):
|
||||
user = create_pending_user
|
||||
session = User.session
|
||||
session = Account.session
|
||||
|
||||
assert user.get_account_status() == 'PENDING'
|
||||
user.activate_account()
|
||||
|
@ -8,7 +8,7 @@ import celery
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.error import ActionDataNotFoundError
|
||||
from cic_ussd.conversions import from_wei
|
||||
|
||||
@ -29,7 +29,7 @@ def test_successful_process_account_creation_callback_task(account_creation_acti
|
||||
# WARNING: [THE SETTING OF THE ROOT ID IS A HACK AND SHOULD BE REVIEWED OR IMPROVED]
|
||||
mocked_task_request.root_id = task_id
|
||||
|
||||
user = init_database.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = init_database.query(Account).filter_by(phone_number=phone_number).first()
|
||||
assert user is None
|
||||
|
||||
redis_cache = init_redis_cache
|
||||
@ -48,7 +48,7 @@ def test_successful_process_account_creation_callback_task(account_creation_acti
|
||||
)
|
||||
s_process_callback_request.apply_async().get()
|
||||
|
||||
user = init_database.query(User).filter_by(phone_number=phone_number).first()
|
||||
user = init_database.query(Account).filter_by(phone_number=phone_number).first()
|
||||
assert user.blockchain_address == result
|
||||
|
||||
action_data = redis_cache.get(task_id)
|
||||
|
@ -2,7 +2,7 @@
|
||||
import json
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import User
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.requests import (get_query_parameters,
|
||||
get_request_endpoint,
|
||||
get_request_method,
|
||||
@ -58,8 +58,8 @@ def test_process_locked_accounts_requests(create_locked_accounts, valid_locked_a
|
||||
assert len(locked_account_addresses) == 10
|
||||
|
||||
# check that blockchain addresses are ordered by most recently accessed
|
||||
user_1 = User.session.query(User).filter_by(blockchain_address=locked_account_addresses[2]).first()
|
||||
user_2 = User.session.query(User).filter_by(blockchain_address=locked_account_addresses[7]).first()
|
||||
user_1 = Account.session.query(Account).filter_by(blockchain_address=locked_account_addresses[2]).first()
|
||||
user_2 = Account.session.query(Account).filter_by(blockchain_address=locked_account_addresses[7]).first()
|
||||
|
||||
assert user_1.updated > user_2.updated
|
||||
|
||||
|
14
apps/cic-ussd/tests/fixtures/user.py
vendored
14
apps/cic-ussd/tests/fixtures/user.py
vendored
@ -9,7 +9,7 @@ from cic_types.models.person import generate_metadata_pointer
|
||||
from faker import Faker
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.redis import cache_data
|
||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||
|
||||
@ -19,7 +19,7 @@ fake = Faker()
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_activated_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
user = Account(
|
||||
blockchain_address='0xFD9c5aD15C72C6F60f1a119A608931226674243f',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
@ -33,7 +33,7 @@ def create_activated_user(init_database, set_fernet_key):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_valid_tx_recipient(init_database, set_fernet_key):
|
||||
user = User(
|
||||
user = Account(
|
||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
@ -47,7 +47,7 @@ def create_valid_tx_recipient(init_database, set_fernet_key):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_valid_tx_sender(init_database, set_fernet_key):
|
||||
user = User(
|
||||
user = Account(
|
||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||
phone_number='+25498765433'
|
||||
)
|
||||
@ -61,7 +61,7 @@ def create_valid_tx_sender(init_database, set_fernet_key):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_pending_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
user = Account(
|
||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
@ -72,7 +72,7 @@ def create_pending_user(init_database, set_fernet_key):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_pin_blocked_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
user = Account(
|
||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
@ -90,7 +90,7 @@ def create_locked_accounts(init_database, set_fernet_key):
|
||||
blockchain_address = str(uuid.uuid4())
|
||||
phone_number = fake.phone_number()
|
||||
pin = f'{randint(1000, 9999)}'
|
||||
user = User(phone_number=phone_number, blockchain_address=blockchain_address)
|
||||
user = Account(phone_number=phone_number, blockchain_address=blockchain_address)
|
||||
user.create_password(password=pin)
|
||||
user.failed_pin_attempts = 3
|
||||
user.account_status = AccountStatus.LOCKED.value
|
||||
|
@ -25,13 +25,13 @@ from chainlib.eth.block import (
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.hash import keccak256_string_to_hex
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import TxFactory
|
||||
from chainlib.eth.rpc import jsonrpc_template
|
||||
from chainlib.jsonrpc import jsonrpc_template
|
||||
from chainlib.eth.error import EthException
|
||||
from chainlib.chain import ChainSpec
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
|
@ -25,13 +25,13 @@ from chainlib.eth.block import (
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.hash import keccak256_string_to_hex
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import TxFactory
|
||||
from chainlib.eth.rpc import jsonrpc_template
|
||||
from chainlib.jsonrpc import jsonrpc_template
|
||||
from chainlib.eth.error import EthException
|
||||
from chainlib.chain import ChainSpec
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
|
@ -32,7 +32,7 @@ from chainlib.eth.block import (
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.hash import keccak256_string_to_hex
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.gas import (
|
||||
@ -40,7 +40,7 @@ from chainlib.eth.gas import (
|
||||
balance,
|
||||
)
|
||||
from chainlib.eth.tx import TxFactory
|
||||
from chainlib.eth.rpc import jsonrpc_template
|
||||
from chainlib.jsonrpc import jsonrpc_template
|
||||
from chainlib.eth.error import EthException
|
||||
from cic_types.models.person import (
|
||||
Person,
|
||||
@ -57,6 +57,7 @@ custodial_tests = [
|
||||
'local_key',
|
||||
'gas',
|
||||
'faucet',
|
||||
'ussd'
|
||||
]
|
||||
|
||||
metadata_tests = [
|
||||
|
@ -446,7 +446,7 @@ services:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command: "/root/start_tasker.sh -q cic-notify"
|
||||
command: "/root/start_tasker.sh -q cic-notify -vv"
|
||||
|
||||
|
||||
cic-meta-server:
|
||||
@ -494,6 +494,8 @@ services:
|
||||
DATABASE_NAME: cic_ussd
|
||||
DATABASE_ENGINE: postgresql
|
||||
DATABASE_DRIVER: psycopg2
|
||||
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||
CELERY_RESULT_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||
PGP_PASSPHRASE: merman
|
||||
SERVER_PORT: 9000
|
||||
CIC_META_URL: ${CIC_META_URL:-http://meta:8000}
|
||||
|
@ -5,6 +5,7 @@ CREATE DATABASE "cic_notify";
|
||||
CREATE DATABASE "cic_meta";
|
||||
CREATE DATABASE "cic_signer";
|
||||
CREATE DATABASE "cic_ussd";
|
||||
CREATE DATABASE "chain_sync";
|
||||
GRANT ALL PRIVILEGES
|
||||
ON DATABASE "cic_cache", "cic_eth", "cic_notify", "cic_meta", "cic_signer", "cic_ussd"
|
||||
ON DATABASE "cic_cache", "cic_eth", "cic_notify", "cic_meta", "cic_signer", "cic_ussd", "chain_sync"
|
||||
TO grassroots;
|
||||
|
Loading…
Reference in New Issue
Block a user