Merge branch 'master' of gitlab.com:grassrootseconomics/cic-internal-integration into philip/expect-scripts
This commit is contained in:
commit
ee37a23ac5
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
|||||||
service-configs/*
|
service-configs/*
|
||||||
!service-configs/.gitkeep
|
!service-configs/.gitkeep
|
||||||
|
**/node_modules/
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.o
|
||||||
|
@ -67,6 +67,7 @@ class ERC20TransferFilter(SyncFilter):
|
|||||||
tx.status == Status.SUCCESS,
|
tx.status == Status.SUCCESS,
|
||||||
block.timestamp,
|
block.timestamp,
|
||||||
)
|
)
|
||||||
db_session.flush()
|
#db_session.flush()
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -26,7 +26,7 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend import SyncerBackend
|
from chainsyncer.backend.sql import SQLBackend
|
||||||
from chainsyncer.driver import (
|
from chainsyncer.driver import (
|
||||||
HeadSyncer,
|
HeadSyncer,
|
||||||
HistorySyncer,
|
HistorySyncer,
|
||||||
@ -71,13 +71,13 @@ def main():
|
|||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SyncerBackend.first(chain_spec):
|
#if SQLBackend.first(chain_spec):
|
||||||
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
# backend = SQLBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SyncerBackend.resume(chain_spec, block_offset)
|
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
||||||
|
|
||||||
if len(syncer_backends) == 0:
|
if len(syncer_backends) == 0:
|
||||||
logg.info('found no backends to resume')
|
logg.info('found no backends to resume')
|
||||||
syncers.append(SyncerBackend.initial(chain_spec, block_offset))
|
syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
|
||||||
else:
|
else:
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||||
@ -85,7 +85,7 @@ def main():
|
|||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
syncers.append(HistorySyncer(syncer_backend))
|
syncers.append(HistorySyncer(syncer_backend))
|
||||||
|
|
||||||
syncer_backend = SyncerBackend.live(chain_spec, block_offset+1)
|
syncer_backend = SQLBackend.live(chain_spec, block_offset+1)
|
||||||
syncers.append(HeadSyncer(syncer_backend))
|
syncers.append(HeadSyncer(syncer_backend))
|
||||||
|
|
||||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
|
@ -17,7 +17,7 @@ RUN apt-get update && \
|
|||||||
|
|
||||||
# Copy shared requirements from top of mono-repo
|
# Copy shared requirements from top of mono-repo
|
||||||
RUN echo "copying root req file ${root_requirement_file}"
|
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/requirements.txt ./
|
||||||
COPY cic-cache/setup.cfg \
|
COPY cic-cache/setup.cfg \
|
||||||
@ -47,6 +47,9 @@ RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
|
|||||||
mkdir -p /usr/local/share/cic/solidity && \
|
mkdir -p /usr/local/share/cic/solidity && \
|
||||||
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
|
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
|
||||||
|
|
||||||
|
COPY cic-cache/docker/start_tracker.sh ./start_tracker.sh
|
||||||
|
COPY cic-cache/docker/db.sh ./db.sh
|
||||||
|
RUN chmod 755 ./*.sh
|
||||||
# Tracker
|
# Tracker
|
||||||
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
||||||
# Server
|
# Server
|
||||||
|
6
apps/cic-cache/docker/db.sh
Normal file
6
apps/cic-cache/docker/db.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
>&2 echo executing database migration
|
||||||
|
python scripts/migrate.py -c /usr/local/etc/cic-cache --migrations-dir /usr/local/share/cic-cache/alembic -vv
|
||||||
|
set +e
|
5
apps/cic-cache/docker/start_tracker.sh
Normal file
5
apps/cic-cache/docker/start_tracker.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
. ./db.sh
|
||||||
|
|
||||||
|
/usr/local/bin/cic-cache-trackerd $@
|
@ -1,13 +1,12 @@
|
|||||||
cic-base~=0.1.2a66
|
cic-base~=0.1.2a77
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
confini~=0.3.6rc3
|
confini~=0.3.6rc3
|
||||||
uwsgi==2.0.19.1
|
uwsgi==2.0.19.1
|
||||||
moolb~=0.1.0
|
moolb~=0.1.0
|
||||||
cic-eth-registry~=0.5.4a13
|
cic-eth-registry~=0.5.4a16
|
||||||
SQLAlchemy==1.3.20
|
SQLAlchemy==1.3.20
|
||||||
semver==2.13.0
|
semver==2.13.0
|
||||||
psycopg2==2.8.6
|
psycopg2==2.8.6
|
||||||
celery==4.4.7
|
celery==4.4.7
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
chainlib~=0.0.2a10
|
chainsyncer[sql]~=0.0.2a2
|
||||||
chainsyncer[sql]~=0.0.2a1
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
# extended imports
|
# external imports
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.status import Status as TxStatus
|
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:
|
class ExtendedTx:
|
||||||
@ -27,12 +31,12 @@ class ExtendedTx:
|
|||||||
self.status_code = TxStatus.PENDING.value
|
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.sender = sender
|
||||||
self.recipient = recipient
|
self.recipient = recipient
|
||||||
if trusted_declarator_addresses != None:
|
if trusted_declarator_addresses != None:
|
||||||
self.sender_label = translate_address(sender, 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)
|
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):
|
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
||||||
@ -40,8 +44,8 @@ class ExtendedTx:
|
|||||||
destination = source
|
destination = source
|
||||||
if destination_value == None:
|
if destination_value == None:
|
||||||
destination_value = source_value
|
destination_value = source_value
|
||||||
st = ERC20Token(self.rpc, source)
|
st = ERC20Token(self.chain_spec, self.rpc, source)
|
||||||
dt = ERC20Token(self.rpc, destination)
|
dt = ERC20Token(self.chain_spec, self.rpc, destination)
|
||||||
self.source_token = source
|
self.source_token = source
|
||||||
self.source_token_symbol = st.symbol
|
self.source_token_symbol = st.symbol
|
||||||
self.source_token_name = st.name
|
self.source_token_name = st.name
|
||||||
@ -62,10 +66,10 @@ class ExtendedTx:
|
|||||||
self.status_code = n
|
self.status_code = n
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self):
|
def asdict(self):
|
||||||
o = {}
|
o = {}
|
||||||
for attr in dir(self):
|
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
|
continue
|
||||||
o[attr] = getattr(self, attr)
|
o[attr] = getattr(self, attr)
|
||||||
return o
|
return o
|
||||||
|
@ -15,7 +15,6 @@ from cic_eth_registry import CICRegistry
|
|||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
from chainsyncer.error import SyncDone
|
|
||||||
from hexathon import strip_0x
|
from hexathon import strip_0x
|
||||||
from chainqueue.db.enum import (
|
from chainqueue.db.enum import (
|
||||||
StatusEnum,
|
StatusEnum,
|
||||||
@ -153,10 +152,7 @@ class DispatchSyncer:
|
|||||||
def main():
|
def main():
|
||||||
syncer = DispatchSyncer(chain_spec)
|
syncer = DispatchSyncer(chain_spec)
|
||||||
conn = RPCConnection.connect(chain_spec, 'default')
|
conn = RPCConnection.connect(chain_spec, 'default')
|
||||||
try:
|
|
||||||
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
|
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
|
||||||
except SyncDone as e:
|
|
||||||
sys.stderr.write("dispatcher done at block {}\n".format(e))
|
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
from cic_eth_registry.error import UnknownContractError
|
from cic_eth_registry.error import UnknownContractError
|
||||||
from chainlib.status import Status as TxStatus
|
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.error import RequestMismatchException
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.eth.erc20 import ERC20
|
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
|
# local imports
|
||||||
from .base import SyncFilter
|
from .base import SyncFilter
|
||||||
@ -18,65 +24,73 @@ from cic_eth.eth.meta import ExtendedTx
|
|||||||
logg = logging.getLogger().getChild(__name__)
|
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):
|
class CallbackFilter(SyncFilter):
|
||||||
|
|
||||||
trusted_addresses = []
|
trusted_addresses = []
|
||||||
|
|
||||||
def __init__(self, chain_spec, method, queue):
|
def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.method = method
|
self.method = method
|
||||||
self.chain_spec = chain_spec
|
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):
|
def call_back(self, transfer_type, result):
|
||||||
logg.debug('result {}'.format(result))
|
result['chain_spec'] = result['chain_spec'].asdict()
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
self.method,
|
self.method,
|
||||||
[
|
[
|
||||||
result,
|
result,
|
||||||
transfer_type,
|
transfer_type,
|
||||||
int(result['status_code'] == 0),
|
int(result['status_code'] != 0),
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
@ -92,26 +106,29 @@ class CallbackFilter(SyncFilter):
|
|||||||
# s_translate.link(s)
|
# s_translate.link(s)
|
||||||
# s_translate.apply_async()
|
# s_translate.apply_async()
|
||||||
t = s.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_type = None
|
||||||
transfer_data = None
|
transfer_data = None
|
||||||
# TODO: what's with the mix of attributes and dict keys
|
# TODO: what's with the mix of attributes and dict keys
|
||||||
logg.debug('have payload {}'.format(tx.payload))
|
logg.debug('have payload {}'.format(tx.payload))
|
||||||
method_signature = tx.payload[:8]
|
|
||||||
|
|
||||||
logg.debug('tx status {}'.format(tx.status))
|
logg.debug('tx status {}'.format(tx.status))
|
||||||
|
|
||||||
for parser in [
|
for parser in [
|
||||||
parse_transfer,
|
self.parse_transfer,
|
||||||
parse_transferfrom,
|
self.parse_transferfrom,
|
||||||
parse_giftto,
|
self.parse_giftto,
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
(transfer_type, transfer_data) = parser(tx)
|
if tx:
|
||||||
break
|
(transfer_type, transfer_data) = parser(tx, conn)
|
||||||
|
if transfer_type == None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
pass
|
||||||
except RequestMismatchException:
|
except RequestMismatchException:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -128,7 +145,7 @@ class CallbackFilter(SyncFilter):
|
|||||||
transfer_data = None
|
transfer_data = None
|
||||||
transfer_type = None
|
transfer_type = None
|
||||||
try:
|
try:
|
||||||
(transfer_type, transfer_data) = self.parse_data(tx)
|
(transfer_type, transfer_data) = self.parse_data(tx, conn)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logg.debug('invalid method data length for tx {}'.format(tx.hash))
|
logg.debug('invalid method data length for tx {}'.format(tx.hash))
|
||||||
return
|
return
|
||||||
@ -144,16 +161,17 @@ class CallbackFilter(SyncFilter):
|
|||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
|
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'])
|
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
|
||||||
if transfer_data['status'] == 0:
|
if transfer_data['status'] == 0:
|
||||||
tokentx.set_status(1)
|
tokentx.set_status(1)
|
||||||
else:
|
else:
|
||||||
tokentx.set_status(0)
|
tokentx.set_status(0)
|
||||||
t = self.call_back(transfer_type, tokentx.to_dict())
|
result = tokentx.asdict()
|
||||||
logg.info('callback success task id {} tx {}'.format(t, tx.hash))
|
t = self.call_back(transfer_type, result)
|
||||||
|
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
|
||||||
except UnknownContractError:
|
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):
|
def __str__(self):
|
||||||
|
@ -39,6 +39,7 @@ from cic_eth.queue import (
|
|||||||
from cic_eth.callbacks import (
|
from cic_eth.callbacks import (
|
||||||
Callback,
|
Callback,
|
||||||
http,
|
http,
|
||||||
|
noop,
|
||||||
#tcp,
|
#tcp,
|
||||||
redis,
|
redis,
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,6 @@ import cic_base.config
|
|||||||
import cic_base.log
|
import cic_base.log
|
||||||
import cic_base.argparse
|
import cic_base.argparse
|
||||||
import cic_base.rpc
|
import cic_base.rpc
|
||||||
from cic_eth_registry import CICRegistry
|
|
||||||
from cic_eth_registry.error import UnknownContractError
|
from cic_eth_registry.error import UnknownContractError
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
@ -26,7 +25,7 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend import SyncerBackend
|
from chainsyncer.backend.sql import SQLBackend
|
||||||
from chainsyncer.driver import (
|
from chainsyncer.driver import (
|
||||||
HeadSyncer,
|
HeadSyncer,
|
||||||
HistorySyncer,
|
HistorySyncer,
|
||||||
@ -43,6 +42,12 @@ from cic_eth.runnable.daemons.filters import (
|
|||||||
TransferAuthFilter,
|
TransferAuthFilter,
|
||||||
)
|
)
|
||||||
from cic_eth.stat import init_chain_stat
|
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__))
|
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
|
||||||
@ -88,18 +93,18 @@ def main():
|
|||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SyncerBackend.first(chain_spec):
|
#if SQLBackend.first(chain_spec):
|
||||||
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
# backend = SQLBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SyncerBackend.resume(chain_spec, block_offset)
|
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
||||||
|
|
||||||
if len(syncer_backends) == 0:
|
if len(syncer_backends) == 0:
|
||||||
logg.info('found no backends to resume')
|
logg.info('found no backends to resume')
|
||||||
syncer_backends.append(SyncerBackend.initial(chain_spec, block_offset))
|
syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
|
||||||
else:
|
else:
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||||
|
|
||||||
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1))
|
syncer_backends.append(SQLBackend.live(chain_spec, block_offset+1))
|
||||||
|
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
try:
|
try:
|
||||||
@ -109,6 +114,8 @@ def main():
|
|||||||
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
||||||
syncers.append(HeadSyncer(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')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
if trusted_addresses_src == None:
|
if trusted_addresses_src == None:
|
||||||
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
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(',')
|
trusted_addresses = trusted_addresses_src.split(',')
|
||||||
for address in trusted_addresses:
|
for address in trusted_addresses:
|
||||||
logg.info('using trusted address {}'.format(address))
|
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
|
CallbackFilter.trusted_addresses = trusted_addresses
|
||||||
|
|
||||||
callback_filters = []
|
callback_filters = []
|
||||||
|
@ -4,7 +4,7 @@ import datetime
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainsyncer.backend import MemBackend
|
from chainsyncer.backend.memory import MemBackend
|
||||||
from chainsyncer.error import NoBlockForYou
|
from chainsyncer.error import NoBlockForYou
|
||||||
from chainlib.eth.block import (
|
from chainlib.eth.block import (
|
||||||
block_by_number,
|
block_by_number,
|
||||||
|
@ -10,7 +10,7 @@ version = (
|
|||||||
0,
|
0,
|
||||||
11,
|
11,
|
||||||
0,
|
0,
|
||||||
'beta.3',
|
'beta.6',
|
||||||
)
|
)
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
|
@ -29,7 +29,7 @@ RUN /usr/local/bin/python -m pip install --upgrade pip
|
|||||||
# python merge_requirements.py | tee merged_requirements.txt
|
# python merge_requirements.py | tee merged_requirements.txt
|
||||||
#RUN cd cic-base && \
|
#RUN cd cic-base && \
|
||||||
# pip install $pip_extra_index_url_flag -r ./merged_requirements.txt
|
# 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.2a62
|
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a77
|
||||||
|
|
||||||
COPY cic-eth/scripts/ scripts/
|
COPY cic-eth/scripts/ scripts/
|
||||||
COPY cic-eth/setup.cfg cic-eth/setup.py ./
|
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
|
celery==4.4.7
|
||||||
crypto-dev-signer~=0.4.14a17
|
crypto-dev-signer~=0.4.14b2
|
||||||
confini~=0.3.6rc3
|
confini~=0.3.6rc3
|
||||||
cic-eth-registry~=0.5.4a13
|
cic-eth-registry~=0.5.4a16
|
||||||
#cic-bancor~=0.0.6
|
#cic-bancor~=0.0.6
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
websockets==8.1
|
websockets==8.1
|
||||||
requests~=2.24.0
|
requests~=2.24.0
|
||||||
eth_accounts_index~=0.0.11a7
|
eth_accounts_index~=0.0.11a9
|
||||||
erc20-transfer-authorization~=0.3.1a3
|
erc20-transfer-authorization~=0.3.1a5
|
||||||
#simple-rlp==0.1.2
|
|
||||||
uWSGI==2.0.19.1
|
uWSGI==2.0.19.1
|
||||||
semver==2.13.0
|
semver==2.13.0
|
||||||
websocket-client==0.57.0
|
websocket-client==0.57.0
|
||||||
moolb~=0.1.1b2
|
moolb~=0.1.1b2
|
||||||
eth-address-index~=0.1.1a7
|
eth-address-index~=0.1.1a9
|
||||||
chainlib~=0.0.2a5
|
chainlib~=0.0.2a13
|
||||||
hexathon~=0.0.1a7
|
hexathon~=0.0.1a7
|
||||||
chainsyncer[sql]~=0.0.2a1
|
chainsyncer[sql]~=0.0.2a2
|
||||||
chainqueue~=0.0.1a7
|
chainqueue~=0.0.1a7
|
||||||
pysha3==1.0.2
|
pysha3==1.0.2
|
||||||
coincurve==15.0.0
|
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
|
pytest-cov==2.10.1
|
||||||
eth-tester==0.5.0b3
|
eth-tester==0.5.0b3
|
||||||
py-evm==0.3.0a20
|
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 = confini.Config(args.c, args.env_prefix)
|
||||||
config.process()
|
config.process()
|
||||||
|
config.add(args.q, '_CELERY_QUEUE', True)
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
|
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||||
|
|
||||||
# connect to database
|
# connect to database
|
||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
|
@ -6,11 +6,10 @@ import time
|
|||||||
import semver
|
import semver
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_notify.error import PleaseCommitFirstError
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
version = (0, 4, 0, 'alpha.3')
|
version = (0, 4, 0, 'alpha.4')
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
major=version[0],
|
major=version[0],
|
||||||
@ -18,27 +17,4 @@ version_object = semver.VersionInfo(
|
|||||||
patch=version[2],
|
patch=version[2],
|
||||||
prerelease=version[3],
|
prerelease=version[3],
|
||||||
)
|
)
|
||||||
|
|
||||||
version_string = str(version_object)
|
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]
|
[metadata]
|
||||||
name = cic-notify
|
name = cic-notify
|
||||||
version= 0.4.0a3
|
|
||||||
description = CIC notifications service
|
description = CIC notifications service
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
|
@ -1,9 +1,31 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
|
|
||||||
# local 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 = []
|
requirements = []
|
||||||
@ -25,6 +47,6 @@ while True:
|
|||||||
test_requirements_file.close()
|
test_requirements_file.close()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
version=version_string,
|
||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
tests_require=test_requirements,
|
tests_require=test_requirements)
|
||||||
)
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[celery]
|
[celery]
|
||||||
BROKER_URL=redis://redis:6379
|
BROKER_URL=redis://
|
||||||
RESULT_URL=redis://redis:6379
|
RESULT_URL=redis://
|
||||||
|
|
||||||
[redis]
|
[redis]
|
||||||
HOSTNAME=redis
|
HOSTNAME=redis
|
||||||
|
@ -8,12 +8,12 @@ from cic_types.processor import generate_metadata_pointer
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.chain import Chain
|
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.metadata import blockchain_address_to_metadata_pointer
|
||||||
from cic_ussd.redis import get_cached_data
|
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
|
# get sender metadata
|
||||||
identifier = blockchain_address_to_metadata_pointer(
|
identifier = blockchain_address_to_metadata_pointer(
|
||||||
blockchain_address=user.blockchain_address
|
blockchain_address=user.blockchain_address
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Create user table
|
"""Create account table
|
||||||
|
|
||||||
Revision ID: f289e8510444
|
Revision ID: f289e8510444
|
||||||
Revises:
|
Revises:
|
||||||
@ -17,7 +17,7 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
op.create_table('user',
|
op.create_table('account',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('blockchain_address', sa.String(), nullable=False),
|
sa.Column('blockchain_address', sa.String(), nullable=False),
|
||||||
sa.Column('phone_number', 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.Column('updated', sa.DateTime(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True)
|
op.create_index(op.f('ix_account_phone_number'), 'account', ['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_blockchain_address'), 'account', ['blockchain_address'], unique=True)
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
op.drop_index(op.f('ix_user_blockchain_address'), table_name='user')
|
op.drop_index(op.f('ix_account_blockchain_address'), table_name='account')
|
||||||
op.drop_index(op.f('ix_user_phone_number'), table_name='user')
|
op.drop_index(op.f('ix_account_phone_number'), table_name='account')
|
||||||
op.drop_table('user')
|
op.drop_table('account')
|
||||||
|
@ -16,12 +16,12 @@ class AccountStatus(IntEnum):
|
|||||||
RESET = 4
|
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
|
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.
|
subsequently verifying a password's validity given an input to compare against the persisted hash.
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'user'
|
__tablename__ = 'account'
|
||||||
|
|
||||||
blockchain_address = Column(String)
|
blockchain_address = Column(String)
|
||||||
phone_number = Column(String)
|
phone_number = Column(String)
|
||||||
@ -38,7 +38,7 @@ class User(SessionBase):
|
|||||||
self.account_status = AccountStatus.PENDING.value
|
self.account_status = AccountStatus.PENDING.value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<User: {self.blockchain_address}>'
|
return f'<Account: {self.blockchain_address}>'
|
||||||
|
|
||||||
def create_password(self, password):
|
def create_password(self, password):
|
||||||
"""This method takes a password value and hashes the value before assigning it to the corresponding
|
"""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
|
from typing import Optional
|
||||||
|
|
||||||
# local imports
|
# 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.ussd_session import UssdSession
|
||||||
from cic_ussd.db.models.task_tracker import TaskTracker
|
from cic_ussd.db.models.task_tracker import TaskTracker
|
||||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||||
@ -143,10 +143,10 @@ def get_account_status(phone_number) -> str:
|
|||||||
:return: The user account status.
|
:return: The user account status.
|
||||||
:rtype: str
|
: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()
|
status = user.get_account_status()
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
Account.session.commit()
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
@ -269,12 +269,12 @@ def cache_account_creation_task_id(phone_number: str, task_id: str):
|
|||||||
redis_cache.persist(name=task_id)
|
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
|
"""This function checks user input and returns a corresponding ussd menu
|
||||||
:param ussd_session: An in db ussd session object.
|
:param ussd_session: An in db ussd session object.
|
||||||
:type ussd_session: UssdSession
|
:type ussd_session: UssdSession
|
||||||
:param user: A user object.
|
:param user: A user object.
|
||||||
:type user: User
|
:type user: Account
|
||||||
:param user_input: The user's input.
|
:param user_input: The user's input.
|
||||||
:type user_input: str
|
:type user_input: str
|
||||||
:return: An in memory ussd menu object.
|
:return: An in memory ussd menu object.
|
||||||
@ -324,7 +324,7 @@ def process_menu_interaction_requests(chain_str: str,
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# get user
|
# 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
|
# find any existing ussd session
|
||||||
existing_ussd_session = UssdSession.session.query(UssdSession).filter_by(
|
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.
|
:return: The status of the pin reset.
|
||||||
:rtype: str
|
: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.reset_account_pin()
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
Account.session.commit()
|
||||||
|
|
||||||
response = f'Pin reset for user {phone_number} is successful!'
|
response = f'Pin reset for user {phone_number} is successful!'
|
||||||
return response
|
return response
|
||||||
|
@ -5,7 +5,7 @@ from typing import Optional
|
|||||||
import phonenumbers
|
import phonenumbers
|
||||||
|
|
||||||
# local imports
|
# 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):
|
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
|
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.
|
"""This function queries the database for a user based on the provided phone number.
|
||||||
:param phone_number: A valid phone number.
|
:param phone_number: A valid phone number.
|
||||||
:type phone_number: str
|
:type phone_number: str
|
||||||
:return: A user object matching a given phone number
|
:return: A user object matching a given phone number
|
||||||
:rtype: User|None
|
:rtype: Account|None
|
||||||
"""
|
"""
|
||||||
# consider adding region to user's metadata
|
# consider adding region to user's metadata
|
||||||
phone_number = process_phone_number(phone_number=phone_number, region='KE')
|
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
|
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.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.balance import BalanceManager, compute_operational_balance, get_cached_operational_balance
|
||||||
from cic_ussd.chain import Chain
|
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.db.models.ussd_session import UssdSession
|
||||||
from cic_ussd.error import MetadataNotFoundError
|
from cic_ussd.error import MetadataNotFoundError
|
||||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
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__)
|
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.
|
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
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
:type display_key: str
|
:type display_key: str
|
||||||
:param user: The user in a running USSD session.
|
: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.
|
:param kwargs: Any additional information required by the text values in the internationalization files.
|
||||||
:type kwargs
|
:type kwargs
|
||||||
:return: A string value corresponding the ussd menu's text value.
|
: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
|
"""This function processes the exit menu letting users their account balance is insufficient to perform a specific
|
||||||
transaction.
|
transaction.
|
||||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
:type display_key: str
|
:type display_key: str
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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
|
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||||
:type ussd_session: dict
|
:type ussd_session: dict
|
||||||
:return: Corresponding translation text response
|
: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.
|
"""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
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
:type display_key: str
|
:type display_key: str
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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
|
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||||
:type ussd_session: dict
|
:type ussd_session: dict
|
||||||
:return: Corresponding translation text response
|
: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
|
"""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.
|
pre-transaction response menu that shows the details of the transaction.
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
:type display_key: str
|
:type display_key: str
|
||||||
:param ussd_session: The USSD session determining what user data needs to be extracted and added to the menu's
|
: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:
|
:param user:
|
||||||
:type user:
|
:type user:
|
||||||
@ -205,7 +205,7 @@ def format_transactions(transactions: list, preferred_language: str):
|
|||||||
return formatted_transactions
|
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:
|
:param user:
|
||||||
:type 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}')
|
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:
|
:param user:
|
||||||
:type 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
|
"""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
|
title. It passes said arguments to the translation function and returns the appropriate corresponding text from the
|
||||||
translation files.
|
translation files.
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
:type display_key: str
|
:type display_key: str
|
||||||
:return: Corresponding translation text response
|
:return: Corresponding translation text response
|
||||||
@ -361,13 +361,13 @@ def retrieve_most_recent_ussd_session(phone_number: str) -> UssdSession:
|
|||||||
return last_ussd_session
|
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
|
"""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
|
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.
|
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.
|
It then returns the appropriate ussd menu text values.
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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.
|
:param user_input: The value a user enters in the ussd menu.
|
||||||
:type user_input: str
|
:type user_input: str
|
||||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||||
@ -419,14 +419,14 @@ def process_request(user_input: str, user: User, ussd_session: Optional[dict] =
|
|||||||
return UssdMenu.find_by_name(name='initial_pin_entry')
|
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.
|
"""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
|
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.
|
state attribute with the new state.
|
||||||
:param ussd_session: A JSON serialized in-memory ussd session object
|
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||||
:type ussd_session: dict
|
:type ussd_session: dict
|
||||||
:param user: The user requesting access to the ussd menu.
|
: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.
|
:param user_input: The value a user enters in the ussd menu.
|
||||||
:type user_input: str
|
:type user_input: str
|
||||||
:return: A string value corresponding the successive give a specific state in the state machine.
|
:return: A string value corresponding the successive give a specific state in the state machine.
|
||||||
@ -442,7 +442,7 @@ def custom_display_text(
|
|||||||
display_key: str,
|
display_key: str,
|
||||||
menu_name: str,
|
menu_name: str,
|
||||||
ussd_session: dict,
|
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
|
"""This function extracts the appropriate session data based on the current menu name. It then inserts them as
|
||||||
keywords in the i18n function.
|
keywords in the i18n function.
|
||||||
:param display_key: The path in the translation files defining an appropriate ussd response
|
:param display_key: The path in the translation files defining an appropriate ussd response
|
||||||
@ -450,7 +450,7 @@ def custom_display_text(
|
|||||||
:param menu_name: The name by which a specific menu can be identified.
|
:param menu_name: The name by which a specific menu can be identified.
|
||||||
:type menu_name: str
|
:type menu_name: str
|
||||||
:param user: The user in a running USSD session.
|
: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
|
:param ussd_session: A JSON serialized in-memory ussd session object
|
||||||
:type ussd_session: dict
|
:type ussd_session: dict
|
||||||
:return: A string value corresponding the ussd menu's text value.
|
: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
|
from sqlalchemy import desc
|
||||||
|
|
||||||
# local imports
|
# 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.operations import get_account_status, reset_pin
|
||||||
from cic_ussd.validator import check_known_user
|
from cic_ussd.validator import check_known_user
|
||||||
|
|
||||||
@ -123,9 +123,9 @@ def process_locked_accounts_requests(env: dict) -> tuple:
|
|||||||
else:
|
else:
|
||||||
limit = r[1]
|
limit = r[1]
|
||||||
|
|
||||||
locked_accounts = User.session.query(User.blockchain_address).filter(
|
locked_accounts = Account.session.query(Account.blockchain_address).filter(
|
||||||
User.account_status == AccountStatus.LOCKED.value,
|
Account.account_status == AccountStatus.LOCKED.value,
|
||||||
User.failed_pin_attempts >= 3).order_by(desc(User.updated)).offset(offset).limit(limit).all()
|
Account.failed_pin_attempts >= 3).order_by(desc(Account.updated)).offset(offset).limit(limit).all()
|
||||||
|
|
||||||
# convert lists to scalar blockchain addresses
|
# convert lists to scalar blockchain addresses
|
||||||
locked_accounts = [blockchain_address for (blockchain_address, ) in locked_accounts]
|
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')
|
help='environment prefix for variables to overwrite configuration')
|
||||||
args = arg_parser.parse_args()
|
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
|
# define log levels
|
||||||
if args.vv:
|
if args.vv:
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
elif args.v:
|
elif args.v:
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
# log config vars
|
# parse config
|
||||||
logg.debug(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
|
# initialize elements
|
||||||
# set up translations
|
# set up translations
|
||||||
|
@ -6,6 +6,7 @@ import tempfile
|
|||||||
|
|
||||||
# third party imports
|
# third party imports
|
||||||
import celery
|
import celery
|
||||||
|
import i18n
|
||||||
import redis
|
import redis
|
||||||
from confini import Config
|
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')
|
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()
|
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
|
# define log levels
|
||||||
if args.vv:
|
if args.vv:
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
elif args.v:
|
elif args.v:
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
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
|
# connect to database
|
||||||
data_source_name = dsn_from_config(config)
|
data_source_name = dsn_from_config(config)
|
||||||
@ -77,6 +78,10 @@ if key_file_path:
|
|||||||
validate_presence(path=key_file_path)
|
validate_presence(path=key_file_path)
|
||||||
Signer.key_file_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
|
# set up celery
|
||||||
current_app = celery.Celery(__name__)
|
current_app = celery.Celery(__name__)
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ from typing import Tuple
|
|||||||
# third-party imports
|
# third-party imports
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.db.models.user import User
|
from cic_ussd.db.models.account import Account
|
||||||
|
|
||||||
logg = logging.getLogger(__file__)
|
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
|
"""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.
|
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.
|
: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
|
from typing import Tuple
|
||||||
|
|
||||||
# local imports
|
# 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'
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
: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'
|
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'
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
: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'
|
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'
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
: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'
|
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'
|
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.
|
: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'
|
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'
|
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.
|
: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'
|
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'
|
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.
|
: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'
|
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'
|
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.
|
: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
|
import bcrypt
|
||||||
|
|
||||||
# local imports
|
# 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.encoder import PasswordEncoder, create_password_hash
|
||||||
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
|
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
|
||||||
from cic_ussd.redis import InMemoryStore
|
from cic_ussd.redis import InMemoryStore
|
||||||
@ -21,7 +21,7 @@ from cic_ussd.redis import InMemoryStore
|
|||||||
logg = logging.getLogger(__file__)
|
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
|
"""This function checks a pin's validity by ensuring it has a length of for characters and the characters are
|
||||||
numeric.
|
numeric.
|
||||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
: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
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -56,7 +56,7 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
|||||||
return pin_validity
|
return pin_validity
|
||||||
|
|
||||||
|
|
||||||
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -67,7 +67,7 @@ def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> bool:
|
|||||||
return user.get_account_status() == AccountStatus.LOCKED.name
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -102,7 +102,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')
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -116,7 +116,7 @@ def pins_match(state_machine_data: Tuple[str, dict, User]) -> bool:
|
|||||||
return bcrypt.checkpw(user_input.encode(), initial_pin)
|
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
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -124,11 +124,11 @@ def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
|
|||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
password_hash = ussd_session.get('session_data').get('initial_pin')
|
password_hash = ussd_session.get('session_data').get('initial_pin')
|
||||||
user.password_hash = password_hash
|
user.password_hash = password_hash
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
@ -139,7 +139,7 @@ def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
|
|||||||
return user.get_account_status() == AccountStatus.LOCKED.name
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
|
@ -3,21 +3,21 @@ import logging
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.db.models.user import User
|
from cic_ussd.db.models.account import Account
|
||||||
|
|
||||||
logg = logging.getLogger()
|
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
|
user_input, ussd_session, user = state_machine_data
|
||||||
logg.debug('Requires integration to cic-notify.')
|
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
|
user_input, ussd_session, user = state_machine_data
|
||||||
logg.debug('Requires integration to cic-notify.')
|
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
|
user_input, ussd_session, user = state_machine_data
|
||||||
logg.debug('Requires integration to cic-notify.')
|
logg.debug('Requires integration to cic-notify.')
|
@ -9,7 +9,7 @@ import celery
|
|||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.balance import BalanceManager, compute_operational_balance
|
from cic_ussd.balance import BalanceManager, compute_operational_balance
|
||||||
from cic_ussd.chain import Chain
|
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.operations import save_to_in_memory_ussd_session_data
|
||||||
from cic_ussd.phone_number import get_user_by_phone_number
|
from cic_ussd.phone_number import get_user_by_phone_number
|
||||||
from cic_ussd.redis import create_cached_data_key, get_cached_data
|
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__)
|
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
|
"""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.
|
and is authorized to perform standard transactions.
|
||||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
: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
|
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
|
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
|
||||||
being attempted.
|
being attempted.
|
||||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
: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
|
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
|
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
|
||||||
being attempted.
|
being attempted.
|
||||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
: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
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
: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)
|
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:
|
:param state_machine_data:
|
||||||
:type 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')
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
: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)
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
:type state_machine_data: str
|
||||||
|
@ -10,7 +10,7 @@ from cic_types.models.person import generate_vcard_from_contact_data, manage_ide
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.chain import Chain
|
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.error import MetadataNotFoundError
|
||||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||||
from cic_ussd.operations import save_to_in_memory_ussd_session_data
|
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__)
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
user.preferred_language = 'en'
|
user.preferred_language = 'en'
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
user.preferred_language = 'sw'
|
user.preferred_language = 'sw'
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
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.
|
"""This function sets user's account to active.
|
||||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
:type state_machine_data: tuple
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
user.activate_account()
|
user.activate_account()
|
||||||
User.session.add(user)
|
Account.session.add(user)
|
||||||
User.session.commit()
|
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:
|
:param user:
|
||||||
:type user:
|
:type user:
|
||||||
@ -74,7 +74,7 @@ def process_gender_user_input(user: User, user_input: str):
|
|||||||
return gender
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
: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)
|
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:
|
:param metadata:
|
||||||
:type 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
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: tuple
|
: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')
|
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
|
user_input, ussd_session, user = state_machine_data
|
||||||
blockchain_address = user.blockchain_address
|
blockchain_address = user.blockchain_address
|
||||||
key = generate_metadata_pointer(
|
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')
|
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
|
user_input, ussd_session, user = state_machine_data
|
||||||
blockchain_address = user.blockchain_address
|
blockchain_address = user.blockchain_address
|
||||||
s_get_user_metadata = celery.signature(
|
s_get_user_metadata = celery.signature(
|
||||||
|
@ -7,14 +7,14 @@ from typing import Tuple
|
|||||||
from cic_types.models.person import generate_metadata_pointer
|
from cic_types.models.person import generate_metadata_pointer
|
||||||
|
|
||||||
# local imports
|
# 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.metadata import blockchain_address_to_metadata_pointer
|
||||||
from cic_ussd.redis import get_cached_data
|
from cic_ussd.redis import get_cached_data
|
||||||
|
|
||||||
logg = logging.getLogger()
|
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.
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
: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
|
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
|
"""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.
|
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||||
:type state_machine_data: str
|
:type state_machine_data: str
|
||||||
@ -43,7 +43,7 @@ def is_valid_name(state_machine_data: Tuple[str, dict, User]):
|
|||||||
return False
|
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:
|
:param state_machine_data:
|
||||||
:type 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
|
"""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.
|
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
|
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.
|
:cvar states: A list of pre-defined states.
|
||||||
:type states: list
|
:type states: list
|
||||||
:cvar transitions: A list of pre-defined transitions.
|
:cvar transitions: A list of pre-defined transitions.
|
||||||
|
@ -9,7 +9,7 @@ import celery
|
|||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.conversions import from_wei
|
from cic_ussd.conversions import from_wei
|
||||||
from cic_ussd.db.models.base import SessionBase
|
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.account import define_account_tx_metadata
|
||||||
from cic_ussd.error import ActionDataNotFoundError
|
from cic_ussd.error import ActionDataNotFoundError
|
||||||
from cic_ussd.redis import InMemoryStore, cache_data, create_cached_data_key
|
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')
|
phone_number = account_creation_data.get('phone_number')
|
||||||
|
|
||||||
# create user
|
# create user
|
||||||
user = User(blockchain_address=result, phone_number=phone_number)
|
user = Account(blockchain_address=result, phone_number=phone_number)
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
@ -83,12 +83,12 @@ def process_incoming_transfer_callback(result: dict, param: str, status_code: in
|
|||||||
# collect result data
|
# collect result data
|
||||||
recipient_blockchain_address = result.get('recipient')
|
recipient_blockchain_address = result.get('recipient')
|
||||||
sender_blockchain_address = result.get('sender')
|
sender_blockchain_address = result.get('sender')
|
||||||
token_symbol = result.get('token_symbol')
|
token_symbol = result.get('destination_token_symbol')
|
||||||
value = result.get('destination_value')
|
value = result.get('destination_token_value')
|
||||||
|
|
||||||
# try to find users in system
|
# try to find users in system
|
||||||
recipient_user = session.query(User).filter_by(blockchain_address=recipient_blockchain_address).first()
|
recipient_user = session.query(Account).filter_by(blockchain_address=recipient_blockchain_address).first()
|
||||||
sender_user = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
|
sender_user = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||||
|
|
||||||
# check whether recipient is in the system
|
# check whether recipient is in the system
|
||||||
if not recipient_user:
|
if not recipient_user:
|
||||||
@ -188,8 +188,8 @@ def process_statement_callback(result, param: str, status_code: int):
|
|||||||
processed_transaction = {}
|
processed_transaction = {}
|
||||||
|
|
||||||
# check if sender is in the system
|
# check if sender is in the system
|
||||||
sender: User = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
|
sender: Account = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
|
||||||
owner: User = session.query(User).filter_by(blockchain_address=param).first()
|
owner: Account = session.query(Account).filter_by(blockchain_address=param).first()
|
||||||
if sender:
|
if sender:
|
||||||
processed_transaction['sender_phone_number'] = sender.phone_number
|
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'
|
processed_transaction['sender_phone_number'] = 'GRASSROOTS ECONOMICS'
|
||||||
|
|
||||||
# check if recipient is in the system
|
# 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:
|
if recipient:
|
||||||
processed_transaction['recipient_phone_number'] = recipient.phone_number
|
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:
|
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.
|
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
|
:type preferred_language str
|
||||||
:param key: Key to a specific YAML test entry
|
:param key: Key to a specific YAML test entry
|
||||||
:type key: str
|
:type key: str
|
||||||
|
@ -8,7 +8,7 @@ import ipaddress
|
|||||||
from confini import Config
|
from confini import Config
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.db.models.user import User
|
from cic_ussd.db.models.account import Account
|
||||||
|
|
||||||
logg = logging.getLogger(__file__)
|
logg = logging.getLogger(__file__)
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ def check_known_user(phone: str):
|
|||||||
:return: Is known phone number
|
:return: Is known phone number
|
||||||
:rtype: boolean
|
: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
|
return user is not None
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import semver
|
import semver
|
||||||
|
|
||||||
version = (0, 3, 0, 'alpha.8')
|
version = (0, 3, 0, 'alpha.9')
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
major=version[0],
|
major=version[0],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
cic_base[full_graph]~=0.1.2a68
|
cic_base[full_graph]~=0.1.2a68
|
||||||
cic-eth~=0.11.0b3
|
cic-eth~=0.11.0b3
|
||||||
cic-notify~=0.4.0a3
|
cic-notify~=0.4.0a4
|
||||||
cic-types~=0.1.0a10
|
cic-types~=0.1.0a10
|
||||||
|
@ -4,19 +4,19 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# platform imports
|
# 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):
|
def test_user(init_database, set_fernet_key):
|
||||||
user = User(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
|
user = Account(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
|
||||||
phone_number='+254700000000')
|
phone_number='+254700000000')
|
||||||
user.create_password('0000')
|
user.create_password('0000')
|
||||||
|
|
||||||
session = User.session
|
session = Account.session
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
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.blockchain_address == '0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51'
|
||||||
assert queried_user.phone_number == '+254700000000'
|
assert queried_user.phone_number == '+254700000000'
|
||||||
assert queried_user.failed_pin_attempts == 0
|
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):
|
def test_user_state_transition(create_pending_user):
|
||||||
user = create_pending_user
|
user = create_pending_user
|
||||||
session = User.session
|
session = Account.session
|
||||||
|
|
||||||
assert user.get_account_status() == 'PENDING'
|
assert user.get_account_status() == 'PENDING'
|
||||||
user.activate_account()
|
user.activate_account()
|
||||||
|
@ -8,7 +8,7 @@ import celery
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# local imports
|
# 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.error import ActionDataNotFoundError
|
||||||
from cic_ussd.conversions import from_wei
|
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]
|
# WARNING: [THE SETTING OF THE ROOT ID IS A HACK AND SHOULD BE REVIEWED OR IMPROVED]
|
||||||
mocked_task_request.root_id = task_id
|
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
|
assert user is None
|
||||||
|
|
||||||
redis_cache = init_redis_cache
|
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()
|
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
|
assert user.blockchain_address == result
|
||||||
|
|
||||||
action_data = redis_cache.get(task_id)
|
action_data = redis_cache.get(task_id)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
# local imports
|
# 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,
|
from cic_ussd.requests import (get_query_parameters,
|
||||||
get_request_endpoint,
|
get_request_endpoint,
|
||||||
get_request_method,
|
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
|
assert len(locked_account_addresses) == 10
|
||||||
|
|
||||||
# check that blockchain addresses are ordered by most recently accessed
|
# 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_1 = Account.session.query(Account).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_2 = Account.session.query(Account).filter_by(blockchain_address=locked_account_addresses[7]).first()
|
||||||
|
|
||||||
assert user_1.updated > user_2.updated
|
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
|
from faker import Faker
|
||||||
|
|
||||||
# local imports
|
# 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.redis import cache_data
|
||||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ fake = Faker()
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def create_activated_user(init_database, set_fernet_key):
|
def create_activated_user(init_database, set_fernet_key):
|
||||||
user = User(
|
user = Account(
|
||||||
blockchain_address='0xFD9c5aD15C72C6F60f1a119A608931226674243f',
|
blockchain_address='0xFD9c5aD15C72C6F60f1a119A608931226674243f',
|
||||||
phone_number='+25498765432'
|
phone_number='+25498765432'
|
||||||
)
|
)
|
||||||
@ -33,7 +33,7 @@ def create_activated_user(init_database, set_fernet_key):
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def create_valid_tx_recipient(init_database, set_fernet_key):
|
def create_valid_tx_recipient(init_database, set_fernet_key):
|
||||||
user = User(
|
user = Account(
|
||||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||||
phone_number='+25498765432'
|
phone_number='+25498765432'
|
||||||
)
|
)
|
||||||
@ -47,7 +47,7 @@ def create_valid_tx_recipient(init_database, set_fernet_key):
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def create_valid_tx_sender(init_database, set_fernet_key):
|
def create_valid_tx_sender(init_database, set_fernet_key):
|
||||||
user = User(
|
user = Account(
|
||||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||||
phone_number='+25498765433'
|
phone_number='+25498765433'
|
||||||
)
|
)
|
||||||
@ -61,7 +61,7 @@ def create_valid_tx_sender(init_database, set_fernet_key):
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def create_pending_user(init_database, set_fernet_key):
|
def create_pending_user(init_database, set_fernet_key):
|
||||||
user = User(
|
user = Account(
|
||||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||||
phone_number='+25498765432'
|
phone_number='+25498765432'
|
||||||
)
|
)
|
||||||
@ -72,7 +72,7 @@ def create_pending_user(init_database, set_fernet_key):
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def create_pin_blocked_user(init_database, set_fernet_key):
|
def create_pin_blocked_user(init_database, set_fernet_key):
|
||||||
user = User(
|
user = Account(
|
||||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||||
phone_number='+25498765432'
|
phone_number='+25498765432'
|
||||||
)
|
)
|
||||||
@ -90,7 +90,7 @@ def create_locked_accounts(init_database, set_fernet_key):
|
|||||||
blockchain_address = str(uuid.uuid4())
|
blockchain_address = str(uuid.uuid4())
|
||||||
phone_number = fake.phone_number()
|
phone_number = fake.phone_number()
|
||||||
pin = f'{randint(1000, 9999)}'
|
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.create_password(password=pin)
|
||||||
user.failed_pin_attempts = 3
|
user.failed_pin_attempts = 3
|
||||||
user.account_status = AccountStatus.LOCKED.value
|
user.account_status = AccountStatus.LOCKED.value
|
||||||
|
@ -57,19 +57,22 @@ WORKDIR /home/grassroots
|
|||||||
USER grassroots
|
USER grassroots
|
||||||
|
|
||||||
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
|
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
|
||||||
ARG cic_base_version=0.1.2a67
|
ARG cic_base_version=0.1.2a77
|
||||||
ARG cic_eth_version=0.11.0b1
|
ARG cic_eth_version=0.11.0b6
|
||||||
ARG sarafu_faucet_version=0.0.2a19
|
ARG sarafu_faucet_version=0.0.2a28
|
||||||
|
ARG sarafu_token_version==0.0.1a6
|
||||||
ARG cic_contracts_version=0.0.2a2
|
ARG cic_contracts_version=0.0.2a2
|
||||||
RUN pip install --user --extra-index-url $pip_extra_index_url cic-base[full_graph]==$cic_base_version \
|
RUN pip install --user --extra-index-url $pip_extra_index_url cic-base[full_graph]==$cic_base_version \
|
||||||
cic-eth==$cic_eth_version \
|
cic-eth==$cic_eth_version \
|
||||||
cic-contracts==$cic_contracts_version \
|
cic-contracts==$cic_contracts_version \
|
||||||
sarafu-faucet==$sarafu_faucet_version
|
sarafu-faucet==$sarafu_faucet_version \
|
||||||
|
sarafu-token==$sarafu_token_version
|
||||||
|
|
||||||
FROM python:3.8.6-slim-buster as runtime-image
|
FROM python:3.8.6-slim-buster as runtime-image
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
|
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
|
||||||
|
RUN apt-get install -y --no-install-recommends jq
|
||||||
|
|
||||||
COPY --from=compile-image /usr/local/bin/ /usr/local/bin/
|
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/etc/cic/ /usr/local/etc/cic/
|
||||||
|
@ -2,82 +2,112 @@
|
|||||||
|
|
||||||
set -a
|
set -a
|
||||||
|
|
||||||
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
|
CIC_CHAIN_SPEC=${CIC_CHAIN_SPEC:-evm:bloxberg:8995}
|
||||||
|
DEV_TOKEN_TYPE=${DEV_TOKEN_TYPE:-giftable}
|
||||||
DEV_ETH_ACCOUNT_RESERVE_MINTER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
|
DEV_ETH_ACCOUNT_RESERVE_MINTER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
|
||||||
DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
|
DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
|
||||||
DEV_RESERVE_AMOUNT=${DEV_ETH_RESERVE_AMOUNT:-""10000000000000000000000000000000000}
|
DEV_RESERVE_AMOUNT=${DEV_ETH_RESERVE_AMOUNT:-""10000000000000000000000000000000000}
|
||||||
faucet_amount=${DEV_FAUCET_AMOUNT:-0}
|
DEV_FAUCET_AMOUNT=${DEV_FAUCET_AMOUNT:-0}
|
||||||
keystore_file=$(realpath ./keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c)
|
DEV_ETH_KEYSTORE_FILE=${DEV_ETH_KEYSTORE_FILE:-`realpath ./keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c`}
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=`eth-checksum $(cat $DEV_ETH_KEYSTORE_FILE | jq -r .address)`
|
||||||
|
|
||||||
|
if [ ! -z $DEV_ETH_GAS_PRICE ]; then
|
||||||
|
gas_price_arg="--gas-price $DEV_ETH_GAS_PRICE"
|
||||||
|
>&2 echo using static gas price $DEV_ETH_GAS_PRICE
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $DEV_TOKEN_TYPE != 'giftable' && $DEV_TOKEN_TYPE != 'sarafu' ]]; then
|
||||||
|
echo $DEV_TOKEN_TYPE
|
||||||
|
>&2 echo DEV_TOKEN_TYPE must be one of [giftable,sarafu]
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "environment:"
|
echo "environment:"
|
||||||
printenv
|
printenv
|
||||||
echo \n
|
echo \n
|
||||||
|
|
||||||
|
echo "using wallet address '$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER' from keystore file $DEV_ETH_KEYSTORE_FILE"
|
||||||
|
|
||||||
# This is a grassroots team convention for building the Bancor contracts using the bancor protocol repository truffle setup
|
# This is a grassroots team convention for building the Bancor contracts using the bancor protocol repository truffle setup
|
||||||
# Running this in docker-internal dev container (built from Docker folder in this repo) will write a
|
# Running this in docker-internal dev container (built from Docker folder in this repo) will write a
|
||||||
# source-able env file to CIC_DATA_DIR. Services dependent on these contracts can mount this file OR
|
# source-able env file to CIC_DATA_DIR. Services dependent on these contracts can mount this file OR
|
||||||
# define these parameters at runtime
|
# define these parameters at runtime
|
||||||
# pushd /usr/src
|
# pushd /usr/src
|
||||||
|
|
||||||
|
if [ -z $CIC_DATA_DIR ]; then
|
||||||
|
CIC_DATA_DIR=`mktemp -d`
|
||||||
|
fi
|
||||||
|
>&2 echo using data dir $CIC_DATA_DIR
|
||||||
|
|
||||||
init_level_file=${CIC_DATA_DIR}/.init
|
init_level_file=${CIC_DATA_DIR}/.init
|
||||||
if [ ! -f ${CIC_DATA_DIR}/.init ]; then
|
if [ ! -f ${CIC_DATA_DIR}/.init ]; then
|
||||||
echo "Creating .init file..."
|
echo "Creating .init file..."
|
||||||
mkdir -p $CIC_DATA_DIR
|
mkdir -p $CIC_DATA_DIR
|
||||||
touch /tmp/cic/config/.init
|
touch $CIC_DATA_DIR/.init
|
||||||
# touch $init_level_file
|
# touch $init_level_file
|
||||||
fi
|
fi
|
||||||
echo -n 1 > $init_level_file
|
echo -n 1 > $init_level_file
|
||||||
|
|
||||||
# Abort on any error (including if wait-for-it fails).
|
# Abort on any error (including if wait-for-it fails).
|
||||||
set -e
|
|
||||||
|
|
||||||
# Wait for the backend to be up, if we know where it is.
|
# Wait for the backend to be up, if we know where it is.
|
||||||
if [[ -n "${ETH_PROVIDER}" ]]; then
|
if [[ -n "${ETH_PROVIDER}" ]]; then
|
||||||
|
|
||||||
|
if [ ! -z "$DEV_USE_DOCKER_WAIT_SCRIPT" ]; then
|
||||||
echo "waiting for ${ETH_PROVIDER}..."
|
echo "waiting for ${ETH_PROVIDER}..."
|
||||||
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
|
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
|
||||||
|
fi
|
||||||
|
|
||||||
DEV_RESERVE_ADDRESS=`giftable-token-deploy -p $ETH_PROVIDER -y $keystore_file -i $CIC_CHAIN_SPEC -v -w --name "Sarafu" --symbol "SRF" --decimals 6`
|
if [ $DEV_TOKEN_TYPE == 'giftable' ]; then
|
||||||
giftable-token-gift -p $ETH_PROVIDER -y $keystore_file -i $CIC_CHAIN_SPEC -v -w -a $DEV_RESERVE_ADDRESS $DEV_RESERVE_AMOUNT
|
>&2 echo "deploying 'giftable token'"
|
||||||
|
DEV_RESERVE_ADDRESS=`giftable-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Giftable Token" --symbol "GFT" --decimals 6 -vv`
|
||||||
|
else
|
||||||
|
>&2 echo "deploying 'sarafu' token'"
|
||||||
|
DEV_RESERVE_ADDRESS=`sarafu-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Sarafu" --decimals 6 -vv SRF $DEV_SARAFU_DEMURRAGE_LEVEL`
|
||||||
|
fi
|
||||||
|
giftable-token-gift $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w -a $DEV_RESERVE_ADDRESS $DEV_RESERVE_AMOUNT
|
||||||
|
|
||||||
#BANCOR_REGISTRY_ADDRESS=`cic-bancor-deploy --bancor-dir /usr/local/share/cic/bancor -z $DEV_ETH_RESERVE_ADDRESS -p $ETH_PROVIDER -o $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER`
|
#BANCOR_REGISTRY_ADDRESS=`cic-bancor-deploy $gas_price_arg --bancor-dir /usr/local/share/cic/bancor -z $DEV_ETH_RESERVE_ADDRESS -p $ETH_PROVIDER -o $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER`
|
||||||
|
|
||||||
>&2 echo "deploy account index contract"
|
>&2 echo "deploy account index contract"
|
||||||
DEV_ACCOUNT_INDEX_ADDRESS=`eth-accounts-index-deploy -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -y $keystore_file -vv -w`
|
DEV_ACCOUNT_INDEX_ADDRESS=`eth-accounts-index-deploy $gas_price_arg -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -vv -w`
|
||||||
>&2 echo "add deployer address as account index writer"
|
>&2 echo "add deployer address as account index writer"
|
||||||
eth-accounts-index-writer -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_ACCOUNT_INDEX_ADDRESS -ww $debug $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER
|
eth-accounts-index-writer $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_ACCOUNT_INDEX_ADDRESS -ww -vv $debug $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER
|
||||||
|
|
||||||
CIC_REGISTRY_ADDRESS=`eth-contract-registry-deploy -i $CIC_CHAIN_SPEC -y $keystore_file --identifier BancorRegistry --identifier AccountRegistry --identifier TokenRegistry --identifier AddressDeclarator --identifier Faucet --identifier TransferAuthorization -p $ETH_PROVIDER -vv -w`
|
CIC_REGISTRY_ADDRESS=`eth-contract-registry-deploy $gas_price_arg -i $CIC_CHAIN_SPEC -y $DEV_ETH_KEYSTORE_FILE --identifier BancorRegistry --identifier AccountRegistry --identifier TokenRegistry --identifier AddressDeclarator --identifier Faucet --identifier TransferAuthorization -p $ETH_PROVIDER -vv -w`
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv ContractRegistry $CIC_REGISTRY_ADDRESS
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv ContractRegistry $CIC_REGISTRY_ADDRESS
|
||||||
#cic-registry-set -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -k BancorRegistry -p $ETH_PROVIDER $BANCOR_REGISTRY_ADDRESS -vv
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AccountRegistry $DEV_ACCOUNT_INDEX_ADDRESS
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AccountRegistry $DEV_ACCOUNT_INDEX_ADDRESS
|
|
||||||
|
|
||||||
# Deploy address declarator registry
|
# Deploy address declarator registry
|
||||||
>&2 echo "deploy address declarator contract"
|
>&2 echo "deploy address declarator contract"
|
||||||
declarator_description=0x546869732069732074686520434943206e6574776f726b000000000000000000
|
declarator_description=0x546869732069732074686520434943206e6574776f726b000000000000000000
|
||||||
DEV_DECLARATOR_ADDRESS=`eth-address-declarator-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v $declarator_description`
|
DEV_DECLARATOR_ADDRESS=`eth-address-declarator-deploy -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv $declarator_description`
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AddressDeclarator $DEV_DECLARATOR_ADDRESS
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AddressDeclarator $DEV_DECLARATOR_ADDRESS
|
||||||
|
|
||||||
# Deploy transfer authorization contact
|
# Deploy transfer authorization contact
|
||||||
>&2 echo "deploy address declarator contract"
|
>&2 echo "deploy address declarator contract"
|
||||||
DEV_TRANSFER_AUTHORIZATION_ADDRESS=`erc20-transfer-auth-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v`
|
DEV_TRANSFER_AUTHORIZATION_ADDRESS=`erc20-transfer-auth-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv`
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TransferAuthorization $DEV_TRANSFER_AUTHORIZATION_ADDRESS
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TransferAuthorization $DEV_TRANSFER_AUTHORIZATION_ADDRESS
|
||||||
|
|
||||||
# Deploy token index contract
|
# Deploy token index contract
|
||||||
>&2 echo "deploy token index contract"
|
>&2 echo "deploy token index contract"
|
||||||
DEV_TOKEN_INDEX_ADDRESS=`eth-token-index-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v`
|
DEV_TOKEN_INDEX_ADDRESS=`eth-token-index-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv`
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TokenRegistry $DEV_TOKEN_INDEX_ADDRESS
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TokenRegistry $DEV_TOKEN_INDEX_ADDRESS
|
||||||
>&2 echo "add reserve token to token index"
|
>&2 echo "add reserve token to token index"
|
||||||
eth-token-index-add -w -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv -a $DEV_TOKEN_INDEX_ADDRESS $DEV_RESERVE_ADDRESS
|
eth-token-index-add $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv -a $DEV_TOKEN_INDEX_ADDRESS $DEV_RESERVE_ADDRESS
|
||||||
|
|
||||||
# Sarafu faucet contract
|
# Sarafu faucet contract
|
||||||
>&2 echo "deploy token faucet contract"
|
>&2 echo "deploy token faucet contract"
|
||||||
DEV_FAUCET_ADDRESS=`sarafu-faucet-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v --account-index-address $DEV_ACCOUNT_INDEX_ADDRESS $DEV_RESERVE_ADDRESS`
|
DEV_FAUCET_ADDRESS=`sarafu-faucet-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv --account-index-address $DEV_ACCOUNT_INDEX_ADDRESS $DEV_RESERVE_ADDRESS`
|
||||||
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv Faucet $DEV_FAUCET_ADDRESS
|
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv Faucet $DEV_FAUCET_ADDRESS
|
||||||
>&2 echo "set faucet as token minter"
|
>&2 echo "set faucet as token minter"
|
||||||
giftable-token-minter -w -y $keystore_file -a $DEV_RESERVE_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv $DEV_FAUCET_ADDRESS
|
giftable-token-minter $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -a $DEV_RESERVE_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv $DEV_FAUCET_ADDRESS
|
||||||
|
|
||||||
>&2 echo "set token faucet amount"
|
>&2 echo "set token faucet amount"
|
||||||
sarafu-faucet-set -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_FAUCET_ADDRESS $faucet_amount
|
sarafu-faucet-set $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_FAUCET_ADDRESS -vv $DEV_FAUCET_AMOUNT
|
||||||
|
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -17,7 +17,7 @@ from hexathon import (
|
|||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend import MemBackend
|
from chainsyncer.backend.memory import MemBackend
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.block import (
|
from chainlib.eth.block import (
|
||||||
@ -25,13 +25,13 @@ from chainlib.eth.block import (
|
|||||||
block_by_number,
|
block_by_number,
|
||||||
Block,
|
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.address import to_checksum_address
|
||||||
from chainlib.eth.erc20 import ERC20
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.gas import OverrideGasOracle
|
from chainlib.eth.gas import OverrideGasOracle
|
||||||
from chainlib.eth.nonce import RPCNonceOracle
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
from chainlib.eth.tx import TxFactory
|
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.eth.error import EthException
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
@ -17,7 +17,7 @@ from hexathon import (
|
|||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend import MemBackend
|
from chainsyncer.backend.memory import MemBackend
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.block import (
|
from chainlib.eth.block import (
|
||||||
@ -25,13 +25,13 @@ from chainlib.eth.block import (
|
|||||||
block_by_number,
|
block_by_number,
|
||||||
Block,
|
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.address import to_checksum_address
|
||||||
from chainlib.eth.erc20 import ERC20
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.gas import OverrideGasOracle
|
from chainlib.eth.gas import OverrideGasOracle
|
||||||
from chainlib.eth.nonce import RPCNonceOracle
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
from chainlib.eth.tx import TxFactory
|
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.eth.error import EthException
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
@ -24,7 +24,7 @@ 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 import AccountRegistry
|
||||||
from 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
|
||||||
from crypto_dev_signer.keystore.keyfile import to_dict as to_keyfile_dict
|
from crypto_dev_signer.keystore.keyfile import to_dict as to_keyfile_dict
|
||||||
|
2261
apps/contract-migration/scripts/package-lock.json
generated
2261
apps/contract-migration/scripts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cic-client-meta": "^0.0.7-alpha.6",
|
"cic-client-meta": "0.0.7-alpha.6",
|
||||||
"vcard-parser": "^1.0.0"
|
"vcard-parser": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
cic-base[full_graph]==0.1.2a67
|
cic-base[full_graph]==0.1.2a77
|
||||||
sarafu-faucet==0.0.2a20
|
sarafu-faucet==0.0.2a28
|
||||||
cic-eth==0.11.0b3
|
cic-eth==0.11.0b6
|
||||||
cic-types==0.1.0a10
|
cic-types==0.1.0a10
|
||||||
crypto-dev-signer==0.4.14b1
|
crypto-dev-signer==0.4.14b2
|
||||||
|
@ -22,7 +22,7 @@ from hexathon import (
|
|||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend import MemBackend
|
from chainsyncer.backend.memory import MemBackend
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
@ -32,7 +32,7 @@ from chainlib.eth.block import (
|
|||||||
block_by_number,
|
block_by_number,
|
||||||
Block,
|
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.address import to_checksum_address
|
||||||
from chainlib.eth.erc20 import ERC20
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.gas import (
|
from chainlib.eth.gas import (
|
||||||
@ -40,7 +40,7 @@ from chainlib.eth.gas import (
|
|||||||
balance,
|
balance,
|
||||||
)
|
)
|
||||||
from chainlib.eth.tx import TxFactory
|
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.eth.error import EthException
|
||||||
from cic_types.models.person import (
|
from cic_types.models.person import (
|
||||||
Person,
|
Person,
|
||||||
@ -57,6 +57,7 @@ custodial_tests = [
|
|||||||
'local_key',
|
'local_key',
|
||||||
'gas',
|
'gas',
|
||||||
'faucet',
|
'faucet',
|
||||||
|
'ussd'
|
||||||
]
|
]
|
||||||
|
|
||||||
metadata_tests = [
|
metadata_tests = [
|
||||||
|
@ -24,7 +24,7 @@ variables:
|
|||||||
IMAGE_TAG_BASE: $CI_REGISTRY_IMAGE/$APP_NAME:$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
IMAGE_TAG_BASE: $CI_REGISTRY_IMAGE/$APP_NAME:$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
||||||
LATEST_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:latest
|
LATEST_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:latest
|
||||||
script:
|
script:
|
||||||
- export IMAGE_TAG="$IMAGE_TAG_BASE-$(date +%F.%H%M%S)"
|
- export IMAGE_TAG="$IMAGE_TAG_BASE-$(date +%s)"
|
||||||
- mkdir -p /kaniko/.docker
|
- mkdir -p /kaniko/.docker
|
||||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
|
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
|
||||||
# - /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
|
# - /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
|
||||||
|
@ -85,6 +85,7 @@ services:
|
|||||||
# ETH_PROVIDER should be broken out into host/port but cic-eth expects this
|
# ETH_PROVIDER should be broken out into host/port but cic-eth expects this
|
||||||
ETH_PROVIDER: http://eth:8545
|
ETH_PROVIDER: http://eth:8545
|
||||||
# And these two are for wait-for-it (could parse this)
|
# And these two are for wait-for-it (could parse this)
|
||||||
|
DEV_USE_DOCKER_WAIT_SCRIPT: 1
|
||||||
ETH_PROVIDER_HOST: eth
|
ETH_PROVIDER_HOST: eth
|
||||||
ETH_PROVIDER_PORT: 8545
|
ETH_PROVIDER_PORT: 8545
|
||||||
CIC_CHAIN_SPEC: ${CIC_CHAIN_SPEC:-evm:bloxberg:8996}
|
CIC_CHAIN_SPEC: ${CIC_CHAIN_SPEC:-evm:bloxberg:8996}
|
||||||
@ -103,6 +104,9 @@ services:
|
|||||||
DEV_PIP_EXTRA_INDEX_URL: ${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomics.net:8433}
|
DEV_PIP_EXTRA_INDEX_URL: ${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomics.net:8433}
|
||||||
RUN_MASK: ${RUN_MASK:-0} # bit flags; 1: contract migrations 2: seed data
|
RUN_MASK: ${RUN_MASK:-0} # bit flags; 1: contract migrations 2: seed data
|
||||||
DEV_FAUCET_AMOUNT: ${DEV_FAUCET_AMOUNT:-0}
|
DEV_FAUCET_AMOUNT: ${DEV_FAUCET_AMOUNT:-0}
|
||||||
|
DEV_TOKEN_TYPE: ${DEV_TOKEN_TYPE:-giftable}
|
||||||
|
DEV_SARAFU_DEMURRAGE_LEVEL: ${DEV_SARAFU_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
|
||||||
|
DEV_ETH_GAS_PRICE: ${DEV_ETH_GAS_PRICE:-1}
|
||||||
command: ["./run_job.sh"]
|
command: ["./run_job.sh"]
|
||||||
#command: ["./reset.sh"]
|
#command: ["./reset.sh"]
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -145,7 +149,7 @@ services:
|
|||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
if [[ -f /tmp/cic/config/.env ]]; then source /tmp/cic/config/.env; fi
|
if [[ -f /tmp/cic/config/.env ]]; then source /tmp/cic/config/.env; fi
|
||||||
/usr/local/bin/cic-cache-trackerd -vv -c /usr/local/etc/cic-cache
|
./start_tracker.sh -c /usr/local/etc/cic-cache -vv
|
||||||
volumes:
|
volumes:
|
||||||
- contract-config:/tmp/cic/config/:ro
|
- contract-config:/tmp/cic/config/:ro
|
||||||
|
|
||||||
@ -191,13 +195,13 @@ services:
|
|||||||
context: apps
|
context: apps
|
||||||
dockerfile: cic-cache/docker/Dockerfile
|
dockerfile: cic-cache/docker/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
DATABASE_USER: $DATABASE_USER
|
DATABASE_USER: ${DATABASE_USER:-grassroots}
|
||||||
DATABASE_HOST: $DATABASE_HOST
|
DATABASE_HOST: ${DATABASE_HOST:-postgres}
|
||||||
DATABASE_PORT: $DATABASE_PORT
|
DATABASE_PORT: ${DATABASE_PORT:-5432}
|
||||||
DATABASE_PASSWORD: $DATABASE_PASSWORD
|
#DATABASE_PASSWORD: ${DATABASE_PASSWORD:-
|
||||||
DATABASE_NAME: $DATABASE_NAME_CIC_CACHE
|
DATABASE_NAME: ${DATABASE_NAME_CIC_CACHE:-cic_cache}
|
||||||
DATABASE_DEBUG: 1
|
DATABASE_DEBUG: 1
|
||||||
PGPASSWORD: $DATABASE_PASSWORD
|
#PGPASSWORD: $DATABASE_PASSWORD
|
||||||
SERVER_PORT: 8000
|
SERVER_PORT: 8000
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT_CIC_CACHE:-63313}:8000
|
- ${HTTP_PORT_CIC_CACHE:-63313}:8000
|
||||||
@ -212,9 +216,10 @@ services:
|
|||||||
- |
|
- |
|
||||||
if [[ -f /tmp/cic/config/.env ]]; then source /tmp/cic/config/.env; fi
|
if [[ -f /tmp/cic/config/.env ]]; then source /tmp/cic/config/.env; fi
|
||||||
"/usr/local/bin/uwsgi" \
|
"/usr/local/bin/uwsgi" \
|
||||||
--wsgi-file /usr/src/cic-cache/cic_cache/runnable/serverd.py \
|
--wsgi-file /usr/src/cic-cache/cic_cache/runnable/daemons/server.py \
|
||||||
--http :8000 \
|
--http :8000 \
|
||||||
--pyargv -vv
|
--pyargv "-vv"
|
||||||
|
|
||||||
|
|
||||||
cic-eth-tasker:
|
cic-eth-tasker:
|
||||||
# image: grassrootseconomics:cic-eth-service
|
# image: grassrootseconomics:cic-eth-service
|
||||||
@ -445,7 +450,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
command: "/root/start_tasker.sh -q cic-notify"
|
command: "/root/start_tasker.sh -q cic-notify -vv"
|
||||||
|
|
||||||
|
|
||||||
cic-meta-server:
|
cic-meta-server:
|
||||||
@ -493,6 +498,8 @@ services:
|
|||||||
DATABASE_NAME: cic_ussd
|
DATABASE_NAME: cic_ussd
|
||||||
DATABASE_ENGINE: postgresql
|
DATABASE_ENGINE: postgresql
|
||||||
DATABASE_DRIVER: psycopg2
|
DATABASE_DRIVER: psycopg2
|
||||||
|
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||||
|
CELERY_RESULT_URL: ${CELERY_BROKER_URL:-redis://redis}
|
||||||
PGP_PASSPHRASE: merman
|
PGP_PASSPHRASE: merman
|
||||||
SERVER_PORT: 9000
|
SERVER_PORT: 9000
|
||||||
CIC_META_URL: ${CIC_META_URL:-http://meta:8000}
|
CIC_META_URL: ${CIC_META_URL:-http://meta:8000}
|
||||||
|
@ -5,6 +5,7 @@ CREATE DATABASE "cic_notify";
|
|||||||
CREATE DATABASE "cic_meta";
|
CREATE DATABASE "cic_meta";
|
||||||
CREATE DATABASE "cic_signer";
|
CREATE DATABASE "cic_signer";
|
||||||
CREATE DATABASE "cic_ussd";
|
CREATE DATABASE "cic_ussd";
|
||||||
|
CREATE DATABASE "chain_sync";
|
||||||
GRANT ALL PRIVILEGES
|
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;
|
TO grassroots;
|
||||||
|
Loading…
Reference in New Issue
Block a user