Compare commits

...

38 Commits

Author SHA1 Message Date
nolash
803d06d2e4 Merge remote-tracking branch 'origin/master' into lash/fix-chainlib-upgrade 2021-04-25 14:11:08 +02:00
Louis Holbrook
7504a899a1 Merge branch 'lash/fix-chainlib-upgrade' into 'master'
Upgrade chainlib

See merge request grassrootseconomics/cic-internal-integration!119
2021-04-25 12:08:40 +00:00
Louis Holbrook
c20c5af27c Upgrade chainlib 2021-04-25 12:08:40 +00:00
nolash
5899348a54 Revert result extended setting 2021-04-25 14:08:19 +02:00
nolash
965aeeacb9 Patch up failed merge 2021-04-25 13:52:03 +02:00
nolash
3b1cc00c32 Merge remote-tracking branch 'origin/master' into lash/fix-chainlib-upgrade 2021-04-25 12:37:31 +02:00
nolash
da324ce7d6 Upgrade chainlib 2021-04-25 12:35:34 +02:00
Louis Holbrook
32b72274f5 Merge branch 'lash/emergency-shutdown-II' into 'master'
Add remaining health checks and shutdown on critical errors

See merge request grassrootseconomics/cic-internal-integration!115
2021-04-24 17:53:45 +00:00
Louis Holbrook
f50da54274 Add remaining health checks and shutdown on critical errors 2021-04-24 17:53:45 +00:00
Louis Holbrook
dd94b8a190 Merge branch 'lash/default-token' into 'master'
cic-eth: Add default token setting to cic-eth with api

See merge request grassrootseconomics/cic-internal-integration!113
2021-04-24 17:49:21 +00:00
Louis Holbrook
16dd210965 cic-eth: Add default token setting to cic-eth with api 2021-04-24 17:49:21 +00:00
Louis Holbrook
cd0e702e3a Merge branch 'lash/custom-meta' into 'master'
Add custom meta tags

See merge request grassrootseconomics/cic-internal-integration!114
2021-04-24 06:14:25 +00:00
Louis Holbrook
cfab16f4a9 Add custom meta tags 2021-04-24 06:14:24 +00:00
Louis Holbrook
60fdb06034 Merge branch 'lash/emergency-shutdown' into 'master'
cic-eth: Add sanity checks for emergency shutdown / liveness tests

See merge request grassrootseconomics/cic-internal-integration!110
2021-04-23 21:02:52 +00:00
Louis Holbrook
3129a78e06 cic-eth: Add sanity checks for emergency shutdown / liveness tests 2021-04-23 21:02:51 +00:00
Louis Holbrook
6b6ec8659b Merge branch 'lash/simpler-token-selector' into 'master'
Simplify token selector

See merge request grassrootseconomics/cic-internal-integration!112
2021-04-23 08:17:59 +00:00
nolash
96e755b54d Simplify token selector 2021-04-22 11:58:39 +02:00
nolash
f38458ff4c Merge branch 'master' of gitlab.com:grassrootseconomics/cic-internal-integration 2021-04-22 11:57:15 +02:00
Louis Holbrook
660d524401 Merge branch 'lash/health-util' into 'master'
K8s health utilities for cic containers

See merge request grassrootseconomics/cic-internal-integration!108
2021-04-21 17:34:13 +00:00
Louis Holbrook
1bc7cde1f0 K8s health utilities for cic containers 2021-04-21 17:34:13 +00:00
Louis Holbrook
9c22ffca38 Merge branch 'lash/ussd-final-steps' into 'master'
USSD final steps

See merge request grassrootseconomics/cic-internal-integration!111
2021-04-21 17:25:57 +00:00
Louis Holbrook
39fe4a14ec USSD final steps 2021-04-21 17:25:57 +00:00
nolash
65250196cc cic-eth versionbump 2021-04-21 19:03:14 +02:00
Louis Holbrook
0123ce13ea Merge branch 'lash/settable-gas-price' into 'master'
Adapt deployment to Bloxberg

See merge request grassrootseconomics/cic-internal-integration!99
2021-04-21 05:46:42 +00:00
Louis Holbrook
03b3e8cd3f Adapt deployment to Bloxberg 2021-04-21 05:46:42 +00:00
Louis Holbrook
3ee84f780e Merge branch 'lash/cic-cache-syncer-backend-mixup' into 'master'
CIC-cache backend syncer mixup

See merge request grassrootseconomics/cic-internal-integration!106
2021-04-20 13:25:03 +00:00
Louis Holbrook
95269f69ed CIC-cache backend syncer mixup 2021-04-20 13:25:02 +00:00
621780e9b6 Update .cic-template.yml 2021-04-19 17:56:19 +00:00
eecdca1a55 Merge branch 'philip/ussd-db-fixes' into 'master'
Philip/ussd db fixes

See merge request grassrootseconomics/cic-internal-integration!107
2021-04-19 08:44:41 +00:00
6fef0ecec9 Philip/ussd db fixes 2021-04-19 08:44:40 +00:00
Louis Holbrook
6b89a2da89 Merge branch 'lash/chainlib-regression' into 'master'
Correct chainlib import paths

See merge request grassrootseconomics/cic-internal-integration!101
2021-04-16 21:44:14 +00:00
Louis Holbrook
254f2a266b Correct chainlib import paths 2021-04-16 21:44:14 +00:00
ba18914498 Merge branch 'philip/fix-filter-callbacks' into 'master'
Philip/fix filter callbacks

See merge request grassrootseconomics/cic-internal-integration!95
2021-04-16 20:24:07 +00:00
f410e8b7e3 Philip/fix filter callbacks 2021-04-16 20:24:07 +00:00
01454c9ac0 Merge branch 'add-chainsync-db' into 'master'
Add chainsync db

See merge request grassrootseconomics/cic-internal-integration!105
2021-04-15 21:22:07 +00:00
462d7046ed Add chainsync db 2021-04-15 21:22:07 +00:00
f91b491251 Merge branch 'ida/changes-to-args-commandline' into 'master'
Ida/changes to args commandline

See merge request grassrootseconomics/cic-internal-integration!104
2021-04-15 17:04:17 +00:00
0de79521dc Ida/changes to args commandline 2021-04-15 17:04:16 +00:00
100 changed files with 1914 additions and 543 deletions

8
.gitignore vendored
View File

@@ -1,2 +1,10 @@
service-configs/*
!service-configs/.gitkeep
**/node_modules/
__pycache__
*.pyc
*.o
gmon.out
*.egg-info
dist/
build/

View File

@@ -67,6 +67,7 @@ class ERC20TransferFilter(SyncFilter):
tx.status == Status.SUCCESS,
block.timestamp,
)
db_session.flush()
#db_session.flush()
db_session.commit()
return True

View File

@@ -77,7 +77,7 @@ def main():
if len(syncer_backends) == 0:
logg.info('found no backends to resume')
syncers.append(SQLBackend.initial(chain_spec, block_offset))
syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
else:
for syncer_backend in syncer_backends:
logg.info('resuming sync session {}'.format(syncer_backend))

View File

@@ -17,7 +17,7 @@ RUN apt-get update && \
# Copy shared requirements from top of mono-repo
RUN echo "copying root req file ${root_requirement_file}"
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
COPY cic-cache/requirements.txt ./
COPY cic-cache/setup.cfg \

View File

@@ -2,4 +2,9 @@
. ./db.sh
if [ $? -ne "0" ]; then
>&2 echo db migrate fail
exit 1
fi
/usr/local/bin/cic-cache-trackerd $@

View File

@@ -2,7 +2,7 @@
import datetime
import logging
# third-party imports
# external imports
import celery
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.chain import ChainSpec
@@ -32,7 +32,9 @@ def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.AL
:returns: New lock state for address
:rtype: number
"""
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = '::'
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)
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
return chained_input
@@ -51,7 +53,9 @@ def unlock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.
:returns: New lock state for address
:rtype: number
"""
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.reset(chain_str, flags, address=address)
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
return chained_input
@@ -127,7 +131,9 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
@celery_app.task(base=CriticalSQLAlchemyTask)
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
session = SessionBase.create_session()
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
if address != None:
@@ -139,3 +145,9 @@ def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
session.flush()
session.close()
return chained_input
@celery_app.task()
def shutdown(message):
logg.critical('shutdown called: {}'.format(message))
celery_app.control.shutdown() #broadcast('shutdown')

View File

@@ -0,0 +1,19 @@
# 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,
}

View File

@@ -62,6 +62,18 @@ 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):
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.

View File

View File

@@ -0,0 +1,8 @@
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

View File

@@ -0,0 +1,48 @@
# 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

View File

@@ -0,0 +1,18 @@
# 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

View File

@@ -0,0 +1,37 @@
# 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

View File

@@ -74,10 +74,11 @@ class LockEnum(enum.IntEnum):
QUEUE: Disable queueing new or modified transactions
"""
STICKY=1
CREATE=2
SEND=4
QUEUE=8
QUERY=16
INIT=2
CREATE=4
SEND=8
QUEUE=16
QUERY=32
ALL=int(0xfffffffffffffffe)

View File

@@ -5,8 +5,11 @@ Revises: 1f1b3b641d08
Create Date: 2021-04-02 18:41:20.864265
"""
import datetime
from alembic import op
import sqlalchemy as sa
from chainlib.eth.constant import ZERO_ADDRESS
from cic_eth.db.enum import LockEnum
# revision identifiers, used by Alembic.
@@ -23,10 +26,11 @@ def upgrade():
sa.Column("address", sa.String(42), nullable=True),
sa.Column('blockchain', sa.String),
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
sa.Column("date_created", sa.DateTime, nullable=False),
sa.Column("date_created", sa.DateTime, nullable=False, default=datetime.datetime.utcnow),
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=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():

View File

@@ -48,6 +48,8 @@ class RoleMissingError(Exception):
pass
class IntegrityError(Exception):
"""Exception raised to signal irregularities with deduplication and ordering of tasks
@@ -62,15 +64,19 @@ class LockedError(Exception):
pass
class SignerError(Exception):
class SeppukuError(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
"""
pass
class EthError(Exception):
"""Exception raised when unspecified error from evm node is encountered
class RoleAgencyError(SeppukuError):
"""Exception raise when a role cannot perform its function. This is a critical exception
"""
pass

View File

@@ -4,10 +4,10 @@ import logging
# external imports
import celery
from erc20_single_shot_faucet import SingleShotFaucet as Faucet
from chainlib.eth.constant import ZERO_ADDRESS
from hexathon import (
strip_0x,
)
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.connection import RPCConnection
from chainlib.eth.sign import (
new_account,
@@ -19,6 +19,7 @@ from chainlib.eth.tx import (
unpack,
)
from chainlib.chain import ChainSpec
from chainlib.error import JSONRPCException
from eth_accounts_index import AccountRegistry
from sarafu_faucet import MinterFaucet as Faucet
from chainqueue.db.models.tx import TxCache
@@ -70,11 +71,18 @@ def create(self, password, chain_spec_dict):
a = None
conn = RPCConnection.connect(chain_spec, 'signer')
o = new_account()
a = conn.do(o)
try:
a = conn.do(o)
except ConnectionError as e:
raise SignerError(e)
except FileNotFoundError as e:
raise SignerError(e)
conn.disconnect()
# TODO: It seems infeasible that a can be None in any case, verify
if a == None:
raise SignerError('create account')
logg.debug('created account {}'.format(a))
# Initialize nonce provider record for account
@@ -219,21 +227,22 @@ def have(self, account, chain_spec_dict):
"""
chain_spec = ChainSpec.from_dict(chain_spec_dict)
o = sign_message(account, '0x2a')
try:
conn = RPCConnection.connect(chain_spec, 'signer')
except Exception as e:
logg.debug('cannot sign with {}: {}'.format(account, e))
return None
conn = RPCConnection.connect(chain_spec, 'signer')
try:
conn.do(o)
conn.disconnect()
return account
except Exception as e:
except ConnectionError as e:
raise SignerError(e)
except FileNotFoundError as e:
raise SignerError(e)
except JSONRPCException as e:
logg.debug('cannot sign with {}: {}'.format(account, e))
conn.disconnect()
return None
conn.disconnect()
return account
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
def set_role(self, tag, address, chain_spec_dict):

View File

@@ -108,7 +108,13 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_d
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
try:
(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.disconnect()
@@ -171,7 +177,12 @@ def approve(self, tokens, holder_address, spender_address, value, chain_spec_dic
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
try:
(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.disconnect()

View File

@@ -328,7 +328,12 @@ def refill_gas(self, recipient_address, chain_spec_dict):
# build and add transaction
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
try:
(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))
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)
@@ -404,7 +409,12 @@ 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)
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
tx['gasPrice'] = new_gas_price
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
try:
(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(
chain_spec,
tx['nonce'],

View File

@@ -1,6 +1,10 @@
# extended imports
# external imports
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.status import Status as TxStatus
from cic_eth_registry.erc20 import ERC20Token
# local imports
from cic_eth.ext.address import translate_address
class ExtendedTx:
@@ -27,12 +31,12 @@ class ExtendedTx:
self.status_code = TxStatus.PENDING.value
def set_actors(self, sender, recipient, trusted_declarator_addresses=None):
def set_actors(self, sender, recipient, trusted_declarator_addresses=None, caller_address=ZERO_ADDRESS):
self.sender = sender
self.recipient = recipient
if trusted_declarator_addresses != None:
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec)
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec)
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
def set_tokens(self, source, source_value, destination=None, destination_value=None):
@@ -40,8 +44,8 @@ class ExtendedTx:
destination = source
if destination_value == None:
destination_value = source_value
st = ERC20Token(self.rpc, source)
dt = ERC20Token(self.rpc, destination)
st = ERC20Token(self.chain_spec, self.rpc, source)
dt = ERC20Token(self.chain_spec, self.rpc, destination)
self.source_token = source
self.source_token_symbol = st.symbol
self.source_token_name = st.name
@@ -62,10 +66,10 @@ class ExtendedTx:
self.status_code = n
def to_dict(self):
def asdict(self):
o = {}
for attr in dir(self):
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'to_dict']:
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'asdict', 'rpc']:
continue
o[attr] = getattr(self, attr)
return o

View File

@@ -59,6 +59,7 @@ args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
}
# override args
config.dict_override(args_override, 'cli')
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
@@ -67,7 +68,9 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
queue = args.q
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
chain_spec = None
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)
@@ -82,6 +85,9 @@ def lock_names_to_flag(s):
# TODO: move each command to submodule
def main():
chain_spec_dict = None
if chain_spec != None:
chain_spec_dict = chain_spec.asdict()
if args.command == 'unlock':
flags = lock_names_to_flag(args.flags)
if not is_checksum_address(args.address):
@@ -91,7 +97,7 @@ def main():
'cic_eth.admin.ctrl.unlock',
[
None,
chain_spec.asdict(),
chain_spec_dict,
args.address,
flags,
],
@@ -110,7 +116,7 @@ def main():
'cic_eth.admin.ctrl.lock',
[
None,
chain_spec.asdict(),
chain_spec_dict,
args.address,
flags,
],

