Compare commits
11 Commits
lash/fix-c
...
lash/exter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428c0983f6 | ||
|
|
e2879cd22f
|
||
|
|
a32955cc6a
|
||
|
|
b1e228b8e3
|
||
|
|
6210fe05ef
|
||
|
|
65d3efd72b
|
||
|
|
d3ccac7dac
|
||
|
|
0079fc3fbb
|
||
|
|
08fba819e4
|
||
|
|
ffe7416040
|
||
|
|
68e02ba5b8
|
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,10 +1,2 @@
|
|||||||
service-configs/*
|
service-configs/*
|
||||||
!service-configs/.gitkeep
|
!service-configs/.gitkeep
|
||||||
**/node_modules/
|
|
||||||
__pycache__
|
|
||||||
*.pyc
|
|
||||||
*.o
|
|
||||||
gmon.out
|
|
||||||
*.egg-info
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ 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,10 +26,9 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend.sql import SQLBackend
|
from chainsyncer.backend import SyncerBackend
|
||||||
from chainsyncer.driver import (
|
from chainsyncer.driver import (
|
||||||
HeadSyncer,
|
HeadSyncer,
|
||||||
HistorySyncer,
|
|
||||||
)
|
)
|
||||||
from chainsyncer.db.models.base import SessionBase
|
from chainsyncer.db.models.base import SessionBase
|
||||||
|
|
||||||
@@ -71,21 +70,19 @@ def main():
|
|||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SQLBackend.first(chain_spec):
|
#if SyncerBackend.first(chain_spec):
|
||||||
# backend = SQLBackend.initial(chain_spec, block_offset)
|
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
syncer_backends = SyncerBackend.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(SQLBackend.initial(chain_spec, block_offset))
|
syncer_backends.append(SyncerBackend.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))
|
||||||
|
|
||||||
for syncer_backend in syncer_backends:
|
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1))
|
||||||
syncers.append(HistorySyncer(syncer_backend))
|
|
||||||
|
|
||||||
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.2a76
|
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58
|
||||||
|
|
||||||
COPY cic-cache/requirements.txt ./
|
COPY cic-cache/requirements.txt ./
|
||||||
COPY cic-cache/setup.cfg \
|
COPY cic-cache/setup.cfg \
|
||||||
@@ -47,9 +47,6 @@ 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
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
. ./db.sh
|
|
||||||
|
|
||||||
if [ $? -ne "0" ]; then
|
|
||||||
>&2 echo db migrate fail
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
/usr/local/bin/cic-cache-trackerd $@
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
cic-base~=0.1.2a77
|
cic-base~=0.1.2a58
|
||||||
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.4a16
|
cic-eth-registry~=0.5.4a10
|
||||||
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
|
||||||
chainsyncer[sql]~=0.0.2a2
|
chainlib~=0.0.2a2
|
||||||
|
chainsyncer~=0.0.1a21
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import celery
|
import celery
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
@@ -32,9 +32,7 @@ def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.AL
|
|||||||
:returns: New lock state for address
|
:returns: New lock state for address
|
||||||
:rtype: number
|
:rtype: number
|
||||||
"""
|
"""
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
|
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
|
||||||
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
|
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
|
||||||
return chained_input
|
return chained_input
|
||||||
@@ -53,9 +51,7 @@ def unlock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.
|
|||||||
:returns: New lock state for address
|
:returns: New lock state for address
|
||||||
:rtype: number
|
:rtype: number
|
||||||
"""
|
"""
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
r = Lock.reset(chain_str, flags, address=address)
|
r = Lock.reset(chain_str, flags, address=address)
|
||||||
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
|
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
|
||||||
return chained_input
|
return chained_input
|
||||||
@@ -131,9 +127,7 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
|
|||||||
|
|
||||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
@celery_app.task(base=CriticalSQLAlchemyTask)
|
||||||
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
|
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
|
||||||
if address != None:
|
if address != None:
|
||||||
@@ -145,9 +139,3 @@ def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
|||||||
session.flush()
|
session.flush()
|
||||||
session.close()
|
session.close()
|
||||||
return chained_input
|
return chained_input
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
|
||||||
def shutdown(message):
|
|
||||||
logg.critical('shutdown called: {}'.format(message))
|
|
||||||
celery_app.control.shutdown() #broadcast('shutdown')
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=BaseTask)
|
|
||||||
def default_token(self):
|
|
||||||
return {
|
|
||||||
'symbol': self.default_token_symbol,
|
|
||||||
'address': self.default_token_address,
|
|
||||||
}
|
|
||||||
@@ -62,18 +62,6 @@ class Api:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_token(self):
|
|
||||||
s_token = celery.signature(
|
|
||||||
'cic_eth.admin.token.default_token',
|
|
||||||
[],
|
|
||||||
queue=self.queue,
|
|
||||||
)
|
|
||||||
if self.callback_param != None:
|
|
||||||
s_token.link(self.callback_success)
|
|
||||||
|
|
||||||
return s_token.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
||||||
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
from cic_eth.db.models.base import SessionBase
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
session.execute('SELECT count(*) from alembic_version')
|
|
||||||
session.close()
|
|
||||||
return True
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
from chainlib.eth.gas import balance
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.db.models.role import AccountRole
|
|
||||||
from cic_eth.db.models.base import SessionBase
|
|
||||||
from cic_eth.db.enum import LockEnum
|
|
||||||
from cic_eth.error import LockedError
|
|
||||||
from cic_eth.admin.ctrl import check_lock
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
|
|
||||||
config = kwargs['config']
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|
||||||
logg.debug('check gas balance of gas gifter for chain {}'.format(chain_spec))
|
|
||||||
|
|
||||||
try:
|
|
||||||
check_lock(None, None, LockEnum.INIT)
|
|
||||||
except LockedError:
|
|
||||||
logg.warning('INIT lock is set, skipping GAS GIFTER balance check.')
|
|
||||||
return True
|
|
||||||
|
|
||||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
|
||||||
o = balance(gas_provider)
|
|
||||||
r = rpc.do(o)
|
|
||||||
try:
|
|
||||||
r = int(r, 16)
|
|
||||||
except TypeError:
|
|
||||||
r = int(r)
|
|
||||||
gas_min = int(config.get('ETH_GAS_GIFTER_MINIMUM_BALANCE'))
|
|
||||||
if r < gas_min:
|
|
||||||
logg.error('EEK! gas gifter has balance {}, below minimum {}'.format(r, gas_min))
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# external imports
|
|
||||||
import redis
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
r = redis.Redis(
|
|
||||||
host=kwargs['config'].get('REDIS_HOST'),
|
|
||||||
port=kwargs['config'].get('REDIS_PORT'),
|
|
||||||
db=kwargs['config'].get('REDIS_DB'),
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
r.set(kwargs['unit'], os.getpid())
|
|
||||||
except redis.connection.ConnectionError:
|
|
||||||
return False
|
|
||||||
except redis.connection.ResponseError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from urllib.error import URLError
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from chainlib.eth.sign import sign_message
|
|
||||||
from chainlib.error import JSONRPCException
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
blocked = True
|
|
||||||
max_attempts = 5
|
|
||||||
conn = RPCConnection.connect(kwargs['config'].get('CIC_CHAIN_SPEC'), tag='signer')
|
|
||||||
for i in range(max_attempts):
|
|
||||||
idx = i + 1
|
|
||||||
logg.debug('attempt signer connection check {}/{}'.format(idx, max_attempts))
|
|
||||||
try:
|
|
||||||
conn.do(sign_message(ZERO_ADDRESS, '0x2a'))
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except ConnectionError:
|
|
||||||
pass
|
|
||||||
except URLError:
|
|
||||||
pass
|
|
||||||
except JSONRPCException:
|
|
||||||
logg.debug('signer connection succeeded')
|
|
||||||
return True
|
|
||||||
|
|
||||||
if idx < max_attempts:
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
return False
|
|
||||||
@@ -74,11 +74,10 @@ class LockEnum(enum.IntEnum):
|
|||||||
QUEUE: Disable queueing new or modified transactions
|
QUEUE: Disable queueing new or modified transactions
|
||||||
"""
|
"""
|
||||||
STICKY=1
|
STICKY=1
|
||||||
INIT=2
|
CREATE=2
|
||||||
CREATE=4
|
SEND=4
|
||||||
SEND=8
|
QUEUE=8
|
||||||
QUEUE=16
|
QUERY=16
|
||||||
QUERY=32
|
|
||||||
ALL=int(0xfffffffffffffffe)
|
ALL=int(0xfffffffffffffffe)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ Revises: 1f1b3b641d08
|
|||||||
Create Date: 2021-04-02 18:41:20.864265
|
Create Date: 2021-04-02 18:41:20.864265
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from cic_eth.db.enum import LockEnum
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -26,11 +23,10 @@ def upgrade():
|
|||||||
sa.Column("address", sa.String(42), nullable=True),
|
sa.Column("address", sa.String(42), nullable=True),
|
||||||
sa.Column('blockchain', sa.String),
|
sa.Column('blockchain', sa.String),
|
||||||
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
|
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
|
||||||
sa.Column("date_created", sa.DateTime, nullable=False, default=datetime.datetime.utcnow),
|
sa.Column("date_created", sa.DateTime, nullable=False),
|
||||||
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True),
|
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True),
|
||||||
)
|
)
|
||||||
op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True)
|
op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True)
|
||||||
op.execute("INSERT INTO lock (address, date_created, blockchain, flags) VALUES('{}', '{}', '::', {})".format(ZERO_ADDRESS, datetime.datetime.utcnow(), LockEnum.INIT | LockEnum.SEND | LockEnum.QUEUE))
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from sqlalchemy.pool import (
|
|||||||
StaticPool,
|
StaticPool,
|
||||||
QueuePool,
|
QueuePool,
|
||||||
AssertionPool,
|
AssertionPool,
|
||||||
NullPool,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -65,7 +64,6 @@ class SessionBase(Model):
|
|||||||
if SessionBase.poolable:
|
if SessionBase.poolable:
|
||||||
poolclass = QueuePool
|
poolclass = QueuePool
|
||||||
if pool_size > 1:
|
if pool_size > 1:
|
||||||
logg.info('db using queue pool')
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
max_overflow=pool_size*3,
|
max_overflow=pool_size*3,
|
||||||
@@ -76,22 +74,17 @@ class SessionBase(Model):
|
|||||||
echo=debug,
|
echo=debug,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if pool_size == 0:
|
if debug:
|
||||||
logg.info('db using nullpool')
|
|
||||||
poolclass = NullPool
|
|
||||||
elif debug:
|
|
||||||
logg.info('db using assertion pool')
|
|
||||||
poolclass = AssertionPool
|
poolclass = AssertionPool
|
||||||
else:
|
else:
|
||||||
logg.info('db using static pool')
|
|
||||||
poolclass = StaticPool
|
poolclass = StaticPool
|
||||||
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
poolclass=poolclass,
|
poolclass=poolclass,
|
||||||
echo=debug,
|
echo=debug,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logg.info('db not poolable')
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
echo=debug,
|
echo=debug,
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ class RoleMissingError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IntegrityError(Exception):
|
class IntegrityError(Exception):
|
||||||
"""Exception raised to signal irregularities with deduplication and ordering of tasks
|
"""Exception raised to signal irregularities with deduplication and ordering of tasks
|
||||||
|
|
||||||
@@ -64,19 +62,15 @@ class LockedError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SeppukuError(Exception):
|
class SignerError(Exception):
|
||||||
"""Exception base class for all errors that should cause system shutdown
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SignerError(SeppukuError):
|
|
||||||
"""Exception raised when signer is unavailable or generates an error
|
"""Exception raised when signer is unavailable or generates an error
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RoleAgencyError(SeppukuError):
|
class EthError(Exception):
|
||||||
"""Exception raise when a role cannot perform its function. This is a critical exception
|
"""Exception raised when unspecified error from evm node is encountered
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import logging
|
|||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
from erc20_single_shot_faucet import SingleShotFaucet as Faucet
|
from erc20_single_shot_faucet import SingleShotFaucet as Faucet
|
||||||
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
from chainlib.eth.sign import (
|
from chainlib.eth.sign import (
|
||||||
new_account,
|
new_account,
|
||||||
@@ -19,7 +19,6 @@ from chainlib.eth.tx import (
|
|||||||
unpack,
|
unpack,
|
||||||
)
|
)
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.error import JSONRPCException
|
|
||||||
from eth_accounts_index import AccountRegistry
|
from eth_accounts_index import AccountRegistry
|
||||||
from sarafu_faucet import MinterFaucet as Faucet
|
from sarafu_faucet import MinterFaucet as Faucet
|
||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
@@ -71,18 +70,11 @@ def create(self, password, chain_spec_dict):
|
|||||||
a = None
|
a = None
|
||||||
conn = RPCConnection.connect(chain_spec, 'signer')
|
conn = RPCConnection.connect(chain_spec, 'signer')
|
||||||
o = new_account()
|
o = new_account()
|
||||||
try:
|
a = conn.do(o)
|
||||||
a = conn.do(o)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
|
|
||||||
# TODO: It seems infeasible that a can be None in any case, verify
|
|
||||||
if a == None:
|
if a == None:
|
||||||
raise SignerError('create account')
|
raise SignerError('create account')
|
||||||
|
|
||||||
logg.debug('created account {}'.format(a))
|
logg.debug('created account {}'.format(a))
|
||||||
|
|
||||||
# Initialize nonce provider record for account
|
# Initialize nonce provider record for account
|
||||||
@@ -227,22 +219,21 @@ def have(self, account, chain_spec_dict):
|
|||||||
"""
|
"""
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
o = sign_message(account, '0x2a')
|
o = sign_message(account, '0x2a')
|
||||||
conn = RPCConnection.connect(chain_spec, 'signer')
|
try:
|
||||||
|
conn = RPCConnection.connect(chain_spec, 'signer')
|
||||||
|
except Exception as e:
|
||||||
|
logg.debug('cannot sign with {}: {}'.format(account, e))
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
except ConnectionError as e:
|
conn.disconnect()
|
||||||
raise SignerError(e)
|
return account
|
||||||
except FileNotFoundError as e:
|
except Exception as e:
|
||||||
raise SignerError(e)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
logg.debug('cannot sign with {}: {}'.format(account, e))
|
logg.debug('cannot sign with {}: {}'.format(account, e))
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
conn.disconnect()
|
|
||||||
return account
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
|
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
|
||||||
def set_role(self, tag, address, chain_spec_dict):
|
def set_role(self, tag, address, chain_spec_dict):
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ def balance(tokens, holder_address, chain_spec_dict):
|
|||||||
|
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
address = t['address']
|
address = t['address']
|
||||||
token = ERC20Token(chain_spec, rpc, address)
|
token = ERC20Token(rpc, address)
|
||||||
c = ERC20(chain_spec)
|
c = ERC20()
|
||||||
o = c.balance_of(address, holder_address, sender_address=caller_address)
|
o = c.balance_of(address, holder_address, sender_address=caller_address)
|
||||||
r = rpc.do(o)
|
r = rpc.do(o)
|
||||||
t['balance_network'] = c.parse_balance(r)
|
t['balance_network'] = c.parse_balance(r)
|
||||||
@@ -108,13 +108,7 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_d
|
|||||||
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
||||||
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
|
|
||||||
|
|
||||||
rpc_signer.disconnect()
|
rpc_signer.disconnect()
|
||||||
rpc.disconnect()
|
rpc.disconnect()
|
||||||
@@ -177,12 +171,7 @@ def approve(self, tokens, holder_address, spender_address, value, chain_spec_dic
|
|||||||
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
||||||
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
|
|
||||||
rpc_signer.disconnect()
|
rpc_signer.disconnect()
|
||||||
rpc.disconnect()
|
rpc.disconnect()
|
||||||
|
|||||||
@@ -328,12 +328,7 @@ def refill_gas(self, recipient_address, chain_spec_dict):
|
|||||||
|
|
||||||
# build and add transaction
|
# build and add transaction
|
||||||
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
|
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
|
||||||
cache_task = 'cic_eth.eth.gas.cache_gas_data'
|
cache_task = 'cic_eth.eth.gas.cache_gas_data'
|
||||||
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
||||||
@@ -409,12 +404,7 @@ def resend_with_higher_gas(self, txold_hash_hex, chain_spec_dict, gas=None, defa
|
|||||||
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
|
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
|
||||||
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
|
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
|
||||||
tx['gasPrice'] = new_gas_price
|
tx['gasPrice'] = new_gas_price
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
queue_create(
|
queue_create(
|
||||||
chain_spec,
|
chain_spec,
|
||||||
tx['nonce'],
|
tx['nonce'],
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
# external imports
|
# extended 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:
|
||||||
@@ -31,12 +27,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, caller_address=ZERO_ADDRESS):
|
def set_actors(self, sender, recipient, trusted_declarator_addresses=None):
|
||||||
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, sender_address=caller_address)
|
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec)
|
||||||
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
|
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec)
|
||||||
|
|
||||||
|
|
||||||
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
||||||
@@ -44,8 +40,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.chain_spec, self.rpc, source)
|
st = ERC20Token(self.rpc, source)
|
||||||
dt = ERC20Token(self.chain_spec, self.rpc, destination)
|
dt = ERC20Token(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
|
||||||
@@ -66,10 +62,10 @@ class ExtendedTx:
|
|||||||
self.status_code = n
|
self.status_code = n
|
||||||
|
|
||||||
|
|
||||||
def asdict(self):
|
def to_dict(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', 'asdict', 'rpc']:
|
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'to_dict']:
|
||||||
continue
|
continue
|
||||||
o[attr] = getattr(self, attr)
|
o[attr] = getattr(self, attr)
|
||||||
return o
|
return o
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
|||||||
except NotFoundEthException as e:
|
except NotFoundEthException as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TODO: apply receipt in tx object to validate and normalize input
|
|
||||||
if rcpt != None:
|
if rcpt != None:
|
||||||
success = rcpt['status'] == 1
|
success = rcpt['status'] == 1
|
||||||
logg.debug('sync tx {} mined block {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], success))
|
logg.debug('sync tx {} mined block {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], success))
|
||||||
@@ -181,7 +180,6 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
|||||||
[
|
[
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
rcpt['blockNumber'],
|
rcpt['blockNumber'],
|
||||||
rcpt['transactionIndex'],
|
|
||||||
not success,
|
not success,
|
||||||
],
|
],
|
||||||
queue=queue,
|
queue=queue,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def translate_address(address, trusted_addresses, chain_spec, sender_address=ZER
|
|||||||
registry = CICRegistry(chain_spec, rpc)
|
registry = CICRegistry(chain_spec, rpc)
|
||||||
|
|
||||||
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
||||||
c = AddressDeclarator(chain_spec)
|
c = AddressDeclarator()
|
||||||
|
|
||||||
for trusted_address in trusted_addresses:
|
for trusted_address in trusted_addresses:
|
||||||
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
|
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ def set_sent(chain_spec_dict, tx_hash, fail=False):
|
|||||||
|
|
||||||
|
|
||||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
@celery_app.task(base=CriticalSQLAlchemyTask)
|
||||||
def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
|
def set_final(chain_spec_dict, tx_hash, block=None, fail=False):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
|
r = chainqueue.state.set_final(chain_spec, tx_hash, block, fail, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ args_override = {
|
|||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
||||||
}
|
}
|
||||||
# override args
|
# override args
|
||||||
config.dict_override(args_override, 'cli')
|
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
config.censor('PASSWORD', 'SSL')
|
config.censor('PASSWORD', 'SSL')
|
||||||
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
||||||
@@ -68,9 +67,7 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
|
|||||||
|
|
||||||
queue = args.q
|
queue = args.q
|
||||||
|
|
||||||
chain_spec = None
|
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||||
if config.get('CIC_CHAIN_SPEC') != None and config.get('CIC_CHAIN_SPEC') != '::':
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|
||||||
admin_api = AdminApi(None)
|
admin_api = AdminApi(None)
|
||||||
|
|
||||||
|
|
||||||
@@ -85,9 +82,6 @@ def lock_names_to_flag(s):
|
|||||||
|
|
||||||
# TODO: move each command to submodule
|
# TODO: move each command to submodule
|
||||||
def main():
|
def main():
|
||||||
chain_spec_dict = None
|
|
||||||
if chain_spec != None:
|
|
||||||
chain_spec_dict = chain_spec.asdict()
|
|
||||||
if args.command == 'unlock':
|
if args.command == 'unlock':
|
||||||
flags = lock_names_to_flag(args.flags)
|
flags = lock_names_to_flag(args.flags)
|
||||||
if not is_checksum_address(args.address):
|
if not is_checksum_address(args.address):
|
||||||
@@ -97,7 +91,7 @@ def main():
|
|||||||
'cic_eth.admin.ctrl.unlock',
|
'cic_eth.admin.ctrl.unlock',
|
||||||
[
|
[
|
||||||
None,
|
None,
|
||||||
chain_spec_dict,
|
chain_spec.asdict(),
|
||||||
args.address,
|
args.address,
|
||||||
flags,
|
flags,
|
||||||
],
|
],
|
||||||
@@ -116,7 +110,7 @@ def main():
|
|||||||
'cic_eth.admin.ctrl.lock',
|
'cic_eth.admin.ctrl.lock',
|
||||||
[
|
[
|
||||||
None,
|
None,
|
||||||
chain_spec_dict,
|
chain_spec.asdict(),
|
||||||
args.address,
|
args.address,
|
||||||
flags,
|
flags,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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,
|
||||||
@@ -152,7 +153,10 @@ 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')
|
||||||
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
# external imports
|
# third-party 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,13 +9,7 @@ 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 (
|
from hexathon import strip_0x
|
||||||
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
|
||||||
@@ -24,73 +18,65 @@ 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, caller_address=ZERO_ADDRESS):
|
def __init__(self, chain_spec, method, queue):
|
||||||
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):
|
||||||
result['chain_spec'] = result['chain_spec'].asdict()
|
logg.debug('result {}'.format(result))
|
||||||
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,
|
||||||
)
|
)
|
||||||
@@ -106,29 +92,26 @@ 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 t
|
return s
|
||||||
|
|
||||||
|
|
||||||
def parse_data(self, tx, conn):
|
def parse_data(self, tx):
|
||||||
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 [
|
||||||
self.parse_transfer,
|
parse_transfer,
|
||||||
self.parse_transferfrom,
|
parse_transferfrom,
|
||||||
self.parse_giftto,
|
parse_giftto,
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
if tx:
|
(transfer_type, transfer_data) = parser(tx)
|
||||||
(transfer_type, transfer_data) = parser(tx, conn)
|
break
|
||||||
if transfer_type == None:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
except RequestMismatchException:
|
except RequestMismatchException:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -145,7 +128,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, conn)
|
(transfer_type, transfer_data) = self.parse_data(tx)
|
||||||
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
|
||||||
@@ -161,17 +144,16 @@ 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, caller_address=self.caller_address)
|
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses)
|
||||||
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)
|
||||||
result = tokentx.asdict()
|
t = self.call_back(transfer_type, tokentx.to_dict())
|
||||||
t = self.call_back(transfer_type, result)
|
logg.info('callback success task id {} tx {}'.format(t, tx.hash))
|
||||||
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(tx.queue, tx.method, transfer_data['to'], tx.hash))
|
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tc.queue, tc.method, transfer_data['to'], tx.hash))
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class TxFilter(SyncFilter):
|
|||||||
self.chain_spec.asdict(),
|
self.chain_spec.asdict(),
|
||||||
add_0x(tx_hash_hex),
|
add_0x(tx_hash_hex),
|
||||||
tx.block.number,
|
tx.block.number,
|
||||||
tx.index,
|
|
||||||
tx.status == Status.ERROR,
|
tx.status == Status.ERROR,
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from cic_eth.admin.ctrl import lock_send
|
|||||||
from cic_eth.db.enum import LockEnum
|
from cic_eth.db.enum import LockEnum
|
||||||
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
||||||
from cic_eth.sync.retry import RetrySyncer
|
from cic_eth.sync.retry import RetrySyncer
|
||||||
from cic_eth.stat import init_chain_stat
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -72,21 +71,57 @@ RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, tag='def
|
|||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
||||||
|
|
||||||
|
straggler_delay = int(config.get('CIC_TX_RETRY_DELAY'))
|
||||||
|
|
||||||
|
## TODO: we already have the signed raw tx in get, so its a waste of cycles to get_tx here
|
||||||
|
#def sendfail_filter(w3, tx_hash, rcpt, chain_spec):
|
||||||
|
# tx_dict = get_tx(tx_hash)
|
||||||
|
# tx = unpack(tx_dict['signed_tx'], chain_spec)
|
||||||
|
# logg.debug('submitting tx {} for retry'.format(tx_hash))
|
||||||
|
# s_check = celery.signature(
|
||||||
|
# 'cic_eth.admin.ctrl.check_lock',
|
||||||
|
[
|
||||||
|
# tx_hash,
|
||||||
|
# chain_str,
|
||||||
|
# LockEnum.QUEUE,
|
||||||
|
# tx['from'],
|
||||||
|
# ],
|
||||||
|
# queue=queue,
|
||||||
|
# )
|
||||||
|
## s_resume = celery.signature(
|
||||||
|
## 'cic_eth.eth.tx.resume_tx',
|
||||||
|
## [
|
||||||
|
## chain_str,
|
||||||
|
## ],
|
||||||
|
## queue=queue,
|
||||||
|
## )
|
||||||
|
#
|
||||||
|
## s_retry_status = celery.signature(
|
||||||
|
## 'cic_eth.queue.state.set_ready',
|
||||||
|
## [],
|
||||||
|
## queue=queue,
|
||||||
|
## )
|
||||||
|
# s_resend = celery.signature(
|
||||||
|
# 'cic_eth.eth.gas.resend_with_higher_gas',
|
||||||
|
# [
|
||||||
|
# chain_str,
|
||||||
|
# ],
|
||||||
|
# queue=queue,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# #s_resume.link(s_retry_status)
|
||||||
|
# #s_check.link(s_resume)
|
||||||
|
# s_check.link(s_resend)
|
||||||
|
# s_check.apply_async()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conn = RPCConnection.connect(chain_spec, 'default')
|
conn = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
straggler_delay = int(config.get('CIC_TX_RETRY_DELAY'))
|
|
||||||
loop_interval = config.get('SYNCER_LOOP_INTERVAL')
|
|
||||||
if loop_interval == None:
|
|
||||||
stat = init_chain_stat(conn)
|
|
||||||
loop_interval = stat.block_average()
|
|
||||||
|
|
||||||
syncer = RetrySyncer(conn, chain_spec, straggler_delay, batch_size=config.get('_BATCH_SIZE'))
|
syncer = RetrySyncer(conn, chain_spec, straggler_delay, batch_size=config.get('_BATCH_SIZE'))
|
||||||
syncer.backend.set(0, 0)
|
syncer.backend.set(0, 0)
|
||||||
fltr = StragglerFilter(chain_spec, queue=queue)
|
fltr = StragglerFilter(chain_spec, queue=queue)
|
||||||
syncer.add_filter(fltr)
|
syncer.add_filter(fltr)
|
||||||
syncer.loop(int(loop_interval), conn)
|
syncer.loop(float(straggler_delay), conn)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -11,19 +11,10 @@ import websocket
|
|||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
import confini
|
import confini
|
||||||
from chainlib.connection import (
|
from chainlib.connection import RPCConnection
|
||||||
RPCConnection,
|
from chainlib.eth.connection import EthUnixSignerConnection
|
||||||
ConnType,
|
|
||||||
)
|
|
||||||
from chainlib.eth.connection import (
|
|
||||||
EthUnixSignerConnection,
|
|
||||||
EthHTTPSignerConnection,
|
|
||||||
)
|
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainqueue.db.models.otx import Otx
|
from chainqueue.db.models.otx import Otx
|
||||||
from cic_eth_registry.error import UnknownContractError
|
|
||||||
import liveness.linux
|
|
||||||
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.eth import (
|
from cic_eth.eth import (
|
||||||
@@ -36,7 +27,6 @@ from cic_eth.eth import (
|
|||||||
from cic_eth.admin import (
|
from cic_eth.admin import (
|
||||||
debug,
|
debug,
|
||||||
ctrl,
|
ctrl,
|
||||||
token,
|
|
||||||
)
|
)
|
||||||
from cic_eth.queue import (
|
from cic_eth.queue import (
|
||||||
query,
|
query,
|
||||||
@@ -49,7 +39,6 @@ from cic_eth.queue import (
|
|||||||
from cic_eth.callbacks import (
|
from cic_eth.callbacks import (
|
||||||
Callback,
|
Callback,
|
||||||
http,
|
http,
|
||||||
noop,
|
|
||||||
#tcp,
|
#tcp,
|
||||||
redis,
|
redis,
|
||||||
)
|
)
|
||||||
@@ -61,8 +50,6 @@ from cic_eth.registry import (
|
|||||||
connect_declarator,
|
connect_declarator,
|
||||||
connect_token_registry,
|
connect_token_registry,
|
||||||
)
|
)
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -74,7 +61,6 @@ argparser.add_argument('-p', '--provider', dest='p', type=str, help='rpc provide
|
|||||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||||
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
||||||
argparser.add_argument('-r', type=str, help='CIC registry address')
|
argparser.add_argument('-r', type=str, help='CIC registry address')
|
||||||
argparser.add_argument('--default-token-symbol', dest='default_token_symbol', type=str, help='Symbol of default token to use')
|
|
||||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi')
|
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi')
|
||||||
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
|
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
||||||
@@ -94,7 +80,6 @@ config.process()
|
|||||||
args_override = {
|
args_override = {
|
||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
||||||
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
|
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
|
||||||
'CIC_DEFAULT_TOKEN_SYMBOL': getattr(args, 'default_token_symbol'),
|
|
||||||
'ETH_PROVIDER': getattr(args, 'p'),
|
'ETH_PROVIDER': getattr(args, 'p'),
|
||||||
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
|
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
|
||||||
}
|
}
|
||||||
@@ -104,15 +89,14 @@ config.censor('PASSWORD', 'DATABASE')
|
|||||||
config.censor('PASSWORD', 'SSL')
|
config.censor('PASSWORD', 'SSL')
|
||||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||||
|
|
||||||
health_modules = config.get('CIC_HEALTH_MODULES', [])
|
|
||||||
if len(health_modules) != 0:
|
|
||||||
health_modules = health_modules.split(',')
|
|
||||||
logg.debug('health mods {}'.format(health_modules))
|
|
||||||
|
|
||||||
# connect to database
|
# connect to database
|
||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(dsn, pool_size=50, debug=config.true('DATABASE_DEBUG'))
|
||||||
|
|
||||||
|
# verify database connection with minimal sanity query
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
session.execute('select version_num from alembic_version')
|
||||||
|
session.close()
|
||||||
|
|
||||||
# set up celery
|
# set up celery
|
||||||
current_app = celery.Celery(__name__)
|
current_app = celery.Celery(__name__)
|
||||||
@@ -149,18 +133,11 @@ else:
|
|||||||
})
|
})
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||||
RPCConnection.register_constructor(ConnType.UNIX, EthUnixSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_constructor(ConnType.HTTP, EthHTTPSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_constructor(ConnType.HTTP_SSL, EthHTTPSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
||||||
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer')
|
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection)
|
||||||
|
|
||||||
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
|
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
|
||||||
|
|
||||||
#import cic_eth.checks.gas
|
|
||||||
#if not cic_eth.checks.gas.health(config=config):
|
|
||||||
# raise RuntimeError()
|
|
||||||
liveness.linux.load(health_modules, rundir=config.get('CIC_RUN_DIR'), config=config, unit='cic-eth-tasker')
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argv = ['worker']
|
argv = ['worker']
|
||||||
@@ -184,11 +161,7 @@ def main():
|
|||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
try:
|
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
||||||
registry = connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
|
||||||
except UnknownContractError as e:
|
|
||||||
logg.exception('Registry contract connection failed for {}: {}'.format(config.get('CIC_REGISTRY_ADDRESS'), e))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -197,18 +170,10 @@ 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_declarator(rpc, chain_spec, trusted_addresses)
|
||||||
connect_token_registry(rpc, chain_spec)
|
connect_token_registry(rpc, chain_spec)
|
||||||
|
|
||||||
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
|
|
||||||
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
|
|
||||||
BaseTask.run_dir = config.get('CIC_RUN_DIR')
|
|
||||||
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))
|
|
||||||
|
|
||||||
liveness.linux.set(rundir=config.get('CIC_RUN_DIR'))
|
|
||||||
current_app.worker_main(argv)
|
current_app.worker_main(argv)
|
||||||
liveness.linux.reset(rundir=config.get('CIC_RUN_DIR'))
|
|
||||||
|
|
||||||
|
|
||||||
@celery.signals.eventlet_pool_postshutdown.connect
|
@celery.signals.eventlet_pool_postshutdown.connect
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import confini
|
import confini
|
||||||
import celery
|
import celery
|
||||||
import rlp
|
import rlp
|
||||||
@@ -15,6 +15,7 @@ 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
|
||||||
@@ -25,7 +26,7 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend.sql import SQLBackend
|
from chainsyncer.backend import SyncerBackend
|
||||||
from chainsyncer.driver import (
|
from chainsyncer.driver import (
|
||||||
HeadSyncer,
|
HeadSyncer,
|
||||||
HistorySyncer,
|
HistorySyncer,
|
||||||
@@ -41,13 +42,6 @@ from cic_eth.runnable.daemons.filters import (
|
|||||||
RegistrationFilter,
|
RegistrationFilter,
|
||||||
TransferAuthFilter,
|
TransferAuthFilter,
|
||||||
)
|
)
|
||||||
from cic_eth.stat import init_chain_stat
|
|
||||||
from cic_eth.registry import (
|
|
||||||
connect as connect_registry,
|
|
||||||
connect_declarator,
|
|
||||||
connect_token_registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
|
||||||
@@ -72,6 +66,7 @@ chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|||||||
#RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
#RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
||||||
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# connect to celery
|
# connect to celery
|
||||||
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
||||||
@@ -81,30 +76,24 @@ def main():
|
|||||||
|
|
||||||
o = block_latest()
|
o = block_latest()
|
||||||
r = rpc.do(o)
|
r = rpc.do(o)
|
||||||
block_current = int(r, 16)
|
block_offset = int(strip_0x(r), 16) + 1
|
||||||
block_offset = block_current + 1
|
|
||||||
|
|
||||||
loop_interval = config.get('SYNCER_LOOP_INTERVAL')
|
|
||||||
if loop_interval == None:
|
|
||||||
stat = init_chain_stat(rpc, block_start=block_current)
|
|
||||||
loop_interval = stat.block_average()
|
|
||||||
|
|
||||||
logg.debug('starting at block {}'.format(block_offset))
|
logg.debug('starting at block {}'.format(block_offset))
|
||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SQLBackend.first(chain_spec):
|
#if SyncerBackend.first(chain_spec):
|
||||||
# backend = SQLBackend.initial(chain_spec, block_offset)
|
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
syncer_backends = SyncerBackend.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(SQLBackend.initial(chain_spec, block_offset))
|
syncer_backends.append(SyncerBackend.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(SQLBackend.live(chain_spec, block_offset+1))
|
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1))
|
||||||
|
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
try:
|
try:
|
||||||
@@ -114,8 +103,6 @@ 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')
|
||||||
@@ -123,8 +110,6 @@ 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 = []
|
||||||
@@ -155,8 +140,7 @@ def main():
|
|||||||
for cf in callback_filters:
|
for cf in callback_filters:
|
||||||
syncer.add_filter(cf)
|
syncer.add_filter(cf)
|
||||||
|
|
||||||
#r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
||||||
r = syncer.loop(int(loop_interval), rpc)
|
|
||||||
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import confini
|
|
||||||
import celery
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.api import Api
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
default_format = 'terminal'
|
|
||||||
default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
|
|
||||||
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
|
||||||
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
|
|
||||||
argparser.add_argument('-q', type=str, default='cic-eth', help='celery queue to submit transaction tasks to')
|
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
|
||||||
argparser.add_argument('-vv', help='be more verbose', action='store_true')
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
if args.v == True:
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
elif args.vv == True:
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
config_dir = os.path.join(args.c)
|
|
||||||
os.makedirs(config_dir, 0o777, True)
|
|
||||||
config = confini.Config(config_dir, args.env_prefix)
|
|
||||||
config.process()
|
|
||||||
args_override = {
|
|
||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
|
||||||
}
|
|
||||||
config.dict_override(args_override, 'cli args')
|
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
|
||||||
config.censor('PASSWORD', 'SSL')
|
|
||||||
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
|
||||||
|
|
||||||
|
|
||||||
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
|
||||||
|
|
||||||
queue = args.q
|
|
||||||
|
|
||||||
api = Api(config.get('CIC_CHAIN_SPEC'), queue=queue)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
t = api.default_token()
|
|
||||||
token_info = t.get()
|
|
||||||
print('Default token symbol: {}'.format(token_info['symbol']))
|
|
||||||
print('Default token address: {}'.format(token_info['address']))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.stat import ChainStat
|
|
||||||
from chainlib.eth.block import (
|
|
||||||
block_latest,
|
|
||||||
block_by_number,
|
|
||||||
Block,
|
|
||||||
)
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
BLOCK_SAMPLES = 10
|
|
||||||
|
|
||||||
|
|
||||||
def init_chain_stat(rpc, block_start=0):
|
|
||||||
stat = ChainStat()
|
|
||||||
|
|
||||||
if block_start == 0:
|
|
||||||
o = block_latest()
|
|
||||||
r = rpc.do(o)
|
|
||||||
block_start = int(r, 16)
|
|
||||||
|
|
||||||
for i in range(BLOCK_SAMPLES):
|
|
||||||
o = block_by_number(block_start-10+i)
|
|
||||||
block_src = rpc.do(o)
|
|
||||||
logg.debug('block {}'.format(block_src))
|
|
||||||
block = Block(block_src)
|
|
||||||
stat.block_apply(block)
|
|
||||||
|
|
||||||
logg.debug('calculated block time {} from {} block samples'.format(stat.block_average(), BLOCK_SAMPLES))
|
|
||||||
return stat
|
|
||||||
@@ -4,7 +4,7 @@ import datetime
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainsyncer.backend.memory import MemBackend
|
from chainsyncer.backend 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,13 +10,15 @@ import sqlalchemy
|
|||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.eth.nonce import RPCNonceOracle
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
from chainlib.eth.gas import RPCGasOracle
|
from chainlib.eth.gas import RPCGasOracle
|
||||||
import liveness.linux
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.error import SeppukuError
|
from cic_eth.error import (
|
||||||
|
SignerError,
|
||||||
|
EthError,
|
||||||
|
)
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
celery_app = celery.current_app
|
celery_app = celery.current_app
|
||||||
|
|
||||||
@@ -27,9 +29,6 @@ class BaseTask(celery.Task):
|
|||||||
call_address = ZERO_ADDRESS
|
call_address = ZERO_ADDRESS
|
||||||
create_nonce_oracle = RPCNonceOracle
|
create_nonce_oracle = RPCNonceOracle
|
||||||
create_gas_oracle = RPCGasOracle
|
create_gas_oracle = RPCGasOracle
|
||||||
default_token_address = None
|
|
||||||
default_token_symbol = None
|
|
||||||
run_dir = '/run'
|
|
||||||
|
|
||||||
def create_session(self):
|
def create_session(self):
|
||||||
return BaseTask.session_func()
|
return BaseTask.session_func()
|
||||||
@@ -39,19 +38,6 @@ class BaseTask(celery.Task):
|
|||||||
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
|
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
|
||||||
if isinstance(exc, SeppukuError):
|
|
||||||
liveness.linux.reset(rundir=self.run_dir)
|
|
||||||
logg.critical(einfo)
|
|
||||||
msg = 'received critical exception {}, calling shutdown'.format(str(exc))
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.admin.ctrl.shutdown',
|
|
||||||
[msg],
|
|
||||||
queue=self.request.delivery_info.get('routing_key'),
|
|
||||||
)
|
|
||||||
s.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
class CriticalTask(BaseTask):
|
class CriticalTask(BaseTask):
|
||||||
retry_jitter = True
|
retry_jitter = True
|
||||||
@@ -81,6 +67,7 @@ class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
|
|||||||
sqlalchemy.exc.TimeoutError,
|
sqlalchemy.exc.TimeoutError,
|
||||||
requests.exceptions.ConnectionError,
|
requests.exceptions.ConnectionError,
|
||||||
sqlalchemy.exc.ResourceClosedError,
|
sqlalchemy.exc.ResourceClosedError,
|
||||||
|
EthError,
|
||||||
)
|
)
|
||||||
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
||||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||||
@@ -91,11 +78,13 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
|
|||||||
sqlalchemy.exc.DatabaseError,
|
sqlalchemy.exc.DatabaseError,
|
||||||
sqlalchemy.exc.TimeoutError,
|
sqlalchemy.exc.TimeoutError,
|
||||||
sqlalchemy.exc.ResourceClosedError,
|
sqlalchemy.exc.ResourceClosedError,
|
||||||
|
SignerError,
|
||||||
)
|
)
|
||||||
|
|
||||||
class CriticalWeb3AndSignerTask(CriticalTask):
|
class CriticalWeb3AndSignerTask(CriticalTask):
|
||||||
autoretry_for = (
|
autoretry_for = (
|
||||||
requests.exceptions.ConnectionError,
|
requests.exceptions.ConnectionError,
|
||||||
|
SignerError,
|
||||||
)
|
)
|
||||||
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
||||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||||
@@ -105,8 +94,3 @@ class CriticalWeb3AndSignerTask(CriticalTask):
|
|||||||
def hello(self):
|
def hello(self):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return id(SessionBase.create_session)
|
return id(SessionBase.create_session)
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
|
||||||
def check_health(self):
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = (
|
|||||||
0,
|
0,
|
||||||
11,
|
11,
|
||||||
0,
|
0,
|
||||||
'beta.9',
|
'alpha.4',
|
||||||
)
|
)
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ registry_address =
|
|||||||
chain_spec = evm:bloxberg:8996
|
chain_spec = evm:bloxberg:8996
|
||||||
tx_retry_delay =
|
tx_retry_delay =
|
||||||
trust_address =
|
trust_address =
|
||||||
default_token_symbol = GFT
|
|
||||||
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
|
|
||||||
run_dir = /run
|
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
POOL_SIZE=50
|
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
|
|||||||
@@ -2,7 +2,3 @@
|
|||||||
registry_address =
|
registry_address =
|
||||||
chain_spec = evm:bloxberg:8996
|
chain_spec = evm:bloxberg:8996
|
||||||
trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
|
trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
|
||||||
tx_retry_delay = 20
|
|
||||||
default_token_symbol = GFT
|
|
||||||
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
|
|
||||||
run_dir = /run
|
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ HOST=localhost
|
|||||||
PORT=63432
|
PORT=63432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
POOL_SIZE=50
|
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
[eth]
|
[eth]
|
||||||
|
#ws_provider = ws://localhost:8546
|
||||||
|
#ttp_provider = http://localhost:8545
|
||||||
provider = http://localhost:63545
|
provider = http://localhost:63545
|
||||||
gas_gifter_minimum_balance = 10000000000000000000000
|
gas_provider_address =
|
||||||
|
#chain_id =
|
||||||
|
abi_dir = /home/lash/src/ext/cic/grassrootseconomics/cic-contracts/abis
|
||||||
|
account_accounts_index_writer =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[signer]
|
[signer]
|
||||||
socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc
|
socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc
|
||||||
secret = deedbeef
|
secret = deedbeef
|
||||||
database_name = signer_test
|
database_name = signer_test
|
||||||
dev_keys_path =
|
dev_keys_path =
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[SYNCER]
|
[SYNCER]
|
||||||
loop_interval =
|
loop_interval = 1
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
[eth]
|
[eth]
|
||||||
|
#ws_provider = ws://localhost:8546
|
||||||
|
#ttp_provider = http://localhost:8545
|
||||||
provider = http://localhost:8545
|
provider = http://localhost:8545
|
||||||
gas_gifter_minimum_balance = 10000000000000000000000
|
gas_provider_address =
|
||||||
|
#chain_id =
|
||||||
|
abi_dir = /usr/local/share/cic/solidity/abi
|
||||||
|
account_accounts_index_writer =
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[SYNCER]
|
[SYNCER]
|
||||||
loop_interval =
|
loop_interval = 1
|
||||||
|
|||||||
@@ -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.2a77
|
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a60
|
||||||
|
|
||||||
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 ./
|
||||||
@@ -53,5 +53,3 @@ COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
|
|||||||
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
|
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 util/liveness/health.sh /usr/local/bin/health.sh
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
cic-base==0.1.2b3
|
cic-base~=0.1.2a60
|
||||||
celery==4.4.7
|
celery==4.4.7
|
||||||
crypto-dev-signer~=0.4.14b3
|
crypto-dev-signer~=0.4.14a17
|
||||||
confini~=0.3.6rc3
|
confini~=0.3.6rc3
|
||||||
cic-eth-registry~=0.5.4a16
|
cic-eth-registry~=0.5.4a11
|
||||||
#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.11a9
|
eth_accounts_index~=0.0.11a7
|
||||||
erc20-transfer-authorization~=0.3.1a5
|
erc20-transfer-authorization~=0.3.1a3
|
||||||
|
#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.1a9
|
eth-address-index~=0.1.1a7
|
||||||
chainlib~=0.0.2a18
|
chainlib~=0.0.2a4
|
||||||
hexathon~=0.0.1a7
|
hexathon~=0.0.1a7
|
||||||
chainsyncer[sql]~=0.0.2a2
|
chainsyncer~=0.0.1a21
|
||||||
chainqueue~=0.0.1a7
|
chainqueue~=0.0.1a5
|
||||||
pysha3==1.0.2
|
pysha3==1.0.2
|
||||||
coincurve==15.0.0
|
coincurve==15.0.0
|
||||||
sarafu-faucet==0.0.2a28
|
sarafu-faucet~=0.0.2a16
|
||||||
potaahto~=0.0.1a1
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ packages =
|
|||||||
cic_eth.runnable.daemons.filters
|
cic_eth.runnable.daemons.filters
|
||||||
cic_eth.callbacks
|
cic_eth.callbacks
|
||||||
cic_eth.sync
|
cic_eth.sync
|
||||||
cic_eth.check
|
|
||||||
scripts =
|
scripts =
|
||||||
./scripts/migrate.py
|
./scripts/migrate.py
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ console_scripts =
|
|||||||
cic-eth-create = cic_eth.runnable.create:main
|
cic-eth-create = cic_eth.runnable.create:main
|
||||||
cic-eth-inspect = cic_eth.runnable.view:main
|
cic-eth-inspect = cic_eth.runnable.view:main
|
||||||
cic-eth-ctl = cic_eth.runnable.ctrl:main
|
cic-eth-ctl = cic_eth.runnable.ctrl:main
|
||||||
cic-eth-info = cic_eth.runnable.info:main
|
|
||||||
# TODO: Merge this with ctl when subcmds sorted to submodules
|
# TODO: Merge this with ctl when subcmds sorted to submodules
|
||||||
cic-eth-tag = cic_eth.runnable.tag:main
|
cic-eth-tag = cic_eth.runnable.tag:main
|
||||||
cic-eth-resend = cic_eth.runnable.resend:main
|
cic-eth-resend = cic_eth.runnable.resend:main
|
||||||
|
|||||||
@@ -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.8a9
|
giftable-erc20-token==0.0.8a4
|
||||||
|
|||||||
@@ -3,12 +3,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.eth.erc20 import ERC20
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.api import Api
|
from cic_eth.api import Api
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
root_dir = os.path.dirname(script_dir)
|
root_dir = os.path.dirname(script_dir)
|
||||||
@@ -32,26 +28,3 @@ def api(
|
|||||||
):
|
):
|
||||||
chain_str = str(default_chain_spec)
|
chain_str = str(default_chain_spec)
|
||||||
return Api(chain_str, queue=None, callback_param='foo')
|
return Api(chain_str, queue=None, callback_param='foo')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def foo_token_symbol(
|
|
||||||
default_chain_spec,
|
|
||||||
foo_token,
|
|
||||||
eth_rpc,
|
|
||||||
contract_roles,
|
|
||||||
):
|
|
||||||
|
|
||||||
c = ERC20(default_chain_spec)
|
|
||||||
o = c.symbol(foo_token, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
|
||||||
r = eth_rpc.do(o)
|
|
||||||
return c.parse_symbol(r)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def default_token(
|
|
||||||
foo_token,
|
|
||||||
foo_token_symbol,
|
|
||||||
):
|
|
||||||
BaseTask.default_token_symbol = foo_token_symbol
|
|
||||||
BaseTask.default_token_address = foo_token
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def test_tx(
|
|||||||
eth_rpc,
|
eth_rpc,
|
||||||
eth_signer,
|
eth_signer,
|
||||||
agent_roles,
|
agent_roles,
|
||||||
celery_session_worker,
|
celery_worker,
|
||||||
):
|
):
|
||||||
|
|
||||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -37,7 +37,7 @@ def test_tx(
|
|||||||
eth_rpc,
|
eth_rpc,
|
||||||
eth_signer,
|
eth_signer,
|
||||||
agent_roles,
|
agent_roles,
|
||||||
celery_session_worker,
|
celery_worker,
|
||||||
):
|
):
|
||||||
|
|
||||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||||
@@ -34,7 +34,6 @@ def celery_includes():
|
|||||||
'cic_eth.admin.ctrl',
|
'cic_eth.admin.ctrl',
|
||||||
'cic_eth.admin.nonce',
|
'cic_eth.admin.nonce',
|
||||||
'cic_eth.admin.debug',
|
'cic_eth.admin.debug',
|
||||||
'cic_eth.admin.token',
|
|
||||||
'cic_eth.eth.account',
|
'cic_eth.eth.account',
|
||||||
'cic_eth.callbacks.noop',
|
'cic_eth.callbacks.noop',
|
||||||
'cic_eth.callbacks.http',
|
'cic_eth.callbacks.http',
|
||||||
|
|||||||
@@ -38,12 +38,11 @@ def test_transfer_api(
|
|||||||
agent_roles,
|
agent_roles,
|
||||||
cic_registry,
|
cic_registry,
|
||||||
register_tokens,
|
register_tokens,
|
||||||
register_lookups,
|
|
||||||
celery_session_worker,
|
celery_session_worker,
|
||||||
):
|
):
|
||||||
|
|
||||||
#token = CICRegistry.get_address(default_chain_spec, bancor_tokens[0])
|
#token = CICRegistry.get_address(default_chain_spec, bancor_tokens[0])
|
||||||
foo_token_cache = ERC20Token(default_chain_spec, eth_rpc, foo_token)
|
foo_token_cache = ERC20Token(eth_rpc, foo_token)
|
||||||
|
|
||||||
api = Api(str(default_chain_spec), callback_param='transfer', callback_task='cic_eth.callbacks.noop.noop', queue=None)
|
api = Api(str(default_chain_spec), callback_param='transfer', callback_task='cic_eth.callbacks.noop.noop', queue=None)
|
||||||
t = api.transfer(custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], 1024, foo_token_cache.symbol)
|
t = api.transfer(custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], 1024, foo_token_cache.symbol)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def test_register_account(
|
|||||||
|
|
||||||
init_eth_tester.mine_block()
|
init_eth_tester.mine_block()
|
||||||
|
|
||||||
c = AccountRegistry(default_chain_spec)
|
c = AccountRegistry()
|
||||||
o = c.have(account_registry, eth_empty_accounts[0], sender_address=call_sender)
|
o = c.have(account_registry, eth_empty_accounts[0], sender_address=call_sender)
|
||||||
r = eth_rpc.do(o)
|
r = eth_rpc.do(o)
|
||||||
assert int(strip_0x(r), 16) == 1
|
assert int(strip_0x(r), 16) == 1
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# external imports
|
|
||||||
import celery
|
|
||||||
|
|
||||||
|
|
||||||
def test_default_token(
|
|
||||||
default_token,
|
|
||||||
celery_session_worker,
|
|
||||||
foo_token,
|
|
||||||
foo_token_symbol,
|
|
||||||
):
|
|
||||||
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.admin.token.default_token',
|
|
||||||
[],
|
|
||||||
queue=None,
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
r = t.get()
|
|
||||||
|
|
||||||
assert r['address'] == foo_token
|
|
||||||
assert r['symbol'] == foo_token_symbol
|
|
||||||
@@ -43,7 +43,7 @@ def test_filter_process(
|
|||||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc)
|
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc)
|
||||||
|
|
||||||
init_eth_tester.mine_blocks(13)
|
init_eth_tester.mine_blocks(13)
|
||||||
c = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle)
|
||||||
(tx_hash_hex, o) = c.transfer(foo_token, agent_roles['ALICE'], agent_roles['BOB'], 1024)
|
(tx_hash_hex, o) = c.transfer(foo_token, agent_roles['ALICE'], agent_roles['BOB'], 1024)
|
||||||
eth_rpc.do(o)
|
eth_rpc.do(o)
|
||||||
o = receipt(tx_hash_hex)
|
o = receipt(tx_hash_hex)
|
||||||
@@ -56,7 +56,7 @@ def test_filter_process(
|
|||||||
|
|
||||||
# external tx
|
# external tx
|
||||||
init_eth_tester.mine_blocks(28)
|
init_eth_tester.mine_blocks(28)
|
||||||
c = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle)
|
||||||
(tx_hash_hex, o) = c.transfer(foo_token, agent_roles['ALICE'], agent_roles['BOB'], 512)
|
(tx_hash_hex, o) = c.transfer(foo_token, agent_roles['ALICE'], agent_roles['BOB'], 512)
|
||||||
eth_rpc.do(o)
|
eth_rpc.do(o)
|
||||||
o = receipt(tx_hash_hex)
|
o = receipt(tx_hash_hex)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cic-client-meta",
|
"name": "cic-client-meta",
|
||||||
"version": "0.0.7-alpha.6",
|
"version": "0.0.7-alpha.3",
|
||||||
"description": "Signed CRDT metadata graphs for the CIC network",
|
"description": "Signed CRDT metadata graphs for the CIC network",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -40,6 +40,6 @@
|
|||||||
],
|
],
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "~14.16.1"
|
"node": "~15.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ async function processRequest(req, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!['PUT', 'GET', 'POST'].includes(req.method)) {
|
if (!['PUT', 'GET', 'POST'].includes(req.method)) {
|
||||||
res.writeHead(405, {"Content-Type": "text/plain"});
|
res.writeHead(405, {"Content-Type": "text/plain"});
|
||||||
res.end();
|
res.end();
|
||||||
@@ -124,7 +123,6 @@ async function processRequest(req, res) {
|
|||||||
try {
|
try {
|
||||||
digest = parseDigest(req.url);
|
digest = parseDigest(req.url);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('digest error: ' + e)
|
|
||||||
res.writeHead(400, {"Content-Type": "text/plain"});
|
res.writeHead(400, {"Content-Type": "text/plain"});
|
||||||
res.end();
|
res.end();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ArgPair, Syncable } from '../sync';
|
import { ArgPair, Syncable } from '../sync';
|
||||||
import { Addressable, mergeKey } from '../digest';
|
import { Addressable, addressToBytes, bytesToHex, toKey } from '../digest';
|
||||||
|
|
||||||
class Phone extends Syncable implements Addressable {
|
class Phone extends Syncable implements Addressable {
|
||||||
|
|
||||||
address: string
|
address: string
|
||||||
value: number
|
value: number
|
||||||
|
|
||||||
constructor(address:string, v:string) {
|
constructor(address:string, v:number) {
|
||||||
const o = {
|
const o = {
|
||||||
msisdn: v,
|
msisdn: v,
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ class Phone extends Syncable implements Addressable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async toKey(msisdn:string) {
|
public static async toKey(msisdn:number) {
|
||||||
return await mergeKey(Buffer.from(msisdn), Buffer.from(':cic.phone'));
|
return await toKey(msisdn.toString(), ':cic.msisdn');
|
||||||
}
|
}
|
||||||
|
|
||||||
public key(): string {
|
public key(): string {
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ function addressToBytes(s:string) {
|
|||||||
export {
|
export {
|
||||||
toKey,
|
toKey,
|
||||||
toAddressKey,
|
toAddressKey,
|
||||||
mergeKey,
|
|
||||||
bytesToHex,
|
bytesToHex,
|
||||||
addressToBytes,
|
addressToBytes,
|
||||||
Addressable,
|
Addressable,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import logging
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from celery.app.control import Inspect
|
|
||||||
import celery
|
import celery
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@@ -16,29 +15,6 @@ logg = logging.getLogger()
|
|||||||
sms_tasks_matcher = r"^(cic_notify.tasks.sms)(\.\w+)?"
|
sms_tasks_matcher = r"^(cic_notify.tasks.sms)(\.\w+)?"
|
||||||
|
|
||||||
|
|
||||||
re_q = r'^cic-notify'
|
|
||||||
def get_sms_queue_tasks(app, task_prefix='cic_notify.tasks.sms.'):
|
|
||||||
host_queues = []
|
|
||||||
|
|
||||||
i = Inspect(app=app)
|
|
||||||
qs = i.active_queues()
|
|
||||||
for host in qs.keys():
|
|
||||||
for q in qs[host]:
|
|
||||||
if re.match(re_q, q['name']):
|
|
||||||
host_queues.append((host, q['name'],))
|
|
||||||
|
|
||||||
task_prefix_len = len(task_prefix)
|
|
||||||
queue_tasks = []
|
|
||||||
for (host, queue) in host_queues:
|
|
||||||
i = Inspect(app=app, destination=[host])
|
|
||||||
for tasks in i.registered_tasks().values():
|
|
||||||
for task in tasks:
|
|
||||||
if len(task) >= task_prefix_len and task[:task_prefix_len] == task_prefix:
|
|
||||||
queue_tasks.append((queue, task,))
|
|
||||||
|
|
||||||
return queue_tasks
|
|
||||||
|
|
||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
# TODO: Implement callback strategy
|
# TODO: Implement callback strategy
|
||||||
def __init__(self, queue='cic-notify'):
|
def __init__(self, queue='cic-notify'):
|
||||||
@@ -46,9 +22,17 @@ class Api:
|
|||||||
:param queue: The queue on which to execute notification tasks
|
:param queue: The queue on which to execute notification tasks
|
||||||
:type queue: str
|
:type queue: str
|
||||||
"""
|
"""
|
||||||
self.sms_tasks = get_sms_queue_tasks(app)
|
registered_tasks = app.tasks
|
||||||
logg.debug('sms tasks {}'.format(self.sms_tasks))
|
self.sms_tasks = []
|
||||||
|
|
||||||
|
for task in registered_tasks.keys():
|
||||||
|
logg.debug(f'Found: {task} {registered_tasks[task]}')
|
||||||
|
match = re.match(sms_tasks_matcher, task)
|
||||||
|
if match:
|
||||||
|
self.sms_tasks.append(task)
|
||||||
|
|
||||||
|
self.queue = queue
|
||||||
|
logg.info(f'api using queue: {self.queue}')
|
||||||
|
|
||||||
def sms(self, message, recipient):
|
def sms(self, message, recipient):
|
||||||
"""This function chains all sms tasks in order to send a message, log and persist said data to disk
|
"""This function chains all sms tasks in order to send a message, log and persist said data to disk
|
||||||
@@ -60,17 +44,12 @@ class Api:
|
|||||||
:rtype: Celery.Task
|
:rtype: Celery.Task
|
||||||
"""
|
"""
|
||||||
signatures = []
|
signatures = []
|
||||||
for q in self.sms_tasks:
|
for task in self.sms_tasks:
|
||||||
signature = celery.signature(
|
signature = celery.signature(task)
|
||||||
q[1],
|
|
||||||
[
|
|
||||||
message,
|
|
||||||
recipient,
|
|
||||||
],
|
|
||||||
queue=q[0],
|
|
||||||
)
|
|
||||||
signatures.append(signature)
|
signatures.append(signature)
|
||||||
|
signature_group = celery.group(signatures)
|
||||||
t = celery.group(signatures)()
|
result = signature_group.apply_async(
|
||||||
|
args=[message, recipient],
|
||||||
return t
|
queue=self.queue
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
import confini
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_notify.api import Api
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
config_dir = os.path.join('/usr/local/etc/cic-notify')
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
|
||||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
|
||||||
argparser.add_argument('-v', action='store_true', help='be verbose')
|
|
||||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
|
||||||
argparser.add_argument('recipient', type=str, help='notification recipient')
|
|
||||||
argparser.add_argument('message', type=str, help='message text')
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
if args.vv:
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
elif args.v:
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
|
|
||||||
config = confini.Config(args.c, args.env_prefix)
|
|
||||||
config.process()
|
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
|
||||||
config.add(args.recipient, '_RECIPIENT', True)
|
|
||||||
config.add(args.message, '_MESSAGE', True)
|
|
||||||
|
|
||||||
# set up celery
|
|
||||||
app = celery.Celery(__name__)
|
|
||||||
|
|
||||||
broker = config.get('CELERY_BROKER_URL')
|
|
||||||
if broker[:4] == 'file':
|
|
||||||
bq = tempfile.mkdtemp()
|
|
||||||
bp = tempfile.mkdtemp()
|
|
||||||
app.conf.update({
|
|
||||||
'broker_url': broker,
|
|
||||||
'broker_transport_options': {
|
|
||||||
'data_folder_in': bq,
|
|
||||||
'data_folder_out': bq,
|
|
||||||
'data_folder_processed': bp,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
logg.warning('celery broker dirs queue i/o {} processed {}, will NOT be deleted on shutdown'.format(bq, bp))
|
|
||||||
else:
|
|
||||||
app.conf.update({
|
|
||||||
'broker_url': broker,
|
|
||||||
})
|
|
||||||
|
|
||||||
result = config.get('CELERY_RESULT_URL')
|
|
||||||
if result[:4] == 'file':
|
|
||||||
rq = tempfile.mkdtemp()
|
|
||||||
app.conf.update({
|
|
||||||
'result_backend': 'file://{}'.format(rq),
|
|
||||||
})
|
|
||||||
logg.warning('celery backend store dir {} created, will NOT be deleted on shutdown'.format(rq))
|
|
||||||
else:
|
|
||||||
app.conf.update({
|
|
||||||
'result_backend': result,
|
|
||||||
})
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
a = Api()
|
|
||||||
t = a.sms(config.get('_RECIPIENT'), config.get('_MESSAGE'))
|
|
||||||
@@ -33,9 +33,7 @@ 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,10 +6,11 @@ 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.4')
|
version = (0, 4, 0, 'alpha.3')
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
major=version[0],
|
major=version[0],
|
||||||
@@ -17,4 +18,27 @@ 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,4 +1,4 @@
|
|||||||
FROM python:3.8.6-slim-buster
|
FROM python:3.8.6
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps
|
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps
|
||||||
@@ -6,7 +6,7 @@ RUN apt-get update && \
|
|||||||
WORKDIR /usr/src/cic-notify
|
WORKDIR /usr/src/cic-notify
|
||||||
|
|
||||||
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||||
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.2a44
|
||||||
|
|
||||||
COPY cic-notify/setup.cfg \
|
COPY cic-notify/setup.cfg \
|
||||||
cic-notify/setup.py \
|
cic-notify/setup.py \
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
cic_base[full_graph]~=0.1.2a61
|
cic_base[full_graph]~=0.1.2a46
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = cic-notify
|
name = cic-notify
|
||||||
|
version= 0.4.0a2
|
||||||
description = CIC notifications service
|
description = CIC notifications service
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
@@ -44,4 +45,3 @@ testing =
|
|||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
cic-notify-tasker = cic_notify.runnable.tasker:main
|
cic-notify-tasker = cic_notify.runnable.tasker:main
|
||||||
cic-notify-send = cic_notify.runnable.send:main
|
|
||||||
|
|||||||
@@ -1,31 +1,9 @@
|
|||||||
# 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 = []
|
||||||
@@ -47,6 +25,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,24 +1,14 @@
|
|||||||
[app]
|
[app]
|
||||||
ALLOWED_IP=0.0.0.0/0
|
ALLOWED_IP=127.0.0.1
|
||||||
LOCALE_FALLBACK=en
|
LOCALE_FALLBACK=en
|
||||||
LOCALE_PATH=/usr/src/cic-ussd/var/lib/locale/
|
LOCALE_PATH=var/lib/locale/
|
||||||
MAX_BODY_LENGTH=1024
|
MAX_BODY_LENGTH=1024
|
||||||
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||||
SERVICE_CODE=*483*46#
|
SERVICE_CODE=*483*46#
|
||||||
|
|
||||||
[phone_number]
|
|
||||||
REGION=KE
|
|
||||||
|
|
||||||
[ussd]
|
[ussd]
|
||||||
MENU_FILE=/usr/src/data/ussd_menu.json
|
MENU_FILE=/usr/src/data/ussd_menu.json
|
||||||
user =
|
|
||||||
pass =
|
|
||||||
|
|
||||||
[statemachine]
|
[statemachine]
|
||||||
STATES=/usr/src/cic-ussd/states/
|
STATES=/usr/src/cic-ussd/states/
|
||||||
TRANSITIONS=/usr/src/cic-ussd/transitions/
|
TRANSITIONS=/usr/src/cic-ussd/transitions/
|
||||||
|
|
||||||
[client]
|
|
||||||
host =
|
|
||||||
port =
|
|
||||||
ssl =
|
|
||||||
|
|||||||
@@ -6,5 +6,3 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
DEBUG=0
|
|
||||||
POOL_SIZE=1
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ BROKER_URL=redis://
|
|||||||
RESULT_URL=redis://
|
RESULT_URL=redis://
|
||||||
|
|
||||||
[redis]
|
[redis]
|
||||||
HOSTNAME=redis
|
HOSTNAME=localhost
|
||||||
PASSWORD=
|
PASSWORD=
|
||||||
PORT=6379
|
PORT=6379
|
||||||
DATABASE=0
|
DATABASE=0
|
||||||
|
|||||||
@@ -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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
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: Account):
|
def define_account_tx_metadata(user: User):
|
||||||
# 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
|
||||||
@@ -27,7 +27,7 @@ def define_account_tx_metadata(user: Account):
|
|||||||
if account_metadata:
|
if account_metadata:
|
||||||
account_metadata = json.loads(account_metadata)
|
account_metadata = json.loads(account_metadata)
|
||||||
person = Person()
|
person = Person()
|
||||||
deserialized_person = person.deserialize(person_data=account_metadata)
|
deserialized_person = person.deserialize(metadata=account_metadata)
|
||||||
given_name = deserialized_person.given_name
|
given_name = deserialized_person.given_name
|
||||||
family_name = deserialized_person.family_name
|
family_name = deserialized_person.family_name
|
||||||
phone_number = deserialized_person.tel
|
phone_number = deserialized_person.tel
|
||||||
@@ -46,4 +46,4 @@ def retrieve_account_statement(blockchain_address: str):
|
|||||||
callback_task='cic_ussd.tasks.callback_handler.process_statement_callback',
|
callback_task='cic_ussd.tasks.callback_handler.process_statement_callback',
|
||||||
callback_param=blockchain_address
|
callback_param=blockchain_address
|
||||||
)
|
)
|
||||||
cic_eth_api.list(address=blockchain_address, limit=9)
|
result = cic_eth_api.list(address=blockchain_address, limit=9)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Create account table
|
"""Create user table
|
||||||
|
|
||||||
Revision ID: f289e8510444
|
Revision ID: f289e8510444
|
||||||
Revises:
|
Revises:
|
||||||
@@ -17,7 +17,7 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
op.create_table('account',
|
op.create_table('user',
|
||||||
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_account_phone_number'), 'account', ['phone_number'], unique=True)
|
op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True)
|
||||||
op.create_index(op.f('ix_account_blockchain_address'), 'account', ['blockchain_address'], unique=True)
|
op.create_index(op.f('ix_user_blockchain_address'), 'user', ['blockchain_address'], unique=True)
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
op.drop_index(op.f('ix_account_blockchain_address'), table_name='account')
|
op.drop_index(op.f('ix_user_blockchain_address'), table_name='user')
|
||||||
op.drop_index(op.f('ix_account_phone_number'), table_name='account')
|
op.drop_index(op.f('ix_user_phone_number'), table_name='user')
|
||||||
op.drop_table('account')
|
op.drop_table('user')
|
||||||
|
|||||||
@@ -1,129 +1,47 @@
|
|||||||
# stanard imports
|
# standard imports
|
||||||
import logging
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
from sqlalchemy import Column, Integer, DateTime
|
from sqlalchemy import Column, Integer, DateTime
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.pool import (
|
|
||||||
StaticPool,
|
|
||||||
QueuePool,
|
|
||||||
AssertionPool,
|
|
||||||
NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
Model = declarative_base(name='Model')
|
Model = declarative_base(name='Model')
|
||||||
|
|
||||||
|
|
||||||
class SessionBase(Model):
|
class SessionBase(Model):
|
||||||
"""The base object for all SQLAlchemy enabled models. All other models must extend this.
|
|
||||||
"""
|
|
||||||
__abstract__ = True
|
__abstract__ = True
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
updated = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
|
|
||||||
engine = None
|
engine = None
|
||||||
"""Database connection engine of the running aplication"""
|
session = None
|
||||||
sessionmaker = None
|
query = None
|
||||||
"""Factory object responsible for creating sessions from the connection pool"""
|
|
||||||
transactional = True
|
|
||||||
"""Whether the database backend supports query transactions. Should be explicitly set by initialization code"""
|
|
||||||
poolable = True
|
|
||||||
"""Whether the database backend supports connection pools. Should be explicitly set by initialization code"""
|
|
||||||
procedural = True
|
|
||||||
"""Whether the database backend supports stored procedures"""
|
|
||||||
localsessions = {}
|
|
||||||
"""Contains dictionary of sessions initiated by db model components"""
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_session():
|
def create_session():
|
||||||
"""Creates a new database session.
|
session = sessionmaker(bind=SessionBase.engine)
|
||||||
"""
|
return session()
|
||||||
return SessionBase.sessionmaker()
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _set_engine(engine):
|
def _set_engine(engine):
|
||||||
"""Sets the database engine static property
|
|
||||||
"""
|
|
||||||
SessionBase.engine = engine
|
SessionBase.engine = engine
|
||||||
SessionBase.sessionmaker = sessionmaker(bind=SessionBase.engine)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def connect(dsn, pool_size=16, debug=False):
|
def build():
|
||||||
"""Create new database connection engine and connect to database backend.
|
Model.metadata.create_all(bind=SessionBase.engine)
|
||||||
|
|
||||||
:param dsn: DSN string defining connection.
|
|
||||||
:type dsn: str
|
|
||||||
"""
|
|
||||||
e = None
|
|
||||||
if SessionBase.poolable:
|
|
||||||
poolclass = QueuePool
|
|
||||||
if pool_size > 1:
|
|
||||||
logg.info('db using queue pool')
|
|
||||||
e = create_engine(
|
|
||||||
dsn,
|
|
||||||
max_overflow=pool_size*3,
|
|
||||||
pool_pre_ping=True,
|
|
||||||
pool_size=pool_size,
|
|
||||||
pool_recycle=60,
|
|
||||||
poolclass=poolclass,
|
|
||||||
echo=debug,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if pool_size == 0:
|
|
||||||
poolclass = NullPool
|
|
||||||
elif debug:
|
|
||||||
poolclass = AssertionPool
|
|
||||||
else:
|
|
||||||
poolclass = StaticPool
|
|
||||||
e = create_engine(
|
|
||||||
dsn,
|
|
||||||
poolclass=poolclass,
|
|
||||||
echo=debug,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logg.info('db connection not poolable')
|
|
||||||
e = create_engine(
|
|
||||||
dsn,
|
|
||||||
echo=debug,
|
|
||||||
)
|
|
||||||
|
|
||||||
SessionBase._set_engine(e)
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
# https://docs.sqlalchemy.org/en/13/core/pooling.html#pool-disconnects
|
||||||
|
def connect(data_source_name):
|
||||||
|
engine = create_engine(data_source_name, pool_pre_ping=True)
|
||||||
|
SessionBase._set_engine(engine)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def disconnect():
|
def disconnect():
|
||||||
"""Disconnect from database and free resources.
|
|
||||||
"""
|
|
||||||
SessionBase.engine.dispose()
|
SessionBase.engine.dispose()
|
||||||
SessionBase.engine = None
|
SessionBase.engine = None
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def bind_session(session=None):
|
|
||||||
localsession = session
|
|
||||||
if localsession == None:
|
|
||||||
localsession = SessionBase.create_session()
|
|
||||||
localsession_key = str(id(localsession))
|
|
||||||
logg.debug('creating new session {}'.format(localsession_key))
|
|
||||||
SessionBase.localsessions[localsession_key] = localsession
|
|
||||||
return localsession
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def release_session(session=None):
|
|
||||||
session_key = str(id(session))
|
|
||||||
if SessionBase.localsessions.get(session_key) != None:
|
|
||||||
logg.debug('commit and destroy session {}'.format(session_key))
|
|
||||||
session.commit()
|
|
||||||
session.close()
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ class AccountStatus(IntEnum):
|
|||||||
RESET = 4
|
RESET = 4
|
||||||
|
|
||||||
|
|
||||||
class Account(SessionBase):
|
class User(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__ = 'account'
|
__tablename__ = 'user'
|
||||||
|
|
||||||
blockchain_address = Column(String)
|
blockchain_address = Column(String)
|
||||||
phone_number = Column(String)
|
phone_number = Column(String)
|
||||||
@@ -38,7 +38,7 @@ class Account(SessionBase):
|
|||||||
self.account_status = AccountStatus.PENDING.value
|
self.account_status = AccountStatus.PENDING.value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<Account: {self.blockchain_address}>'
|
return f'<User: {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
|
||||||
@@ -128,8 +128,8 @@
|
|||||||
},
|
},
|
||||||
"22": {
|
"22": {
|
||||||
"description": "Pin entry menu.",
|
"description": "Pin entry menu.",
|
||||||
"display_key": "ussd.kenya.display_metadata_pin_authorization",
|
"display_key": "ussd.kenya.standard_pin_authorization",
|
||||||
"name": "display_metadata_pin_authorization",
|
"name": "standard_pin_authorization",
|
||||||
"parent": "start"
|
"parent": "start"
|
||||||
},
|
},
|
||||||
"23": {
|
"23": {
|
||||||
@@ -230,22 +230,9 @@
|
|||||||
},
|
},
|
||||||
"39": {
|
"39": {
|
||||||
"description": "Menu to instruct users to call the office.",
|
"description": "Menu to instruct users to call the office.",
|
||||||
"display_key": "ussd.kenya.help",
|
"display_key": "ussd.key.help",
|
||||||
"name": "help",
|
"name": "help",
|
||||||
"parent": null
|
"parent": null
|
||||||
},
|
|
||||||
"40": {
|
|
||||||
"description": "Menu to display a user's entire profile",
|
|
||||||
"display_key": "ussd.kenya.display_user_metadata",
|
|
||||||
"name": "display_user_metadata",
|
|
||||||
"parent": "account_management"
|
|
||||||
},
|
|
||||||
"41": {
|
|
||||||
"description": "The recipient is not in the system",
|
|
||||||
"display_key": "ussd.kenya.exit_invalid_recipient",
|
|
||||||
"name": "exit_invalid_recipient",
|
|
||||||
"parent": null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ class ActionDataNotFoundError(OSError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MetadataNotFoundError(OSError):
|
class UserMetadataNotFoundError(OSError):
|
||||||
"""Raised when metadata is expected but not available in cache."""
|
"""Raised when metadata is expected but not available in cache."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -31,10 +31,3 @@ class UnsupportedMethodError(OSError):
|
|||||||
class CachedDataNotFoundError(OSError):
|
class CachedDataNotFoundError(OSError):
|
||||||
"""Raised when the method passed to the make request function is unsupported."""
|
"""Raised when the method passed to the make request function is unsupported."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MetadataStoreError(Exception):
|
|
||||||
"""Raised when metadata storage fails"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
# third-party imports
|
# third-party imports
|
||||||
import requests
|
import requests
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.address import to_checksum
|
||||||
from hexathon import (
|
from hexathon import add_0x
|
||||||
add_0x,
|
|
||||||
strip_0x,
|
|
||||||
)
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_ussd.error import UnsupportedMethodError
|
from cic_ussd.error import UnsupportedMethodError
|
||||||
@@ -43,4 +40,4 @@ def blockchain_address_to_metadata_pointer(blockchain_address: str):
|
|||||||
:return:
|
:return:
|
||||||
:rtype:
|
:rtype:
|
||||||
"""
|
"""
|
||||||
return bytes.fromhex(strip_0x(blockchain_address))
|
return bytes.fromhex(blockchain_address[2:])
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from typing import Dict, Union
|
|
||||||
|
|
||||||
# third-part imports
|
|
||||||
import requests
|
|
||||||
from cic_types.models.person import generate_metadata_pointer, Person
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_ussd.metadata import make_request
|
|
||||||
from cic_ussd.metadata.signer import Signer
|
|
||||||
from cic_ussd.redis import cache_data
|
|
||||||
from cic_ussd.error import MetadataStoreError
|
|
||||||
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Metadata:
|
|
||||||
"""
|
|
||||||
:cvar base_url: The base url or the metadata server.
|
|
||||||
:type base_url: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
base_url = None
|
|
||||||
|
|
||||||
|
|
||||||
def metadata_http_error_handler(result: requests.Response):
|
|
||||||
""" This function handles and appropriately raises errors from http requests interacting with the metadata server.
|
|
||||||
:param result: The response object from a http request.
|
|
||||||
:type result: requests.Response
|
|
||||||
"""
|
|
||||||
status_code = result.status_code
|
|
||||||
|
|
||||||
if 100 <= status_code < 200:
|
|
||||||
raise MetadataStoreError(f'Informational errors: {status_code}, reason: {result.reason}')
|
|
||||||
|
|
||||||
elif 300 <= status_code < 400:
|
|
||||||
raise MetadataStoreError(f'Redirect Issues: {status_code}, reason: {result.reason}')
|
|
||||||
|
|
||||||
elif 400 <= status_code < 500:
|
|
||||||
raise MetadataStoreError(f'Client Error: {status_code}, reason: {result.reason}')
|
|
||||||
|
|
||||||
elif 500 <= status_code < 600:
|
|
||||||
raise MetadataStoreError(f'Server Error: {status_code}, reason: {result.reason}')
|
|
||||||
|
|
||||||
|
|
||||||
class MetadataRequestsHandler(Metadata):
|
|
||||||
|
|
||||||
def __init__(self, cic_type: str, identifier: bytes, engine: str = 'pgp'):
|
|
||||||
"""
|
|
||||||
:param cic_type: The salt value with which to hash a specific metadata identifier.
|
|
||||||
:type cic_type: str
|
|
||||||
:param engine: Encryption used for sending data to the metadata server.
|
|
||||||
:type engine: str
|
|
||||||
:param identifier: A unique element of data in bytes necessary for creating a metadata pointer.
|
|
||||||
:type identifier: bytes
|
|
||||||
"""
|
|
||||||
self.cic_type = cic_type
|
|
||||||
self.engine = engine
|
|
||||||
self.headers = {
|
|
||||||
'X-CIC-AUTOMERGE': 'server',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
self.identifier = identifier
|
|
||||||
self.metadata_pointer = generate_metadata_pointer(
|
|
||||||
identifier=self.identifier,
|
|
||||||
cic_type=self.cic_type
|
|
||||||
)
|
|
||||||
if self.base_url:
|
|
||||||
self.url = os.path.join(self.base_url, self.metadata_pointer)
|
|
||||||
|
|
||||||
def create(self, data: Union[Dict, str]):
|
|
||||||
""" This function is responsible for posting data to the metadata server with a corresponding metadata pointer
|
|
||||||
for storage.
|
|
||||||
:param data: The data to be stored in the metadata server.
|
|
||||||
:type data: dict|str
|
|
||||||
"""
|
|
||||||
data = json.dumps(data).encode('utf-8')
|
|
||||||
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
|
|
||||||
metadata_http_error_handler(result=result)
|
|
||||||
metadata = result.content
|
|
||||||
self.edit(data=metadata)
|
|
||||||
|
|
||||||
def edit(self, data: bytes):
|
|
||||||
""" This function is responsible for editing data in the metadata server corresponding to a unique pointer.
|
|
||||||
:param data: The data to be edited in the metadata server.
|
|
||||||
:type data: bytes
|
|
||||||
"""
|
|
||||||
cic_meta_signer = Signer()
|
|
||||||
signature = cic_meta_signer.sign_digest(data=data)
|
|
||||||
algorithm = cic_meta_signer.get_operational_key().get('algo')
|
|
||||||
decoded_data = data.decode('utf-8')
|
|
||||||
formatted_data = {
|
|
||||||
'm': data.decode('utf-8'),
|
|
||||||
's': {
|
|
||||||
'engine': self.engine,
|
|
||||||
'algo': algorithm,
|
|
||||||
'data': signature,
|
|
||||||
'digest': json.loads(data).get('digest'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formatted_data = json.dumps(formatted_data).encode('utf-8')
|
|
||||||
result = make_request(method='PUT', url=self.url, data=formatted_data, headers=self.headers)
|
|
||||||
logg.info(f'signed metadata submission status: {result.status_code}.')
|
|
||||||
metadata_http_error_handler(result=result)
|
|
||||||
try:
|
|
||||||
decoded_identifier = self.identifier.decode("utf-8")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
decoded_identifier = self.identifier.hex()
|
|
||||||
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {decoded_data}.')
|
|
||||||
|
|
||||||
def query(self):
|
|
||||||
"""This function is responsible for querying the metadata server for data corresponding to a unique pointer."""
|
|
||||||
result = make_request(method='GET', url=self.url)
|
|
||||||
metadata_http_error_handler(result=result)
|
|
||||||
response_data = result.content
|
|
||||||
data = json.loads(response_data.decode('utf-8'))
|
|
||||||
if result.status_code == 200 and self.cic_type == 'cic.person':
|
|
||||||
person = Person()
|
|
||||||
deserialized_person = person.deserialize(person_data=json.loads(data))
|
|
||||||
data = json.dumps(deserialized_person.serialize())
|
|
||||||
cache_data(self.metadata_pointer, data=data)
|
|
||||||
logg.debug(f'caching: {data} with key: {self.metadata_pointer}')
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from .base import MetadataRequestsHandler
|
|
||||||
|
|
||||||
|
|
||||||
class PersonMetadata(MetadataRequestsHandler):
|
|
||||||
|
|
||||||
def __init__(self, identifier: bytes):
|
|
||||||
super().__init__(cic_type='cic.person', identifier=identifier)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from .base import MetadataRequestsHandler
|
|
||||||
|
|
||||||
|
|
||||||
class PhonePointerMetadata(MetadataRequestsHandler):
|
|
||||||
|
|
||||||
def __init__(self, identifier: bytes):
|
|
||||||
super().__init__(cic_type='cic.msisdn', identifier=identifier)
|
|
||||||
@@ -44,7 +44,7 @@ class Signer:
|
|||||||
gpg_keys = self.gpg.list_keys()
|
gpg_keys = self.gpg.list_keys()
|
||||||
key_algorithm = gpg_keys[0].get('algo')
|
key_algorithm = gpg_keys[0].get('algo')
|
||||||
key_id = gpg_keys[0].get("keyid")
|
key_id = gpg_keys[0].get("keyid")
|
||||||
logg.debug(f'using signing key: {key_id}, algorithm: {key_algorithm}')
|
logg.info(f'using signing key: {key_id}, algorithm: {key_algorithm}')
|
||||||
return gpg_keys[0]
|
return gpg_keys[0]
|
||||||
|
|
||||||
def sign_digest(self, data: bytes):
|
def sign_digest(self, data: bytes):
|
||||||
|
|||||||
102
apps/cic-ussd/cic_ussd/metadata/user.py
Normal file
102
apps/cic-ussd/cic_ussd/metadata/user.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# standard imports
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import requests
|
||||||
|
from cic_types.models.person import generate_metadata_pointer, Person
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_ussd.chain import Chain
|
||||||
|
from cic_ussd.metadata import make_request
|
||||||
|
from cic_ussd.metadata.signer import Signer
|
||||||
|
from cic_ussd.redis import cache_data
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class UserMetadata:
|
||||||
|
"""
|
||||||
|
:cvar base_url:
|
||||||
|
:type base_url:
|
||||||
|
"""
|
||||||
|
base_url = None
|
||||||
|
|
||||||
|
def __init__(self, identifier: bytes):
|
||||||
|
"""
|
||||||
|
:param identifier:
|
||||||
|
:type identifier:
|
||||||
|
"""
|
||||||
|
self. headers = {
|
||||||
|
'X-CIC-AUTOMERGE': 'server',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
self.identifier = identifier
|
||||||
|
self.metadata_pointer = generate_metadata_pointer(
|
||||||
|
identifier=self.identifier,
|
||||||
|
cic_type='cic.person'
|
||||||
|
)
|
||||||
|
if self.base_url:
|
||||||
|
self.url = os.path.join(self.base_url, self.metadata_pointer)
|
||||||
|
|
||||||
|
def create(self, data: dict):
|
||||||
|
try:
|
||||||
|
data = json.dumps(data).encode('utf-8')
|
||||||
|
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
|
||||||
|
metadata = result.content
|
||||||
|
self.edit(data=metadata, engine='pgp')
|
||||||
|
logg.info(f'Get sign material response status: {result.status_code}')
|
||||||
|
result.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError as error:
|
||||||
|
raise RuntimeError(error)
|
||||||
|
|
||||||
|
def edit(self, data: bytes, engine: str):
|
||||||
|
"""
|
||||||
|
:param data:
|
||||||
|
:type data:
|
||||||
|
:param engine:
|
||||||
|
:type engine:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
cic_meta_signer = Signer()
|
||||||
|
signature = cic_meta_signer.sign_digest(data=data)
|
||||||
|
algorithm = cic_meta_signer.get_operational_key().get('algo')
|
||||||
|
formatted_data = {
|
||||||
|
'm': data.decode('utf-8'),
|
||||||
|
's': {
|
||||||
|
'engine': engine,
|
||||||
|
'algo': algorithm,
|
||||||
|
'data': signature,
|
||||||
|
'digest': json.loads(data).get('digest'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatted_data = json.dumps(formatted_data).encode('utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = make_request(method='PUT', url=self.url, data=formatted_data, headers=self.headers)
|
||||||
|
logg.info(f'Signed content submission status: {result.status_code}.')
|
||||||
|
result.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError as error:
|
||||||
|
raise RuntimeError(error)
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
result = make_request(method='GET', url=self.url)
|
||||||
|
status = result.status_code
|
||||||
|
logg.info(f'Get latest data status: {status}')
|
||||||
|
try:
|
||||||
|
if status == 200:
|
||||||
|
response_data = result.content
|
||||||
|
data = json.loads(response_data.decode())
|
||||||
|
|
||||||
|
# validate data
|
||||||
|
person = Person()
|
||||||
|
deserialized_person = person.deserialize(metadata=json.loads(data))
|
||||||
|
|
||||||
|
cache_data(key=self.metadata_pointer, data=json.dumps(deserialized_person.serialize()))
|
||||||
|
elif status == 404:
|
||||||
|
logg.info('The data is not available and might need to be added.')
|
||||||
|
result.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError as error:
|
||||||
|
raise RuntimeError(error)
|
||||||
@@ -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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
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 = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||||
status = user.get_account_status()
|
status = user.get_account_status()
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.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: Account, user_input: str) -> Document:
|
def process_current_menu(ussd_session: Optional[dict], user: User, 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: Account
|
:type user: User
|
||||||
: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 = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
user = User.session.query(User).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 = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||||
user.reset_account_pin()
|
user.reset_account_pin()
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
|
|
||||||
|
|
||||||
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[Account]:
|
def get_user_by_phone_number(phone_number: str) -> Optional[User]:
|
||||||
"""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: Account|None
|
:rtype: User|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 = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
user = User.session.query(User).filter_by(phone_number=phone_number).first()
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ 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.account import AccountStatus, Account
|
from cic_ussd.db.models.user import AccountStatus, User
|
||||||
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.menu.ussd_menu import UssdMenu
|
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||||
from cic_ussd.phone_number import get_user_by_phone_number
|
from cic_ussd.phone_number import get_user_by_phone_number
|
||||||
@@ -23,18 +22,17 @@ from cic_ussd.redis import cache_data, create_cached_data_key, get_cached_data
|
|||||||
from cic_ussd.state_machine import UssdStateMachine
|
from cic_ussd.state_machine import UssdStateMachine
|
||||||
from cic_ussd.conversions import to_wei, from_wei
|
from cic_ussd.conversions import to_wei, from_wei
|
||||||
from cic_ussd.translation import translation_for
|
from cic_ussd.translation import translation_for
|
||||||
from cic_types.models.person import generate_metadata_pointer, get_contact_data_from_vcard
|
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def process_pin_authorization(display_key: str, user: Account, **kwargs) -> str:
|
def process_pin_authorization(display_key: str, user: User, **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: Account
|
:type user: User
|
||||||
: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 +53,13 @@ def process_pin_authorization(display_key: str, user: Account, **kwargs) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_exit_insufficient_balance(display_key: str, user: Account, ussd_session: dict):
|
def process_exit_insufficient_balance(display_key: str, user: User, 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: Account
|
:type user: User
|
||||||
: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 +88,12 @@ def process_exit_insufficient_balance(display_key: str, user: Account, ussd_sess
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_exit_successful_transaction(display_key: str, user: Account, ussd_session: dict):
|
def process_exit_successful_transaction(display_key: str, user: User, 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: Account
|
:type user: User
|
||||||
: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 +116,11 @@ def process_exit_successful_transaction(display_key: str, user: Account, ussd_se
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_transaction_pin_authorization(user: Account, display_key: str, ussd_session: dict):
|
def process_transaction_pin_authorization(user: User, 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: Account
|
:type user: User
|
||||||
: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
|
||||||
@@ -138,7 +136,7 @@ def process_transaction_pin_authorization(user: Account, display_key: str, ussd_
|
|||||||
tx_sender_information = define_account_tx_metadata(user=user)
|
tx_sender_information = define_account_tx_metadata(user=user)
|
||||||
|
|
||||||
token_symbol = 'SRF'
|
token_symbol = 'SRF'
|
||||||
user_input = ussd_session.get('session_data').get('transaction_amount')
|
user_input = ussd_session.get('user_input').split('*')[-1]
|
||||||
transaction_amount = to_wei(value=int(user_input))
|
transaction_amount = to_wei(value=int(user_input))
|
||||||
logg.debug('Requires integration to determine user tokens.')
|
logg.debug('Requires integration to determine user tokens.')
|
||||||
return process_pin_authorization(
|
return process_pin_authorization(
|
||||||
@@ -151,7 +149,7 @@ def process_transaction_pin_authorization(user: Account, display_key: str, ussd_
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_account_balances(user: Account, display_key: str, ussd_session: dict):
|
def process_account_balances(user: User, display_key: str, ussd_session: dict):
|
||||||
"""
|
"""
|
||||||
:param user:
|
:param user:
|
||||||
:type user:
|
:type user:
|
||||||
@@ -189,56 +187,22 @@ def format_transactions(transactions: list, preferred_language: str):
|
|||||||
value = transaction.get('to_value')
|
value = transaction.get('to_value')
|
||||||
timestamp = transaction.get('timestamp')
|
timestamp = transaction.get('timestamp')
|
||||||
action_tag = transaction.get('action_tag')
|
action_tag = transaction.get('action_tag')
|
||||||
direction = transaction.get('direction')
|
|
||||||
token_symbol = 'SRF'
|
token_symbol = 'SRF'
|
||||||
|
|
||||||
if action_tag == 'SENT' or action_tag == 'ULITUMA':
|
if action_tag == 'SENT' or action_tag == 'ULITUMA':
|
||||||
formatted_transactions += f'{action_tag} {value} {token_symbol} {direction} {recipient_phone_number} {timestamp}.\n'
|
formatted_transactions += f'{action_tag} {value} {token_symbol} {recipient_phone_number} {timestamp}.\n'
|
||||||
else:
|
else:
|
||||||
formatted_transactions += f'{action_tag} {value} {token_symbol} {direction} {sender_phone_number} {timestamp}. \n'
|
formatted_transactions += f'{action_tag} {value} {token_symbol} {sender_phone_number} {timestamp}. \n'
|
||||||
return formatted_transactions
|
return formatted_transactions
|
||||||
else:
|
else:
|
||||||
if preferred_language == 'en':
|
if preferred_language == 'en':
|
||||||
formatted_transactions = 'NO TRANSACTION HISTORY'
|
formatted_transactions = 'Empty'
|
||||||
else:
|
else:
|
||||||
formatted_transactions = 'HAMNA RIPOTI YA MATUMIZI'
|
formatted_transactions = 'Hamna historia'
|
||||||
return formatted_transactions
|
return formatted_transactions
|
||||||
|
|
||||||
|
|
||||||
def process_display_user_metadata(user: Account, display_key: str):
|
def process_account_statement(user: User, display_key: str, ussd_session: dict):
|
||||||
"""
|
|
||||||
:param user:
|
|
||||||
:type user:
|
|
||||||
:param display_key:
|
|
||||||
:type display_key:
|
|
||||||
"""
|
|
||||||
key = generate_metadata_pointer(
|
|
||||||
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
|
|
||||||
cic_type='cic.person'
|
|
||||||
)
|
|
||||||
user_metadata = get_cached_data(key)
|
|
||||||
if user_metadata:
|
|
||||||
user_metadata = json.loads(user_metadata)
|
|
||||||
contact_data = get_contact_data_from_vcard(vcard=user_metadata.get('vcard'))
|
|
||||||
logg.debug(f'{contact_data}')
|
|
||||||
full_name = f'{contact_data.get("given")} {contact_data.get("family")}'
|
|
||||||
gender = user_metadata.get('gender')
|
|
||||||
products = ', '.join(user_metadata.get('products'))
|
|
||||||
location = user_metadata.get('location').get('area_name')
|
|
||||||
|
|
||||||
return translation_for(
|
|
||||||
key=display_key,
|
|
||||||
preferred_language=user.preferred_language,
|
|
||||||
full_name=full_name,
|
|
||||||
gender=gender,
|
|
||||||
location=location,
|
|
||||||
products=products
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
|
|
||||||
|
|
||||||
|
|
||||||
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
|
|
||||||
"""
|
"""
|
||||||
:param user:
|
:param user:
|
||||||
:type user:
|
:type user:
|
||||||
@@ -265,7 +229,7 @@ def process_account_statement(user: Account, display_key: str, ussd_session: dic
|
|||||||
middle_transaction_set += transactions[3:][:3]
|
middle_transaction_set += transactions[3:][:3]
|
||||||
first_transaction_set += transactions[:3]
|
first_transaction_set += transactions[:3]
|
||||||
# there are probably much cleaner and operational inexpensive ways to do this so find them
|
# there are probably much cleaner and operational inexpensive ways to do this so find them
|
||||||
elif 3 < len(transactions) < 7:
|
elif 4 < len(transactions) < 7:
|
||||||
middle_transaction_set += transactions[3:]
|
middle_transaction_set += transactions[3:]
|
||||||
first_transaction_set += transactions[:3]
|
first_transaction_set += transactions[:3]
|
||||||
else:
|
else:
|
||||||
@@ -301,12 +265,12 @@ def process_account_statement(user: Account, display_key: str, ussd_session: dic
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_start_menu(display_key: str, user: Account):
|
def process_start_menu(display_key: str, user: User):
|
||||||
"""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: Account
|
:type user: User
|
||||||
: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
|
||||||
@@ -331,11 +295,11 @@ def process_start_menu(display_key: str, user: Account):
|
|||||||
operational_balance = compute_operational_balance(balances=balances_data)
|
operational_balance = compute_operational_balance(balances=balances_data)
|
||||||
|
|
||||||
# retrieve and cache account's metadata
|
# retrieve and cache account's metadata
|
||||||
s_query_person_metadata = celery.signature(
|
s_query_user_metadata = celery.signature(
|
||||||
'cic_ussd.tasks.metadata.query_person_metadata',
|
'cic_ussd.tasks.metadata.query_user_metadata',
|
||||||
[blockchain_address]
|
[blockchain_address]
|
||||||
)
|
)
|
||||||
s_query_person_metadata.apply_async(queue='cic-ussd')
|
s_query_user_metadata.apply_async(queue='cic-ussd')
|
||||||
|
|
||||||
# retrieve and cache account's statement
|
# retrieve and cache account's statement
|
||||||
retrieve_account_statement(blockchain_address=blockchain_address)
|
retrieve_account_statement(blockchain_address=blockchain_address)
|
||||||
@@ -361,13 +325,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: Account, ussd_session: Optional[dict] = None) -> Document:
|
def process_request(user_input: str, user: User, 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: Account
|
:type user: User
|
||||||
: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
|
||||||
@@ -385,24 +349,18 @@ def process_request(user_input: str, user: Account, ussd_session: Optional[dict]
|
|||||||
if user.has_valid_pin():
|
if user.has_valid_pin():
|
||||||
last_ussd_session = retrieve_most_recent_ussd_session(phone_number=user.phone_number)
|
last_ussd_session = retrieve_most_recent_ussd_session(phone_number=user.phone_number)
|
||||||
|
|
||||||
key = generate_metadata_pointer(
|
key = create_cached_data_key(
|
||||||
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
|
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
|
||||||
cic_type='cic.person'
|
salt='cic.person'
|
||||||
)
|
)
|
||||||
person_metadata = get_cached_data(key=key)
|
user_metadata = get_cached_data(key=key)
|
||||||
|
|
||||||
if last_ussd_session:
|
if last_ussd_session:
|
||||||
# get last state
|
# get last state
|
||||||
last_state = last_ussd_session.state
|
last_state = last_ussd_session.state
|
||||||
|
logg.debug(f'LAST USSD SESSION STATE: {last_state}')
|
||||||
# if last state is account_creation_prompt and metadata exists, show start menu
|
# if last state is account_creation_prompt and metadata exists, show start menu
|
||||||
if last_state in [
|
if last_state == 'account_creation_prompt' and user_metadata is not None:
|
||||||
'account_creation_prompt',
|
|
||||||
'exit',
|
|
||||||
'exit_invalid_pin',
|
|
||||||
'exit_invalid_new_pin',
|
|
||||||
'exit_pin_mismatch',
|
|
||||||
'exit_invalid_request'
|
|
||||||
] and person_metadata is not None:
|
|
||||||
return UssdMenu.find_by_name(name='start')
|
return UssdMenu.find_by_name(name='start')
|
||||||
else:
|
else:
|
||||||
return UssdMenu.find_by_name(name=last_state)
|
return UssdMenu.find_by_name(name=last_state)
|
||||||
@@ -415,14 +373,14 @@ def process_request(user_input: str, user: Account, 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: Account, user_input: str) -> str:
|
def next_state(ussd_session: dict, user: User, 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: Account
|
:type user: User
|
||||||
: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.
|
||||||
@@ -438,7 +396,7 @@ def custom_display_text(
|
|||||||
display_key: str,
|
display_key: str,
|
||||||
menu_name: str,
|
menu_name: str,
|
||||||
ussd_session: dict,
|
ussd_session: dict,
|
||||||
user: Account) -> str:
|
user: User) -> 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
|
||||||
@@ -446,7 +404,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: Account
|
:type user: User
|
||||||
: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.
|
||||||
@@ -462,13 +420,9 @@ def custom_display_text(
|
|||||||
return process_start_menu(display_key=display_key, user=user)
|
return process_start_menu(display_key=display_key, user=user)
|
||||||
elif 'pin_authorization' in menu_name:
|
elif 'pin_authorization' in menu_name:
|
||||||
return process_pin_authorization(display_key=display_key, user=user)
|
return process_pin_authorization(display_key=display_key, user=user)
|
||||||
elif 'enter_current_pin' in menu_name:
|
|
||||||
return process_pin_authorization(display_key=display_key, user=user)
|
|
||||||
elif menu_name == 'account_balances':
|
elif menu_name == 'account_balances':
|
||||||
return process_account_balances(display_key=display_key, user=user, ussd_session=ussd_session)
|
return process_account_balances(display_key=display_key, user=user, ussd_session=ussd_session)
|
||||||
elif 'transaction_set' in menu_name:
|
elif 'transaction_set' in menu_name:
|
||||||
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
|
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
|
||||||
elif menu_name == 'display_user_metadata':
|
|
||||||
return process_display_user_metadata(display_key=display_key, user=user)
|
|
||||||
else:
|
else:
|
||||||
return translation_for(key=display_key, preferred_language=user.preferred_language)
|
return translation_for(key=display_key, preferred_language=user.preferred_language)
|
||||||
|
|||||||
@@ -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.account import AccountStatus, Account
|
from cic_ussd.db.models.user import AccountStatus, User
|
||||||
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 = Account.session.query(Account.blockchain_address).filter(
|
locked_accounts = User.session.query(User.blockchain_address).filter(
|
||||||
Account.account_status == AccountStatus.LOCKED.value,
|
User.account_status == AccountStatus.LOCKED.value,
|
||||||
Account.failed_pin_attempts >= 3).order_by(desc(Account.updated)).offset(offset).limit(limit).all()
|
User.failed_pin_attempts >= 3).order_by(desc(User.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]
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ from cic_ussd.encoder import PasswordEncoder
|
|||||||
from cic_ussd.files.local_files import create_local_file_data_stores, json_file_parser
|
from cic_ussd.files.local_files import create_local_file_data_stores, json_file_parser
|
||||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||||
from cic_ussd.metadata.signer import Signer
|
from cic_ussd.metadata.signer import Signer
|
||||||
from cic_ussd.metadata.base import Metadata
|
from cic_ussd.metadata.user import UserMetadata
|
||||||
from cic_ussd.operations import (define_response_with_content,
|
from cic_ussd.operations import (define_response_with_content,
|
||||||
process_menu_interaction_requests,
|
process_menu_interaction_requests,
|
||||||
define_multilingual_responses)
|
define_multilingual_responses)
|
||||||
from cic_ussd.phone_number import process_phone_number
|
|
||||||
from cic_ussd.redis import InMemoryStore
|
from cic_ussd.redis import InMemoryStore
|
||||||
from cic_ussd.requests import (get_request_endpoint,
|
from cic_ussd.requests import (get_request_endpoint,
|
||||||
get_request_method,
|
get_request_method,
|
||||||
@@ -36,8 +35,7 @@ from cic_ussd.requests import (get_request_endpoint,
|
|||||||
process_pin_reset_requests)
|
process_pin_reset_requests)
|
||||||
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
||||||
from cic_ussd.state_machine import UssdStateMachine
|
from cic_ussd.state_machine import UssdStateMachine
|
||||||
from cic_ussd.validator import check_ip, check_request_content_length, check_service_code, validate_phone_number, \
|
from cic_ussd.validator import check_ip, check_request_content_length, check_service_code, validate_phone_number
|
||||||
validate_presence
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -57,17 +55,20 @@ 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()
|
||||||
|
|
||||||
# define log levels
|
|
||||||
if args.vv:
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
elif args.v:
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# parse config
|
# parse config
|
||||||
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
|
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
|
||||||
config.process()
|
config.process()
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
|
||||||
|
# define log levels
|
||||||
|
if args.vv:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
|
||||||
|
elif args.v:
|
||||||
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# log config vars
|
||||||
|
logg.debug(config)
|
||||||
|
|
||||||
# initialize elements
|
# initialize elements
|
||||||
# set up translations
|
# set up translations
|
||||||
@@ -84,7 +85,7 @@ UssdMenu.ussd_menu_db = ussd_menu_db
|
|||||||
|
|
||||||
# set up db
|
# set up db
|
||||||
data_source_name = dsn_from_config(config)
|
data_source_name = dsn_from_config(config)
|
||||||
SessionBase.connect(data_source_name, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(data_source_name=data_source_name)
|
||||||
# create session for the life time of http request
|
# create session for the life time of http request
|
||||||
SessionBase.session = SessionBase.create_session()
|
SessionBase.session = SessionBase.create_session()
|
||||||
|
|
||||||
@@ -97,18 +98,12 @@ InMemoryStore.cache = redis.StrictRedis(host=config.get('REDIS_HOSTNAME'),
|
|||||||
InMemoryUssdSession.redis_cache = InMemoryStore.cache
|
InMemoryUssdSession.redis_cache = InMemoryStore.cache
|
||||||
|
|
||||||
# define metadata URL
|
# define metadata URL
|
||||||
Metadata.base_url = config.get('CIC_META_URL')
|
UserMetadata.base_url = config.get('CIC_META_URL')
|
||||||
|
|
||||||
# define signer values
|
# define signer values
|
||||||
export_dir = config.get('PGP_EXPORT_DIR')
|
Signer.gpg_path = config.get('PGP_EXPORT_DIR')
|
||||||
if export_dir:
|
|
||||||
validate_presence(path=export_dir)
|
|
||||||
Signer.gpg_path = export_dir
|
|
||||||
Signer.gpg_passphrase = config.get('PGP_PASSPHRASE')
|
Signer.gpg_passphrase = config.get('PGP_PASSPHRASE')
|
||||||
key_file_path = f"{config.get('PGP_KEYS_PATH')}{config.get('PGP_PRIVATE_KEYS')}"
|
Signer.key_file_path = f"{config.get('PGP_KEYS_PATH')}{config.get('PGP_PRIVATE_KEYS')}"
|
||||||
if key_file_path:
|
|
||||||
validate_presence(path=key_file_path)
|
|
||||||
Signer.key_file_path = key_file_path
|
|
||||||
|
|
||||||
# initialize celery app
|
# initialize celery app
|
||||||
celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY_BROKER_URL'))
|
celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY_BROKER_URL'))
|
||||||
@@ -149,10 +144,6 @@ def application(env, start_response):
|
|||||||
external_session_id = post_data.get('sessionId')
|
external_session_id = post_data.get('sessionId')
|
||||||
user_input = post_data.get('text')
|
user_input = post_data.get('text')
|
||||||
|
|
||||||
# add validation for phone number
|
|
||||||
if phone_number:
|
|
||||||
phone_number = process_phone_number(phone_number=phone_number, region=config.get('PHONE_NUMBER_REGION'))
|
|
||||||
|
|
||||||
# validate ip address
|
# validate ip address
|
||||||
if not check_ip(config=config, env=env):
|
if not check_ip(config=config, env=env):
|
||||||
start_response('403 Sneaky, sneaky', errors_headers)
|
start_response('403 Sneaky, sneaky', errors_headers)
|
||||||
@@ -176,10 +167,8 @@ def application(env, start_response):
|
|||||||
|
|
||||||
# validate phone number
|
# validate phone number
|
||||||
if not validate_phone_number(phone_number):
|
if not validate_phone_number(phone_number):
|
||||||
logg.error('invalid phone number {}'.format(phone_number))
|
|
||||||
start_response('400 Invalid phone number format', errors_headers)
|
start_response('400 Invalid phone number format', errors_headers)
|
||||||
return []
|
return []
|
||||||
logg.debug('session {} started for {}'.format(external_session_id, phone_number))
|
|
||||||
|
|
||||||
# handle menu interaction requests
|
# handle menu interaction requests
|
||||||
chain_str = chain_spec.__str__()
|
chain_str = chain_spec.__str__()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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
|
||||||
|
|
||||||
@@ -14,14 +13,12 @@ from confini import Config
|
|||||||
from cic_ussd.db import dsn_from_config
|
from cic_ussd.db import dsn_from_config
|
||||||
from cic_ussd.db.models.base import SessionBase
|
from cic_ussd.db.models.base import SessionBase
|
||||||
from cic_ussd.metadata.signer import Signer
|
from cic_ussd.metadata.signer import Signer
|
||||||
from cic_ussd.metadata.base import Metadata
|
from cic_ussd.metadata.user import UserMetadata
|
||||||
from cic_ussd.redis import InMemoryStore
|
from cic_ussd.redis import InMemoryStore
|
||||||
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
||||||
from cic_ussd.validator import validate_presence
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
logging.getLogger('gnupg').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
config_directory = '/usr/local/etc/cic-ussd/'
|
config_directory = '/usr/local/etc/cic-ussd/'
|
||||||
|
|
||||||
@@ -34,22 +31,22 @@ 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)
|
||||||
|
|
||||||
# parse config
|
logg.debug(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)
|
||||||
SessionBase.connect(data_source_name, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(data_source_name=data_source_name)
|
||||||
|
|
||||||
# verify database connection with minimal sanity query
|
# verify database connection with minimal sanity query
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
@@ -65,22 +62,12 @@ InMemoryStore.cache = redis.StrictRedis(host=config.get('REDIS_HOSTNAME'),
|
|||||||
InMemoryUssdSession.redis_cache = InMemoryStore.cache
|
InMemoryUssdSession.redis_cache = InMemoryStore.cache
|
||||||
|
|
||||||
# define metadata URL
|
# define metadata URL
|
||||||
Metadata.base_url = config.get('CIC_META_URL')
|
UserMetadata.base_url = config.get('CIC_META_URL')
|
||||||
|
|
||||||
# define signer values
|
# define signer values
|
||||||
export_dir = config.get('PGP_EXPORT_DIR')
|
Signer.gpg_path = config.get('PGP_EXPORT_DIR')
|
||||||
if export_dir:
|
|
||||||
validate_presence(path=export_dir)
|
|
||||||
Signer.gpg_path = export_dir
|
|
||||||
Signer.gpg_passphrase = config.get('PGP_PASSPHRASE')
|
Signer.gpg_passphrase = config.get('PGP_PASSPHRASE')
|
||||||
key_file_path = f"{config.get('PGP_KEYS_PATH')}{config.get('PGP_PRIVATE_KEYS')}"
|
Signer.key_file_path = f"{config.get('PGP_KEYS_PATH')}{config.get('PGP_PRIVATE_KEYS')}"
|
||||||
if key_file_path:
|
|
||||||
validate_presence(path=key_file_path)
|
|
||||||
Signer.key_file_path = key_file_path
|
|
||||||
|
|
||||||
# set up translations
|
|
||||||
i18n.load_path.append(config.get('APP_LOCALE_PATH'))
|
|
||||||
i18n.set('fallback', config.get('APP_LOCALE_FALLBACK'))
|
|
||||||
|
|
||||||
# set up celery
|
# 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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
|
|
||||||
logg = logging.getLogger(__file__)
|
logg = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
|
||||||
def process_mini_statement_request(state_machine_data: Tuple[str, dict, Account]):
|
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
|
|
||||||
|
|
||||||
def menu_one_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_one_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return user_input == '1'
|
return user_input == '1'
|
||||||
|
|
||||||
|
|
||||||
def menu_two_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_two_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return user_input == '2'
|
return user_input == '2'
|
||||||
|
|
||||||
|
|
||||||
def menu_three_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_three_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return user_input == '3'
|
return user_input == '3'
|
||||||
|
|
||||||
|
|
||||||
def menu_four_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_four_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return user_input == '4'
|
return user_input == '4'
|
||||||
|
|
||||||
|
|
||||||
def menu_five_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_five_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return user_input == '5'
|
return user_input == '5'
|
||||||
|
|
||||||
|
|
||||||
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bo
|
|||||||
return user_input == '00'
|
return user_input == '00'
|
||||||
|
|
||||||
|
|
||||||
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, User]) -> 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.account import AccountStatus, Account
|
from cic_ussd.db.models.user import AccountStatus, User
|
||||||
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, Account]) -> bool:
|
def is_valid_pin(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> bool:
|
|||||||
return pin_is_valid
|
return pin_is_valid
|
||||||
|
|
||||||
|
|
||||||
def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> 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
|
||||||
@@ -48,7 +48,7 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
|||||||
return user.verify_password(password=user_input)
|
return user.verify_password(password=user_input)
|
||||||
|
|
||||||
|
|
||||||
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> 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
|
||||||
@@ -59,7 +59,7 @@ def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> 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, Account]):
|
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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
|
||||||
@@ -94,7 +94,7 @@ def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Accoun
|
|||||||
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, Account]) -> bool:
|
def pins_match(state_machine_data: Tuple[str, dict, User]) -> 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
|
||||||
@@ -108,7 +108,7 @@ def pins_match(state_machine_data: Tuple[str, dict, Account]) -> 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, Account]):
|
def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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
|
||||||
@@ -116,11 +116,11 @@ def complete_pin_change(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
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
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def is_blocked_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> 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
|
||||||
@@ -131,7 +131,7 @@ def is_blocked_pin(state_machine_data: Tuple[str, dict, Account]) -> 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, Account]) -> bool:
|
def is_valid_new_pin(state_machine_data: Tuple[str, dict, User]) -> 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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, Account]):
|
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, User]):
|
||||||
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, Account]):
|
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
|
||||||
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, Account]):
|
def upsell_unregistered_recipient(state_machine_data: Tuple[str, dict, User]):
|
||||||
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.account import AccountStatus, Account
|
from cic_ussd.db.models.user import AccountStatus, User
|
||||||
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, Account]) -> bool:
|
def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -> 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, Account]) -> bool:
|
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> 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, Account]) -
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def has_sufficient_balance(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> 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,20 +72,19 @@ def has_sufficient_balance(state_machine_data: Tuple[str, dict, Account]) -> boo
|
|||||||
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, Account]):
|
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
|
session_data = {
|
||||||
session_data = ussd_session.get('session_data') or {}
|
'recipient_phone_number': user_input
|
||||||
session_data['recipient_phone_number'] = user_input
|
}
|
||||||
|
|
||||||
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, Account]):
|
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""
|
"""
|
||||||
:param state_machine_data:
|
:param state_machine_data:
|
||||||
:type state_machine_data:
|
:type state_machine_data:
|
||||||
@@ -97,27 +96,26 @@ def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
recipient = get_user_by_phone_number(phone_number=user_input)
|
recipient = get_user_by_phone_number(phone_number=user_input)
|
||||||
blockchain_address = recipient.blockchain_address
|
blockchain_address = recipient.blockchain_address
|
||||||
# retrieve and cache account's metadata
|
# retrieve and cache account's metadata
|
||||||
s_query_person_metadata = celery.signature(
|
s_query_user_metadata = celery.signature(
|
||||||
'cic_ussd.tasks.metadata.query_person_metadata',
|
'cic_ussd.tasks.metadata.query_user_metadata',
|
||||||
[blockchain_address]
|
[blockchain_address]
|
||||||
)
|
)
|
||||||
s_query_person_metadata.apply_async(queue='cic-ussd')
|
s_query_user_metadata.apply_async(queue='cic-ussd')
|
||||||
|
|
||||||
|
|
||||||
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
|
session_data = {
|
||||||
session_data = ussd_session.get('session_data') or {}
|
'transaction_amount': user_input
|
||||||
session_data['transaction_amount'] = user_input
|
}
|
||||||
|
|
||||||
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, Account]):
|
def process_transaction_request(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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,8 +10,8 @@ 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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
from cic_ussd.error import MetadataNotFoundError
|
from cic_ussd.error import UserMetadataNotFoundError
|
||||||
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
|
||||||
from cic_ussd.redis import get_cached_data
|
from cic_ussd.redis import get_cached_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, Account]):
|
def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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'
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, Account]):
|
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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'
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def update_account_status_to_active(state_machine_data: Tuple[str, dict, Account]):
|
def update_account_status_to_active(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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()
|
||||||
Account.session.add(user)
|
User.session.add(user)
|
||||||
Account.session.commit()
|
User.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def process_gender_user_input(user: Account, user_input: str):
|
def process_gender_user_input(user: User, user_input: str):
|
||||||
"""
|
"""
|
||||||
:param user:
|
:param user:
|
||||||
:type user:
|
:type user:
|
||||||
@@ -74,7 +74,7 @@ def process_gender_user_input(user: Account, user_input: str):
|
|||||||
return gender
|
return gender
|
||||||
|
|
||||||
|
|
||||||
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, Account]):
|
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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: Account):
|
def format_user_metadata(metadata: dict, user: User):
|
||||||
"""
|
"""
|
||||||
:param metadata:
|
:param metadata:
|
||||||
:type metadata:
|
:type metadata:
|
||||||
@@ -150,7 +150,7 @@ def format_user_metadata(metadata: dict, user: Account):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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
|
||||||
@@ -164,14 +164,14 @@ def save_complete_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
user_metadata = format_user_metadata(metadata=metadata, user=user)
|
user_metadata = format_user_metadata(metadata=metadata, user=user)
|
||||||
|
|
||||||
blockchain_address = user.blockchain_address
|
blockchain_address = user.blockchain_address
|
||||||
s_create_person_metadata = celery.signature(
|
s_create_user_metadata = celery.signature(
|
||||||
'cic_ussd.tasks.metadata.create_person_metadata',
|
'cic_ussd.tasks.metadata.create_user_metadata',
|
||||||
[blockchain_address, user_metadata]
|
[blockchain_address, user_metadata]
|
||||||
)
|
)
|
||||||
s_create_person_metadata.apply_async(queue='cic-ussd')
|
s_create_user_metadata.apply_async(queue='cic-ussd')
|
||||||
|
|
||||||
|
|
||||||
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
|
||||||
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(
|
||||||
@@ -181,7 +181,7 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
user_metadata = get_cached_data(key=key)
|
user_metadata = get_cached_data(key=key)
|
||||||
|
|
||||||
if not user_metadata:
|
if not user_metadata:
|
||||||
raise MetadataNotFoundError(f'Expected user metadata but found none in cache for key: {blockchain_address}')
|
raise UserMetadataNotFoundError(f'Expected user metadata but found none in cache for key: {blockchain_address}')
|
||||||
|
|
||||||
given_name = ussd_session.get('session_data').get('given_name')
|
given_name = ussd_session.get('session_data').get('given_name')
|
||||||
family_name = ussd_session.get('session_data').get('family_name')
|
family_name = ussd_session.get('session_data').get('family_name')
|
||||||
@@ -192,7 +192,7 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
# validate user metadata
|
# validate user metadata
|
||||||
person = Person()
|
person = Person()
|
||||||
user_metadata = json.loads(user_metadata)
|
user_metadata = json.loads(user_metadata)
|
||||||
deserialized_person = person.deserialize(person_data=user_metadata)
|
deserialized_person = person.deserialize(metadata=user_metadata)
|
||||||
|
|
||||||
# edit specific metadata attribute
|
# edit specific metadata attribute
|
||||||
if given_name:
|
if given_name:
|
||||||
@@ -211,18 +211,18 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
|||||||
|
|
||||||
edited_metadata = deserialized_person.serialize()
|
edited_metadata = deserialized_person.serialize()
|
||||||
|
|
||||||
s_edit_person_metadata = celery.signature(
|
s_edit_user_metadata = celery.signature(
|
||||||
'cic_ussd.tasks.metadata.edit_person_metadata',
|
'cic_ussd.tasks.metadata.edit_user_metadata',
|
||||||
[blockchain_address, edited_metadata]
|
[blockchain_address, edited_metadata, 'pgp']
|
||||||
)
|
)
|
||||||
s_edit_person_metadata.apply_async(queue='cic-ussd')
|
s_edit_user_metadata.apply_async(queue='cic-ussd')
|
||||||
|
|
||||||
|
|
||||||
def get_user_metadata(state_machine_data: Tuple[str, dict, Account]):
|
def get_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||||
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(
|
||||||
'cic_ussd.tasks.metadata.query_person_metadata',
|
'cic_ussd.tasks.metadata.query_user_metadata',
|
||||||
[blockchain_address]
|
[blockchain_address]
|
||||||
)
|
)
|
||||||
s_get_user_metadata.apply_async(queue='cic-ussd')
|
s_get_user_metadata.apply_async(queue='cic-ussd')
|
||||||
|
|||||||
@@ -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.account import Account
|
from cic_ussd.db.models.user import User
|
||||||
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, Account]):
|
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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, Account]):
|
|||||||
return user_metadata is not None
|
return user_metadata is not None
|
||||||
|
|
||||||
|
|
||||||
def is_valid_name(state_machine_data: Tuple[str, dict, Account]):
|
def is_valid_name(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""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, Account]):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, Account]):
|
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, User]):
|
||||||
"""
|
"""
|
||||||
: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
|
||||||
Account, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
|
User, 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.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user