View File

@@ -1,7 +1,7 @@
# standard imports
import logging
# third-party imports
# external imports
import celery
from cic_eth_registry.error import UnknownContractError
from chainlib.status import Status as TxStatus
@@ -9,7 +9,13 @@ from chainlib.eth.address import to_checksum_address
from chainlib.eth.error import RequestMismatchException
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.erc20 import ERC20
from hexathon import strip_0x
from hexathon import (
strip_0x,
add_0x,
)
# TODO: use sarafu_Faucet for both when inheritance has been implemented
from erc20_single_shot_faucet import SingleShotFaucet
from sarafu_faucet import MinterFaucet as Faucet
# local imports
from .base import SyncFilter
@@ -18,65 +24,73 @@ from cic_eth.eth.meta import ExtendedTx
logg = logging.getLogger().getChild(__name__)
def parse_transfer(tx):
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = r[1]
transfer_data['from'] = tx['from']
transfer_data['token_address'] = tx['to']
return ('transfer', transfer_data)
def parse_transferfrom(tx):
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = unpack_transferfrom(tx.payload)
transfer_data['from'] = r[0]
transfer_data['to'] = r[1]
transfer_data['value'] = r[2]
transfer_data['token_address'] = tx['to']
return ('transferfrom', transfer_data)
def parse_giftto(tx):
# TODO: broken
logg.error('broken')
return
transfer_data = unpack_gift(tx.payload)
transfer_data['from'] = tx.inputs[0]
transfer_data['value'] = 0
transfer_data['token_address'] = ZERO_ADDRESS
# TODO: would be better to query the gift amount from the block state
for l in tx.logs:
topics = l['topics']
logg.debug('topixx {}'.format(topics))
if strip_0x(topics[0]) == '45c201a59ac545000ead84f30b2db67da23353aa1d58ac522c48505412143ffa':
#transfer_data['value'] = web3.Web3.toInt(hexstr=strip_0x(l['data']))
transfer_data['value'] = int.from_bytes(bytes.fromhex(strip_0x(l_data)))
#token_address_bytes = topics[2][32-20:]
token_address = strip_0x(topics[2])[64-40:]
transfer_data['token_address'] = to_checksum_address(token_address)
return ('tokengift', transfer_data)
class CallbackFilter(SyncFilter):
trusted_addresses = []
def __init__(self, chain_spec, method, queue):
def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS):
self.queue = queue
self.method = method
self.chain_spec = chain_spec
self.caller_address = caller_address
def parse_transfer(self, tx, conn):
if not tx.payload:
return (None, None)
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = r[1]
transfer_data['from'] = tx.outputs[0]
transfer_data['token_address'] = tx.inputs[0]
return ('transfer', transfer_data)
def parse_transferfrom(self, tx, conn):
if not tx.payload:
return (None, None)
r = ERC20.parse_transfer_from_request(tx.payload)
transfer_data = {}
transfer_data['from'] = r[0]
transfer_data['to'] = r[1]
transfer_data['value'] = r[2]
transfer_data['token_address'] = tx.inputs[0]
return ('transferfrom', transfer_data)
def parse_giftto(self, tx, conn):
if not tx.payload:
return (None, None)
r = Faucet.parse_give_to_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = tx.value
transfer_data['from'] = tx.outputs[0]
#transfer_data['token_address'] = tx.inputs[0]
faucet_contract = tx.inputs[0]
c = SingleShotFaucet(self.chain_spec)
o = c.token(faucet_contract, sender_address=self.caller_address)
r = conn.do(o)
transfer_data['token_address'] = add_0x(c.parse_token(r))
o = c.amount(faucet_contract, sender_address=self.caller_address)
r = conn.do(o)
transfer_data['value'] = c.parse_amount(r)
return ('tokengift', transfer_data)
def call_back(self, transfer_type, result):
logg.debug('result {}'.format(result))
result['chain_spec'] = result['chain_spec'].asdict()
s = celery.signature(
self.method,
[
result,
transfer_type,
int(result['status_code'] == 0),
int(result['status_code'] != 0),
],
queue=self.queue,
)
@@ -92,26 +106,29 @@ class CallbackFilter(SyncFilter):
# s_translate.link(s)
# s_translate.apply_async()
t = s.apply_async()
return s
return t
def parse_data(self, tx):
def parse_data(self, tx, conn):
transfer_type = None
transfer_data = None
# TODO: what's with the mix of attributes and dict keys
logg.debug('have payload {}'.format(tx.payload))
method_signature = tx.payload[:8]
logg.debug('tx status {}'.format(tx.status))
for parser in [
parse_transfer,
parse_transferfrom,
parse_giftto,
self.parse_transfer,
self.parse_transferfrom,
self.parse_giftto,
]:
try:
(transfer_type, transfer_data) = parser(tx)
break
if tx:
(transfer_type, transfer_data) = parser(tx, conn)
if transfer_type == None:
continue
else:
pass
except RequestMismatchException:
continue
@@ -128,7 +145,7 @@ class CallbackFilter(SyncFilter):
transfer_data = None
transfer_type = None
try:
(transfer_type, transfer_data) = self.parse_data(tx)
(transfer_type, transfer_data) = self.parse_data(tx, conn)
except TypeError:
logg.debug('invalid method data length for tx {}'.format(tx.hash))
return
@@ -144,16 +161,17 @@ class CallbackFilter(SyncFilter):
result = None
try:
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses)
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses, caller_address=self.caller_address)
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
if transfer_data['status'] == 0:
tokentx.set_status(1)
else:
tokentx.set_status(0)
t = self.call_back(transfer_type, tokentx.to_dict())
logg.info('callback success task id {} tx {}'.format(t, tx.hash))
result = tokentx.asdict()
t = self.call_back(transfer_type, result)
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
except UnknownContractError:
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tc.queue, tc.method, transfer_data['to'], tx.hash))
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tx.queue, tx.method, transfer_data['to'], tx.hash))
def __str__(self):

View File

@@ -11,10 +11,19 @@ import websocket
# external imports
import celery
import confini
from chainlib.connection import RPCConnection
from chainlib.eth.connection import EthUnixSignerConnection
from chainlib.connection import (
RPCConnection,
ConnType,
)
from chainlib.eth.connection import (
EthUnixSignerConnection,
EthHTTPSignerConnection,
)
from chainlib.chain import ChainSpec
from chainqueue.db.models.otx import Otx
from cic_eth_registry.error import UnknownContractError
import liveness.linux
# local imports
from cic_eth.eth import (
@@ -27,6 +36,7 @@ from cic_eth.eth import (
from cic_eth.admin import (
debug,
ctrl,
token,
)
from cic_eth.queue import (
query,
@@ -39,6 +49,7 @@ from cic_eth.queue import (
from cic_eth.callbacks import (
Callback,
http,
noop,
#tcp,
redis,
)
@@ -50,6 +61,8 @@ from cic_eth.registry import (
connect_declarator,
connect_token_registry,
)
from cic_eth.task import BaseTask
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -61,6 +74,7 @@ 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('-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('--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('--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')
@@ -80,6 +94,7 @@ config.process()
args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
'CIC_DEFAULT_TOKEN_SYMBOL': getattr(args, 'default_token_symbol'),
'ETH_PROVIDER': getattr(args, 'p'),
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
}
@@ -89,14 +104,15 @@ config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
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
dsn = dsn_from_config(config)
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), 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
current_app = celery.Celery(__name__)
@@ -133,11 +149,18 @@ else:
})
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('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection)
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer')
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():
argv = ['worker']
@@ -161,7 +184,11 @@ def main():
rpc = RPCConnection.connect(chain_spec, 'default')
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
try:
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')
if trusted_addresses_src == None:
@@ -170,10 +197,18 @@ def main():
trusted_addresses = trusted_addresses_src.split(',')
for address in trusted_addresses:
logg.info('using trusted address {}'.format(address))
connect_declarator(rpc, chain_spec, trusted_addresses)
connect_token_registry(rpc, chain_spec)
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)
liveness.linux.reset(rundir=config.get('CIC_RUN_DIR'))
@celery.signals.eventlet_pool_postshutdown.connect

View File

@@ -15,7 +15,6 @@ import cic_base.config
import cic_base.log
import cic_base.argparse
import cic_base.rpc
from cic_eth_registry import CICRegistry
from cic_eth_registry.error import UnknownContractError
from chainlib.chain import ChainSpec
from chainlib.eth.constant import ZERO_ADDRESS
@@ -43,6 +42,12 @@ from cic_eth.runnable.daemons.filters import (
TransferAuthFilter,
)
from cic_eth.stat import init_chain_stat
from cic_eth.registry import (
connect as connect_registry,
connect_declarator,
connect_token_registry,
)
script_dir = os.path.realpath(os.path.dirname(__file__))
@@ -109,6 +114,8 @@ def main():
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
syncers.append(HeadSyncer(syncer_backend))
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
if trusted_addresses_src == None:
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
@@ -116,6 +123,8 @@ def main():
trusted_addresses = trusted_addresses_src.split(',')
for address in trusted_addresses:
logg.info('using trusted address {}'.format(address))
connect_declarator(rpc, chain_spec, trusted_addresses)
connect_token_registry(rpc, chain_spec)
CallbackFilter.trusted_addresses = trusted_addresses
callback_filters = []

View File

@@ -0,0 +1,65 @@
#!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()

View File

@@ -10,15 +10,13 @@ import sqlalchemy
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import RPCGasOracle
import liveness.linux
# local imports
from cic_eth.error import (
SignerError,
EthError,
)
from cic_eth.error import SeppukuError
from cic_eth.db.models.base import SessionBase
logg = logging.getLogger(__name__)
logg = logging.getLogger().getChild(__name__)
celery_app = celery.current_app
@@ -29,6 +27,9 @@ class BaseTask(celery.Task):
call_address = ZERO_ADDRESS
create_nonce_oracle = RPCNonceOracle
create_gas_oracle = RPCGasOracle
default_token_address = None
default_token_symbol = None
run_dir = '/run'
def create_session(self):
return BaseTask.session_func()
@@ -38,6 +39,19 @@ class BaseTask(celery.Task):
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
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):
retry_jitter = True
@@ -67,7 +81,6 @@ class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
sqlalchemy.exc.TimeoutError,
requests.exceptions.ConnectionError,
sqlalchemy.exc.ResourceClosedError,
EthError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
@@ -78,13 +91,11 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
sqlalchemy.exc.DatabaseError,
sqlalchemy.exc.TimeoutError,
sqlalchemy.exc.ResourceClosedError,
SignerError,
)
class CriticalWeb3AndSignerTask(CriticalTask):
autoretry_for = (
requests.exceptions.ConnectionError,
SignerError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
@@ -94,3 +105,8 @@ class CriticalWeb3AndSignerTask(CriticalTask):
def hello(self):
time.sleep(0.1)
return id(SessionBase.create_session)
@celery_app.task()
def check_health(self):
pass

View File

@@ -10,7 +10,7 @@ version = (
0,
11,
0,
'beta.3',
'beta.9',
)
version_object = semver.VersionInfo(

View File

@@ -3,3 +3,6 @@ registry_address =
chain_spec = evm:bloxberg:8996
tx_retry_delay =
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

View File

@@ -3,3 +3,6 @@ registry_address =
chain_spec = evm:bloxberg:8996
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

View File

@@ -1,8 +1,3 @@
[eth]
#ws_provider = ws://localhost:8546
#ttp_provider = http://localhost:8545
provider = http://localhost:63545
gas_provider_address =
#chain_id =
abi_dir = /home/lash/src/ext/cic/grassrootseconomics/cic-contracts/abis
account_accounts_index_writer =
gas_gifter_minimum_balance = 10000000000000000000000

View File

@@ -1,5 +1,5 @@
[signer]
socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc
socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc
secret = deedbeef
database_name = signer_test
dev_keys_path =

View File

@@ -1,8 +1,3 @@
[eth]
#ws_provider = ws://localhost:8546
#ttp_provider = http://localhost:8545
provider = http://localhost:8545
gas_provider_address =
#chain_id =
abi_dir = /usr/local/share/cic/solidity/abi
account_accounts_index_writer =
gas_gifter_minimum_balance = 10000000000000000000000

View File

@@ -29,8 +29,7 @@ RUN /usr/local/bin/python -m pip install --upgrade pip
# python merge_requirements.py | tee merged_requirements.txt
#RUN cd cic-base && \
# pip install $pip_extra_index_url_flag -r ./merged_requirements.txt
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
RUN pip install $pip_extra_index_url_flag chainsyncer~=0.0.2a2
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a77
COPY cic-eth/scripts/ scripts/
COPY cic-eth/setup.cfg cic-eth/setup.py ./
@@ -54,3 +53,5 @@ COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
mkdir -p /usr/local/share/cic/solidity && \
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
COPY util/liveness/health.sh /usr/local/bin/health.sh

View File

@@ -1,25 +1,25 @@
cic-base~=0.1.2a67
cic-base==0.1.2b3
celery==4.4.7
crypto-dev-signer~=0.4.14a17
crypto-dev-signer~=0.4.14b3
confini~=0.3.6rc3
cic-eth-registry~=0.5.4a13
cic-eth-registry~=0.5.4a16
#cic-bancor~=0.0.6
redis==3.5.3
alembic==1.4.2
websockets==8.1
requests~=2.24.0
eth_accounts_index~=0.0.11a7
erc20-transfer-authorization~=0.3.1a3
#simple-rlp==0.1.2
eth_accounts_index~=0.0.11a9
erc20-transfer-authorization~=0.3.1a5
uWSGI==2.0.19.1
semver==2.13.0
websocket-client==0.57.0
moolb~=0.1.1b2
eth-address-index~=0.1.1a7
chainlib~=0.0.2a5
eth-address-index~=0.1.1a9
chainlib~=0.0.2a18
hexathon~=0.0.1a7
chainsyncer[sql]~=0.0.2a2
chainqueue~=0.0.1a7
pysha3==1.0.2
coincurve==15.0.0
sarafu-faucet~=0.0.2a19
sarafu-faucet==0.0.2a28
potaahto~=0.0.1a1

View File

@@ -38,6 +38,7 @@ packages =
cic_eth.runnable.daemons.filters
cic_eth.callbacks
cic_eth.sync
cic_eth.check
scripts =
./scripts/migrate.py
@@ -52,6 +53,7 @@ console_scripts =
cic-eth-create = cic_eth.runnable.create:main
cic-eth-inspect = cic_eth.runnable.view: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
cic-eth-tag = cic_eth.runnable.tag:main
cic-eth-resend = cic_eth.runnable.resend:main

View File

@@ -4,4 +4,4 @@ pytest-mock==3.3.1
pytest-cov==2.10.1
eth-tester==0.5.0b3
py-evm==0.3.0a20
giftable-erc20-token==0.0.8a4
giftable-erc20-token==0.0.8a9

View File

@@ -3,8 +3,12 @@ import os
import sys
import logging
# external imports
from chainlib.eth.erc20 import ERC20
# local imports
from cic_eth.api import Api
from cic_eth.task import BaseTask
script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.dirname(script_dir)
@@ -28,3 +32,26 @@ def api(
):
chain_str = str(default_chain_spec)
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

View File

@@ -0,0 +1,223 @@
# standard import
import logging
import datetime
import os
# external imports
import pytest
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.tx import (
receipt,
transaction,
Tx,
)
from chainlib.eth.block import Block
from chainlib.eth.erc20 import ERC20
from sarafu_faucet import MinterFaucet
from eth_accounts_index import AccountRegistry
from potaahto.symbols import snake_and_camel
from hexathon import add_0x
# local imports
from cic_eth.runnable.daemons.filters.callback import CallbackFilter
logg = logging.getLogger()
@pytest.mark.skip()
def test_transfer_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
token_roles,
contract_roles,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
logg.debug(r)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_transfer(tx, eth_rpc)
assert transfer_type == 'transfer'
@pytest.mark.skip()
def test_transfer_from_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
token_roles,
contract_roles,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.approve(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer_from(foo_token, agent_roles['ALICE'], token_roles['FOO_TOKEN_OWNER'], agent_roles['BOB'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_transferfrom(tx, eth_rpc)
assert transfer_type == 'transferfrom'
def test_faucet_gift_to_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
contract_roles,
faucet,
account_registry,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
gas_oracle = OverrideGasOracle(conn=rpc, limit=800000)
nonce_oracle = RPCNonceOracle(contract_roles['ACCOUNT_REGISTRY_WRITER'], rpc)
txf = AccountRegistry(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.add(account_registry, contract_roles['ACCOUNT_REGISTRY_WRITER'], agent_roles['ALICE'])
r = rpc.do(o)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
txf = MinterFaucet(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.give_to(faucet, agent_roles['ALICE'], agent_roles['ALICE'])
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_giftto(tx, eth_rpc)
assert transfer_type == 'tokengift'
assert transfer_data['token_address'] == foo_token
def test_callback_filter(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
token_roles,
agent_roles,
contract_roles,
register_lookups,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
logg.debug(r)
mockblock_src = {
'hash': add_0x(os.urandom(32).hex()),
'number': '0x2a',
'transactions': [tx_hash_hex],
'timestamp': datetime.datetime.utcnow().timestamp(),
}
mockblock = Block(mockblock_src)
tx_src = snake_and_camel(r)
tx = Tx(tx_src, block=mockblock)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
class CallbackMock:
def __init__(self):
self.results = {}
def call_back(self, transfer_type, result):
self.results[transfer_type] = result
mock = CallbackMock()
fltr.call_back = mock.call_back
fltr.filter(eth_rpc, mockblock, tx, init_database)
assert mock.results.get('transfer') != None
assert mock.results['transfer']['destination_token'] == foo_token

View File

@@ -34,6 +34,7 @@ def celery_includes():
'cic_eth.admin.ctrl',
'cic_eth.admin.nonce',
'cic_eth.admin.debug',
'cic_eth.admin.token',
'cic_eth.eth.account',
'cic_eth.callbacks.noop',
'cic_eth.callbacks.http',

View File

@@ -0,0 +1,21 @@
# 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

View File

@@ -33,7 +33,9 @@ elif args.v:
config = confini.Config(args.c, args.env_prefix)
config.process()
config.add(args.q, '_CELERY_QUEUE', True)
config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
# connect to database
dsn = dsn_from_config(config)

View File

@@ -6,11 +6,10 @@ import time
import semver
# local imports
from cic_notify.error import PleaseCommitFirstError
logg = logging.getLogger()
version = (0, 4, 0, 'alpha.3')
version = (0, 4, 0, 'alpha.4')
version_object = semver.VersionInfo(
major=version[0],
@@ -18,27 +17,4 @@ version_object = semver.VersionInfo(
patch=version[2],
prerelease=version[3],
)
version_string = str(version_object)
def git_hash():
import subprocess
git_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True)
git_hash_brief = git_hash.stdout.decode('utf-8')[:8]
return git_hash_brief
try:
version_git = git_hash()
version_string += '+build.{}'.format(version_git)
except FileNotFoundError:
time_string_pair = str(time.time()).split('.')
version_string += '+build.{}{:<09d}'.format(
time_string_pair[0],
int(time_string_pair[1]),
)
logg.info(f'Final version string will be {version_string}')
__version_string__ = version_string

View File

@@ -1,6 +1,5 @@
[metadata]
name = cic-notify
version= 0.4.0a3
description = CIC notifications service
author = Louis Holbrook
author_email = dev@holbrook.no

View File

@@ -1,9 +1,31 @@
# standard imports
import logging
import subprocess
import time
from setuptools import setup
# third-party imports
# local imports
from cic_notify.version import version_string
logg = logging.getLogger()
def git_hash():
git_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True)
git_hash_brief = git_hash.stdout.decode('utf-8')[:8]
return git_hash_brief
try:
version_git = git_hash()
version_string += '+build.{}'.format(version_git)
except FileNotFoundError:
time_string_pair = str(time.time()).split('.')
version_string += '+build.{}{:<09d}'.format(
time_string_pair[0],
int(time_string_pair[1]),
)
logg.info(f'Final version string will be {version_string}')
requirements = []
@@ -25,6 +47,6 @@ while True:
test_requirements_file.close()
setup(
version=version_string,
install_requires=requirements,
tests_require=test_requirements,
)
tests_require=test_requirements)

View File

@@ -20,5 +20,5 @@ TRANSITIONS=/usr/src/cic-ussd/transitions/
[client]
host =
port =
port =
ssl =

View File

@@ -1,6 +1,6 @@
[celery]
BROKER_URL=redis://redis:6379
RESULT_URL=redis://redis:6379
BROKER_URL=redis://
RESULT_URL=redis://
[redis]
HOSTNAME=redis

View File

@@ -8,12 +8,12 @@ from cic_types.processor import generate_metadata_pointer
# local imports
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.redis import get_cached_data
def define_account_tx_metadata(user: User):
def define_account_tx_metadata(user: Account):
# get sender metadata
identifier = blockchain_address_to_metadata_pointer(
blockchain_address=user.blockchain_address

View File

@@ -1,4 +1,4 @@
"""Create user table
"""Create account table
Revision ID: f289e8510444
Revises:
@@ -17,7 +17,7 @@ depends_on = None
def upgrade():
op.create_table('user',
op.create_table('account',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('blockchain_address', sa.String(), nullable=False),
sa.Column('phone_number', sa.String(), nullable=False),
@@ -29,11 +29,11 @@ def upgrade():
sa.Column('updated', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True)
op.create_index(op.f('ix_user_blockchain_address'), 'user', ['blockchain_address'], unique=True)
op.create_index(op.f('ix_account_phone_number'), 'account', ['phone_number'], unique=True)
op.create_index(op.f('ix_account_blockchain_address'), 'account', ['blockchain_address'], unique=True)
def downgrade():
op.drop_index(op.f('ix_user_blockchain_address'), table_name='user')
op.drop_index(op.f('ix_user_phone_number'), table_name='user')
op.drop_table('user')
op.drop_index(op.f('ix_account_blockchain_address'), table_name='account')
op.drop_index(op.f('ix_account_phone_number'), table_name='account')
op.drop_table('account')

View File

@@ -16,12 +16,12 @@ class AccountStatus(IntEnum):
RESET = 4
class User(SessionBase):
class Account(SessionBase):
"""
This class defines a user record along with functions responsible for hashing the user's corresponding password and
subsequently verifying a password's validity given an input to compare against the persisted hash.
"""
__tablename__ = 'user'
__tablename__ = 'account'
blockchain_address = Column(String)
phone_number = Column(String)
@@ -38,7 +38,7 @@ class User(SessionBase):
self.account_status = AccountStatus.PENDING.value
def __repr__(self):
return f'<User: {self.blockchain_address}>'
return f'<Account: {self.blockchain_address}>'
def create_password(self, password):
"""This method takes a password value and hashes the value before assigning it to the corresponding

View File

@@ -10,7 +10,7 @@ from tinydb.table import Document
from typing import Optional
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.db.models.ussd_session import UssdSession
from cic_ussd.db.models.task_tracker import TaskTracker
from cic_ussd.menu.ussd_menu import UssdMenu
@@ -143,10 +143,10 @@ def get_account_status(phone_number) -> str:
:return: The user account status.
:rtype: str
"""
user = User.session.query(User).filter_by(phone_number=phone_number).first()
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
status = user.get_account_status()
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
return status
@@ -269,12 +269,12 @@ def cache_account_creation_task_id(phone_number: str, task_id: str):
redis_cache.persist(name=task_id)
def process_current_menu(ussd_session: Optional[dict], user: User, user_input: str) -> Document:
def process_current_menu(ussd_session: Optional[dict], user: Account, user_input: str) -> Document:
"""This function checks user input and returns a corresponding ussd menu
:param ussd_session: An in db ussd session object.
:type ussd_session: UssdSession
:param user: A user object.
:type user: User
:type user: Account
:param user_input: The user's input.
:type user_input: str
:return: An in memory ussd menu object.
@@ -324,7 +324,7 @@ def process_menu_interaction_requests(chain_str: str,
else:
# get user
user = User.session.query(User).filter_by(phone_number=phone_number).first()
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
# find any existing ussd session
existing_ussd_session = UssdSession.session.query(UssdSession).filter_by(
@@ -390,10 +390,10 @@ def reset_pin(phone_number: str) -> str:
:return: The status of the pin reset.
:rtype: str
"""
user = User.session.query(User).filter_by(phone_number=phone_number).first()
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
user.reset_account_pin()
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
response = f'Pin reset for user {phone_number} is successful!'
return response

View File

@@ -5,7 +5,7 @@ from typing import Optional
import phonenumbers
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
def process_phone_number(phone_number: str, region: str):
@@ -30,14 +30,14 @@ def process_phone_number(phone_number: str, region: str):
return parsed_phone_number
def get_user_by_phone_number(phone_number: str) -> Optional[User]:
def get_user_by_phone_number(phone_number: str) -> Optional[Account]:
"""This function queries the database for a user based on the provided phone number.
:param phone_number: A valid phone number.
:type phone_number: str
:return: A user object matching a given phone number
:rtype: User|None
:rtype: Account|None
"""
# consider adding region to user's metadata
phone_number = process_phone_number(phone_number=phone_number, region='KE')
user = User.session.query(User).filter_by(phone_number=phone_number).first()
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
return user

View File

@@ -13,7 +13,7 @@ from tinydb.table import Document
from cic_ussd.account import define_account_tx_metadata, retrieve_account_statement
from cic_ussd.balance import BalanceManager, compute_operational_balance, get_cached_operational_balance
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.db.models.ussd_session import UssdSession
from cic_ussd.error import MetadataNotFoundError
from cic_ussd.menu.ussd_menu import UssdMenu
@@ -28,13 +28,13 @@ from cic_types.models.person import generate_metadata_pointer, get_contact_data_
logg = logging.getLogger(__name__)
def process_pin_authorization(display_key: str, user: User, **kwargs) -> str:
def process_pin_authorization(display_key: str, user: Account, **kwargs) -> str:
"""
This method provides translation for all ussd menu entries that follow the pin authorization pattern.
:param display_key: The path in the translation files defining an appropriate ussd response
:type display_key: str
:param user: The user in a running USSD session.
:type user: User
:type user: Account
:param kwargs: Any additional information required by the text values in the internationalization files.
:type kwargs
:return: A string value corresponding the ussd menu's text value.
@@ -55,13 +55,13 @@ def process_pin_authorization(display_key: str, user: User, **kwargs) -> str:
)
def process_exit_insufficient_balance(display_key: str, user: User, ussd_session: dict):
def process_exit_insufficient_balance(display_key: str, user: Account, ussd_session: dict):
"""This function processes the exit menu letting users their account balance is insufficient to perform a specific
transaction.
:param display_key: The path in the translation files defining an appropriate ussd response
:type display_key: str
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param ussd_session: A JSON serialized in-memory ussd session object
:type ussd_session: dict
:return: Corresponding translation text response
@@ -90,12 +90,12 @@ def process_exit_insufficient_balance(display_key: str, user: User, ussd_session
)
def process_exit_successful_transaction(display_key: str, user: User, ussd_session: dict):
def process_exit_successful_transaction(display_key: str, user: Account, ussd_session: dict):
"""This function processes the exit menu after a successful initiation for a transfer of tokens.
:param display_key: The path in the translation files defining an appropriate ussd response
:type display_key: str
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param ussd_session: A JSON serialized in-memory ussd session object
:type ussd_session: dict
:return: Corresponding translation text response
@@ -118,11 +118,11 @@ def process_exit_successful_transaction(display_key: str, user: User, ussd_sessi
)
def process_transaction_pin_authorization(user: User, display_key: str, ussd_session: dict):
def process_transaction_pin_authorization(user: Account, display_key: str, ussd_session: dict):
"""This function processes pin authorization where making a transaction is concerned. It constructs a
pre-transaction response menu that shows the details of the transaction.
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param display_key: The path in the translation files defining an appropriate ussd response
:type display_key: str
:param ussd_session: The USSD session determining what user data needs to be extracted and added to the menu's
@@ -151,7 +151,7 @@ def process_transaction_pin_authorization(user: User, display_key: str, ussd_ses
)
def process_account_balances(user: User, display_key: str, ussd_session: dict):
def process_account_balances(user: Account, display_key: str, ussd_session: dict):
"""
:param user:
:type user:
@@ -205,7 +205,7 @@ def format_transactions(transactions: list, preferred_language: str):
return formatted_transactions
def process_display_user_metadata(user: User, display_key: str):
def process_display_user_metadata(user: Account, display_key: str):
"""
:param user:
:type user:
@@ -238,7 +238,7 @@ def process_display_user_metadata(user: User, display_key: str):
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
def process_account_statement(user: User, display_key: str, ussd_session: dict):
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
"""
:param user:
:type user:
@@ -301,12 +301,12 @@ def process_account_statement(user: User, display_key: str, ussd_session: dict):
)
def process_start_menu(display_key: str, user: User):
def process_start_menu(display_key: str, user: Account):
"""This function gets data on an account's balance and token in order to append it to the start of the start menu's
title. It passes said arguments to the translation function and returns the appropriate corresponding text from the
translation files.
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param display_key: The path in the translation files defining an appropriate ussd response
:type display_key: str
:return: Corresponding translation text response
@@ -361,13 +361,13 @@ def retrieve_most_recent_ussd_session(phone_number: str) -> UssdSession:
return last_ussd_session
def process_request(user_input: str, user: User, ussd_session: Optional[dict] = None) -> Document:
def process_request(user_input: str, user: Account, ussd_session: Optional[dict] = None) -> Document:
"""This function assesses a request based on the user from the request comes, the session_id and the user's
input. It determines whether the request translates to a return to an existing session by checking whether the
provided session id exists in the database or whether the creation of a new ussd session object is warranted.
It then returns the appropriate ussd menu text values.
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param user_input: The value a user enters in the ussd menu.
:type user_input: str
:param ussd_session: A JSON serialized in-memory ussd session object
@@ -415,14 +415,14 @@ def process_request(user_input: str, user: User, ussd_session: Optional[dict] =
return UssdMenu.find_by_name(name='initial_pin_entry')
def next_state(ussd_session: dict, user: User, user_input: str) -> str:
def next_state(ussd_session: dict, user: Account, user_input: str) -> str:
"""This function navigates the state machine based on the ussd session object and user inputs it receives.
It checks the user input and provides the successive state in the state machine. It then updates the session's
state attribute with the new state.
:param ussd_session: A JSON serialized in-memory ussd session object
:type ussd_session: dict
:param user: The user requesting access to the ussd menu.
:type user: User
:type user: Account
:param user_input: The value a user enters in the ussd menu.
:type user_input: str
:return: A string value corresponding the successive give a specific state in the state machine.
@@ -438,7 +438,7 @@ def custom_display_text(
display_key: str,
menu_name: str,
ussd_session: dict,
user: User) -> str:
user: Account) -> str:
"""This function extracts the appropriate session data based on the current menu name. It then inserts them as
keywords in the i18n function.
:param display_key: The path in the translation files defining an appropriate ussd response
@@ -446,7 +446,7 @@ def custom_display_text(
:param menu_name: The name by which a specific menu can be identified.
:type menu_name: str
:param user: The user in a running USSD session.
:type user: User
:type user: Account
:param ussd_session: A JSON serialized in-memory ussd session object
:type ussd_session: dict
:return: A string value corresponding the ussd menu's text value.

View File

@@ -10,7 +10,7 @@ from urllib.parse import urlparse, parse_qs
from sqlalchemy import desc
# local imports
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.operations import get_account_status, reset_pin
from cic_ussd.validator import check_known_user
@@ -123,9 +123,9 @@ def process_locked_accounts_requests(env: dict) -> tuple:
else:
limit = r[1]
locked_accounts = User.session.query(User.blockchain_address).filter(
User.account_status == AccountStatus.LOCKED.value,
User.failed_pin_attempts >= 3).order_by(desc(User.updated)).offset(offset).limit(limit).all()
locked_accounts = Account.session.query(Account.blockchain_address).filter(
Account.account_status == AccountStatus.LOCKED.value,
Account.failed_pin_attempts >= 3).order_by(desc(Account.updated)).offset(offset).limit(limit).all()
# convert lists to scalar blockchain addresses
locked_accounts = [blockchain_address for (blockchain_address, ) in locked_accounts]

View File

@@ -57,19 +57,17 @@ arg_parser.add_argument('--env-prefix',
help='environment prefix for variables to overwrite configuration')
args = arg_parser.parse_args()
# parse config
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
config.process()
config.censor('PASSWORD', 'DATABASE')
# define log levels
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
# log config vars
logg.debug(config)
# parse config
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
config.process()
config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
# initialize elements
# set up translations

View File

@@ -6,6 +6,7 @@ import tempfile
# third party imports
import celery
import i18n
import redis
from confini import Config
@@ -33,18 +34,18 @@ arg_parser.add_argument('-vv', action='store_true', help='be more verbose')
arg_parser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
args = arg_parser.parse_args()
# parse config
config = Config(config_dir=args.c, env_prefix=args.env_prefix)
config.process()
config.censor('PASSWORD', 'DATABASE')
# define log levels
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
logg.debug(config)
# parse config
config = Config(args.c, args.env_prefix)
config.process()
config.add(args.q, '_CELERY_QUEUE', True)
config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
# connect to database
data_source_name = dsn_from_config(config)
@@ -77,6 +78,10 @@ if key_file_path:
validate_presence(path=key_file_path)
Signer.key_file_path = key_file_path
# set up translations
i18n.load_path.append(config.get('APP_LOCALE_PATH'))
i18n.set('fallback', config.get('APP_LOCALE_FALLBACK'))
# set up celery
current_app = celery.Celery(__name__)

View File

@@ -5,12 +5,12 @@ from typing import Tuple
# third-party imports
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
logg = logging.getLogger(__file__)
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
def process_mini_statement_request(state_machine_data: Tuple[str, dict, Account]):
"""This function compiles a brief statement of a user's last three inbound and outbound transactions and send the
same as a message on their selected avenue for notification.
:param state_machine_data: A tuple containing user input, a ussd session and user object.

View File

@@ -6,10 +6,10 @@ ussd menu facilitating the return of appropriate menu responses based on said us
from typing import Tuple
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
def menu_one_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_one_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that user input matches a string with value '1'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
@@ -20,7 +20,7 @@ def menu_one_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '1'
def menu_two_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_two_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that user input matches a string with value '2'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -31,7 +31,7 @@ def menu_two_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '2'
def menu_three_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_three_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that user input matches a string with value '3'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -42,7 +42,7 @@ def menu_three_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '3'
def menu_four_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_four_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
This function checks that user input matches a string with value '4'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -54,7 +54,7 @@ def menu_four_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '4'
def menu_five_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_five_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
This function checks that user input matches a string with value '5'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -66,7 +66,7 @@ def menu_five_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '5'
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
This function checks that user input matches a string with value '00'
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -78,7 +78,7 @@ def menu_zero_zero_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
return user_input == '00'
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, User]) -> bool:
def menu_ninety_nine_selected(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
This function checks that user input matches a string with value '99'
:param state_machine_data: A tuple containing user input, a ussd session and user object.

View File

@@ -12,7 +12,7 @@ from typing import Tuple
import bcrypt
# local imports
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.encoder import PasswordEncoder, create_password_hash
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
from cic_ussd.redis import InMemoryStore
@@ -21,7 +21,7 @@ from cic_ussd.redis import InMemoryStore
logg = logging.getLogger(__file__)
def is_valid_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_valid_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks a pin's validity by ensuring it has a length of for characters and the characters are
numeric.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -37,7 +37,7 @@ def is_valid_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
return pin_is_valid
def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -48,7 +48,7 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
return user.verify_password(password=user_input)
def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks whether a user's account is locked due to too many failed attempts.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -59,7 +59,7 @@ def is_locked_account(state_machine_data: Tuple[str, dict, User]) -> bool:
return user.get_account_status() == AccountStatus.LOCKED.name
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, User]):
def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Account]):
"""This function hashes a pin and stores it in session data.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -94,7 +94,7 @@ def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, User])
persist_session_to_db_task(external_session_id=external_session_id, queue='cic-ussd')
def pins_match(state_machine_data: Tuple[str, dict, User]) -> bool:
def pins_match(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -108,7 +108,7 @@ def pins_match(state_machine_data: Tuple[str, dict, User]) -> bool:
return bcrypt.checkpw(user_input.encode(), initial_pin)
def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
def complete_pin_change(state_machine_data: Tuple[str, dict, Account]):
"""This function persists the user's pin to the database
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -116,11 +116,11 @@ def complete_pin_change(state_machine_data: Tuple[str, dict, User]):
user_input, ussd_session, user = state_machine_data
password_hash = ussd_session.get('session_data').get('initial_pin')
user.password_hash = password_hash
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_blocked_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks whether the user input confirming a specific pin matches the initial pin entered.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -131,7 +131,7 @@ def is_blocked_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
return user.get_account_status() == AccountStatus.LOCKED.name
def is_valid_new_pin(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_valid_new_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks whether the user's new pin is a valid pin and that it isn't the same as the old one.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple

View File

@@ -3,21 +3,21 @@ import logging
from typing import Tuple
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
logg = logging.getLogger()
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, User]):
def send_terms_to_user_if_required(state_machine_data: Tuple[str, dict, Account]):
user_input, ussd_session, user = state_machine_data
logg.debug('Requires integration to cic-notify.')
def process_mini_statement_request(state_machine_data: Tuple[str, dict, User]):
def process_mini_statement_request(state_machine_data: Tuple[str, dict, Account]):
user_input, ussd_session, user = state_machine_data
logg.debug('Requires integration to cic-notify.')
def upsell_unregistered_recipient(state_machine_data: Tuple[str, dict, User]):
def upsell_unregistered_recipient(state_machine_data: Tuple[str, dict, Account]):
user_input, ussd_session, user = state_machine_data
logg.debug('Requires integration to cic-notify.')

View File

@@ -9,7 +9,7 @@ import celery
# local imports
from cic_ussd.balance import BalanceManager, compute_operational_balance
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.operations import save_to_in_memory_ussd_session_data
from cic_ussd.phone_number import get_user_by_phone_number
from cic_ussd.redis import create_cached_data_key, get_cached_data
@@ -19,7 +19,7 @@ from cic_ussd.transactions import OutgoingTransactionProcessor
logg = logging.getLogger(__file__)
def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_valid_recipient(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that a user exists, is not the initiator of the transaction, has an active account status
and is authorized to perform standard transactions.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -34,7 +34,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> bool:
return is_not_initiator and has_active_account_status and recipient is not None
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> bool:
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
being attempted.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -49,7 +49,7 @@ def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> b
return False
def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> bool:
def has_sufficient_balance(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""This function checks that the transaction amount provided is valid as per the criteria for the transaction
being attempted.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -72,7 +72,7 @@ def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> bool:
return int(user_input) <= operational_balance
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, User]):
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Account]):
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
@@ -85,7 +85,7 @@ def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Us
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, Account]):
"""
:param state_machine_data:
:type state_machine_data:
@@ -104,7 +104,7 @@ def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
s_query_person_metadata.apply_async(queue='cic-ussd')
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, User]):
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, Account]):
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
@@ -117,7 +117,7 @@ def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict,
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
def process_transaction_request(state_machine_data: Tuple[str, dict, User]):
def process_transaction_request(state_machine_data: Tuple[str, dict, Account]):
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str

View File

@@ -10,7 +10,7 @@ from cic_types.models.person import generate_vcard_from_contact_data, manage_ide
# local imports
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.error import MetadataNotFoundError
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.operations import save_to_in_memory_ussd_session_data
@@ -19,40 +19,40 @@ from cic_ussd.redis import get_cached_data
logg = logging.getLogger(__file__)
def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, User]):
def change_preferred_language_to_en(state_machine_data: Tuple[str, dict, Account]):
"""This function changes the user's preferred language to english.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
user_input, ussd_session, user = state_machine_data
user.preferred_language = 'en'
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, User]):
def change_preferred_language_to_sw(state_machine_data: Tuple[str, dict, Account]):
"""This function changes the user's preferred language to swahili.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
user_input, ussd_session, user = state_machine_data
user.preferred_language = 'sw'
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
def update_account_status_to_active(state_machine_data: Tuple[str, dict, User]):
def update_account_status_to_active(state_machine_data: Tuple[str, dict, Account]):
"""This function sets user's account to active.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
user_input, ussd_session, user = state_machine_data
user.activate_account()
User.session.add(user)
User.session.commit()
Account.session.add(user)
Account.session.commit()
def process_gender_user_input(user: User, user_input: str):
def process_gender_user_input(user: Account, user_input: str):
"""
:param user:
:type user:
@@ -74,7 +74,7 @@ def process_gender_user_input(user: User, user_input: str):
return gender
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, Account]):
"""This function saves first name data to the ussd session in the redis cache.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -109,7 +109,7 @@ def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict,
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
def format_user_metadata(metadata: dict, user: User):
def format_user_metadata(metadata: dict, user: Account):
"""
:param metadata:
:type metadata:
@@ -150,7 +150,7 @@ def format_user_metadata(metadata: dict, user: User):
}
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, Account]):
"""This function persists elements of the user metadata stored in session data
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -171,7 +171,7 @@ def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
s_create_person_metadata.apply_async(queue='cic-ussd')
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
user_input, ussd_session, user = state_machine_data
blockchain_address = user.blockchain_address
key = generate_metadata_pointer(
@@ -218,7 +218,7 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
s_edit_person_metadata.apply_async(queue='cic-ussd')
def get_user_metadata(state_machine_data: Tuple[str, dict, User]):
def get_user_metadata(state_machine_data: Tuple[str, dict, Account]):
user_input, ussd_session, user = state_machine_data
blockchain_address = user.blockchain_address
s_get_user_metadata = celery.signature(

View File

@@ -7,14 +7,14 @@ from typing import Tuple
from cic_types.models.person import generate_metadata_pointer
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.redis import get_cached_data
logg = logging.getLogger()
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, Account]):
"""This function checks whether the attributes of the user's metadata constituting a profile are filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
@@ -29,7 +29,7 @@ def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
return user_metadata is not None
def is_valid_name(state_machine_data: Tuple[str, dict, User]):
def is_valid_name(state_machine_data: Tuple[str, dict, Account]):
"""This function checks that a user provided name is valid
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
@@ -43,7 +43,7 @@ def is_valid_name(state_machine_data: Tuple[str, dict, User]):
return False
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, User]):
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, Account]):
"""
:param state_machine_data:
:type state_machine_data:

View File

@@ -13,7 +13,7 @@ class UssdStateMachine(Machine):
"""This class describes a finite state machine responsible for maintaining all the states that describe the ussd
menu as well as providing a means for navigating through these states based on different user inputs.
It defines different helper functions that co-ordinate with the stakeholder components of the ussd menu: i.e the
User, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
Account, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
:cvar states: A list of pre-defined states.
:type states: list
:cvar transitions: A list of pre-defined transitions.

View File

@@ -9,7 +9,7 @@ import celery
# local imports
from cic_ussd.conversions import from_wei
from cic_ussd.db.models.base import SessionBase
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.account import define_account_tx_metadata
from cic_ussd.error import ActionDataNotFoundError
from cic_ussd.redis import InMemoryStore, cache_data, create_cached_data_key
@@ -49,7 +49,7 @@ def process_account_creation_callback(self, result: str, url: str, status_code:
phone_number = account_creation_data.get('phone_number')
# create user
user = User(blockchain_address=result, phone_number=phone_number)
user = Account(blockchain_address=result, phone_number=phone_number)
session.add(user)
session.commit()
session.close()
@@ -83,12 +83,12 @@ def process_incoming_transfer_callback(result: dict, param: str, status_code: in
# collect result data
recipient_blockchain_address = result.get('recipient')
sender_blockchain_address = result.get('sender')
token_symbol = result.get('token_symbol')
value = result.get('destination_value')
token_symbol = result.get('destination_token_symbol')
value = result.get('destination_token_value')
# try to find users in system
recipient_user = session.query(User).filter_by(blockchain_address=recipient_blockchain_address).first()
sender_user = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
recipient_user = session.query(Account).filter_by(blockchain_address=recipient_blockchain_address).first()
sender_user = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
# check whether recipient is in the system
if not recipient_user:
@@ -188,8 +188,8 @@ def process_statement_callback(result, param: str, status_code: int):
processed_transaction = {}
# check if sender is in the system
sender: User = session.query(User).filter_by(blockchain_address=sender_blockchain_address).first()
owner: User = session.query(User).filter_by(blockchain_address=param).first()
sender: Account = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first()
owner: Account = session.query(Account).filter_by(blockchain_address=param).first()
if sender:
processed_transaction['sender_phone_number'] = sender.phone_number
@@ -205,7 +205,7 @@ def process_statement_callback(result, param: str, status_code: int):
processed_transaction['sender_phone_number'] = 'GRASSROOTS ECONOMICS'
# check if recipient is in the system
recipient: User = session.query(User).filter_by(blockchain_address=recipient_address).first()
recipient: Account = session.query(Account).filter_by(blockchain_address=recipient_address).first()
if recipient:
processed_transaction['recipient_phone_number'] = recipient.phone_number

View File

@@ -8,7 +8,7 @@ from typing import Optional
def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs) -> str:
"""
Translates text mapped to a specific YAML key into the user's set preferred language.
:param preferred_language: User's preferred language in which to view the ussd menu.
:param preferred_language: Account's preferred language in which to view the ussd menu.
:type preferred_language str
:param key: Key to a specific YAML test entry
:type key: str

View File

@@ -8,7 +8,7 @@ import ipaddress
from confini import Config
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
logg = logging.getLogger(__file__)
@@ -68,7 +68,7 @@ def check_known_user(phone: str):
:return: Is known phone number
:rtype: boolean
"""
user = User.session.query(User).filter_by(phone_number=phone).first()
user = Account.session.query(Account).filter_by(phone_number=phone).first()
return user is not None

View File

@@ -1,7 +1,7 @@
# standard imports
import semver
version = (0, 3, 0, 'alpha.8')
version = (0, 3, 0, 'alpha.9')
version_object = semver.VersionInfo(
major=version[0],

View File

@@ -1,4 +1,4 @@
cic_base[full_graph]~=0.1.2a68
cic-eth~=0.11.0b3
cic-notify~=0.4.0a3
cic_base[full_graph]~=0.1.2b2
cic-eth~=0.11.0b9
cic-notify~=0.4.0a4
cic-types~=0.1.0a10

View File

@@ -4,19 +4,19 @@
import pytest
# platform imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
def test_user(init_database, set_fernet_key):
user = User(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
phone_number='+254700000000')
user = Account(blockchain_address='0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51',
phone_number='+254700000000')
user.create_password('0000')
session = User.session
session = Account.session
session.add(user)
session.commit()
queried_user = session.query(User).get(1)
queried_user = session.query(Account).get(1)
assert queried_user.blockchain_address == '0x417f5962fc52dc33ff0689659b25848680dec6dcedc6785b03d1df60fc6d5c51'
assert queried_user.phone_number == '+254700000000'
assert queried_user.failed_pin_attempts == 0
@@ -25,7 +25,7 @@ def test_user(init_database, set_fernet_key):
def test_user_state_transition(create_pending_user):
user = create_pending_user
session = User.session
session = Account.session
assert user.get_account_status() == 'PENDING'
user.activate_account()

View File

@@ -8,7 +8,7 @@ import celery
import pytest
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.error import ActionDataNotFoundError
from cic_ussd.conversions import from_wei
@@ -29,7 +29,7 @@ def test_successful_process_account_creation_callback_task(account_creation_acti
# WARNING: [THE SETTING OF THE ROOT ID IS A HACK AND SHOULD BE REVIEWED OR IMPROVED]
mocked_task_request.root_id = task_id
user = init_database.query(User).filter_by(phone_number=phone_number).first()
user = init_database.query(Account).filter_by(phone_number=phone_number).first()
assert user is None
redis_cache = init_redis_cache
@@ -48,7 +48,7 @@ def test_successful_process_account_creation_callback_task(account_creation_acti
)
s_process_callback_request.apply_async().get()
user = init_database.query(User).filter_by(phone_number=phone_number).first()
user = init_database.query(Account).filter_by(phone_number=phone_number).first()
assert user.blockchain_address == result
action_data = redis_cache.get(task_id)

View File

@@ -2,7 +2,7 @@
import json
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.db.models.account import Account
from cic_ussd.requests import (get_query_parameters,
get_request_endpoint,
get_request_method,
@@ -58,8 +58,8 @@ def test_process_locked_accounts_requests(create_locked_accounts, valid_locked_a
assert len(locked_account_addresses) == 10
# check that blockchain addresses are ordered by most recently accessed
user_1 = User.session.query(User).filter_by(blockchain_address=locked_account_addresses[2]).first()
user_2 = User.session.query(User).filter_by(blockchain_address=locked_account_addresses[7]).first()
user_1 = Account.session.query(Account).filter_by(blockchain_address=locked_account_addresses[2]).first()
user_2 = Account.session.query(Account).filter_by(blockchain_address=locked_account_addresses[7]).first()
assert user_1.updated > user_2.updated

View File

@@ -9,7 +9,7 @@ from cic_types.models.person import generate_metadata_pointer
from faker import Faker
# local imports
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.redis import cache_data
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
@@ -19,7 +19,7 @@ fake = Faker()
@pytest.fixture(scope='function')
def create_activated_user(init_database, set_fernet_key):
user = User(
user = Account(
blockchain_address='0xFD9c5aD15C72C6F60f1a119A608931226674243f',
phone_number='+25498765432'
)
@@ -33,7 +33,7 @@ def create_activated_user(init_database, set_fernet_key):
@pytest.fixture(scope='function')
def create_valid_tx_recipient(init_database, set_fernet_key):
user = User(
user = Account(
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf0',
phone_number='+25498765432'
)
@@ -47,7 +47,7 @@ def create_valid_tx_recipient(init_database, set_fernet_key):
@pytest.fixture(scope='function')
def create_valid_tx_sender(init_database, set_fernet_key):
user = User(
user = Account(
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf1',
phone_number='+25498765433'
)
@@ -61,7 +61,7 @@ def create_valid_tx_sender(init_database, set_fernet_key):
@pytest.fixture(scope='function')
def create_pending_user(init_database, set_fernet_key):
user = User(
user = Account(
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
phone_number='+25498765432'
)
@@ -72,7 +72,7 @@ def create_pending_user(init_database, set_fernet_key):
@pytest.fixture(scope='function')
def create_pin_blocked_user(init_database, set_fernet_key):
user = User(
user = Account(
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
phone_number='+25498765432'
)
@@ -90,7 +90,7 @@ def create_locked_accounts(init_database, set_fernet_key):
blockchain_address = str(uuid.uuid4())
phone_number = fake.phone_number()
pin = f'{randint(1000, 9999)}'
user = User(phone_number=phone_number, blockchain_address=blockchain_address)
user = Account(phone_number=phone_number, blockchain_address=blockchain_address)
user.create_password(password=pin)
user.failed_pin_attempts = 3
user.account_status = AccountStatus.LOCKED.value

View File

@@ -57,19 +57,22 @@ WORKDIR /home/grassroots
USER grassroots
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
ARG cic_base_version=0.1.2a77
ARG cic_eth_version=0.11.0b6
ARG cic_base_version=0.1.2a79
ARG cic_eth_version=0.11.0b8+build.c2286e5c
ARG sarafu_faucet_version=0.0.2a28
ARG sarafu_token_version==0.0.1a6
ARG cic_contracts_version=0.0.2a2
RUN pip install --user --extra-index-url $pip_extra_index_url cic-base[full_graph]==$cic_base_version \
cic-eth==$cic_eth_version \
cic-contracts==$cic_contracts_version \
sarafu-faucet==$sarafu_faucet_version
sarafu-faucet==$sarafu_faucet_version \
sarafu-token==$sarafu_token_version
FROM python:3.8.6-slim-buster as runtime-image
RUN apt-get update
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
RUN apt-get install -y --no-install-recommends jq
COPY --from=compile-image /usr/local/bin/ /usr/local/bin/
COPY --from=compile-image /usr/local/etc/cic/ /usr/local/etc/cic/

View File

@@ -2,82 +2,111 @@
set -a
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
CIC_CHAIN_SPEC=${CIC_CHAIN_SPEC:-evm:bloxberg:8995}
CIC_DEFAULT_TOKEN_SYMBOL=${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
DEV_ETH_ACCOUNT_RESERVE_MINTER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_RESERVE_AMOUNT=${DEV_ETH_RESERVE_AMOUNT:-""10000000000000000000000000000000000}
faucet_amount=${DEV_FAUCET_AMOUNT:-0}
keystore_file=$(realpath ./keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c)
DEV_FAUCET_AMOUNT=${DEV_FAUCET_AMOUNT:-0}
DEV_ETH_KEYSTORE_FILE=${DEV_ETH_KEYSTORE_FILE:-`realpath ./keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c`}
set -e
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=`eth-checksum $(cat $DEV_ETH_KEYSTORE_FILE | jq -r .address)`
if [ ! -z $DEV_ETH_GAS_PRICE ]; then
gas_price_arg="--gas-price $DEV_ETH_GAS_PRICE"
>&2 echo using static gas price $DEV_ETH_GAS_PRICE
fi
if [[ $CIC_DEFAULT_TOKEN_SYMBOL != 'GFT' && $CIC_DEFAULT_TOKEN_SYMBOL != 'SRF' ]]; then
>&2 echo CIC_DEFAULT_TOKEN_SYMBOL must be one of [GFT,SRF], but was $CIC_DEFAULT_TOKEN_SYMBOL
exit 1
fi
echo "environment:"
printenv
echo \n
echo "using wallet address '$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER' from keystore file $DEV_ETH_KEYSTORE_FILE"
# This is a grassroots team convention for building the Bancor contracts using the bancor protocol repository truffle setup
# Running this in docker-internal dev container (built from Docker folder in this repo) will write a
# source-able env file to CIC_DATA_DIR. Services dependent on these contracts can mount this file OR
# define these parameters at runtime
# pushd /usr/src
if [ -z $CIC_DATA_DIR ]; then
CIC_DATA_DIR=`mktemp -d`
fi
>&2 echo using data dir $CIC_DATA_DIR
init_level_file=${CIC_DATA_DIR}/.init
if [ ! -f ${CIC_DATA_DIR}/.init ]; then
echo "Creating .init file..."
mkdir -p $CIC_DATA_DIR
touch /tmp/cic/config/.init
touch $CIC_DATA_DIR/.init
# touch $init_level_file
fi
echo -n 1 > $init_level_file
# Abort on any error (including if wait-for-it fails).
set -e
# Wait for the backend to be up, if we know where it is.
if [[ -n "${ETH_PROVIDER}" ]]; then
echo "waiting for ${ETH_PROVIDER}..."
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
DEV_RESERVE_ADDRESS=`giftable-token-deploy -p $ETH_PROVIDER -y $keystore_file -i $CIC_CHAIN_SPEC -v -w --name "Sarafu" --symbol "SRF" --decimals 6`
giftable-token-gift -p $ETH_PROVIDER -y $keystore_file -i $CIC_CHAIN_SPEC -v -w -a $DEV_RESERVE_ADDRESS $DEV_RESERVE_AMOUNT
if [ ! -z "$DEV_USE_DOCKER_WAIT_SCRIPT" ]; then
echo "waiting for ${ETH_PROVIDER}..."
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
fi
#BANCOR_REGISTRY_ADDRESS=`cic-bancor-deploy --bancor-dir /usr/local/share/cic/bancor -z $DEV_ETH_RESERVE_ADDRESS -p $ETH_PROVIDER -o $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER`
if [ $CIC_DEFAULT_TOKEN_SYMBOL == 'GFT' ]; then
>&2 echo "deploying 'giftable token'"
DEV_RESERVE_ADDRESS=`giftable-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Giftable Token" --symbol "GFT" --decimals 6 -vv`
else
>&2 echo "deploying 'sarafu' token'"
DEV_RESERVE_ADDRESS=`sarafu-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Sarafu" --decimals 6 -vv SRF $DEV_SARAFU_DEMURRAGE_LEVEL`
fi
giftable-token-gift $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w -a $DEV_RESERVE_ADDRESS $DEV_RESERVE_AMOUNT
#BANCOR_REGISTRY_ADDRESS=`cic-bancor-deploy $gas_price_arg --bancor-dir /usr/local/share/cic/bancor -z $DEV_ETH_RESERVE_ADDRESS -p $ETH_PROVIDER -o $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER`
>&2 echo "deploy account index contract"
DEV_ACCOUNT_INDEX_ADDRESS=`eth-accounts-index-deploy -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -y $keystore_file -vv -w`
DEV_ACCOUNT_INDEX_ADDRESS=`eth-accounts-index-deploy $gas_price_arg -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -vv -w`
>&2 echo "add deployer address as account index writer"
eth-accounts-index-writer -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_ACCOUNT_INDEX_ADDRESS -ww $debug $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER
eth-accounts-index-writer $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_ACCOUNT_INDEX_ADDRESS -ww -vv $debug $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER
CIC_REGISTRY_ADDRESS=`eth-contract-registry-deploy -i $CIC_CHAIN_SPEC -y $keystore_file --identifier BancorRegistry --identifier AccountRegistry --identifier TokenRegistry --identifier AddressDeclarator --identifier Faucet --identifier TransferAuthorization -p $ETH_PROVIDER -vv -w`
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv ContractRegistry $CIC_REGISTRY_ADDRESS
#cic-registry-set -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -k BancorRegistry -p $ETH_PROVIDER $BANCOR_REGISTRY_ADDRESS -vv
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AccountRegistry $DEV_ACCOUNT_INDEX_ADDRESS
CIC_REGISTRY_ADDRESS=`eth-contract-registry-deploy $gas_price_arg -i $CIC_CHAIN_SPEC -y $DEV_ETH_KEYSTORE_FILE --identifier BancorRegistry --identifier AccountRegistry --identifier TokenRegistry --identifier AddressDeclarator --identifier Faucet --identifier TransferAuthorization -p $ETH_PROVIDER -vv -w`
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv ContractRegistry $CIC_REGISTRY_ADDRESS
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AccountRegistry $DEV_ACCOUNT_INDEX_ADDRESS
# Deploy address declarator registry
>&2 echo "deploy address declarator contract"
declarator_description=0x546869732069732074686520434943206e6574776f726b000000000000000000
DEV_DECLARATOR_ADDRESS=`eth-address-declarator-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v $declarator_description`
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AddressDeclarator $DEV_DECLARATOR_ADDRESS
DEV_DECLARATOR_ADDRESS=`eth-address-declarator-deploy -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv $declarator_description`
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv AddressDeclarator $DEV_DECLARATOR_ADDRESS
# Deploy transfer authorization contact
>&2 echo "deploy address declarator contract"
DEV_TRANSFER_AUTHORIZATION_ADDRESS=`erc20-transfer-auth-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v`
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TransferAuthorization $DEV_TRANSFER_AUTHORIZATION_ADDRESS
DEV_TRANSFER_AUTHORIZATION_ADDRESS=`erc20-transfer-auth-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv`
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TransferAuthorization $DEV_TRANSFER_AUTHORIZATION_ADDRESS
# Deploy token index contract
>&2 echo "deploy token index contract"
DEV_TOKEN_INDEX_ADDRESS=`eth-token-index-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v`
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TokenRegistry $DEV_TOKEN_INDEX_ADDRESS
DEV_TOKEN_INDEX_ADDRESS=`eth-token-index-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv`
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv TokenRegistry $DEV_TOKEN_INDEX_ADDRESS
>&2 echo "add reserve token to token index"
eth-token-index-add -w -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv -a $DEV_TOKEN_INDEX_ADDRESS $DEV_RESERVE_ADDRESS
eth-token-index-add $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv -a $DEV_TOKEN_INDEX_ADDRESS $DEV_RESERVE_ADDRESS
# Sarafu faucet contract
>&2 echo "deploy token faucet contract"
DEV_FAUCET_ADDRESS=`sarafu-faucet-deploy -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -v --account-index-address $DEV_ACCOUNT_INDEX_ADDRESS $DEV_RESERVE_ADDRESS`
eth-contract-registry-set -w -y $keystore_file -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv Faucet $DEV_FAUCET_ADDRESS
DEV_FAUCET_ADDRESS=`sarafu-faucet-deploy $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -w -vv --account-index-address $DEV_ACCOUNT_INDEX_ADDRESS $DEV_RESERVE_ADDRESS`
eth-contract-registry-set $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -r $CIC_REGISTRY_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv Faucet $DEV_FAUCET_ADDRESS
>&2 echo "set faucet as token minter"
giftable-token-minter -w -y $keystore_file -a $DEV_RESERVE_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv $DEV_FAUCET_ADDRESS
giftable-token-minter $gas_price_arg -w -y $DEV_ETH_KEYSTORE_FILE -a $DEV_RESERVE_ADDRESS -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -vv $DEV_FAUCET_ADDRESS
>&2 echo "set token faucet amount"
sarafu-faucet-set -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_FAUCET_ADDRESS $faucet_amount
sarafu-faucet-set $gas_price_arg -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_FAUCET_ADDRESS -vv $DEV_FAUCET_AMOUNT
else

View File

@@ -25,13 +25,13 @@ from chainlib.eth.block import (
block_by_number,
Block,
)
from chainlib.eth.hash import keccak256_string_to_hex
from chainlib.hash import keccak256_string_to_hex
from chainlib.eth.address import to_checksum_address
from chainlib.eth.erc20 import ERC20
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import TxFactory
from chainlib.eth.rpc import jsonrpc_template
from chainlib.jsonrpc import jsonrpc_template
from chainlib.eth.error import EthException
from chainlib.chain import ChainSpec
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer

View File

@@ -32,6 +32,7 @@ default_config_dir = '/usr/local/etc/cic'
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=default_config_dir, help='config file')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='Chain specification string')
argparser.add_argument('--old-chain-spec', type=str, dest='old_chain_spec', default='evm:oldchain:1', help='chain spec')
argparser.add_argument('--redis-host', dest='redis_host', type=str, help='redis host to use for task submission')
argparser.add_argument('--redis-port', dest='redis_port', type=int, help='redis host to use for task submission')
argparser.add_argument('--redis-db', dest='redis_db', type=int, help='redis db to use for task submission and callback')
@@ -76,6 +77,11 @@ os.makedirs(user_new_dir)
meta_dir = os.path.join(args.user_dir, 'meta')
os.makedirs(meta_dir)
custom_dir = os.path.join(args.user_dir, 'custom')
os.makedirs(custom_dir)
os.makedirs(os.path.join(custom_dir, 'new'))
os.makedirs(os.path.join(custom_dir, 'meta'))
phone_dir = os.path.join(args.user_dir, 'phone')
os.makedirs(os.path.join(phone_dir, 'meta'))
@@ -85,6 +91,11 @@ os.stat(user_old_dir)
txs_dir = os.path.join(args.user_dir, 'txs')
os.makedirs(txs_dir)
user_dir = args.user_dir
old_chain_spec = ChainSpec.from_chain_str(args.old_chain_spec)
old_chain_str = str(old_chain_spec)
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
chain_str = str(chain_spec)
@@ -133,7 +144,17 @@ def register_eth(i, u):
if __name__ == '__main__':
#fi = open(os.path.join(user_out_dir, 'addresses.csv'), 'a')
user_tags = {}
f = open(os.path.join(user_dir, 'tags.csv'), 'r')
while True:
r = f.readline().rstrip()
if len(r) == 0:
break
(old_address, tags_csv) = r.split(':')
old_address = strip_0x(old_address)
user_tags[old_address] = tags_csv.split(',')
logg.debug('read tags {} for old address {}'.format(user_tags[old_address], old_address))
i = 0
j = 0
@@ -198,6 +219,29 @@ if __name__ == '__main__':
os.symlink(os.path.realpath(filepath), meta_phone_filepath)
# custom data
custom_key = generate_metadata_pointer(phone.encode('utf-8'), ':cic.custom')
custom_filepath = os.path.join(custom_dir, 'meta', custom_key)
filepath = os.path.join(
custom_dir,
'new',
custom_key[:2].upper(),
custom_key[2:4].upper(),
custom_key.upper() + '.json',
)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
sub_old_chain_str = '{}:{}'.format(old_chain_spec.common_name(), old_chain_spec.network_id())
f = open(filepath, 'w')
k = u.identities['evm'][sub_old_chain_str][0]
tag_data = {'tags': user_tags[strip_0x(k)]}
f.write(json.dumps(tag_data))
f.close()
os.symlink(os.path.realpath(filepath), custom_filepath)
i += 1
sys.stdout.write('imported {} {}'.format(i, u).ljust(200) + "\r")

View File

@@ -0,0 +1,139 @@
const fs = require('fs');
const path = require('path');
const http = require('http');
const cic = require('cic-client-meta');
const vcfp = require('vcard-parser');
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
const config = new cic.Config('./config');
config.process();
console.log(config);
function sendit(uid, envelope) {
const d = envelope.toJSON();
const contentLength = (new TextEncoder().encode(d)).length;
const opts = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Content-Length': contentLength,
'X-CIC-AUTOMERGE': 'client',
},
};
let url = config.get('META_URL');
url = url.replace(new RegExp('^(.+://[^/]+)/*$'), '$1/');
console.log('posting to url: ' + url + uid);
const req = http.request(url + uid, opts, (res) => {
res.on('data', process.stdout.write);
res.on('end', () => {
console.log('result', res.statusCode, res.headers);
});
});
if (!req.write(d)) {
console.error('foo', d);
process.exit(1);
}
req.end();
}
function doOne(keystore, filePath, identifier) {
const signer = new cic.PGPSigner(keystore);
const o = JSON.parse(fs.readFileSync(filePath).toString());
//const b = Buffer.from(j['vcard'], 'base64');
//const s = b.toString();
//const o = vcfp.parse(s);
//const phone = o.tel[0].value;
//cic.Phone.toKey(phone).then((uid) => {
//const o = fs.readFileSync(filePath, 'utf-8');
const s = new cic.Syncable(identifier, o);
s.setSigner(signer);
s.onwrap = (env) => {
sendit(identifier, env);
};
s.sign();
//});
}
const privateKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_PRIVATE_KEY_FILE'));
const publicKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_PRIVATE_KEY_FILE'));
pk = fs.readFileSync(privateKeyPath);
pubk = fs.readFileSync(publicKeyPath);
new cic.PGPKeyStore(
config.get('PGP_PASSPHRASE'),
pk,
pubk,
undefined,
undefined,
importMetaCustom,
);
const batchSize = 16;
const batchDelay = 1000;
const total = parseInt(process.argv[3]);
const dataDir = process.argv[2];
const workDir = path.join(dataDir, 'custom/meta');
const userDir = path.join(dataDir, 'custom/new');
let count = 0;
let batchCount = 0;
function importMetaCustom(keystore) {
let err;
let files;
try {
err, files = fs.readdirSync(workDir);
} catch {
console.error('source directory not yet ready', workDir);
setTimeout(importMetaPhone, batchDelay, keystore);
return;
}
let limit = batchSize;
if (files.length < limit) {
limit = files.length;
}
for (let i = 0; i < limit; i++) {
const file = files[i];
if (file.length < 3) {
console.debug('skipping file', file);
continue;
}
//const identifier = file.substr(0,file.length-5);
const identifier = file;
const filePath = path.join(workDir, file);
console.log(filePath);
//const address = fs.readFileSync(filePath).toString().substring(2).toUpperCase();
const custom = JSON.parse(fs.readFileSync(filePath).toString());
const customFilePath = path.join(
userDir,
identifier.substring(0, 2),
identifier.substring(2, 4),
identifier + '.json',
);
doOne(keystore, filePath, identifier);
fs.unlinkSync(filePath);
count++;
batchCount++;
if (batchCount == batchSize) {
console.debug('reached batch size, breathing');
batchCount=0;
setTimeout(importMeta, batchDelay, keystore);
return;
}
}
if (count == total) {
return;
}
setTimeout(importMetaCustom, 100, keystore);
}

View File

@@ -37,6 +37,7 @@ config_dir = os.environ.get('CONFINI_DIR', os.path.join(script_dir, 'config'))
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=config_dir, help='Config dir')
argparser.add_argument('--tag', type=str, action='append', help='Tags to add to record')
argparser.add_argument('--gift-threshold', type=int, help='If set, users will be funded with additional random balance (in token integer units)')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
@@ -81,6 +82,10 @@ phone_idx = []
user_dir = args.dir
user_count = args.user_count
tags = args.tag
if tags == None or len(tags) == 0:
tags = ['individual']
random.seed()
def genPhoneIndex(phone):
@@ -189,6 +194,7 @@ if __name__ == '__main__':
os.makedirs(base_dir, exist_ok=True)
fa = open(os.path.join(user_dir, 'balances.csv'), 'w')
ft = open(os.path.join(user_dir, 'tags.csv'), 'w')
i = 0
while i < user_count:
@@ -215,10 +221,12 @@ if __name__ == '__main__':
f.write(eth)
f.close()
ft.write('{}:{}\n'.format(eth, ','.join(tags)))
amount = genAmount()
fa.write('{},{}\n'.format(eth,amount))
logg.debug('pidx {}, uid {}, eth {}, amount {}, phone {}'.format(pidx, uid, eth, amount, phone))
i += 1
ft.close()
fa.close()

View File

@@ -25,13 +25,13 @@ from chainlib.eth.block import (
block_by_number,
Block,
)
from chainlib.eth.hash import keccak256_string_to_hex
from chainlib.hash import keccak256_string_to_hex
from chainlib.eth.address import to_checksum_address
from chainlib.eth.erc20 import ERC20
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import TxFactory
from chainlib.eth.rpc import jsonrpc_template
from chainlib.jsonrpc import jsonrpc_template
from chainlib.eth.error import EthException
from chainlib.chain import ChainSpec
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer

View File

@@ -24,7 +24,7 @@ from chainlib.eth.gas import RPCGasOracle
from chainlib.eth.nonce import RPCNonceOracle
from cic_types.processor import generate_metadata_pointer
from eth_accounts_index import AccountRegistry
from contract_registry import Registry
from eth_contract_registry import Registry
from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.eth.signer.defaultsigner import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.keyfile import to_dict as to_keyfile_dict
@@ -38,6 +38,7 @@ argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
argparser.add_argument('-c', type=str, default=default_config_dir, help='config file')
argparser.add_argument('--old-chain-spec', type=str, dest='old_chain_spec', default='evm:oldchain:1', help='chain spec')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='Chain specification string')
argparser.add_argument('-r', '--registry', dest='r', type=str, help='Contract registry address')
argparser.add_argument('--batch-size', dest='batch_size', default=50, type=int, help='burst size of sending transactions to node')
@@ -68,6 +69,11 @@ os.makedirs(user_new_dir)
meta_dir = os.path.join(args.user_dir, 'meta')
os.makedirs(meta_dir)
custom_dir = os.path.join(args.user_dir, 'custom')
os.makedirs(custom_dir)
os.makedirs(os.path.join(custom_dir, 'new'))
os.makedirs(os.path.join(custom_dir, 'meta'))
phone_dir = os.path.join(args.user_dir, 'phone')
os.makedirs(os.path.join(phone_dir, 'meta'))
@@ -77,9 +83,14 @@ os.stat(user_old_dir)
txs_dir = os.path.join(args.user_dir, 'txs')
os.makedirs(txs_dir)
user_dir = args.user_dir
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
chain_str = str(chain_spec)
old_chain_spec = ChainSpec.from_chain_str(args.old_chain_spec)
old_chain_str = str(old_chain_spec)
batch_size = args.batch_size
batch_delay = args.batch_delay
@@ -127,12 +138,18 @@ def register_eth(i, u):
return address
def register_ussd(u):
pass
if __name__ == '__main__':
user_tags = {}
f = open(os.path.join(user_dir, 'tags.csv'), 'r')
while True:
r = f.readline().rstrip()
if len(r) == 0:
break
(old_address, tags_csv) = r.split(':')
old_address = strip_0x(old_address)
user_tags[old_address] = tags_csv.split(',')
logg.debug('read tags {} for old address {}'.format(user_tags[old_address], old_address))
i = 0
j = 0
@@ -150,6 +167,7 @@ if __name__ == '__main__':
continue
f.close()
u = Person.deserialize(o)
logg.debug('u {}'.format(o))
new_address = register_eth(i, u)
if u.identities.get('evm') == None:
@@ -157,8 +175,6 @@ if __name__ == '__main__':
sub_chain_str = '{}:{}'.format(chain_spec.common_name(), chain_spec.network_id())
u.identities['evm'][sub_chain_str] = [new_address]
register_ussd(u)
new_address_clean = strip_0x(new_address)
filepath = os.path.join(
user_new_dir,
@@ -198,6 +214,29 @@ if __name__ == '__main__':
os.symlink(os.path.realpath(filepath), meta_phone_filepath)
# custom data
custom_key = generate_metadata_pointer(phone.encode('utf-8'), ':cic.custom')
custom_filepath = os.path.join(custom_dir, 'meta', custom_key)
filepath = os.path.join(
custom_dir,
'new',
custom_key[:2].upper(),
custom_key[2:4].upper(),
custom_key.upper() + '.json',
)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
sub_old_chain_str = '{}:{}'.format(old_chain_spec.common_name(), old_chain_spec.network_id())
f = open(filepath, 'w')
k = u.identities['evm'][sub_old_chain_str][0]
tag_data = {'tags': user_tags[strip_0x(k)]}
f.write(json.dumps(tag_data))
f.close()
os.symlink(os.path.realpath(filepath), custom_filepath)
i += 1
sys.stdout.write('imported {} {}'.format(i, u).ljust(200) + "\r")

View File

@@ -5,39 +5,40 @@
"packages": {
"": {
"dependencies": {
"cic-client-meta": "^0.0.7-alpha.5",
"cic-client-meta": "0.0.7-alpha.6",
"vcard-parser": "^1.0.0"
}
},
"node_modules/@ethereumjs/common": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.0.0.tgz",
"integrity": "sha512-yL0zA7Xwgz8IFHKW0VoXGjdZDVxUJg8BQ/muMHvYPW7zHJNNC80gQmvLH+MpvIg1TCXZkFXxrpYRAyCElSm+aw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
"integrity": "sha512-PyQiTG00MJtBRkJmv46ChZL8u2XWxNBeAthznAUIUiefxPAXjbkuiCZOuncgJS34/XkMbNc9zMt/PlgKRBElig==",
"dependencies": {
"crc-32": "^1.2.0"
"crc-32": "^1.2.0",
"ethereumjs-util": "^7.0.9"
}
},
"node_modules/@ethereumjs/tx": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.0.2.tgz",
"integrity": "sha512-zmFCosjOdj1WoYEiQBdC4sCOAllBEwxdKuY85L9FgZ4zVDfZUVsQ4S9paczt4hVt65A7N8sJwgVEzDaQmrRaqw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.1.3.tgz",
"integrity": "sha512-DJBu6cbwYtiPTFeCUR8DF5p+PF0jxs+0rALJZiEcTz2tiRPIEkM72GEbrkGuqzENLCzBrJHT43O0DxSYTqeo+g==",
"dependencies": {
"@ethereumjs/common": "^2.0.0",
"ethereumjs-util": "^7.0.8"
"@ethereumjs/common": "^2.2.0",
"ethereumjs-util": "^7.0.10"
}
},
"node_modules/@types/bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz",
"integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "14.14.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.30.tgz",
"integrity": "sha512-gUWhy8s45fQp4PqqKecsnOkdW0kt1IaKjgOIR3HPokkzTmQj9ji2wWFID5THu1MKrtO+d4s2lVrlEhXUsPXSvg=="
"version": "14.14.41",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
"integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
},
"node_modules/@types/pbkdf2": {
"version": "3.1.0",
@@ -48,9 +49,9 @@
}
},
"node_modules/@types/secp256k1": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz",
"integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.2.tgz",
"integrity": "sha512-QMg+9v0bbNJ2peLuHRWxzmy0HRJIG6gFZNhaRSp7S3ggSbCCxiqQB2/ybvhXyhHOCequpNkrx7OavNhrWOsW0A==",
"dependencies": {
"@types/node": "*"
}
@@ -158,9 +159,9 @@
}
},
"node_modules/asn1.js/node_modules/bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/assert-plus": {
"version": "1.0.0",
@@ -204,9 +205,9 @@
"optional": true
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base-x": {
"version": "3.0.8",
@@ -243,9 +244,9 @@
}
},
"node_modules/bn.js": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
@@ -317,9 +318,9 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"node_modules/cic-client-meta": {
"version": "0.0.7-alpha.5",
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.5.tgz",
"integrity": "sha512-h+0wmAKZIgezppBNYDmG387w6tI91FSWqONMTZbMuaO1Ej76Gg0Mk2UcDyAF/dmY6doXz3kHAbWkWat7mTzXAQ==",
"version": "0.0.7-alpha.6",
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.6.tgz",
"integrity": "sha512-oIN1aHkPHfsxJKDV6k4f1kX2tcppw3Q+D1b4BoPh0hYjNKNb7gImBMWnGsy8uiD9W6SNYE4sIXyrtct8mvrhsw==",
"dependencies": {
"@ethereumjs/tx": "^3.0.0-beta.1",
"automerge": "^0.14.1",
@@ -331,7 +332,7 @@
"yargs": "^16.1.0"
},
"engines": {
"node": "~15.3.0"
"node": "~14.16.1"
}
},
"node_modules/cipher-base": {
@@ -370,9 +371,9 @@
}
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -562,9 +563,9 @@
}
},
"node_modules/elliptic/node_modules/bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@@ -602,11 +603,11 @@
}
},
"node_modules/ethereumjs-util": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.8.tgz",
"integrity": "sha512-JJt7tDpCAmDPw/sGoFYeq0guOVqT3pTE9xlEbBmc/nlCij3JRCoS2c96SQ6kXVHOT3xWUNLDm5QCJLQaUnVAtQ==",
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz",
"integrity": "sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw==",
"dependencies": {
"@types/bn.js": "^4.11.3",
"@types/bn.js": "^5.1.0",
"bn.js": "^5.1.2",
"create-hash": "^1.1.2",
"ethereum-cryptography": "^0.1.3",
@@ -1026,21 +1027,21 @@
}
},
"node_modules/mime-db": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
"integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==",
"version": "1.47.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
"integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
"optional": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
"version": "2.1.30",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
"integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
"optional": true,
"dependencies": {
"mime-db": "1.46.0"
"mime-db": "1.47.0"
},
"engines": {
"node": ">= 0.6"
@@ -1361,9 +1362,9 @@
}
},
"node_modules/pbkdf2": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"dependencies": {
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4",
@@ -1382,15 +1383,15 @@
"optional": true
},
"node_modules/pg": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz",
"integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.4.0",
"pg-pool": "^3.2.2",
"pg-protocol": "^1.4.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.3.0",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
@@ -1399,9 +1400,9 @@
}
},
"node_modules/pg-connection-string": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz",
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ=="
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"node_modules/pg-int8": {
"version": "1.0.1",
@@ -1412,14 +1413,14 @@
}
},
"node_modules/pg-pool": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz",
"integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA=="
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg=="
},
"node_modules/pg-protocol": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz",
"integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"node_modules/pg-types": {
"version": "2.2.0",
@@ -1625,9 +1626,9 @@
}
},
"node_modules/rlp/node_modules/bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
@@ -1958,9 +1959,9 @@
}
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -2005,9 +2006,9 @@
}
},
"node_modules/y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
@@ -2035,9 +2036,9 @@
}
},
"node_modules/yargs-parser": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.5.tgz",
"integrity": "sha512-jYRGS3zWy20NtDtK2kBgo/TlAoy5YUuhD9/LZ7z7W4j1Fdw2cqD0xEEclf8fxc8xjD6X5Qr+qQQwCEsP8iRiYg==",
"version": "20.2.7",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
"integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
"engines": {
"node": ">=10"
}
@@ -2059,9 +2060,9 @@
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -2085,34 +2086,35 @@
},
"dependencies": {
"@ethereumjs/common": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.0.0.tgz",
"integrity": "sha512-yL0zA7Xwgz8IFHKW0VoXGjdZDVxUJg8BQ/muMHvYPW7zHJNNC80gQmvLH+MpvIg1TCXZkFXxrpYRAyCElSm+aw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz",
"integrity": "sha512-PyQiTG00MJtBRkJmv46ChZL8u2XWxNBeAthznAUIUiefxPAXjbkuiCZOuncgJS34/XkMbNc9zMt/PlgKRBElig==",
"requires": {
"crc-32": "^1.2.0"
"crc-32": "^1.2.0",
"ethereumjs-util": "^7.0.9"
}
},
"@ethereumjs/tx": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.0.2.tgz",
"integrity": "sha512-zmFCosjOdj1WoYEiQBdC4sCOAllBEwxdKuY85L9FgZ4zVDfZUVsQ4S9paczt4hVt65A7N8sJwgVEzDaQmrRaqw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.1.3.tgz",
"integrity": "sha512-DJBu6cbwYtiPTFeCUR8DF5p+PF0jxs+0rALJZiEcTz2tiRPIEkM72GEbrkGuqzENLCzBrJHT43O0DxSYTqeo+g==",
"requires": {
"@ethereumjs/common": "^2.0.0",
"ethereumjs-util": "^7.0.8"
"@ethereumjs/common": "^2.2.0",
"ethereumjs-util": "^7.0.10"
}
},
"@types/bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz",
"integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "14.14.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.30.tgz",
"integrity": "sha512-gUWhy8s45fQp4PqqKecsnOkdW0kt1IaKjgOIR3HPokkzTmQj9ji2wWFID5THu1MKrtO+d4s2lVrlEhXUsPXSvg=="
"version": "14.14.41",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
"integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
},
"@types/pbkdf2": {
"version": "3.1.0",
@@ -2123,9 +2125,9 @@
}
},
"@types/secp256k1": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz",
"integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.2.tgz",
"integrity": "sha512-QMg+9v0bbNJ2peLuHRWxzmy0HRJIG6gFZNhaRSp7S3ggSbCCxiqQB2/ybvhXyhHOCequpNkrx7OavNhrWOsW0A==",
"requires": {
"@types/node": "*"
}
@@ -2229,9 +2231,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
}
}
},
@@ -2271,9 +2273,9 @@
"optional": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base-x": {
"version": "3.0.8",
@@ -2307,9 +2309,9 @@
}
},
"bn.js": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
},
"brace-expansion": {
"version": "1.1.11",
@@ -2378,9 +2380,9 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"cic-client-meta": {
"version": "0.0.7-alpha.5",
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.5.tgz",
"integrity": "sha512-h+0wmAKZIgezppBNYDmG387w6tI91FSWqONMTZbMuaO1Ej76Gg0Mk2UcDyAF/dmY6doXz3kHAbWkWat7mTzXAQ==",
"version": "0.0.7-alpha.6",
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.6.tgz",
"integrity": "sha512-oIN1aHkPHfsxJKDV6k4f1kX2tcppw3Q+D1b4BoPh0hYjNKNb7gImBMWnGsy8uiD9W6SNYE4sIXyrtct8mvrhsw==",
"requires": {
"@ethereumjs/tx": "^3.0.0-beta.1",
"automerge": "^0.14.1",
@@ -2422,9 +2424,9 @@
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -2580,9 +2582,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
}
}
},
@@ -2619,11 +2621,11 @@
}
},
"ethereumjs-util": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.8.tgz",
"integrity": "sha512-JJt7tDpCAmDPw/sGoFYeq0guOVqT3pTE9xlEbBmc/nlCij3JRCoS2c96SQ6kXVHOT3xWUNLDm5QCJLQaUnVAtQ==",
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz",
"integrity": "sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw==",
"requires": {
"@types/bn.js": "^4.11.3",
"@types/bn.js": "^5.1.0",
"bn.js": "^5.1.2",
"create-hash": "^1.1.2",
"ethereum-cryptography": "^0.1.3",
@@ -2980,18 +2982,18 @@
}
},
"mime-db": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
"integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==",
"version": "1.47.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
"integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
"optional": true
},
"mime-types": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
"version": "2.1.30",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
"integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
"optional": true,
"requires": {
"mime-db": "1.46.0"
"mime-db": "1.47.0"
}
},
"minimalistic-assert": {
@@ -3249,9 +3251,9 @@
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"pbkdf2": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"requires": {
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4",
@@ -3267,23 +3269,23 @@
"optional": true
},
"pg": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz",
"integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.4.0",
"pg-pool": "^3.2.2",
"pg-protocol": "^1.4.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.3.0",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-connection-string": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz",
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ=="
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"pg-int8": {
"version": "1.0.1",
@@ -3291,14 +3293,14 @@
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz",
"integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA=="
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg=="
},
"pg-protocol": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz",
"integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"pg-types": {
"version": "2.2.0",
@@ -3459,9 +3461,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
}
}
},
@@ -3734,9 +3736,9 @@
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -3774,9 +3776,9 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yallist": {
"version": "3.1.1",
@@ -3808,9 +3810,9 @@
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -3828,9 +3830,9 @@
}
},
"yargs-parser": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.5.tgz",
"integrity": "sha512-jYRGS3zWy20NtDtK2kBgo/TlAoy5YUuhD9/LZ7z7W4j1Fdw2cqD0xEEclf8fxc8xjD6X5Qr+qQQwCEsP8iRiYg=="
"version": "20.2.7",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
"integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw=="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"cic-client-meta": "^0.0.7-alpha.6",
"cic-client-meta": "0.0.7-alpha.6",
"vcard-parser": "^1.0.0"
}
}

View File

@@ -22,7 +22,7 @@ from hexathon import (
strip_0x,
add_0x,
)
from chainsyncer.backend import MemBackend
from chainsyncer.backend.memory import MemBackend
from chainsyncer.driver import HeadSyncer
from chainlib.chain import ChainSpec
from chainlib.eth.connection import EthHTTPConnection
@@ -32,7 +32,7 @@ from chainlib.eth.block import (
block_by_number,
Block,
)
from chainlib.eth.hash import keccak256_string_to_hex
from chainlib.hash import keccak256_string_to_hex
from chainlib.eth.address import to_checksum_address
from chainlib.eth.erc20 import ERC20
from chainlib.eth.gas import (
@@ -40,7 +40,7 @@ from chainlib.eth.gas import (
balance,
)
from chainlib.eth.tx import TxFactory
from chainlib.eth.rpc import jsonrpc_template
from chainlib.jsonrpc import jsonrpc_template
from chainlib.eth.error import EthException
from cic_types.models.person import (
Person,
@@ -57,6 +57,7 @@ custodial_tests = [
'local_key',
'gas',
'faucet',
'ussd'
]
metadata_tests = [

View File

@@ -13,6 +13,12 @@ DEV_PIP_EXTRA_INDEX_URL=${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomi
DEV_DATABASE_NAME_CIC_ETH=${DEV_DATABASE_NAME_CIC_ETH:-"cic-eth"}
CIC_DATA_DIR=${CIC_DATA_DIR:-/tmp/cic}
ETH_PASSPHRASE=''
DEV_TOKEN_TYPE=${DEV_TOKEN_TYPE:-giftable}
if [ $DEV_TOKEN_TYPE = 'giftable' ]; then
token_symbol='GFT'
else
token_symbol='SRF'
fi
# Debug flag
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
@@ -33,11 +39,11 @@ set -a
# get required addresses from registries
DEV_TOKEN_INDEX_ADDRESS=`eth-contract-registry-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -r $CIC_REGISTRY_ADDRESS -f brief TokenRegistry`
DEV_ACCOUNT_INDEX_ADDRESS=`eth-contract-registry-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -r $CIC_REGISTRY_ADDRESS -f brief AccountRegistry`
DEV_RESERVE_ADDRESS=`eth-token-index-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_TOKEN_INDEX_ADDRESS -f brief SRF`
DEV_RESERVE_ADDRESS=`eth-token-index-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_TOKEN_INDEX_ADDRESS -f brief $CIC_DEFAULT_TOKEN_SYMBOL`
cat <<EOF
Token registry: $DEV_TOKEN_INDEX_ADDRESS
Account reigstry: $DEV_ACCOUNT_INDEX_ADDRESS
Reserve address: $DEV_RESERVE_ADDRESS
Reserve address: $DEV_RESERVE_ADDRESS ($CIC_DEFAULT_TOKEN_SYMBOL)
EOF
>&2 echo "create account for gas gifter"
@@ -46,6 +52,7 @@ DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_H
echo DEV_ETH_ACCOUNT_GAS_GIFTER=$DEV_ETH_ACCOUNT_GAS_GIFTER >> $env_out_file
cic-eth-tag -i $CIC_CHAIN_SPEC GAS_GIFTER $DEV_ETH_ACCOUNT_GAS_GIFTER
>&2 echo "create account for sarafu gifter"
DEV_ETH_ACCOUNT_SARAFU_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
echo DEV_ETH_ACCOUNT_SARAFU_GIFTER=$DEV_ETH_ACCOUNT_SARAFU_GIFTER >> $env_out_file
@@ -97,5 +104,10 @@ export DEV_ETH_SARAFU_TOKEN_ADDRESS=$DEV_ETH_RESERVE_ADDRESS
#echo -n 0 > $init_level_file
# Remove the SEND (8), QUEUE (16) and INIT (2) locks (or'ed), set by default at migration
cic-eth-ctl -i :: unlock INIT
cic-eth-ctl -i :: unlock SEND
cic-eth-ctl -i :: unlock QUEUE
set +a
set +e

View File

@@ -0,0 +1 @@
include *health*.sh

View File

@@ -0,0 +1,10 @@
docs:
mkdir -p doc/texinfo/html
makeinfo doc/texinfo/index.texi --html -o doc/texinfo/html/
markdown: doc
pandoc -f html -t markdown --standalone doc/texinfo/html/liveness.html -o README.md
.PHONY dist:
python setup.py sdist

View File

@@ -0,0 +1,105 @@
---
description: liveness (Untitled Document)
distribution: global
Generator: makeinfo
keywords: liveness (Untitled Document)
lang: en
resource-type: document
title: liveness (Untitled Document)
---
[]{#liveness}[]{#liveness-1}
## 1 liveness {#liveness .chapter}
[]{#ilveness_005foverview}[]{#Overview}
### 1.1 Overview {#overview .section}
This is a cluster-specific convenience setup for enabling a
Kubernetes-style liveness/readiness test as outlined in
<https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>.
Conceptually, it provides an application with means to:
- Run a collection of functions to validate sanity of the environment
- Set a no-error state before execution of the main routine
- Modify the error state during execution
- Invalidating all state when execution ends
[]{#Python-module}
### 1.2 Python module {#python-module .section}
Three python methods are provided.
[]{#load}
#### 1.2.1 load {#load .subsection}
This is meant to be called after configurations and environment has been
set up, but before the execution logic has commenced.
It receives a list of externally defined fully-qualified python modules.
Each of these modules must implement the method `health(*args,**kwargs)`
in its global namespace.
Any module returning `False` will cause a `RuntimeException`.
The component will not trap any other exception from the modules.
If successful, it will write the `pid` of the application to the
specified run data folder. By default this is `/run/<HOSTNAME>`, but the
path can be modified if desired.
[]{#set}
#### 1.2.2 set {#set .subsection}
This is meant to be called during the execution of the main program
routine begins.
[]{#at-startup}
#### 1.2.2.1 at startup {#at-startup .subsubsection}
It should be called once at the *start* of execution of the main program
routine.
For one-shot routines, this would mean the start of any code only run
when the module name is `__main__`.
For daemons, it would be just before handing over execution to the main
loop.
[]{#during-execution}
#### 1.2.2.2 during execution {#during-execution .subsubsection}
Call `set(error_code=<error>, ...` any time the health state temporarily
changes. Any `error` value other than `0` is considered an unhealthy
state.
[]{#at-shutdown}
#### 1.2.2.3 at shutdown {#at-shutdown .subsubsection}
Call `reset(...)`, which will indicate that the state is to be
considered the same as at startup.
[]{#shell}
### 1.3 shell {#shell .section}
A bash script is provided for *Kubernetes* to perform the health check.
It performs the following checks:
1. A numeric value exists in `<rundir>/<unitname>/pid`{.sample}.
2. The numeric value is a directory in `/proc`{.sample} (a valid pid)
3. The file `<rundir>/<unitname>/error`{.sample} contains \"0\"
If any of these checks fail should inditcate that the container is
unhealthy.
------------------------------------------------------------------------

View File

@@ -0,0 +1,71 @@
@node liveness
@chapter liveness
@anchor{ilveness_overview}
@section Overview
This is a cluster-specific convenience setup for enabling a Kubernetes-style liveness/readiness test as outlined in @url{https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/}.
Conceptually, it provides an application with means to:
@itemize
@item Run a collection of functions to validate sanity of the environment
@item Set a no-error state before execution of the main routine
@item Modify the error state during execution
@item Invalidating all state when execution ends
@end itemize
@section Python module
Three python methods are provided.
@subsection load
This is meant to be called after configurations and environment has been set up, but before the execution logic has commenced.
It receives a list of externally defined fully-qualified python modules. Each of these modules must implement the method @code{health(*args,**kwargs)} in its global namespace.
Any module returning @code{False} will cause a @code{RuntimeException}.
The component will not trap any other exception from the modules.
If successful, it will write the @code{pid} of the application to the specified run data folder. By default this is @code{/run/<HOSTNAME>}, but the path can be modified if desired.
@subsection set
This is meant to be called during the execution of the main program routine begins.
@subsubsection at startup
It should be called once at the @emph{start} of execution of the main program routine.
For one-shot routines, this would mean the start of any code only run when the module name is @code{__main__}.
For daemons, it would be just before handing over execution to the main loop.
@subsubsection during execution
Call @code{set(error_code=<error>, ...} any time the health state temporarily changes. Any @code{error} value other than @code{0} is considered an unhealthy state.
@subsubsection at shutdown
Call @code{reset(...)}, which will indicate that the state is to be considered the same as at startup.
@section shell
A bash script is provided for @emph{Kubernetes} to perform the health check.
It performs the following checks:
@enumerate
@item A numeric value exists in @file{<rundir>/<unitname>/pid}.
@item The numeric value is a directory in @file{/proc} (a valid pid)
@item The file @file{<rundir>/<unitname>/error} contains "0"
@end enumerate
If any of these checks fail should inditcate that the container is unhealthy.

View File

@@ -0,0 +1,35 @@
#!/bin/bash
rundir=${CIC_RUNDIR:-/run}
unit=${CIC_UNIT:-$HOSTNAME}
read p < $rundir/$unit/pid
if [ -z $p ]; then
>&2 echo unit $unit has no pid
exit 1
fi
if [ ! -d /proc/$p ]; then
>&2 echo unit $unit reports non-existent pid $p
exit 1
fi
>&2 echo unit $unit has pid $p
if [ ! -f $rundir/$unit/error ]; then
>&2 echo unit $unit has unspecified state
exit 1
fi
read e 2> /dev/null < $rundir/$unit/error
if [ -z $e ]; then
>&2 echo unit $unit has unspecified state
exit 1
fi
>&2 echo unit $unit has error $e
if [ $e -gt 0 ]; then
exit 1;
fi

View File

@@ -0,0 +1,62 @@
# standard imports
import importlib
import sys
import os
import logging
logg = logging.getLogger().getChild(__name__)
pid = os.getpid()
default_namespace = os.environ.get('LIVENESS_UNIT_NAME')
if default_namespace == None:
import socket
default_namespace = socket.gethostname()
def load(check_strs, namespace=default_namespace, rundir='/run', *args, **kwargs):
if namespace == None:
import socket
namespace = socket.gethostname()
logg.info('pid ' + str(pid))
checks = []
for m in check_strs:
logg.debug('added liveness check: {}'.format(str(m)))
module = importlib.import_module(m)
checks.append(module)
for check in checks:
r = check.health(*args, **kwargs)
if r == False:
raise RuntimeError('liveness check {} failed'.format(str(check)))
logg.info('liveness check passed: {}'.format(str(check)))
app_rundir = os.path.join(rundir, namespace)
os.makedirs(app_rundir, exist_ok=True) # should not already exist
f = open(os.path.join(app_rundir, 'pid'), 'w')
f.write(str(pid))
f.close()
def set(error=0, namespace=default_namespace, rundir='/run'):
logg.info('liveness SET error {} for namespace {}'.format(error, namespace))
app_rundir = os.path.join(rundir, namespace)
f = open(os.path.join(app_rundir, 'error'), 'w')
f.write(str(error))
f.close()
def reset(namespace=default_namespace, rundir='/run'):
logg.info('liveness RESET for namespace {}'.format(namespace))
app_rundir = os.path.join(rundir, namespace)
try:
os.unlink(os.path.join(app_rundir, 'pid'))
except FileNotFoundError:
pass
try:
os.unlink(os.path.join(app_rundir, 'error'))
except FileNotFoundError:
pass

View File

@@ -0,0 +1,7 @@
from setuptools import setup
setup(
name='liveness',
version='0.0.1a7',
packages=['liveness'],
include_package_data=True,
)

View File

@@ -0,0 +1,17 @@
#!/bin/bash
export CIC_RUNDIR=`realpath ./tests/testdata/run`
t=`mktemp -d -p $CIC_RUNDIR`
export CIC_UNIT=`basename $t`
>&2 echo test pid $$
echo $$ > $t/pid
echo 0 > $t/error
. health.sh
echo 1 > $t/error
#unlink $t/error
. health.sh
echo if error this is not printed

View File

@@ -0,0 +1,8 @@
a = ['foo']
kw = {
'bar': 42,
}
def health(*args, **kwargs):
args[0] == a[0]
kwargs['bar'] = kw['bar']

View File

@@ -0,0 +1,2 @@
def health(*args, **kwargs):
return False

View File

@@ -0,0 +1,2 @@
def health(*args, **kwargs):
return True

View File

@@ -0,0 +1,129 @@
# standard imports
import os
import unittest
import logging
import tempfile
import socket
# local imports
import liveness.linux
## test imports
import tests.imports
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
script_dir = os.path.realpath(os.path.dirname(__file__))
data_dir = os.path.join(script_dir, 'testdata')
run_base_dir = os.path.join(data_dir, 'run')
class TestImports(unittest.TestCase):
def setUp(self):
os.makedirs(run_base_dir, exist_ok=True)
self.run_dir = tempfile.mkdtemp(dir=run_base_dir)
self.unit = 'unittest'
self.unit_dir = os.path.join(self.run_dir, self.unit)
self.pid_path = os.path.join(self.unit_dir, 'pid')
self.error_path = os.path.join(self.unit_dir, 'error')
self.host_path = os.path.join(self.run_dir, socket.gethostname())
def test_no_import(self):
liveness.linux.load([], namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_hostname(self):
liveness.linux.load([], rundir=self.run_dir)
f = open(os.path.join(self.host_path, 'pid'), 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_import_single_true(self):
checks = ['tests.imports.import_true']
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_import_single_false(self):
checks = ['tests.imports.import_false']
with self.assertRaises(RuntimeError):
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.pid_path)
def test_import_false_then_true(self):
checks = ['tests.imports.import_false', 'tests.imports.import_true']
with self.assertRaises(RuntimeError):
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.pid_path)
def test_import_multiple_true(self):
checks = ['tests.imports.import_true', 'tests.imports.import_true']
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_set(self):
liveness.linux.load([], namespace='unittest', rundir=self.run_dir)
liveness.linux.set(namespace='unittest', rundir=self.run_dir)
f = open(self.error_path, 'r')
r = f.read()
f.close()
self.assertEqual('0', r)
liveness.linux.set(error=42, namespace='unittest', rundir=self.run_dir)
f = open(self.error_path, 'r')
r = f.read()
f.close()
self.assertEqual('42', r)
liveness.linux.reset(namespace='unittest', rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.error_path)
def test_set_hostname(self):
liveness.linux.load([], rundir=self.run_dir)
liveness.linux.set(rundir=self.run_dir)
error_path = os.path.join(self.host_path, 'error')
f = open(error_path, 'r')
r = f.read()
f.close()
self.assertEqual('0', r)
liveness.linux.reset(rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(error_path)
def test_args(self):
checks = ['tests.imports.import_args']
aargs=['foo']
kwaargs={'bar': 42}
liveness.linux.load(checks, self.unit, self.run_dir, *aargs, **kwaargs)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
if __name__ == '__main__':
unittest.main()

View File

@@ -24,7 +24,7 @@ variables:
IMAGE_TAG_BASE: $CI_REGISTRY_IMAGE/$APP_NAME:$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
LATEST_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:latest
script:
- export IMAGE_TAG="$IMAGE_TAG_BASE-$(date +%F.%H%M%S)"
- export IMAGE_TAG="$IMAGE_TAG_BASE-$(date +%s)"
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
# - /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG

View File

@@ -85,6 +85,7 @@ services:
# ETH_PROVIDER should be broken out into host/port but cic-eth expects this
ETH_PROVIDER: http://eth:8545
# And these two are for wait-for-it (could parse this)
DEV_USE_DOCKER_WAIT_SCRIPT: 1
ETH_PROVIDER_HOST: eth
ETH_PROVIDER_PORT: 8545
CIC_CHAIN_SPEC: ${CIC_CHAIN_SPEC:-evm:bloxberg:8996}
@@ -103,6 +104,9 @@ services:
DEV_PIP_EXTRA_INDEX_URL: ${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomics.net:8433}
RUN_MASK: ${RUN_MASK:-0} # bit flags; 1: contract migrations 2: seed data
DEV_FAUCET_AMOUNT: ${DEV_FAUCET_AMOUNT:-0}
CIC_DEFAULT_TOKEN_SYMBOL: ${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
DEV_SARAFU_DEMURRAGE_LEVEL: ${DEV_SARAFU_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
DEV_ETH_GAS_PRICE: ${DEV_ETH_GAS_PRICE:-1}
command: ["./run_job.sh"]
#command: ["./reset.sh"]
depends_on:
@@ -236,6 +240,8 @@ services:
DATABASE_DRIVER: ${DATABASE_DRIVER:-psycopg2}
DATABASE_DEBUG: ${DATABASE_DEBUG:-0}
DATABASE_POOL_SIZE: 0
REDIS_PORT: 6379
REDIS_HOST: redis
PGPASSWORD: ${DATABASE_PASSWORD:-tralala}
CIC_CHAIN_SPEC: ${CIC_CHAIN_SPEC:-evm:bloxberg:8996}
BANCOR_DIR: ${BANCOR_DIR:-/usr/local/share/cic/bancor}
@@ -245,6 +251,7 @@ services:
SIGNER_SECRET: ${SIGNER_SECRET:-deadbeef}
ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER: ${DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER:-0xACB0BC74E1686D62dE7DC6414C999EA60C09F0eA}
TASKS_TRACE_QUEUE_STATUS: ${TASKS_TRACE_QUEUE_STATUS:-1}
CIC_DEFAULT_TOKEN_SYMBOL: ${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
depends_on:
- eth
- postgres
@@ -446,7 +453,7 @@ services:
deploy:
restart_policy:
condition: on-failure
command: "/root/start_tasker.sh -q cic-notify"
command: "/root/start_tasker.sh -q cic-notify -vv"
cic-meta-server:
@@ -494,6 +501,8 @@ services:
DATABASE_NAME: cic_ussd
DATABASE_ENGINE: postgresql
DATABASE_DRIVER: psycopg2
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
CELERY_RESULT_URL: ${CELERY_BROKER_URL:-redis://redis}
PGP_PASSPHRASE: merman
SERVER_PORT: 9000
CIC_META_URL: ${CIC_META_URL:-http://meta:8000}

View File

@@ -5,6 +5,7 @@ CREATE DATABASE "cic_notify";
CREATE DATABASE "cic_meta";
CREATE DATABASE "cic_signer";
CREATE DATABASE "cic_ussd";
CREATE DATABASE "chain_sync";
GRANT ALL PRIVILEGES
ON DATABASE "cic_cache", "cic_eth", "cic_notify", "cic_meta", "cic_signer", "cic_ussd"
ON DATABASE "cic_cache", "cic_eth", "cic_notify", "cic_meta", "cic_signer", "cic_ussd", "chain_sync"
TO grassroots;