Merge master into branch "lash/check-import-ussd".
This commit is contained in:
commit
9624bb3623
@ -89,7 +89,7 @@ def process_transactions_all_data(session, env):
|
||||
|
||||
offset = r[1]
|
||||
end = r[2]
|
||||
if r[2] < r[1]:
|
||||
if int(r[2]) < int(r[1]):
|
||||
raise ValueError('cart before the horse, dude')
|
||||
|
||||
c = DataCache(session)
|
||||
|
@ -5,3 +5,5 @@ omit =
|
||||
cic_eth/db/migrations/*
|
||||
cic_eth/sync/head.py
|
||||
cic_eth/sync/mempool.py
|
||||
cic_eth/queue/state.py
|
||||
*redis*.py
|
||||
|
@ -24,9 +24,7 @@ test-mr-cic-eth:
|
||||
image: $CI_REGISTRY_IMAGE/$APP_NAME-test:latest
|
||||
script:
|
||||
- cd apps/$APP_NAME/
|
||||
- pytest tests/unit/
|
||||
- pytest tests/task/
|
||||
- pytest tests/filters/
|
||||
- pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests
|
||||
needs: ["build-mr-cic-eth"]
|
||||
|
||||
build-push-cic-eth:
|
||||
|
@ -4,11 +4,18 @@ import logging
|
||||
# external imports
|
||||
import celery
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.tx import unpack
|
||||
from chainqueue.query import get_tx
|
||||
from chainqueue.state import set_cancel
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.tx import (
|
||||
unpack,
|
||||
TxFactory,
|
||||
)
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainqueue.sql.query import get_tx
|
||||
from chainqueue.sql.state import set_cancel
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.models.tx import TxCache
|
||||
from hexathon import strip_0x
|
||||
from potaahto.symbols import snake_and_camel
|
||||
|
||||
# local imports
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
@ -21,13 +28,14 @@ from cic_eth.admin.ctrl import (
|
||||
)
|
||||
from cic_eth.queue.tx import queue_create
|
||||
from cic_eth.eth.gas import create_check_gas_task
|
||||
from cic_eth.task import BaseTask
|
||||
|
||||
celery_app = celery.current_app
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
@celery_app.task(bind=True)
|
||||
def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
|
||||
@celery_app.task(bind=True, base=BaseTask)
|
||||
def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
|
||||
"""Shift all transactions with nonces higher than the offset by the provided position delta.
|
||||
|
||||
Transactions who are replaced by transactions that move nonces will be marked as OVERRIDDEN.
|
||||
@ -38,25 +46,29 @@ def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
|
||||
:type tx_hash_orig_hex: str, 0x-hex
|
||||
:param delta: Amount
|
||||
"""
|
||||
chain_spec = ChainSpec.from_dict(chainspec_dict)
|
||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||
queue = None
|
||||
try:
|
||||
queue = self.request.delivery_info.get('routing_key')
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(chain_str)
|
||||
tx_brief = get_tx(tx_hash_orig_hex)
|
||||
tx_raw = bytes.fromhex(strip_0x(tx_brief['signed_tx'][2:]))
|
||||
session = BaseTask.session_func()
|
||||
tx_brief = get_tx(chain_spec, tx_hash_orig_hex, session=session)
|
||||
tx_raw = bytes.fromhex(strip_0x(tx_brief['signed_tx']))
|
||||
tx = unpack(tx_raw, chain_spec)
|
||||
nonce = tx_brief['nonce']
|
||||
address = tx['from']
|
||||
|
||||
logg.debug('shifting nonce {} position(s) for address {}, offset {}'.format(delta, address, nonce))
|
||||
logg.debug('shifting nonce {} position(s) for address {}, offset {}, hash {}'.format(delta, address, nonce, tx['hash']))
|
||||
|
||||
lock_queue(None, chain_str, address)
|
||||
lock_send(None, chain_str, address)
|
||||
lock_queue(None, chain_spec.asdict(), address=address)
|
||||
lock_send(None, chain_spec.asdict(), address=address)
|
||||
|
||||
set_cancel(chain_spec, strip_0x(tx['hash']), manual=True, session=session)
|
||||
|
||||
session = SessionBase.create_session()
|
||||
q = session.query(Otx)
|
||||
q = q.join(TxCache)
|
||||
q = q.filter(TxCache.sender==address)
|
||||
@ -69,49 +81,57 @@ def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
|
||||
for otx in otxs:
|
||||
tx_raw = bytes.fromhex(strip_0x(otx.signed_tx))
|
||||
tx_new = unpack(tx_raw, chain_spec)
|
||||
tx_new = snake_and_camel(tx_new)
|
||||
|
||||
tx_previous_hash_hex = tx_new['hash']
|
||||
tx_previous_nonce = tx_new['nonce']
|
||||
|
||||
del(tx_new['hash'])
|
||||
del(tx_new['hash_unsigned'])
|
||||
tx_new['gas_price'] += 1
|
||||
tx_new['gasPrice'] = tx_new['gas_price']
|
||||
tx_new['nonce'] -= delta
|
||||
|
||||
(tx_hash_hex, tx_signed_raw_hex) = sign_tx(tx_new, chain_str)
|
||||
logg.debug('tx_new {}'.format(tx_new))
|
||||
|
||||
del(tx_new['hash'])
|
||||
del(tx_new['hash_unsigned'])
|
||||
del(tx_new['hashUnsigned'])
|
||||
|
||||
gas_oracle = OverrideGasOracle(limit=tx_new['gas'], price=tx_new['gas_price'] + 1) # TODO: it should be possible to merely set this price here and if missing in the existing struct then fill it in (chainlib.eth.tx)
|
||||
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx_new)
|
||||
logg.debug('tx {} -> {} nonce {} -> {}'.format(tx_previous_hash_hex, tx_hash_hex, tx_previous_nonce, tx_new['nonce']))
|
||||
|
||||
otx = Otx(
|
||||
nonce=tx_new['nonce'],
|
||||
address=tx_new['from'],
|
||||
tx_hash=tx_hash_hex,
|
||||
signed_tx=tx_signed_raw_hex,
|
||||
)
|
||||
tx_new['nonce'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
)
|
||||
session.add(otx)
|
||||
session.commit()
|
||||
|
||||
# TODO: cancel all first, then replace. Otherwise we risk two non-locked states for two different nonces.
|
||||
set_cancel(tx_previous_hash_hex, True)
|
||||
set_cancel(chain_spec, strip_0x(tx_previous_hash_hex), manual=True, session=session)
|
||||
|
||||
TxCache.clone(tx_previous_hash_hex, tx_hash_hex)
|
||||
TxCache.clone(tx_previous_hash_hex, tx_hash_hex, session=session)
|
||||
|
||||
tx_hashes.append(tx_hash_hex)
|
||||
txs.append(tx_signed_raw_hex)
|
||||
session.commit()
|
||||
|
||||
session.close()
|
||||
|
||||
s = create_check_gas_and_send_task(
|
||||
s = create_check_gas_task(
|
||||
txs,
|
||||
chain_str,
|
||||
chain_spec,
|
||||
tx_new['from'],
|
||||
tx_new['gas'],
|
||||
tx_hashes,
|
||||
queue,
|
||||
gas=tx_new['gas'],
|
||||
tx_hashes_hex=tx_hashes,
|
||||
queue=queue,
|
||||
)
|
||||
|
||||
s_unlock_send = celery.signature(
|
||||
'cic_eth.admin.ctrl.unlock_send',
|
||||
[
|
||||
chain_str,
|
||||
chain_spec.asdict(),
|
||||
tx_new['from'],
|
||||
],
|
||||
queue=queue,
|
||||
@ -119,7 +139,7 @@ def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
|
||||
s_unlock_direct = celery.signature(
|
||||
'cic_eth.admin.ctrl.unlock_queue',
|
||||
[
|
||||
chain_str,
|
||||
chain_spec.asdict(),
|
||||
tx_new['from'],
|
||||
],
|
||||
queue=queue,
|
||||
|
@ -8,6 +8,7 @@ from chainlib.eth.constant import (
|
||||
ZERO_ADDRESS,
|
||||
)
|
||||
from cic_eth_registry import CICRegistry
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.contract import code
|
||||
@ -30,13 +31,14 @@ from chainqueue.db.enum import (
|
||||
status_str,
|
||||
)
|
||||
from chainqueue.error import TxStateChangeError
|
||||
from chainqueue.sql.query import get_tx
|
||||
from eth_erc20 import ERC20
|
||||
|
||||
# local imports
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
from cic_eth.db.models.role import AccountRole
|
||||
from cic_eth.db.models.nonce import Nonce
|
||||
from cic_eth.error import InitializationError
|
||||
from cic_eth.queue.query import get_tx
|
||||
|
||||
app = celery.current_app
|
||||
|
||||
@ -188,6 +190,7 @@ class AdminApi:
|
||||
s_manual = celery.signature(
|
||||
'cic_eth.queue.state.set_manual',
|
||||
[
|
||||
chain_spec.asdict(),
|
||||
tx_hash_hex,
|
||||
],
|
||||
queue=self.queue,
|
||||
@ -206,8 +209,9 @@ class AdminApi:
|
||||
s.link(s_gas)
|
||||
|
||||
return s_manual.apply_async()
|
||||
|
||||
def check_nonce(self, address):
|
||||
|
||||
|
||||
def check_nonce(self, chain_spec, address):
|
||||
s = celery.signature(
|
||||
'cic_eth.queue.query.get_account_tx',
|
||||
[
|
||||
@ -228,13 +232,12 @@ class AdminApi:
|
||||
s_get_tx = celery.signature(
|
||||
'cic_eth.queue.query.get_tx',
|
||||
[
|
||||
chain_spec.asdict(),
|
||||
chain_spec.asdict(),
|
||||
k,
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
tx = s_get_tx.apply_async().get()
|
||||
#tx = get_tx(k)
|
||||
logg.debug('checking nonce {} (previous {})'.format(tx['nonce'], last_nonce))
|
||||
nonce_otx = tx['nonce']
|
||||
if not is_alive(tx['status']) and tx['status'] & local_fail > 0:
|
||||
@ -242,7 +245,9 @@ class AdminApi:
|
||||
blocking_tx = k
|
||||
blocking_nonce = nonce_otx
|
||||
elif nonce_otx - last_nonce > 1:
|
||||
logg.error('nonce gap; {} followed {} for account {}'.format(nonce_otx, last_nonce, tx['from']))
|
||||
logg.debug('tx {}'.format(tx))
|
||||
tx_obj = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec)
|
||||
logg.error('nonce gap; {} followed {} for account {}'.format(nonce_otx, last_nonce, tx_obj['from']))
|
||||
blocking_tx = k
|
||||
blocking_nonce = nonce_otx
|
||||
break
|
||||
@ -256,12 +261,13 @@ class AdminApi:
|
||||
'blocking': blocking_nonce,
|
||||
},
|
||||
'tx': {
|
||||
'blocking': blocking_tx,
|
||||
}
|
||||
'blocking': add_0x(blocking_tx),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fix_nonce(self, address, nonce, chain_spec):
|
||||
# TODO: is risky since it does not validate that there is actually a nonce problem?
|
||||
def fix_nonce(self, chain_spec, address, nonce):
|
||||
s = celery.signature(
|
||||
'cic_eth.queue.query.get_account_tx',
|
||||
[
|
||||
@ -275,15 +281,17 @@ class AdminApi:
|
||||
txs = s.apply_async().get()
|
||||
|
||||
tx_hash_hex = None
|
||||
session = SessionBase.create_session()
|
||||
for k in txs.keys():
|
||||
tx_dict = get_tx(k)
|
||||
tx_dict = get_tx(chain_spec, k, session=session)
|
||||
if tx_dict['nonce'] == nonce:
|
||||
tx_hash_hex = k
|
||||
session.close()
|
||||
|
||||
s_nonce = celery.signature(
|
||||
'cic_eth.admin.nonce.shift_nonce',
|
||||
[
|
||||
self.rpc.chain_spec.asdict(),
|
||||
chain_spec.asdict(),
|
||||
tx_hash_hex,
|
||||
],
|
||||
queue=self.queue
|
||||
@ -388,12 +396,13 @@ class AdminApi:
|
||||
|
||||
t = s.apply_async()
|
||||
tx = t.get()
|
||||
|
||||
|
||||
source_token = None
|
||||
if tx['source_token'] != ZERO_ADDRESS:
|
||||
source_token_declaration = None
|
||||
if registry != None:
|
||||
try:
|
||||
source_token = registry.by_address(tx['source_token'])
|
||||
source_token_declaration = registry.by_address(tx['source_token'], sender_address=self.call_address)
|
||||
except UnknownContractError:
|
||||
logg.warning('unknown source token contract {} (direct)'.format(tx['source_token']))
|
||||
else:
|
||||
@ -406,16 +415,21 @@ class AdminApi:
|
||||
queue=self.queue
|
||||
)
|
||||
t = s.apply_async()
|
||||
source_token = t.get()
|
||||
if source_token == None:
|
||||
logg.warning('unknown source token contract {} (task pool)'.format(tx['source_token']))
|
||||
source_token_declaration = t.get()
|
||||
|
||||
if source_token_declaration != None:
|
||||
logg.warning('found declarator record for source token {} but not checking validity'.format(tx['source_token']))
|
||||
source_token = ERC20Token(chain_spec, self.rpc, tx['source_token'])
|
||||
logg.debug('source token set tup {}'.format(source_token))
|
||||
|
||||
|
||||
|
||||
destination_token = None
|
||||
if tx['destination_token'] != ZERO_ADDRESS:
|
||||
destination_token_declaration = None
|
||||
if registry != None:
|
||||
try:
|
||||
destination_token = registry.by_address(tx['destination_token'])
|
||||
destination_token_declaration = registry.by_address(tx['destination_token'], sender_address=self.call_address)
|
||||
except UnknownContractError:
|
||||
logg.warning('unknown destination token contract {}'.format(tx['destination_token']))
|
||||
else:
|
||||
@ -428,10 +442,10 @@ class AdminApi:
|
||||
queue=self.queue
|
||||
)
|
||||
t = s.apply_async()
|
||||
destination_token = t.get()
|
||||
if destination_token == None:
|
||||
logg.warning('unknown destination token contract {} (task pool)'.format(tx['destination_token']))
|
||||
|
||||
destination_token_declaration = t.get()
|
||||
if destination_token_declaration != None:
|
||||
logg.warning('found declarator record for destination token {} but not checking validity'.format(tx['destination_token']))
|
||||
destination_token = ERC20Token(chain_spec, self.rpc, tx['destination_token'])
|
||||
|
||||
tx['sender_description'] = 'Custodial account'
|
||||
tx['recipient_description'] = 'Custodial account'
|
||||
@ -543,13 +557,19 @@ class AdminApi:
|
||||
if role != None:
|
||||
tx['recipient_description'] = role
|
||||
|
||||
erc20_c = ERC20(chain_spec)
|
||||
if source_token != None:
|
||||
tx['source_token_symbol'] = source_token.symbol()
|
||||
tx['sender_token_balance'] = source_token.function('balanceOf')(tx['sender']).call()
|
||||
tx['source_token_symbol'] = source_token.symbol
|
||||
o = erc20_c.balance_of(tx['source_token'], tx['sender'], sender_address=self.call_address)
|
||||
r = self.rpc.do(o)
|
||||
tx['sender_token_balance'] = erc20_c.parse_balance_of(r)
|
||||
|
||||
if destination_token != None:
|
||||
tx['destination_token_symbol'] = destination_token.symbol()
|
||||
tx['recipient_token_balance'] = source_token.function('balanceOf')(tx['recipient']).call()
|
||||
tx['destination_token_symbol'] = destination_token.symbol
|
||||
o = erc20_c.balance_of(tx['destination_token'], tx['recipient'], sender_address=self.call_address)
|
||||
r = self.rpc.do(o)
|
||||
tx['recipient_token_balance'] = erc20_c.parse_balance_of(r)
|
||||
#tx['recipient_token_balance'] = destination_token.function('balanceOf')(tx['recipient']).call()
|
||||
|
||||
# TODO: this can mean either not subitted or culled, need to check other txs with same nonce to determine which
|
||||
tx['network_status'] = 'Not in node'
|
||||
|
@ -74,134 +74,134 @@ class Api:
|
||||
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.
|
||||
|
||||
:param from_address: Ethereum address of sender
|
||||
:type from_address: str, 0x-hex
|
||||
:param to_address: Ethereum address of receipient
|
||||
:type to_address: str, 0x-hex
|
||||
:param target_return: Estimated return from conversion
|
||||
:type target_return: int
|
||||
:param minimum_return: The least value of destination token return to allow
|
||||
:type minimum_return: int
|
||||
:param from_token_symbol: ERC20 token symbol of token being converted
|
||||
:type from_token_symbol: str
|
||||
:param to_token_symbol: ERC20 token symbol of token to receive
|
||||
:type to_token_symbol: str
|
||||
:returns: uuid of root task
|
||||
:rtype: celery.Task
|
||||
"""
|
||||
raise NotImplementedError('out of service until new DEX migration is done')
|
||||
s_check = celery.signature(
|
||||
'cic_eth.admin.ctrl.check_lock',
|
||||
[
|
||||
[from_token_symbol, to_token_symbol],
|
||||
self.chain_spec.asdict(),
|
||||
LockEnum.QUEUE,
|
||||
from_address,
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_nonce = celery.signature(
|
||||
'cic_eth.eth.nonce.reserve_nonce',
|
||||
[
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_tokens = celery.signature(
|
||||
'cic_eth.eth.erc20.resolve_tokens_by_symbol',
|
||||
[
|
||||
self.chain_str,
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_convert = celery.signature(
|
||||
'cic_eth.eth.bancor.convert_with_default_reserve',
|
||||
[
|
||||
from_address,
|
||||
target_return,
|
||||
minimum_return,
|
||||
to_address,
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_nonce.link(s_tokens)
|
||||
s_check.link(s_nonce)
|
||||
if self.callback_param != None:
|
||||
s_convert.link(self.callback_success)
|
||||
s_tokens.link(s_convert).on_error(self.callback_error)
|
||||
else:
|
||||
s_tokens.link(s_convert)
|
||||
|
||||
t = s_check.apply_async(queue=self.queue)
|
||||
return t
|
||||
|
||||
|
||||
def convert(self, from_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
||||
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens.
|
||||
|
||||
:param from_address: Ethereum address of sender
|
||||
:type from_address: str, 0x-hex
|
||||
:param target_return: Estimated return from conversion
|
||||
:type target_return: int
|
||||
:param minimum_return: The least value of destination token return to allow
|
||||
:type minimum_return: int
|
||||
:param from_token_symbol: ERC20 token symbol of token being converted
|
||||
:type from_token_symbol: str
|
||||
:param to_token_symbol: ERC20 token symbol of token to receive
|
||||
:type to_token_symbol: str
|
||||
:returns: uuid of root task
|
||||
:rtype: celery.Task
|
||||
"""
|
||||
raise NotImplementedError('out of service until new DEX migration is done')
|
||||
s_check = celery.signature(
|
||||
'cic_eth.admin.ctrl.check_lock',
|
||||
[
|
||||
[from_token_symbol, to_token_symbol],
|
||||
self.chain_spec.asdict(),
|
||||
LockEnum.QUEUE,
|
||||
from_address,
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_nonce = celery.signature(
|
||||
'cic_eth.eth.nonce.reserve_nonce',
|
||||
[
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_tokens = celery.signature(
|
||||
'cic_eth.eth.erc20.resolve_tokens_by_symbol',
|
||||
[
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_convert = celery.signature(
|
||||
'cic_eth.eth.bancor.convert_with_default_reserve',
|
||||
[
|
||||
from_address,
|
||||
target_return,
|
||||
minimum_return,
|
||||
from_address,
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
s_nonce.link(s_tokens)
|
||||
s_check.link(s_nonce)
|
||||
if self.callback_param != None:
|
||||
s_convert.link(self.callback_success)
|
||||
s_tokens.link(s_convert).on_error(self.callback_error)
|
||||
else:
|
||||
s_tokens.link(s_convert)
|
||||
|
||||
t = s_check.apply_async(queue=self.queue)
|
||||
return t
|
||||
# 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.
|
||||
#
|
||||
# :param from_address: Ethereum address of sender
|
||||
# :type from_address: str, 0x-hex
|
||||
# :param to_address: Ethereum address of receipient
|
||||
# :type to_address: str, 0x-hex
|
||||
# :param target_return: Estimated return from conversion
|
||||
# :type target_return: int
|
||||
# :param minimum_return: The least value of destination token return to allow
|
||||
# :type minimum_return: int
|
||||
# :param from_token_symbol: ERC20 token symbol of token being converted
|
||||
# :type from_token_symbol: str
|
||||
# :param to_token_symbol: ERC20 token symbol of token to receive
|
||||
# :type to_token_symbol: str
|
||||
# :returns: uuid of root task
|
||||
# :rtype: celery.Task
|
||||
# """
|
||||
# raise NotImplementedError('out of service until new DEX migration is done')
|
||||
# s_check = celery.signature(
|
||||
# 'cic_eth.admin.ctrl.check_lock',
|
||||
# [
|
||||
# [from_token_symbol, to_token_symbol],
|
||||
# self.chain_spec.asdict(),
|
||||
# LockEnum.QUEUE,
|
||||
# from_address,
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_nonce = celery.signature(
|
||||
# 'cic_eth.eth.nonce.reserve_nonce',
|
||||
# [
|
||||
# self.chain_spec.asdict(),
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_tokens = celery.signature(
|
||||
# 'cic_eth.eth.erc20.resolve_tokens_by_symbol',
|
||||
# [
|
||||
# self.chain_str,
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_convert = celery.signature(
|
||||
# 'cic_eth.eth.bancor.convert_with_default_reserve',
|
||||
# [
|
||||
# from_address,
|
||||
# target_return,
|
||||
# minimum_return,
|
||||
# to_address,
|
||||
# self.chain_spec.asdict(),
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_nonce.link(s_tokens)
|
||||
# s_check.link(s_nonce)
|
||||
# if self.callback_param != None:
|
||||
# s_convert.link(self.callback_success)
|
||||
# s_tokens.link(s_convert).on_error(self.callback_error)
|
||||
# else:
|
||||
# s_tokens.link(s_convert)
|
||||
#
|
||||
# t = s_check.apply_async(queue=self.queue)
|
||||
# return t
|
||||
#
|
||||
#
|
||||
# def convert(self, from_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
||||
# """Executes a chain of celery tasks that performs conversion between two ERC20 tokens.
|
||||
#
|
||||
# :param from_address: Ethereum address of sender
|
||||
# :type from_address: str, 0x-hex
|
||||
# :param target_return: Estimated return from conversion
|
||||
# :type target_return: int
|
||||
# :param minimum_return: The least value of destination token return to allow
|
||||
# :type minimum_return: int
|
||||
# :param from_token_symbol: ERC20 token symbol of token being converted
|
||||
# :type from_token_symbol: str
|
||||
# :param to_token_symbol: ERC20 token symbol of token to receive
|
||||
# :type to_token_symbol: str
|
||||
# :returns: uuid of root task
|
||||
# :rtype: celery.Task
|
||||
# """
|
||||
# raise NotImplementedError('out of service until new DEX migration is done')
|
||||
# s_check = celery.signature(
|
||||
# 'cic_eth.admin.ctrl.check_lock',
|
||||
# [
|
||||
# [from_token_symbol, to_token_symbol],
|
||||
# self.chain_spec.asdict(),
|
||||
# LockEnum.QUEUE,
|
||||
# from_address,
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_nonce = celery.signature(
|
||||
# 'cic_eth.eth.nonce.reserve_nonce',
|
||||
# [
|
||||
# self.chain_spec.asdict(),
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_tokens = celery.signature(
|
||||
# 'cic_eth.eth.erc20.resolve_tokens_by_symbol',
|
||||
# [
|
||||
# self.chain_spec.asdict(),
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_convert = celery.signature(
|
||||
# 'cic_eth.eth.bancor.convert_with_default_reserve',
|
||||
# [
|
||||
# from_address,
|
||||
# target_return,
|
||||
# minimum_return,
|
||||
# from_address,
|
||||
# self.chain_spec.asdict(),
|
||||
# ],
|
||||
# queue=self.queue,
|
||||
# )
|
||||
# s_nonce.link(s_tokens)
|
||||
# s_check.link(s_nonce)
|
||||
# if self.callback_param != None:
|
||||
# s_convert.link(self.callback_success)
|
||||
# s_tokens.link(s_convert).on_error(self.callback_error)
|
||||
# else:
|
||||
# s_tokens.link(s_convert)
|
||||
#
|
||||
# t = s_check.apply_async(queue=self.queue)
|
||||
# return t
|
||||
|
||||
|
||||
def transfer(self, from_address, to_address, value, token_symbol):
|
||||
|
@ -1,8 +0,0 @@
|
||||
import math
|
||||
|
||||
def num_serialize(n):
|
||||
if n == 0:
|
||||
return b'\x00'
|
||||
binlog = math.log2(n)
|
||||
bytelength = int(binlog / 8 + 1)
|
||||
return n.to_bytes(bytelength, 'big')
|
@ -57,10 +57,12 @@ celery_app = celery.current_app
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
MAXIMUM_FEE_UNITS = 8000000
|
||||
|
||||
class MaxGasOracle:
|
||||
|
||||
def gas(code=None):
|
||||
return 8000000
|
||||
return MAXIMUM_FEE_UNITS
|
||||
|
||||
|
||||
def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None):
|
||||
@ -150,7 +152,7 @@ def cache_gas_data(
|
||||
|
||||
|
||||
@celery_app.task(bind=True, throws=(OutOfGasError), base=CriticalSQLAlchemyAndWeb3Task)
|
||||
def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_required=None):
|
||||
def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_required=MAXIMUM_FEE_UNITS):
|
||||
"""Check the gas level of the sender address of a transaction.
|
||||
|
||||
If the account balance is not sufficient for the required gas, gas refill is requested and OutOfGasError raiser.
|
||||
@ -170,24 +172,30 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
||||
:return: Signed raw transaction data list
|
||||
:rtype: param txs, unchanged
|
||||
"""
|
||||
if len(txs) == 0:
|
||||
for i in range(len(tx_hashes)):
|
||||
o = get_tx(tx_hashes[i])
|
||||
txs.append(o['signed_tx'])
|
||||
if address == None:
|
||||
address = o['address']
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
logg.debug('txs {} tx_hashes {}'.format(txs, tx_hashes))
|
||||
|
||||
addresspass = None
|
||||
if len(txs) == 0:
|
||||
addresspass = []
|
||||
for i in range(len(tx_hashes)):
|
||||
o = get_tx(chain_spec_dict, tx_hashes[i])
|
||||
txs.append(o['signed_tx'])
|
||||
logg.debug('sender {}'.format(o))
|
||||
tx = unpack(bytes.fromhex(strip_0x(o['signed_tx'])), chain_spec)
|
||||
if address == None:
|
||||
address = tx['from']
|
||||
elif address != tx['from']:
|
||||
raise ValueError('txs passed to check gas must all have same sender; had {} got {}'.format(address, tx['from']))
|
||||
addresspass.append(address)
|
||||
|
||||
#if not web3.Web3.isChecksumAddress(address):
|
||||
if not is_checksum_address(address):
|
||||
raise ValueError('invalid address {}'.format(address))
|
||||
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
|
||||
queue = self.request.delivery_info.get('routing_key')
|
||||
|
||||
conn = RPCConnection.connect(chain_spec)
|
||||
|
||||
# TODO: it should not be necessary to pass address explicitly, if not passed should be derived from the tx
|
||||
gas_balance = 0
|
||||
try:
|
||||
o = balance(address)
|
||||
@ -198,6 +206,9 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
||||
conn.disconnect()
|
||||
raise EthError('gas_balance call for {}: {}'.format(address, e))
|
||||
|
||||
if gas_required == None:
|
||||
gas_required = MAXIMUM_FEE_UNITS
|
||||
|
||||
logg.debug('address {} has gas {} needs {}'.format(address, gas_balance, gas_required))
|
||||
session = SessionBase.create_session()
|
||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
||||
@ -268,7 +279,8 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
||||
queue=queue,
|
||||
)
|
||||
ready_tasks.append(s)
|
||||
celery.group(ready_tasks)()
|
||||
t = celery.group(ready_tasks)()
|
||||
logg.debug('group {}'.format(t))
|
||||
|
||||
return txs
|
||||
|
||||
|
@ -21,6 +21,7 @@ from chainqueue.db.models.tx import Otx
|
||||
from chainqueue.db.models.tx import TxCache
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.error import NotLocalTxError
|
||||
from potaahto.symbols import snake_and_camel
|
||||
|
||||
# local imports
|
||||
from cic_eth.db import SessionBase
|
||||
@ -58,6 +59,9 @@ def hashes_to_txs(self, tx_hashes):
|
||||
if len(tx_hashes) == 0:
|
||||
raise ValueError('no transaction to send')
|
||||
|
||||
for i in range(len(tx_hashes)):
|
||||
tx_hashes[i] = strip_0x(tx_hashes[i])
|
||||
|
||||
queue = self.request.delivery_info['routing_key']
|
||||
|
||||
session = SessionBase.create_session()
|
||||
@ -148,7 +152,7 @@ def send(self, txs, chain_spec_dict):
|
||||
|
||||
@celery_app.task(bind=True, throws=(NotFoundEthException,), base=CriticalWeb3Task)
|
||||
def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
||||
"""Force update of network status of a simgle transaction
|
||||
"""Force update of network status of a single transaction
|
||||
|
||||
:param tx_hash_hex: Transaction hash
|
||||
:type tx_hash_hex: str, 0x-hex
|
||||
@ -173,12 +177,14 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
||||
|
||||
# TODO: apply receipt in tx object to validate and normalize input
|
||||
if rcpt != None:
|
||||
rcpt = snake_and_camel(rcpt)
|
||||
success = rcpt['status'] == 1
|
||||
logg.debug('sync tx {} mined block {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], success))
|
||||
logg.debug('sync tx {} mined block {} tx index {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], rcpt['transactionIndex'], success))
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.queue.state.set_final',
|
||||
[
|
||||
chain_spec_dict,
|
||||
tx_hash_hex,
|
||||
rcpt['blockNumber'],
|
||||
rcpt['transactionIndex'],
|
||||
@ -186,12 +192,14 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
# TODO: it's not entirely clear how we can reliable determine that its in mempool without explicitly checking
|
||||
else:
|
||||
logg.debug('sync tx {} mempool'.format(tx_hash_hex))
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.queue.state.set_sent',
|
||||
[
|
||||
chain_spec_dict,
|
||||
tx_hash_hex,
|
||||
],
|
||||
queue=queue,
|
||||
|
@ -19,7 +19,7 @@ from cic_eth_registry import CICRegistry
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusEnum
|
||||
from chainqueue.query import get_tx_cache
|
||||
from chainqueue.sql.query import get_tx_cache
|
||||
from eth_erc20 import ERC20
|
||||
|
||||
# local imports
|
||||
|
@ -5,7 +5,7 @@ import datetime
|
||||
import celery
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.tx import unpack
|
||||
import chainqueue.query
|
||||
import chainqueue.sql.query
|
||||
from chainqueue.db.enum import (
|
||||
StatusEnum,
|
||||
is_alive,
|
||||
@ -28,7 +28,7 @@ celery_app = celery.current_app
|
||||
def get_tx_cache(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.query.get_tx_cache(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.query.get_tx_cache(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -37,7 +37,7 @@ def get_tx_cache(chain_spec_dict, tx_hash):
|
||||
def get_tx(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.query.get_tx(chain_spec, tx_hash)
|
||||
r = chainqueue.sql.query.get_tx(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -46,7 +46,7 @@ def get_tx(chain_spec_dict, tx_hash):
|
||||
def get_account_tx(chain_spec_dict, address, as_sender=True, as_recipient=True, counterpart=None):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
|
||||
r = chainqueue.sql.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -55,17 +55,17 @@ def get_account_tx(chain_spec_dict, address, as_sender=True, as_recipient=True,
|
||||
def get_upcoming_tx_nolock(chain_spec_dict, status=StatusEnum.READYSEND, not_status=None, recipient=None, before=None, limit=0, session=None):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
|
||||
r = chainqueue.sql.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
|
||||
def get_status_tx(chain_spec, status, not_status=None, before=None, exact=False, limit=0, session=None):
|
||||
return chainqueue.query.get_status_tx_cache(chain_spec, status, not_status=not_status, before=before, exact=exact, limit=limit, session=session, decoder=unpack)
|
||||
return chainqueue.sql.query.get_status_tx_cache(chain_spec, status, not_status=not_status, before=before, exact=exact, limit=limit, session=session, decoder=unpack)
|
||||
|
||||
|
||||
def get_paused_tx(chain_spec, status=None, sender=None, session=None, decoder=None):
|
||||
return chainqueue.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
|
||||
return chainqueue.sql.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
|
||||
|
||||
|
||||
def get_nonce_tx(chain_spec, nonce, sender):
|
||||
|
@ -1,6 +1,6 @@
|
||||
# external imports
|
||||
from chainlib.chain import ChainSpec
|
||||
import chainqueue.state
|
||||
import chainqueue.sql.state
|
||||
|
||||
# local imports
|
||||
import celery
|
||||
@ -14,7 +14,7 @@ celery_app = celery.current_app
|
||||
def set_sent(chain_spec_dict, tx_hash, fail=False):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_sent(chain_spec, tx_hash, fail, session=session)
|
||||
r = chainqueue.sql.state.set_sent(chain_spec, tx_hash, fail, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -23,7 +23,7 @@ def set_sent(chain_spec_dict, tx_hash, fail=False):
|
||||
def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
|
||||
r = chainqueue.sql.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -32,7 +32,7 @@ def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
|
||||
def set_cancel(chain_spec_dict, tx_hash, manual=False):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_cancel(chain_spec, tx_hash, manual, session=session)
|
||||
r = chainqueue.sql.state.set_cancel(chain_spec, tx_hash, manual, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -41,7 +41,7 @@ def set_cancel(chain_spec_dict, tx_hash, manual=False):
|
||||
def set_rejected(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_rejected(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_rejected(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -50,7 +50,7 @@ def set_rejected(chain_spec_dict, tx_hash):
|
||||
def set_fubar(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_fubar(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_fubar(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -59,7 +59,7 @@ def set_fubar(chain_spec_dict, tx_hash):
|
||||
def set_manual(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_manual(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_manual(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -68,7 +68,7 @@ def set_manual(chain_spec_dict, tx_hash):
|
||||
def set_ready(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_ready(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_ready(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -77,7 +77,7 @@ def set_ready(chain_spec_dict, tx_hash):
|
||||
def set_reserved(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_reserved(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_reserved(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -86,7 +86,7 @@ def set_reserved(chain_spec_dict, tx_hash):
|
||||
def set_waitforgas(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.set_waitforgas(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.set_waitforgas(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -95,7 +95,7 @@ def set_waitforgas(chain_spec_dict, tx_hash):
|
||||
def get_state_log(chain_spec_dict, tx_hash):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.get_state_log(chain_spec, tx_hash, session=session)
|
||||
r = chainqueue.sql.state.get_state_log(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@ -104,6 +104,6 @@ def get_state_log(chain_spec_dict, tx_hash):
|
||||
def obsolete(chain_spec_dict, tx_hash, final):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
|
||||
r = chainqueue.sql.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
@ -15,14 +15,14 @@ from sqlalchemy import tuple_
|
||||
from sqlalchemy import func
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.tx import unpack
|
||||
import chainqueue.state
|
||||
import chainqueue.sql.state
|
||||
from chainqueue.db.enum import (
|
||||
StatusEnum,
|
||||
StatusBits,
|
||||
is_alive,
|
||||
dead,
|
||||
)
|
||||
from chainqueue.tx import create
|
||||
from chainqueue.sql.tx import create
|
||||
from chainqueue.error import NotLocalTxError
|
||||
from chainqueue.db.enum import status_str
|
||||
|
||||
|
@ -5,29 +5,30 @@ import logging
|
||||
from cic_eth_registry import CICRegistry
|
||||
from cic_eth_registry.lookup.declarator import AddressDeclaratorLookup
|
||||
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def connect_token_registry(rpc, chain_spec):
|
||||
def connect_token_registry(rpc, chain_spec, sender_address=ZERO_ADDRESS):
|
||||
registry = CICRegistry(chain_spec, rpc)
|
||||
token_registry_address = registry.by_name('TokenRegistry')
|
||||
token_registry_address = registry.by_name('TokenRegistry', sender_address=sender_address)
|
||||
logg.debug('using token registry address {}'.format(token_registry_address))
|
||||
lookup = TokenIndexLookup(chain_spec, token_registry_address)
|
||||
CICRegistry.add_lookup(lookup)
|
||||
|
||||
|
||||
def connect_declarator(rpc, chain_spec, trusted_addresses):
|
||||
def connect_declarator(rpc, chain_spec, trusted_addresses, sender_address=ZERO_ADDRESS):
|
||||
registry = CICRegistry(chain_spec, rpc)
|
||||
declarator_address = registry.by_name('AddressDeclarator')
|
||||
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
||||
logg.debug('using declarator address {}'.format(declarator_address))
|
||||
lookup = AddressDeclaratorLookup(chain_spec, declarator_address, trusted_addresses)
|
||||
CICRegistry.add_lookup(lookup)
|
||||
|
||||
|
||||
def connect(rpc, chain_spec, registry_address):
|
||||
def connect(rpc, chain_spec, registry_address, sender_address=ZERO_ADDRESS):
|
||||
CICRegistry.address = registry_address
|
||||
registry = CICRegistry(chain_spec, rpc)
|
||||
registry_address = registry.by_name('ContractRegistry')
|
||||
registry_address = registry.by_name('ContractRegistry', sender_address=sender_address)
|
||||
return registry
|
||||
|
||||
|
@ -21,7 +21,7 @@ from chainqueue.db.enum import (
|
||||
StatusBits,
|
||||
)
|
||||
from chainqueue.error import NotLocalTxError
|
||||
from chainqueue.state import set_reserved
|
||||
from chainqueue.sql.state import set_reserved
|
||||
|
||||
# local imports
|
||||
import cic_eth
|
||||
|
@ -10,14 +10,15 @@ from chainlib.eth.tx import unpack
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.db.models.tx import TxCache
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.query import get_paused_tx_cache as get_paused_tx
|
||||
from chainqueue.sql.query import get_paused_tx_cache as get_paused_tx
|
||||
|
||||
# local imports
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
from cic_eth.eth.gas import create_check_gas_task
|
||||
from .base import SyncFilter
|
||||
|
||||
logg = logging.getLogger().getChild(__name__)
|
||||
#logg = logging.getLogger().getChild(__name__)
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class GasFilter(SyncFilter):
|
||||
@ -27,11 +28,11 @@ class GasFilter(SyncFilter):
|
||||
self.chain_spec = chain_spec
|
||||
|
||||
|
||||
def filter(self, conn, block, tx, session):
|
||||
def filter(self, conn, block, tx, db_session):
|
||||
if tx.value > 0:
|
||||
tx_hash_hex = add_0x(tx.hash)
|
||||
logg.debug('gas refill tx {}'.format(tx_hash_hex))
|
||||
session = SessionBase.bind_session(session)
|
||||
session = SessionBase.bind_session(db_session)
|
||||
q = session.query(TxCache.recipient)
|
||||
q = q.join(Otx)
|
||||
q = q.filter(Otx.tx_hash==strip_0x(tx_hash_hex))
|
||||
@ -56,7 +57,7 @@ class GasFilter(SyncFilter):
|
||||
tx_hashes_hex=list(txs.keys()),
|
||||
queue=self.queue,
|
||||
)
|
||||
s.apply_async()
|
||||
return s.apply_async()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
@ -50,7 +50,8 @@ class RegistrationFilter(SyncFilter):
|
||||
queue=self.queue,
|
||||
)
|
||||
s_nonce.link(s_gift)
|
||||
s_nonce.apply_async()
|
||||
t = s_nonce.apply_async()
|
||||
return t
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
@ -3,7 +3,7 @@ import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
from chainqueue.state import obsolete_by_cache
|
||||
from chainqueue.sql.state import obsolete_by_cache
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
@ -32,7 +32,7 @@ class TransferAuthFilter(SyncFilter):
|
||||
self.transfer_request_contract = registry.by_name('TransferAuthorization', sender_address=call_address)
|
||||
|
||||
|
||||
def filter(self, conn, block, tx, session): #rcpt, chain_str, session=None):
|
||||
def filter(self, conn, block, tx, db_session): #rcpt, chain_str, session=None):
|
||||
|
||||
if tx.payload == None:
|
||||
logg.debug('no payload')
|
||||
@ -45,16 +45,17 @@ class TransferAuthFilter(SyncFilter):
|
||||
return False
|
||||
|
||||
recipient = tx.inputs[0]
|
||||
if recipient != self.transfer_request_contract.address():
|
||||
#if recipient != self.transfer_request_contract.address():
|
||||
if recipient != self.transfer_request_contract:
|
||||
logg.debug('not our transfer auth contract address {}'.format(recipient))
|
||||
return False
|
||||
|
||||
r = TransferAuthorization.parse_create_request_request(tx.payload)
|
||||
|
||||
sender = abi_decode_single(ABIContractType.ADDRESS, r[0])
|
||||
recipient = abi_decode_single(ABIContractType.ADDRESS, r[1])
|
||||
token = abi_decode_single(ABIContractType.ADDRESS, r[2])
|
||||
value = abi_decode_single(ABIContractType.UINT256, r[3])
|
||||
|
||||
sender = r[0]
|
||||
recipient = r[1]
|
||||
token = r[2]
|
||||
value = r[3]
|
||||
|
||||
token_data = {
|
||||
'address': token,
|
||||
@ -64,6 +65,7 @@ class TransferAuthFilter(SyncFilter):
|
||||
'cic_eth.eth.nonce.reserve_nonce',
|
||||
[
|
||||
[token_data],
|
||||
self.chain_spec.asdict(),
|
||||
sender,
|
||||
],
|
||||
queue=self.queue,
|
||||
@ -80,7 +82,7 @@ class TransferAuthFilter(SyncFilter):
|
||||
)
|
||||
s_nonce.link(s_approve)
|
||||
t = s_nonce.apply_async()
|
||||
return True
|
||||
return t
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
@ -20,7 +20,11 @@ def init_chain_stat(rpc, block_start=0):
|
||||
if block_start == 0:
|
||||
o = block_latest()
|
||||
r = rpc.do(o)
|
||||
block_start = int(r, 16)
|
||||
try:
|
||||
block_start = int(r, 16)
|
||||
except TypeError:
|
||||
block_start = int(r)
|
||||
logg.debug('blockstart {}'.format(block_start))
|
||||
|
||||
for i in range(BLOCK_SAMPLES):
|
||||
o = block_by_number(block_start-10+i)
|
||||
|
@ -20,7 +20,8 @@ import liveness.linux
|
||||
from cic_eth.error import SeppukuError
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
|
||||
logg = logging.getLogger().getChild(__name__)
|
||||
#logg = logging.getLogger().getChild(__name__)
|
||||
logg = logging.getLogger()
|
||||
|
||||
celery_app = celery.current_app
|
||||
|
||||
@ -118,12 +119,13 @@ def registry():
|
||||
return CICRegistry.address
|
||||
|
||||
|
||||
@celery_app.task()
|
||||
def registry_address_lookup(chain_spec_dict, address, connection_tag='default'):
|
||||
@celery_app.task(bind=True, base=BaseTask)
|
||||
def registry_address_lookup(self, chain_spec_dict, address, connection_tag='default'):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
|
||||
registry = CICRegistry(chain_spec, conn)
|
||||
return registry.by_address(address)
|
||||
r = registry.by_address(address, sender_address=self.call_address)
|
||||
return r
|
||||
|
||||
|
||||
@celery_app.task(throws=(UnknownContractError,))
|
||||
@ -131,7 +133,7 @@ def registry_name_lookup(chain_spec_dict, name, connection_tag='default'):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
|
||||
registry = CICRegistry(chain_spec, conn)
|
||||
return registry.by_name(name)
|
||||
return registry.by_name(name, sender_address=self.call_address)
|
||||
|
||||
|
||||
@celery_app.task()
|
||||
|
@ -10,7 +10,7 @@ version = (
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
'beta.14',
|
||||
'beta.16',
|
||||
)
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
|
@ -1,23 +1,23 @@
|
||||
cic-base~=0.1.2b11
|
||||
cic-base~=0.1.2b15
|
||||
celery==4.4.7
|
||||
crypto-dev-signer~=0.4.14b3
|
||||
confini~=0.3.6rc3
|
||||
cic-eth-registry~=0.5.5a4
|
||||
cic-eth-registry~=0.5.5a7
|
||||
redis==3.5.3
|
||||
alembic==1.4.2
|
||||
websockets==8.1
|
||||
requests~=2.24.0
|
||||
eth_accounts_index~=0.0.11a12
|
||||
erc20-transfer-authorization~=0.3.1a6
|
||||
erc20-transfer-authorization~=0.3.1a7
|
||||
uWSGI==2.0.19.1
|
||||
semver==2.13.0
|
||||
websocket-client==0.57.0
|
||||
moolb~=0.1.1b2
|
||||
eth-address-index~=0.1.1a11
|
||||
chainlib~=0.0.3a2
|
||||
chainlib~=0.0.3rc2
|
||||
hexathon~=0.0.1a7
|
||||
chainsyncer[sql]~=0.0.2a4
|
||||
chainqueue~=0.0.2a2
|
||||
chainsyncer[sql]~=0.0.2a5
|
||||
chainqueue~=0.0.2b3
|
||||
sarafu-faucet==0.0.3a3
|
||||
erc20-faucet==0.2.1a4
|
||||
coincurve==15.0.0
|
||||
|
8
apps/cic-eth/tests/check/test_check_db.py
Normal file
8
apps/cic-eth/tests/check/test_check_db.py
Normal file
@ -0,0 +1,8 @@
|
||||
# local imports
|
||||
from cic_eth.check.db import health
|
||||
|
||||
def test_check_health(
|
||||
init_database,
|
||||
):
|
||||
|
||||
assert health()
|
20
apps/cic-eth/tests/check/test_check_gas.py
Normal file
20
apps/cic-eth/tests/check/test_check_gas.py
Normal file
@ -0,0 +1,20 @@
|
||||
# local imports
|
||||
from cic_eth.check.gas import health
|
||||
from cic_eth.db.models.role import AccountRole
|
||||
|
||||
def test_check_gas(
|
||||
config,
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
custodial_roles,
|
||||
whoever,
|
||||
):
|
||||
|
||||
config.add(str(default_chain_spec), 'CIC_CHAIN_SPEC', exists_ok=True)
|
||||
config.add(100, 'ETH_GAS_GIFTER_MINIMUM_BALANCE', exists_ok=True)
|
||||
assert health(config=config)
|
||||
|
||||
AccountRole.set('GAS_GIFTER', whoever, session=init_database)
|
||||
init_database.commit()
|
||||
assert not health(config=config)
|
16
apps/cic-eth/tests/check/test_check_redis.py
Normal file
16
apps/cic-eth/tests/check/test_check_redis.py
Normal file
@ -0,0 +1,16 @@
|
||||
# external imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_eth.check.redis import health
|
||||
|
||||
|
||||
def test_check_redis(
|
||||
config,
|
||||
have_redis,
|
||||
):
|
||||
|
||||
if have_redis != None:
|
||||
pytest.skip('cannot connect to redis, skipping test: {}'.format(have_redis))
|
||||
|
||||
assert health(unit='test', config=config)
|
13
apps/cic-eth/tests/check/test_check_signer.py
Normal file
13
apps/cic-eth/tests/check/test_check_signer.py
Normal file
@ -0,0 +1,13 @@
|
||||
# local imports
|
||||
from cic_eth.check.signer import health
|
||||
|
||||
|
||||
def test_check_signer(
|
||||
default_chain_spec,
|
||||
config,
|
||||
eth_signer,
|
||||
eth_rpc,
|
||||
):
|
||||
|
||||
config.add(str(default_chain_spec), 'CIC_CHAIN_SPEC', exists_ok=True)
|
||||
assert health(config=config)
|
@ -2,9 +2,11 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
# external imports
|
||||
from eth_erc20 import ERC20
|
||||
import redis
|
||||
|
||||
# local imports
|
||||
from cic_eth.api import Api
|
||||
@ -19,6 +21,7 @@ from tests.fixtures_config import *
|
||||
from tests.fixtures_database import *
|
||||
from tests.fixtures_celery import *
|
||||
from tests.fixtures_role import *
|
||||
from tests.fixtures_contract import *
|
||||
from chainlib.eth.pytest import *
|
||||
from eth_contract_registry.pytest import *
|
||||
from cic_eth_registry.pytest.fixtures_contracts import *
|
||||
@ -55,3 +58,28 @@ def default_token(
|
||||
):
|
||||
BaseTask.default_token_symbol = foo_token_symbol
|
||||
BaseTask.default_token_address = foo_token
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def have_redis(
|
||||
config,
|
||||
):
|
||||
|
||||
r = redis.Redis(
|
||||
host = config.get('REDIS_HOST'),
|
||||
port = config.get('REDIS_PORT'),
|
||||
db = config.get('REDIS_DB'),
|
||||
)
|
||||
k = str(uuid.uuid4())
|
||||
try:
|
||||
r.set(k, 'foo')
|
||||
r.delete(k)
|
||||
except redis.exceptions.ConnectionError as e:
|
||||
return e
|
||||
except TypeError as e:
|
||||
return e
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
38
apps/cic-eth/tests/filters/test_filter_bogus.py
Normal file
38
apps/cic-eth/tests/filters/test_filter_bogus.py
Normal file
@ -0,0 +1,38 @@
|
||||
# local imports
|
||||
from cic_eth.runnable.daemons.filters.gas import GasFilter
|
||||
from cic_eth.runnable.daemons.filters.transferauth import TransferAuthFilter
|
||||
from cic_eth.runnable.daemons.filters.callback import CallbackFilter
|
||||
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
||||
from cic_eth.runnable.daemons.filters.tx import TxFilter
|
||||
from cic_eth.runnable.daemons.filters.register import RegistrationFilter
|
||||
|
||||
|
||||
# Hit tx mismatch paths on all filters
|
||||
def test_filter_bogus(
|
||||
init_database,
|
||||
bogus_tx_block,
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
transfer_auth,
|
||||
cic_registry,
|
||||
contract_roles,
|
||||
register_lookups,
|
||||
):
|
||||
|
||||
fltrs = [
|
||||
TransferAuthFilter(cic_registry, default_chain_spec, eth_rpc, call_address=contract_roles['CONTRACT_DEPLOYER']),
|
||||
GasFilter(default_chain_spec, queue=None),
|
||||
TxFilter(default_chain_spec, None),
|
||||
CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER']),
|
||||
StragglerFilter(default_chain_spec, None),
|
||||
RegistrationFilter(default_chain_spec, queue=None),
|
||||
]
|
||||
|
||||
for fltr in fltrs:
|
||||
r = None
|
||||
try:
|
||||
r = fltr.filter(eth_rpc, bogus_tx_block[0], bogus_tx_block[1], db_session=init_database)
|
||||
except:
|
||||
pass
|
||||
assert not r
|
101
apps/cic-eth/tests/filters/test_gas_filter.py
Normal file
101
apps/cic-eth/tests/filters/test_gas_filter.py
Normal file
@ -0,0 +1,101 @@
|
||||
# external imports
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import OverrideNonceOracle
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainlib.eth.tx import (
|
||||
TxFormat,
|
||||
unpack,
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
Gas,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainqueue.sql.state import (
|
||||
set_waitforgas,
|
||||
)
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
|
||||
# local imports
|
||||
from cic_eth.runnable.daemons.filters.gas import GasFilter
|
||||
from cic_eth.eth.gas import cache_gas_data
|
||||
|
||||
|
||||
def test_filter_gas(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
set_waitforgas(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
init_database.commit()
|
||||
tx_hash_hex_wait = tx_hash_hex
|
||||
otx = Otx.load(tx_hash_hex_wait, session=init_database)
|
||||
assert otx.status & StatusBits.GAS_ISSUES == StatusBits.GAS_ISSUES
|
||||
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['BOB'], agent_roles['ALICE'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
43,
|
||||
agent_roles['BOB'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
fltr = GasFilter(default_chain_spec, queue=None)
|
||||
|
||||
o = block_latest()
|
||||
r = eth_rpc.do(o)
|
||||
o = block_by_number(r, include_tx=False)
|
||||
r = eth_rpc.do(o)
|
||||
block = Block(r)
|
||||
block.txs = [tx_hash_hex]
|
||||
|
||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
|
||||
tx_src = unpack(tx_signed_raw_bytes, default_chain_spec)
|
||||
tx = Tx(tx_src, block=block)
|
||||
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
|
||||
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
init_database.commit()
|
||||
|
||||
otx = Otx.load(tx_hash_hex_wait, session=init_database)
|
||||
assert otx.status & StatusBits.QUEUED == StatusBits.QUEUED
|
78
apps/cic-eth/tests/filters/test_register_filter.py
Normal file
78
apps/cic-eth/tests/filters/test_register_filter.py
Normal file
@ -0,0 +1,78 @@
|
||||
# external imports
|
||||
from eth_accounts_index.registry import AccountRegistry
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.tx import(
|
||||
receipt,
|
||||
unpack,
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from erc20_faucet import Faucet
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.sql.query import get_account_tx
|
||||
|
||||
# local imports
|
||||
from cic_eth.runnable.daemons.filters.register import RegistrationFilter
|
||||
|
||||
|
||||
def test_register_filter(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
account_registry,
|
||||
faucet,
|
||||
register_lookups,
|
||||
contract_roles,
|
||||
agent_roles,
|
||||
cic_registry,
|
||||
init_celery_tasks,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(contract_roles['ACCOUNT_REGISTRY_WRITER'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(limit=AccountRegistry.gas(), conn=eth_rpc)
|
||||
|
||||
c = AccountRegistry(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = c.add(account_registry, contract_roles['ACCOUNT_REGISTRY_WRITER'], agent_roles['ALICE'])
|
||||
r = eth_rpc.do(o)
|
||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(o['params'][0]))
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
rcpt = eth_rpc.do(o)
|
||||
assert rcpt['status'] == 1
|
||||
|
||||
o = block_latest()
|
||||
r = eth_rpc.do(o)
|
||||
o = block_by_number(r, include_tx=False)
|
||||
r = eth_rpc.do(o)
|
||||
block = Block(r)
|
||||
block.txs = [tx_hash_hex]
|
||||
|
||||
tx_src = unpack(tx_signed_raw_bytes, default_chain_spec)
|
||||
tx = Tx(tx_src, block=block, rcpt=rcpt)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
fltr = RegistrationFilter(default_chain_spec, queue=None)
|
||||
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
|
||||
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
gift_txs = get_account_tx(default_chain_spec.asdict(), agent_roles['ALICE'], as_sender=True, session=init_database)
|
||||
ks = list(gift_txs.keys())
|
||||
assert len(ks) == 1
|
||||
|
||||
tx_raw_signed_hex = strip_0x(gift_txs[ks[0]])
|
||||
tx_raw_signed_bytes = bytes.fromhex(tx_raw_signed_hex)
|
||||
gift_tx = unpack(tx_raw_signed_bytes, default_chain_spec)
|
||||
|
||||
gift = Faucet.parse_give_to_request(gift_tx['data'])
|
||||
assert gift[0] == agent_roles['ALICE']
|
@ -17,8 +17,8 @@ from chainlib.eth.block import (
|
||||
)
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.tx import create as queue_create
|
||||
from chainqueue.state import (
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
set_ready,
|
||||
set_sent,
|
||||
|
79
apps/cic-eth/tests/filters/test_transferauth_filter.py
Normal file
79
apps/cic-eth/tests/filters/test_transferauth_filter.py
Normal file
@ -0,0 +1,79 @@
|
||||
# external imports
|
||||
from erc20_transfer_authorization import TransferAuthorization
|
||||
from eth_erc20 import ERC20
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
unpack,
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.sql.query import get_account_tx
|
||||
|
||||
# local imports
|
||||
from cic_eth.runnable.daemons.filters.transferauth import TransferAuthFilter
|
||||
|
||||
|
||||
def test_filter_transferauth(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
transfer_auth,
|
||||
foo_token,
|
||||
celery_session_worker,
|
||||
register_lookups,
|
||||
init_custodial,
|
||||
cic_registry,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(limit=200000, conn=eth_rpc)
|
||||
c = TransferAuthorization(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = c.create_request(transfer_auth, contract_roles['CONTRACT_DEPLOYER'], agent_roles['ALICE'], agent_roles['BOB'], foo_token, 1024)
|
||||
|
||||
r = rpc.do(o)
|
||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(o['params'][0]))
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
o = block_latest()
|
||||
r = eth_rpc.do(o)
|
||||
o = block_by_number(r, include_tx=False)
|
||||
r = eth_rpc.do(o)
|
||||
block = Block(r)
|
||||
block.txs = [tx_hash_hex]
|
||||
|
||||
#tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
|
||||
tx_src = unpack(tx_signed_raw_bytes, default_chain_spec)
|
||||
tx = Tx(tx_src, block=block)
|
||||
|
||||
fltr = TransferAuthFilter(cic_registry, default_chain_spec, eth_rpc, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
|
||||
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
approve_txs = get_account_tx(default_chain_spec.asdict(), agent_roles['ALICE'], as_sender=True, session=init_database)
|
||||
ks = list(approve_txs.keys())
|
||||
assert len(ks) == 1
|
||||
|
||||
tx_raw_signed_hex = strip_0x(approve_txs[ks[0]])
|
||||
tx_raw_signed_bytes = bytes.fromhex(tx_raw_signed_hex)
|
||||
approve_tx = unpack(tx_raw_signed_bytes, default_chain_spec)
|
||||
|
||||
c = ERC20(default_chain_spec)
|
||||
approve = c.parse_approve_request(approve_tx['data'])
|
||||
assert approve[0] == agent_roles['BOB']
|
@ -17,13 +17,12 @@ from chainlib.eth.block import (
|
||||
)
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.tx import create as queue_create
|
||||
from chainqueue.state import (
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
set_ready,
|
||||
set_sent,
|
||||
)
|
||||
|
||||
from hexathon import strip_0x
|
||||
|
||||
# local imports
|
||||
@ -31,7 +30,7 @@ from cic_eth.runnable.daemons.filters.tx import TxFilter
|
||||
from cic_eth.eth.gas import cache_gas_data
|
||||
|
||||
|
||||
def test_tx(
|
||||
def test_filter_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
|
@ -22,7 +22,6 @@ def init_celery_tasks(
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_includes():
|
||||
return [
|
||||
# 'cic_eth.eth.bancor',
|
||||
'cic_eth.eth.erc20',
|
||||
'cic_eth.eth.tx',
|
||||
'cic_eth.ext.tx',
|
||||
@ -47,8 +46,8 @@ def celery_config():
|
||||
bq = tempfile.mkdtemp()
|
||||
bp = tempfile.mkdtemp()
|
||||
rq = tempfile.mkdtemp()
|
||||
logg.debug('celery broker queue {} processed {}'.format(bq, bp))
|
||||
logg.debug('celery backend store {}'.format(rq))
|
||||
logg.debug('celery broker session queue {} processed {}'.format(bq, bp))
|
||||
logg.debug('celery backend session store {}'.format(rq))
|
||||
yield {
|
||||
'broker_url': 'filesystem://',
|
||||
'broker_transport_options': {
|
||||
@ -58,12 +57,11 @@ def celery_config():
|
||||
},
|
||||
'result_backend': 'file://{}'.format(rq),
|
||||
}
|
||||
logg.debug('cleaning up celery filesystem backend files {} {} {}'.format(bq, bp, rq))
|
||||
logg.debug('cleaning up celery session filesystem backend files {} {} {}'.format(bq, bp, rq))
|
||||
shutil.rmtree(bq)
|
||||
shutil.rmtree(bp)
|
||||
shutil.rmtree(rq)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_worker_parameters():
|
||||
return {
|
||||
|
77
apps/cic-eth/tests/fixtures_contract.py
Normal file
77
apps/cic-eth/tests/fixtures_contract.py
Normal file
@ -0,0 +1,77 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
)
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.gas import OverrideGasOracle
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
unpack,
|
||||
Tx,
|
||||
)
|
||||
from hexathon import strip_0x
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
root_dir = os.path.dirname(script_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def bogus_tx_block(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
contract_roles,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(limit=2000000, conn=eth_rpc)
|
||||
|
||||
f = open(os.path.join(script_dir, 'testdata', 'Bogus.bin'), 'r')
|
||||
bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
c = TxFactory(default_chain_spec, signer=eth_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
tx = c.template(contract_roles['CONTRACT_DEPLOYER'], None, use_nonce=True)
|
||||
tx = c.set_code(tx, bytecode)
|
||||
(tx_hash_hex, o) = c.build(tx)
|
||||
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
contract_address = r['contract_address']
|
||||
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('poke')
|
||||
data = enc.get()
|
||||
tx = c.template(contract_roles['CONTRACT_DEPLOYER'], contract_address, use_nonce=True)
|
||||
tx = c.set_code(tx, data)
|
||||
(tx_hash_hex, o) = c.finalize(tx, TxFormat.JSONRPC)
|
||||
r = eth_rpc.do(o)
|
||||
tx_signed_raw_hex = strip_0x(o['params'][0])
|
||||
|
||||
o = block_latest()
|
||||
r = eth_rpc.do(o)
|
||||
o = block_by_number(r, include_tx=False)
|
||||
r = eth_rpc.do(o)
|
||||
block = Block(r)
|
||||
block.txs = [tx_hash_hex]
|
||||
|
||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
|
||||
tx_src = unpack(tx_signed_raw_bytes, default_chain_spec)
|
||||
tx = Tx(tx_src, block=block)
|
||||
|
||||
return (block, tx)
|
@ -9,8 +9,14 @@ from chainlib.eth.tx import (
|
||||
unpack,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.gas import Gas
|
||||
from chainlib.eth.nonce import (
|
||||
RPCNonceOracle,
|
||||
OverrideNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
Gas,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
@ -23,7 +29,15 @@ from chainqueue.db.enum import (
|
||||
StatusBits,
|
||||
status_str,
|
||||
)
|
||||
from chainqueue.query import get_tx
|
||||
from chainqueue.sql.state import (
|
||||
set_fubar,
|
||||
set_ready,
|
||||
set_reserved,
|
||||
)
|
||||
from chainqueue.sql.query import (
|
||||
get_tx,
|
||||
get_nonce_tx_cache,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from cic_eth.api import AdminApi
|
||||
@ -36,150 +50,6 @@ from cic_eth.queue.tx import queue_create
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
#def test_resend_inplace(
|
||||
# default_chain_spec,
|
||||
# init_database,
|
||||
# init_w3,
|
||||
# celery_session_worker,
|
||||
# ):
|
||||
#
|
||||
# chain_str = str(default_chain_spec)
|
||||
# c = RpcClient(default_chain_spec)
|
||||
#
|
||||
# sigs = []
|
||||
#
|
||||
# gas_provider = c.gas_provider()
|
||||
#
|
||||
# s_nonce = celery.signature(
|
||||
# 'cic_eth.eth.nonce.reserve_nonce',
|
||||
# [
|
||||
# init_w3.eth.accounts[0],
|
||||
# gas_provider,
|
||||
# ],
|
||||
# queue=None,
|
||||
# )
|
||||
# s_refill = celery.signature(
|
||||
# 'cic_eth.eth.gas.refill_gas',
|
||||
# [
|
||||
# chain_str,
|
||||
# ],
|
||||
# queue=None,
|
||||
# )
|
||||
# s_nonce.link(s_refill)
|
||||
# t = s_nonce.apply_async()
|
||||
# t.get()
|
||||
# for r in t.collect():
|
||||
# pass
|
||||
# assert t.successful()
|
||||
#
|
||||
# q = init_database.query(Otx)
|
||||
# q = q.join(TxCache)
|
||||
# q = q.filter(TxCache.recipient==init_w3.eth.accounts[0])
|
||||
# o = q.first()
|
||||
# tx_raw = o.signed_tx
|
||||
#
|
||||
# tx_dict = unpack(bytes.fromhex(tx_raw), default_chain_spec)
|
||||
# gas_price_before = tx_dict['gasPrice']
|
||||
#
|
||||
# s = celery.signature(
|
||||
# 'cic_eth.admin.ctrl.lock_send',
|
||||
# [
|
||||
# chain_str,
|
||||
# init_w3.eth.accounts[0],
|
||||
# ],
|
||||
# queue=None,
|
||||
# )
|
||||
# t = s.apply_async()
|
||||
# t.get()
|
||||
# assert t.successful()
|
||||
#
|
||||
# api = AdminApi(c, queue=None)
|
||||
# t = api.resend(tx_dict['hash'], chain_str, unlock=True)
|
||||
# t.get()
|
||||
# i = 0
|
||||
# tx_hash_new_hex = None
|
||||
# for r in t.collect():
|
||||
# tx_hash_new_hex = r[1]
|
||||
# assert t.successful()
|
||||
#
|
||||
# tx_raw_new = get_tx(tx_hash_new_hex)
|
||||
# logg.debug('get {}'.format(tx_raw_new))
|
||||
# tx_dict_new = unpack(bytes.fromhex(tx_raw_new['signed_tx']), default_chain_spec)
|
||||
# assert tx_hash_new_hex != tx_dict['hash']
|
||||
# assert tx_dict_new['gasPrice'] > gas_price_before
|
||||
#
|
||||
# tx_dict_after = get_tx(tx_dict['hash'])
|
||||
#
|
||||
# logg.debug('logggg {}'.format(status_str(tx_dict_after['status'])))
|
||||
# assert tx_dict_after['status'] & StatusBits.MANUAL
|
||||
|
||||
|
||||
#def test_check_fix_nonce(
|
||||
# default_chain_spec,
|
||||
# init_database,
|
||||
# init_eth_account_roles,
|
||||
# init_w3,
|
||||
# eth_empty_accounts,
|
||||
# celery_session_worker,
|
||||
# ):
|
||||
#
|
||||
# chain_str = str(default_chain_spec)
|
||||
#
|
||||
# sigs = []
|
||||
# for i in range(5):
|
||||
# s = celery.signature(
|
||||
# 'cic_eth.eth.gas.refill_gas',
|
||||
# [
|
||||
# eth_empty_accounts[i],
|
||||
# chain_str,
|
||||
# ],
|
||||
# queue=None,
|
||||
# )
|
||||
# sigs.append(s)
|
||||
#
|
||||
# t = celery.group(sigs)()
|
||||
# txs = t.get()
|
||||
# assert t.successful()
|
||||
#
|
||||
# tx_hash = web3.Web3.keccak(hexstr=txs[2])
|
||||
# c = RpcClient(default_chain_spec)
|
||||
# api = AdminApi(c, queue=None)
|
||||
# address = init_eth_account_roles['eth_account_gas_provider']
|
||||
# nonce_spec = api.check_nonce(address)
|
||||
# assert nonce_spec['nonce']['network'] == 0
|
||||
# assert nonce_spec['nonce']['queue'] == 4
|
||||
# assert nonce_spec['nonce']['blocking'] == None
|
||||
#
|
||||
# s_set = celery.signature(
|
||||
# 'cic_eth.queue.tx.set_rejected',
|
||||
# [
|
||||
# tx_hash.hex(),
|
||||
# ],
|
||||
# queue=None,
|
||||
# )
|
||||
# t = s_set.apply_async()
|
||||
# t.get()
|
||||
# t.collect()
|
||||
# assert t.successful()
|
||||
#
|
||||
#
|
||||
# nonce_spec = api.check_nonce(address)
|
||||
# assert nonce_spec['nonce']['blocking'] == 2
|
||||
# assert nonce_spec['tx']['blocking'] == tx_hash.hex()
|
||||
#
|
||||
# t = api.fix_nonce(address, nonce_spec['nonce']['blocking'])
|
||||
# t.get()
|
||||
# t.collect()
|
||||
# assert t.successful()
|
||||
#
|
||||
# for tx in txs[3:]:
|
||||
# tx_hash = web3.Web3.keccak(hexstr=tx)
|
||||
# tx_dict = get_tx(tx_hash.hex())
|
||||
# assert tx_dict['status'] == StatusEnum.OVERRIDDEN
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def test_have_account(
|
||||
default_chain_spec,
|
||||
custodial_roles,
|
||||
@ -243,28 +113,6 @@ def test_tag_account(
|
||||
assert AccountRole.get_address('bar', init_database) == agent_roles['CAROL']
|
||||
|
||||
|
||||
#def test_ready(
|
||||
# init_database,
|
||||
# agent_roles,
|
||||
# eth_rpc,
|
||||
# ):
|
||||
#
|
||||
# api = AdminApi(eth_rpc)
|
||||
#
|
||||
# with pytest.raises(InitializationError):
|
||||
# api.ready()
|
||||
#
|
||||
# bogus_account = os.urandom(20)
|
||||
# bogus_account_hex = '0x' + bogus_account.hex()
|
||||
#
|
||||
# api.tag_account('ETH_GAS_PROVIDER_ADDRESS', web3.Web3.toChecksumAddress(bogus_account_hex))
|
||||
# with pytest.raises(KeyError):
|
||||
# api.ready()
|
||||
#
|
||||
# api.tag_account('ETH_GAS_PROVIDER_ADDRESS', eth_empty_accounts[0])
|
||||
# api.ready()
|
||||
|
||||
|
||||
def test_tx(
|
||||
default_chain_spec,
|
||||
cic_registry,
|
||||
@ -286,3 +134,168 @@ def test_tx(
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['DEFAULT'])
|
||||
tx = api.tx(default_chain_spec, tx_hash=tx_hash_hex)
|
||||
logg.warning('code missing to verify tx contents {}'.format(tx))
|
||||
|
||||
|
||||
def test_check_nonce_gap(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
# NOTE: this only works as long as agents roles start at nonce 0
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 0)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
tx_hashes = []
|
||||
txs = []
|
||||
|
||||
j = 0
|
||||
for i in range(10):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
if i == 3:
|
||||
j = 1
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], i+1)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
i+j,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes.append(tx_hash_hex)
|
||||
txs.append(tx_signed_raw_hex)
|
||||
|
||||
|
||||
init_database.commit()
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['DEFAULT'])
|
||||
r = api.check_nonce(default_chain_spec, agent_roles['ALICE'])
|
||||
|
||||
assert r['nonce']['blocking'] == 4
|
||||
assert r['tx']['blocking'] == tx_hashes[3] # one less because there is a gap
|
||||
|
||||
|
||||
def test_check_nonce_localfail(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
# NOTE: this only works as long as agents roles start at nonce 0
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 0)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
tx_hashes = []
|
||||
txs = []
|
||||
|
||||
j = 0
|
||||
for i in range(10):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
i,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes.append(tx_hash_hex)
|
||||
txs.append(tx_signed_raw_hex)
|
||||
|
||||
set_ready(default_chain_spec, tx_hashes[4], session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hashes[4], session=init_database)
|
||||
set_fubar(default_chain_spec, tx_hashes[4], session=init_database)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['DEFAULT'])
|
||||
r = api.check_nonce(default_chain_spec, agent_roles['ALICE'])
|
||||
|
||||
assert r['nonce']['blocking'] == 4
|
||||
assert r['tx']['blocking'] == tx_hashes[4]
|
||||
|
||||
|
||||
def test_fix_nonce(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
init_celery_tasks,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 0)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
tx_hashes = []
|
||||
txs = []
|
||||
|
||||
for i in range(10):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
i,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes.append(tx_hash_hex)
|
||||
txs.append(tx_signed_raw_hex)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['DEFAULT'])
|
||||
t = api.fix_nonce(default_chain_spec, agent_roles['ALICE'], 3)
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
init_database.commit()
|
||||
|
||||
txs = get_nonce_tx_cache(default_chain_spec, 3, agent_roles['ALICE'], session=init_database)
|
||||
ks = txs.keys()
|
||||
assert len(ks) == 2
|
||||
for k in ks:
|
||||
hsh = add_0x(k)
|
||||
otx = Otx.load(hsh, session=init_database)
|
||||
init_database.refresh(otx)
|
||||
logg.debug('checking nonce {} tx {} status {}'.format(3, otx.tx_hash, otx.status))
|
||||
if add_0x(k) == tx_hashes[3]:
|
||||
assert otx.status & StatusBits.OBSOLETE == StatusBits.OBSOLETE
|
||||
else:
|
||||
assert otx.status == 1
|
||||
|
373
apps/cic-eth/tests/task/api/test_admin_noncritical.py
Normal file
373
apps/cic-eth/tests/task/api/test_admin_noncritical.py
Normal file
@ -0,0 +1,373 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import io
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import (
|
||||
nonce,
|
||||
OverrideNonceOracle,
|
||||
RPCNonceOracle,
|
||||
)
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainlib.eth.tx import (
|
||||
raw,
|
||||
receipt,
|
||||
TxFormat,
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.block import block_latest
|
||||
from chainlib.eth.gas import (
|
||||
Gas,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
set_sent,
|
||||
set_ready,
|
||||
)
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.sql.query import get_nonce_tx_cache
|
||||
from eth_erc20 import ERC20
|
||||
from cic_eth_registry import CICRegistry
|
||||
|
||||
# local imports
|
||||
from cic_eth.api.api_admin import AdminApi
|
||||
from cic_eth.eth.gas import cache_gas_data
|
||||
from cic_eth.eth.erc20 import cache_transfer_data
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_admin_api_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
init_celery_tasks,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
custodial_roles,
|
||||
celery_session_worker,
|
||||
foo_token,
|
||||
address_declarator,
|
||||
cic_registry,
|
||||
register_tokens,
|
||||
register_lookups,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(custodial_roles['FOO_TOKEN_GIFTER'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(limit=100000, conn=eth_rpc)
|
||||
|
||||
o = nonce(custodial_roles['FOO_TOKEN_GIFTER'])
|
||||
r = eth_rpc.do(o)
|
||||
gifter_nonce = int(r, 16)
|
||||
|
||||
#c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
c = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(foo_token, custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
gifter_nonce, # will only work if agent starts at 0
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_transfer_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
o = raw(tx_signed_raw_hex)
|
||||
eth_rpc.do(o)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = eth_rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
set_ready(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_sent(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
|
||||
# lookup by transaction hash, without registry
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
tx = api.tx(default_chain_spec, tx_hash=tx_hash_hex)
|
||||
logg.debug('deployed {}'.format(contract_roles['CONTRACT_DEPLOYER']))
|
||||
assert tx['tx_hash'] == tx_hash_hex
|
||||
|
||||
# lookup by RLP transaction, without registry
|
||||
tx = api.tx(default_chain_spec, tx_raw=tx_signed_raw_hex)
|
||||
assert tx['tx_hash'] == tx_hash_hex
|
||||
|
||||
# lookup by transaction hash, with registry
|
||||
registry = CICRegistry(default_chain_spec, eth_rpc)
|
||||
tx = api.tx(default_chain_spec, tx_hash=tx_hash_hex, registry=registry)
|
||||
assert tx['tx_hash'] == tx_hash_hex
|
||||
|
||||
# lookup by transaction hash, using writer
|
||||
buf = io.StringIO()
|
||||
api.tx(default_chain_spec, tx_hash=tx_hash_hex, renderer=json.dumps, w=buf)
|
||||
tx = json.loads(buf.getvalue())
|
||||
assert tx['tx_hash'] == tx_hash_hex
|
||||
|
||||
|
||||
def test_admin_api_account(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
tx_hashes_alice = []
|
||||
txs_alice = []
|
||||
|
||||
for i in range(3):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42+i,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes_alice.append(tx_hash_hex)
|
||||
txs_alice.append(tx_signed_raw_hex)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['BOB'], 13)
|
||||
tx_hashes_bob = []
|
||||
txs_bob = []
|
||||
|
||||
for i in range(2):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['BOB'], agent_roles['ALICE'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
13+i,
|
||||
agent_roles['BOB'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes_bob.append(tx_hash_hex)
|
||||
txs_bob.append(tx_signed_raw_hex)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
r = api.account(default_chain_spec, agent_roles['ALICE'])
|
||||
assert len(r) == 5
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
r = api.account(default_chain_spec, agent_roles['ALICE'], include_sender=False)
|
||||
assert len(r) == 2
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
r = api.account(default_chain_spec, agent_roles['ALICE'], include_recipient=False)
|
||||
assert len(r) == 3
|
||||
|
||||
|
||||
def test_admin_api_account_writer(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
buf = io.StringIO()
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
api.account(default_chain_spec, agent_roles['ALICE'], renderer=json.dumps, w=buf)
|
||||
|
||||
# TODO: improve eval
|
||||
tx = json.loads(buf.getvalue())
|
||||
assert tx['tx_hash'] == tx_hash_hex
|
||||
|
||||
|
||||
def test_registry(
|
||||
eth_rpc,
|
||||
cic_registry,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
t = api.registry()
|
||||
r = t.get_leaf()
|
||||
assert r == cic_registry
|
||||
|
||||
|
||||
def test_proxy_do(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
o = block_latest()
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
t = api.proxy_do(default_chain_spec, o)
|
||||
rr = t.get_leaf()
|
||||
|
||||
assert r == rr
|
||||
|
||||
|
||||
def test_resend_inplace(
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
set_ready(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_sent(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
t = api.resend(tx_hash_hex, default_chain_spec, unlock=True)
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
|
||||
otx = Otx.load(tx_hash_hex, session=init_database)
|
||||
assert otx.status & StatusBits.OBSOLETE == StatusBits.OBSOLETE
|
||||
|
||||
txs = get_nonce_tx_cache(default_chain_spec, otx.nonce, agent_roles['ALICE'], session=init_database)
|
||||
assert len(txs) == 2
|
||||
|
||||
|
||||
|
||||
@pytest.mark.xfail()
|
||||
def test_resend_clone(
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
contract_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
set_ready(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_sent(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
t = api.resend(tx_hash_hex, default_chain_spec, in_place=False)
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
otx = Otx.load(tx_hash_hex, session=init_database)
|
||||
assert otx.status & StatusBits.IN_NETWORK == StatusBits.IN_NETWORK
|
||||
assert otx.status & StatusBits.OBSOLETE == StatusBits.OBSOLETE
|
||||
|
||||
txs = get_nonce_tx_cache(default_chain_spec, otx.nonce, agent_roles['ALICE'], session=init_database)
|
||||
assert len(txs) == 1
|
||||
|
||||
txs = get_nonce_tx_cache(default_chain_spec, otx.nonce + 1, agent_roles['ALICE'], session=init_database)
|
||||
assert len(txs) == 1
|
||||
|
||||
otx = Otx.load(txs[0], session=init_database)
|
||||
assert otx.status == 0
|
@ -8,11 +8,20 @@ import pytest
|
||||
import celery
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
from chainlib.chain import ChainSpec
|
||||
from eth_accounts_index import AccountsIndex
|
||||
from chainlib.eth.tx import (
|
||||
transaction,
|
||||
)
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from cic_eth.api import Api
|
||||
from cic_eth.queue.query import get_tx
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
#logg = logging.getLogger(__name__)
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_account_api(
|
||||
@ -29,6 +38,47 @@ def test_account_api(
|
||||
assert t.successful()
|
||||
|
||||
|
||||
def test_account_api_register(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
account_registry,
|
||||
faucet,
|
||||
custodial_roles,
|
||||
cic_registry,
|
||||
register_lookups,
|
||||
eth_rpc,
|
||||
celery_session_worker,
|
||||
):
|
||||
api = Api(str(default_chain_spec), callback_param='accounts', callback_task='cic_eth.callbacks.noop.noop', queue=None)
|
||||
t = api.create_account('')
|
||||
register_tx_hash = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
set_reserved(default_chain_spec, register_tx_hash, session=init_database)
|
||||
|
||||
tx = get_tx(default_chain_spec.asdict(), register_tx_hash)
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.tx.send',
|
||||
[
|
||||
[tx['signed_tx']],
|
||||
default_chain_spec.asdict(),
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
o = transaction(register_tx_hash)
|
||||
tx_src = eth_rpc.do(o)
|
||||
|
||||
c = AccountsIndex(default_chain_spec)
|
||||
address = c.parse_add_request(tx_src['data'])
|
||||
o = c.have(account_registry, address[0], sender_address=custodial_roles['CONTRACT_DEPLOYER'])
|
||||
r = eth_rpc.do(o)
|
||||
assert c.parse_have(r)
|
||||
|
||||
|
||||
def test_transfer_api(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
@ -37,16 +87,15 @@ def test_transfer_api(
|
||||
custodial_roles,
|
||||
agent_roles,
|
||||
cic_registry,
|
||||
register_tokens,
|
||||
token_registry,
|
||||
register_lookups,
|
||||
celery_session_worker,
|
||||
register_tokens,
|
||||
foo_token_symbol,
|
||||
):
|
||||
|
||||
#token = CICRegistry.get_address(default_chain_spec, bancor_tokens[0])
|
||||
foo_token_cache = ERC20Token(default_chain_spec, eth_rpc, foo_token)
|
||||
|
||||
api = Api(str(default_chain_spec), callback_param='transfer', callback_task='cic_eth.callbacks.noop.noop', queue=None)
|
||||
t = api.transfer(custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], 1024, foo_token_cache.symbol)
|
||||
t = api.transfer(custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], 1, foo_token_symbol)
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
|
19
apps/cic-eth/tests/task/api/test_app_noncritical.py
Normal file
19
apps/cic-eth/tests/task/api/test_app_noncritical.py
Normal file
@ -0,0 +1,19 @@
|
||||
# local imports
|
||||
from cic_eth.api.api_task import Api
|
||||
from cic_eth.task import BaseTask
|
||||
|
||||
def test_default_token(
|
||||
default_chain_spec,
|
||||
foo_token,
|
||||
default_token,
|
||||
token_registry,
|
||||
register_tokens,
|
||||
register_lookups,
|
||||
cic_registry,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
api = Api(str(default_chain_spec), queue=None)
|
||||
t = api.default_token()
|
||||
r = t.get_leaf()
|
||||
assert r['address'] == foo_token
|
@ -156,6 +156,7 @@ def test_gift(
|
||||
eth_signer,
|
||||
init_celery_tasks,
|
||||
cic_registry,
|
||||
register_lookups,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
|
88
apps/cic-eth/tests/task/test_task_admin.py
Normal file
88
apps/cic-eth/tests/task/test_task_admin.py
Normal file
@ -0,0 +1,88 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import OverrideNonceOracle
|
||||
from chainqueue.sql.tx import (
|
||||
create as queue_create,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
Gas,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainlib.eth.tx import TxFormat
|
||||
from chainqueue.sql.query import get_nonce_tx_cache
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from hexathon import add_0x
|
||||
|
||||
# local imports
|
||||
from cic_eth.admin.nonce import shift_nonce
|
||||
from cic_eth.eth.gas import cache_gas_data
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_shift_nonce(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
celery_session_worker,
|
||||
caplog,
|
||||
):
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(limit=21000, conn=eth_rpc)
|
||||
|
||||
tx_hashes = []
|
||||
txs = []
|
||||
|
||||
for i in range(10):
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42+i,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_hashes.append(tx_hash_hex)
|
||||
txs.append(tx_signed_raw_hex)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.admin.nonce.shift_nonce',
|
||||
[
|
||||
default_chain_spec.asdict(),
|
||||
tx_hashes[3],
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
init_database.commit()
|
||||
|
||||
|
||||
for i in range(42+3, 42+10):
|
||||
txs = get_nonce_tx_cache(default_chain_spec, i, agent_roles['ALICE'], session=init_database)
|
||||
for k in txs.keys():
|
||||
hsh = add_0x(k)
|
||||
otx = Otx.load(hsh, session=init_database)
|
||||
logg.debug('checking nonce {} tx {} status {}'.format(i, otx.tx_hash, otx.status))
|
||||
if add_0x(k) == tx_hashes[i-42]:
|
||||
assert otx.status & StatusBits.OBSOLETE == StatusBits.OBSOLETE
|
||||
else:
|
||||
assert otx.status == 1
|
286
apps/cic-eth/tests/task/test_task_gas.py
Normal file
286
apps/cic-eth/tests/task/test_task_gas.py
Normal file
@ -0,0 +1,286 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
import pytest
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import (
|
||||
OverrideNonceOracle,
|
||||
RPCNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
OverrideGasOracle,
|
||||
Gas,
|
||||
)
|
||||
from chainlib.eth.tx import (
|
||||
unpack,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.eth.constant import (
|
||||
MINIMUM_FEE_UNITS,
|
||||
MINIMUM_FEE_PRICE,
|
||||
)
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainqueue.sql.query import get_tx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from chainqueue.sql.state import (
|
||||
set_ready,
|
||||
set_reserved,
|
||||
set_sent,
|
||||
)
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from hexathon import strip_0x
|
||||
|
||||
# local imports
|
||||
from cic_eth.eth.gas import cache_gas_data
|
||||
from cic_eth.error import OutOfGasError
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_task_check_gas_ok(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
init_database,
|
||||
agent_roles,
|
||||
custodial_roles,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
0,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.gas.check_gas',
|
||||
[
|
||||
[
|
||||
tx_hash_hex,
|
||||
],
|
||||
default_chain_spec.asdict(),
|
||||
[],
|
||||
None,
|
||||
8000000,
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
init_database.commit()
|
||||
|
||||
tx = get_tx(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
assert tx['status'] & StatusBits.QUEUED == StatusBits.QUEUED
|
||||
|
||||
|
||||
def test_task_check_gas_insufficient(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
init_database,
|
||||
agent_roles,
|
||||
custodial_roles,
|
||||
celery_session_worker,
|
||||
whoever,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = OverrideNonceOracle(whoever, 42)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(whoever, agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
whoever,
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.gas.check_gas',
|
||||
[
|
||||
[
|
||||
tx_hash_hex,
|
||||
],
|
||||
default_chain_spec.asdict(),
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
try:
|
||||
r = t.get_leaf()
|
||||
except OutOfGasError:
|
||||
pass
|
||||
|
||||
init_database.commit()
|
||||
|
||||
tx = get_tx(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
assert tx['status'] & StatusBits.GAS_ISSUES == StatusBits.GAS_ISSUES
|
||||
|
||||
|
||||
def test_task_check_gas_low(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
init_database,
|
||||
agent_roles,
|
||||
custodial_roles,
|
||||
celery_session_worker,
|
||||
whoever,
|
||||
):
|
||||
|
||||
gas_oracle = OverrideGasOracle(price=MINIMUM_FEE_PRICE, limit=MINIMUM_FEE_UNITS)
|
||||
nonce_oracle = RPCNonceOracle(custodial_roles['GAS_GIFTER'], conn=eth_rpc)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, o) = c.create(custodial_roles['GAS_GIFTER'], whoever, 100 * (10 ** 6))
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(whoever, conn=eth_rpc)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(whoever, agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
0,
|
||||
whoever,
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.gas.check_gas',
|
||||
[
|
||||
[
|
||||
tx_hash_hex,
|
||||
],
|
||||
default_chain_spec.asdict(),
|
||||
],
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
init_database.commit()
|
||||
|
||||
tx = get_tx(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
assert tx['status'] & StatusBits.QUEUED == StatusBits.QUEUED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'_gas_price,_gas_factor',
|
||||
[
|
||||
(None, 1.1),
|
||||
(MINIMUM_FEE_PRICE * 1.1, 0.9),
|
||||
(None, 1.3),
|
||||
]
|
||||
)
|
||||
def test_task_resend_explicit(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
agent_roles,
|
||||
custodial_roles,
|
||||
celery_session_worker,
|
||||
_gas_price,
|
||||
_gas_factor,
|
||||
):
|
||||
|
||||
rpc = RPCConnection.connect(default_chain_spec, 'default')
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
0,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
tx_before = unpack(bytes.fromhex(strip_0x(tx_signed_raw_hex)), default_chain_spec)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
set_ready(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_sent(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
[
|
||||
tx_hash_hex,
|
||||
default_chain_spec.asdict(),
|
||||
_gas_price,
|
||||
_gas_factor,
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
q = init_database.query(Otx)
|
||||
q = q.filter(Otx.tx_hash==strip_0x(r))
|
||||
otx = q.first()
|
||||
if otx == None:
|
||||
raise NotLocalTxError(r)
|
||||
|
||||
tx_after = unpack(bytes.fromhex(strip_0x(otx.signed_tx)), default_chain_spec)
|
||||
logg.debug('gasprices before {} after {}'.format(tx_before['gasPrice'], tx_after['gasPrice']))
|
||||
assert tx_after['gasPrice'] > tx_before['gasPrice']
|
||||
|
@ -4,16 +4,27 @@ import logging
|
||||
# external imports
|
||||
import pytest
|
||||
import celery
|
||||
from chainlib.eth.gas import Gas
|
||||
from chainlib.eth.gas import (
|
||||
OverrideGasOracle,
|
||||
Gas,
|
||||
)
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import (
|
||||
TxFormat,
|
||||
unpack,
|
||||
transaction,
|
||||
receipt,
|
||||
raw,
|
||||
)
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
set_ready,
|
||||
set_sent,
|
||||
)
|
||||
from chainqueue.db.enum import StatusBits
|
||||
|
||||
# local imports
|
||||
from cic_eth.queue.tx import register_tx
|
||||
@ -60,15 +71,6 @@ def test_tx_send(
|
||||
|
||||
|
||||
def test_sync_tx(
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
celery_session_worker,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def test_resend_with_higher_gas(
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
eth_rpc,
|
||||
@ -77,31 +79,48 @@ def test_resend_with_higher_gas(
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 1024, tx_format=TxFormat.RLP_SIGNED)
|
||||
register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database)
|
||||
cache_gas_data(tx_hash_hex, tx_signed_raw_hex, default_chain_spec.asdict())
|
||||
tx_before = unpack(bytes.fromhex(strip_0x(tx_signed_raw_hex)), default_chain_spec)
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
session=init_database,
|
||||
)
|
||||
cache_gas_data(
|
||||
tx_hash_hex,
|
||||
tx_signed_raw_hex,
|
||||
default_chain_spec.asdict(),
|
||||
)
|
||||
set_ready(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
set_sent(default_chain_spec, tx_hash_hex, session=init_database)
|
||||
|
||||
o = raw(tx_signed_raw_hex)
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = eth_rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
'cic_eth.eth.tx.sync_tx',
|
||||
[
|
||||
tx_hash_hex,
|
||||
default_chain_spec.asdict(),
|
||||
],
|
||||
queue=None,
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
q = init_database.query(Otx)
|
||||
q = q.filter(Otx.tx_hash==strip_0x(r))
|
||||
otx = q.first()
|
||||
if otx == None:
|
||||
raise NotLocalTxError(r)
|
||||
|
||||
tx_after = unpack(bytes.fromhex(strip_0x(otx.signed_tx)), default_chain_spec)
|
||||
logg.debug('gasprices before {} after {}'.format(tx_before['gasPrice'], tx_after['gasPrice']))
|
||||
assert tx_after['gasPrice'] > tx_before['gasPrice']
|
||||
init_database.commit()
|
||||
|
||||
o = Otx.load(tx_hash_hex, session=init_database)
|
||||
assert o.status & StatusBits.FINAL == StatusBits.FINAL
|
||||
|
170
apps/cic-eth/tests/task/test_task_tx_misc.py
Normal file
170
apps/cic-eth/tests/task/test_task_tx_misc.py
Normal file
@ -0,0 +1,170 @@
|
||||
# standard imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
import celery
|
||||
from chainqueue.sql.tx import create as queue_create
|
||||
from chainlib.eth.nonce import (
|
||||
RPCNonceOracle,
|
||||
OverrideNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
OverrideGasOracle,
|
||||
Gas,
|
||||
)
|
||||
from chainlib.eth.tx import (
|
||||
TxFormat,
|
||||
unpack,
|
||||
receipt,
|
||||
)
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
)
|
||||
from chainqueue.sql.state import (
|
||||
set_reserved,
|
||||
set_ready,
|
||||
)
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_hashes_to_txs(
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
agent_roles,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 42)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex_one, tx_signed_raw_hex_one) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex_one,
|
||||
tx_signed_raw_hex_one,
|
||||
session=init_database,
|
||||
)
|
||||
|
||||
#nonce_oracle = OverrideNonceOracle(agent_roles['ALICE'], 43)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex_two, tx_signed_raw_hex_two) = c.create(agent_roles['ALICE'], agent_roles['CAROL'], 200 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
43,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex_two,
|
||||
tx_signed_raw_hex_two,
|
||||
session=init_database,
|
||||
)
|
||||
|
||||
init_database.commit()
|
||||
|
||||
bogus_one = add_0x(os.urandom(32).hex())
|
||||
bogus_two = add_0x(os.urandom(32).hex())
|
||||
|
||||
yarrgs = [
|
||||
bogus_one,
|
||||
tx_hash_hex_two,
|
||||
bogus_two,
|
||||
tx_hash_hex_one,
|
||||
]
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.tx.hashes_to_txs',
|
||||
[
|
||||
yarrgs,
|
||||
],
|
||||
queue=None,
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
assert len(r) == 2
|
||||
|
||||
logg.debug('r {}'.format(r))
|
||||
txs = [
|
||||
tx_signed_raw_hex_two,
|
||||
tx_signed_raw_hex_one,
|
||||
]
|
||||
for tx in r:
|
||||
txs.remove(add_0x(tx))
|
||||
assert len(txs) == 0
|
||||
|
||||
|
||||
|
||||
def test_double_send(
|
||||
init_database,
|
||||
default_chain_spec,
|
||||
agent_roles,
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], conn=eth_rpc)
|
||||
gas_oracle = OverrideGasOracle(price=1000000000, limit=21000)
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex_one, tx_signed_raw_hex_one) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
42,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex_one,
|
||||
tx_signed_raw_hex_one,
|
||||
session=init_database,
|
||||
)
|
||||
set_ready(default_chain_spec, tx_hash_hex_one, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex_one, session=init_database)
|
||||
|
||||
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex_two, tx_signed_raw_hex_two) = c.create(agent_roles['ALICE'], agent_roles['CAROL'], 200 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
|
||||
|
||||
queue_create(
|
||||
default_chain_spec,
|
||||
43,
|
||||
agent_roles['ALICE'],
|
||||
tx_hash_hex_two,
|
||||
tx_signed_raw_hex_two,
|
||||
session=init_database,
|
||||
)
|
||||
|
||||
set_ready(default_chain_spec, tx_hash_hex_two, session=init_database)
|
||||
set_reserved(default_chain_spec, tx_hash_hex_two, session=init_database)
|
||||
init_database.commit()
|
||||
|
||||
yarrgs = [
|
||||
tx_signed_raw_hex_one,
|
||||
tx_signed_raw_hex_two,
|
||||
]
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.tx.send',
|
||||
[
|
||||
yarrgs,
|
||||
default_chain_spec.asdict(),
|
||||
],
|
||||
queue=None
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get_leaf()
|
||||
assert t.successful()
|
||||
|
||||
o = receipt(tx_hash_hex_one)
|
||||
r = eth_rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
o = receipt(tx_hash_hex_two)
|
||||
r = eth_rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
|
||||
|
1
apps/cic-eth/tests/testdata/Bogus.bin
vendored
Normal file
1
apps/cic-eth/tests/testdata/Bogus.bin
vendored
Normal file
@ -0,0 +1 @@
|
||||
60806040526000805534801561001457600080fd5b50610181806100246000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c0100000000000000000000000000000000000000000000000000000000900480630dbe671f146100585780631817835814610076575b600080fd5b610060610080565b60405161006d91906100ae565b60405180910390f35b61007e610086565b005b60005481565b600080815480929190610098906100d3565b9190505550565b6100a8816100c9565b82525050565b60006020820190506100c3600083018461009f565b92915050565b6000819050919050565b60006100de826100c9565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101115761011061011c565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea264697066735822122034ad8e91e864f030d47f5b93e281869206c1b203c36dc79a209ac9c9c16e577564736f6c63430008040033
|
10
apps/cic-eth/tests/testdata/Bogus.sol
vendored
Normal file
10
apps/cic-eth/tests/testdata/Bogus.sol
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract Bogus {
|
||||
|
||||
uint256 public a = 0;
|
||||
|
||||
function poke() public {
|
||||
a++;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ def test_translate(
|
||||
agent_roles,
|
||||
cic_registry,
|
||||
init_celery_tasks,
|
||||
register_lookups,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc)
|
||||
|
22
apps/cic-eth/tests/unit/test_registry_connect.py
Normal file
22
apps/cic-eth/tests/unit/test_registry_connect.py
Normal file
@ -0,0 +1,22 @@
|
||||
# local imports
|
||||
from cic_eth.registry import *
|
||||
|
||||
def test_registry_connect(
|
||||
eth_rpc,
|
||||
default_chain_spec,
|
||||
address_declarator,
|
||||
token_registry,
|
||||
contract_roles,
|
||||
purge_lookups,
|
||||
registry,
|
||||
agent_roles,
|
||||
):
|
||||
|
||||
r = connect(eth_rpc, default_chain_spec, registry, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
|
||||
connect_declarator(eth_rpc, default_chain_spec, [agent_roles['ALICE']], sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
r.by_name('AddressDeclarator', sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
|
||||
connect_token_registry(eth_rpc, default_chain_spec, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
r.by_name('TokenRegistry', sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
||||
|
18
apps/cic-eth/tests/unit/test_stat.py
Normal file
18
apps/cic-eth/tests/unit/test_stat.py
Normal file
@ -0,0 +1,18 @@
|
||||
# standard imports
|
||||
import datetime
|
||||
|
||||
# local imports
|
||||
from cic_eth.stat import init_chain_stat
|
||||
|
||||
|
||||
def test_chain_stat(
|
||||
eth_rpc,
|
||||
init_eth_tester,
|
||||
):
|
||||
|
||||
now = int(datetime.datetime.now().timestamp()) + 1
|
||||
for i in range(11):
|
||||
init_eth_tester.time_travel(now + (i * 2))
|
||||
|
||||
s = init_chain_stat(eth_rpc, block_start=0)
|
||||
assert s.block_average() == 2
|
@ -31,7 +31,7 @@ function handleNoMergeGet(db, digest, keystore) {
|
||||
doh(e);
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error('mesage', e);
|
||||
console.error('message', e);
|
||||
doh(e);
|
||||
});
|
||||
})
|
||||
@ -46,7 +46,7 @@ function handleServerMergePost(data, db, digest, keystore, signer) {
|
||||
let e = undefined;
|
||||
let s = undefined;
|
||||
if (v === undefined) {
|
||||
s = new Syncable(digest, data);
|
||||
s = new Syncable(digest, o);
|
||||
s.onwrap = (e) => {
|
||||
whohoo(e.toJSON());
|
||||
};
|
||||
|
@ -56,7 +56,7 @@ class AfricasTalkingNotifier:
|
||||
response = self.api_client.send(message=message, recipients=[recipient])
|
||||
logg.debug(f'africastalking response no-sender-id {response}')
|
||||
|
||||
recipients = response.get('Recipients')
|
||||
recipients = response.get('SMSMessageData').get('Recipients')
|
||||
|
||||
if len(recipients) != 1:
|
||||
status = response.get('SMSMessageData').get('Message')
|
||||
|
@ -28,6 +28,7 @@ packages =
|
||||
cic_notify
|
||||
cic_notify.db
|
||||
cic_notify.db.models
|
||||
cic_notify.ext
|
||||
cic_notify.tasks.sms
|
||||
cic_notify.runnable
|
||||
scripts =
|
||||
|
@ -5,6 +5,7 @@ LOCALE_PATH=/usr/src/cic-ussd/var/lib/locale/
|
||||
MAX_BODY_LENGTH=1024
|
||||
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||
SERVICE_CODE=*483*46#,*483*061#,*384*96#
|
||||
SUPPORT_PHONE_NUMBER=0757628885
|
||||
|
||||
[phone_number]
|
||||
REGION=KE
|
||||
|
@ -5,6 +5,7 @@ LOCALE_PATH=var/lib/locale/
|
||||
MAX_BODY_LENGTH=1024
|
||||
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||
SERVICE_CODE=*483*46#
|
||||
SUPPORT_PHONE_NUMBER=0757628885
|
||||
|
||||
[ussd]
|
||||
MENU_FILE=/usr/local/lib/python3.8/site-packages/cic_ussd/db/ussd_menu.json
|
||||
|
4
apps/cic-ussd/.config/test/integration.ini
Normal file
4
apps/cic-ussd/.config/test/integration.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[test]
|
||||
gift_value = 50.00
|
||||
server_url = http://localhost:63315/
|
||||
token_symbol = GFT
|
@ -238,13 +238,43 @@
|
||||
"description": "Menu to display a user's entire profile",
|
||||
"display_key": "ussd.kenya.display_user_metadata",
|
||||
"name": "display_user_metadata",
|
||||
"parent": "account_management"
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"41": {
|
||||
"description": "The recipient is not in the system",
|
||||
"display_key": "ussd.kenya.exit_invalid_recipient",
|
||||
"name": "exit_invalid_recipient",
|
||||
"parent": null
|
||||
},
|
||||
"42": {
|
||||
"description": "Pin entry menu for changing name data.",
|
||||
"display_key": "ussd.kenya.name_edit_pin_authorization",
|
||||
"name": "name_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"43": {
|
||||
"description": "Pin entry menu for changing gender data.",
|
||||
"display_key": "ussd.kenya.gender_edit_pin_authorization",
|
||||
"name": "gender_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"44": {
|
||||
"description": "Pin entry menu for changing location data.",
|
||||
"display_key": "ussd.kenya.location_edit_pin_authorization",
|
||||
"name": "location_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"45": {
|
||||
"description": "Pin entry menu for changing products data.",
|
||||
"display_key": "ussd.kenya.products_edit_pin_authorization",
|
||||
"name": "products_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"46": {
|
||||
"description": "Pin confirmation for pin change.",
|
||||
"display_key": "ussd.kenya.new_pin_confirmation",
|
||||
"name": "new_pin_confirmation",
|
||||
"parent": "metadata_management"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,28 +78,27 @@ class MetadataRequestsHandler(Metadata):
|
||||
:param data: The data to be stored in the metadata server.
|
||||
:type data: dict|str
|
||||
"""
|
||||
data = json.dumps(data).encode('utf-8')
|
||||
data = json.dumps(data)
|
||||
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
|
||||
metadata_http_error_handler(result=result)
|
||||
metadata = result.content
|
||||
metadata = result.json()
|
||||
self.edit(data=metadata)
|
||||
|
||||
def edit(self, data: bytes):
|
||||
def edit(self, data: Union[Dict, str]):
|
||||
""" This function is responsible for editing data in the metadata server corresponding to a unique pointer.
|
||||
:param data: The data to be edited in the metadata server.
|
||||
:type data: bytes
|
||||
:type data: dict
|
||||
"""
|
||||
cic_meta_signer = Signer()
|
||||
signature = cic_meta_signer.sign_digest(data=data)
|
||||
algorithm = cic_meta_signer.get_operational_key().get('algo')
|
||||
decoded_data = data.decode('utf-8')
|
||||
formatted_data = {
|
||||
'm': data.decode('utf-8'),
|
||||
'm': json.dumps(data),
|
||||
's': {
|
||||
'engine': self.engine,
|
||||
'algo': algorithm,
|
||||
'data': signature,
|
||||
'digest': json.loads(data).get('digest'),
|
||||
'digest': data.get('digest'),
|
||||
}
|
||||
}
|
||||
formatted_data = json.dumps(formatted_data)
|
||||
@ -110,19 +109,32 @@ class MetadataRequestsHandler(Metadata):
|
||||
decoded_identifier = self.identifier.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
decoded_identifier = self.identifier.hex()
|
||||
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {decoded_data}.')
|
||||
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {data}.')
|
||||
|
||||
def query(self):
|
||||
"""This function is responsible for querying the metadata server for data corresponding to a unique pointer."""
|
||||
"""
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
# retrieve the metadata
|
||||
result = make_request(method='GET', url=self.url)
|
||||
metadata_http_error_handler(result=result)
|
||||
response_data = result.json()
|
||||
data = json.loads(response_data)
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f'Invalid data object: {data}.')
|
||||
|
||||
# json serialize retrieved data
|
||||
result_data = result.json()
|
||||
|
||||
# validate result data format
|
||||
if not isinstance(result_data, dict):
|
||||
raise ValueError(f'Invalid result data object: {result_data}.')
|
||||
|
||||
if result.status_code == 200 and self.cic_type == ':cic.person':
|
||||
# validate person metadata
|
||||
person = Person()
|
||||
deserialized_person = person.deserialize(person_data=data)
|
||||
data = json.dumps(deserialized_person.serialize())
|
||||
cache_data(self.metadata_pointer, data=data)
|
||||
person_data = person.deserialize(person_data=result_data)
|
||||
|
||||
# format new person data for caching
|
||||
data = json.dumps(person_data.serialize())
|
||||
|
||||
# cache metadata
|
||||
cache_data(key=self.metadata_pointer, data=data)
|
||||
logg.debug(f'caching: {data} with key: {self.metadata_pointer}')
|
||||
|
@ -47,14 +47,13 @@ class Signer:
|
||||
logg.debug(f'using signing key: {key_id}, algorithm: {key_algorithm}')
|
||||
return gpg_keys[0]
|
||||
|
||||
def sign_digest(self, data: bytes):
|
||||
def sign_digest(self, data: dict):
|
||||
"""
|
||||
:param data:
|
||||
:type data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
data = json.loads(data)
|
||||
digest = data['digest']
|
||||
key_id = self.get_operational_key().get('keyid')
|
||||
signature = self.gpg.sign(digest, passphrase=self.gpg_passphrase, keyid=key_id)
|
||||
|
@ -41,3 +41,7 @@ def get_user_by_phone_number(phone_number: str) -> Optional[Account]:
|
||||
phone_number = process_phone_number(phone_number=phone_number, region='KE')
|
||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||
return user
|
||||
|
||||
|
||||
class Support:
|
||||
phone_number = None
|
||||
|
@ -19,7 +19,7 @@ from cic_ussd.db.models.ussd_session import UssdSession
|
||||
from cic_ussd.error import MetadataNotFoundError, SeppukuError
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||
from cic_ussd.phone_number import get_user_by_phone_number
|
||||
from cic_ussd.phone_number import get_user_by_phone_number, Support
|
||||
from cic_ussd.redis import cache_data, create_cached_data_key, get_cached_data
|
||||
from cic_ussd.state_machine import UssdStateMachine
|
||||
from cic_ussd.conversions import to_wei, from_wei
|
||||
@ -251,9 +251,9 @@ def process_display_user_metadata(user: Account, display_key: str):
|
||||
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
|
||||
cic_type=':cic.person'
|
||||
)
|
||||
user_metadata = get_cached_data(key)
|
||||
if user_metadata:
|
||||
user_metadata = json.loads(user_metadata)
|
||||
cached_metadata = get_cached_data(key)
|
||||
if cached_metadata:
|
||||
user_metadata = json.loads(cached_metadata)
|
||||
contact_data = get_contact_data_from_vcard(vcard=user_metadata.get('vcard'))
|
||||
logg.debug(f'{contact_data}')
|
||||
full_name = f'{contact_data.get("given")} {contact_data.get("family")}'
|
||||
@ -270,7 +270,24 @@ def process_display_user_metadata(user: Account, display_key: str):
|
||||
products=products
|
||||
)
|
||||
else:
|
||||
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
|
||||
# TODO [Philip]: All these translations could be moved to translation files.
|
||||
logg.warning(f'Expected person metadata but found none in cache for key: {key}')
|
||||
|
||||
absent = ''
|
||||
if user.preferred_language == 'en':
|
||||
absent = 'Not provided'
|
||||
elif user.preferred_language == 'sw':
|
||||
absent = 'Haijawekwa'
|
||||
|
||||
return translation_for(
|
||||
key=display_key,
|
||||
preferred_language=user.preferred_language,
|
||||
full_name=absent,
|
||||
gender=absent,
|
||||
location=absent,
|
||||
products=absent
|
||||
)
|
||||
|
||||
|
||||
|
||||
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
|
||||
@ -433,7 +450,8 @@ def process_request(user_input: str, user: Account, ussd_session: Optional[dict]
|
||||
'exit_invalid_pin',
|
||||
'exit_invalid_new_pin',
|
||||
'exit_pin_mismatch',
|
||||
'exit_invalid_request'
|
||||
'exit_invalid_request',
|
||||
'exit_successful_transaction'
|
||||
] and person_metadata is not None:
|
||||
return UssdMenu.find_by_name(name='start')
|
||||
else:
|
||||
@ -466,6 +484,14 @@ def next_state(ussd_session: dict, user: Account, user_input: str) -> str:
|
||||
return new_state
|
||||
|
||||
|
||||
def process_exit_invalid_menu_option(display_key: str, preferred_language: str):
|
||||
return translation_for(
|
||||
key=display_key,
|
||||
preferred_language=preferred_language,
|
||||
support_phone=Support.phone_number
|
||||
)
|
||||
|
||||
|
||||
def custom_display_text(
|
||||
display_key: str,
|
||||
menu_name: str,
|
||||
@ -502,5 +528,7 @@ def custom_display_text(
|
||||
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
|
||||
elif menu_name == 'display_user_metadata':
|
||||
return process_display_user_metadata(display_key=display_key, user=user)
|
||||
elif menu_name == 'exit_invalid_menu_option':
|
||||
return process_exit_invalid_menu_option(display_key=display_key, preferred_language=user.preferred_language)
|
||||
else:
|
||||
return translation_for(key=display_key, preferred_language=user.preferred_language)
|
||||
|
@ -26,7 +26,7 @@ from cic_ussd.metadata.base import Metadata
|
||||
from cic_ussd.operations import (define_response_with_content,
|
||||
process_menu_interaction_requests,
|
||||
define_multilingual_responses)
|
||||
from cic_ussd.phone_number import process_phone_number
|
||||
from cic_ussd.phone_number import process_phone_number, Support
|
||||
from cic_ussd.processor import get_default_token_data
|
||||
from cic_ussd.redis import cache_data, create_cached_data_key, InMemoryStore
|
||||
from cic_ussd.requests import (get_request_endpoint,
|
||||
@ -126,6 +126,8 @@ else:
|
||||
|
||||
valid_service_codes = config.get('APP_SERVICE_CODE').split(",")
|
||||
|
||||
Support.phone_number = config.get('APP_SUPPORT_PHONE_NUMBER')
|
||||
|
||||
|
||||
def application(env, start_response):
|
||||
"""Loads python code for application to be accessible over web server
|
||||
|
@ -13,7 +13,7 @@ import bcrypt
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.account import AccountStatus, Account
|
||||
from cic_ussd.encoder import PasswordEncoder, create_password_hash
|
||||
from cic_ussd.encoder import PasswordEncoder, create_password_hash, check_password_hash
|
||||
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
|
||||
from cic_ussd.redis import InMemoryStore
|
||||
|
||||
@ -78,9 +78,13 @@ def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Accoun
|
||||
|
||||
# set initial pin data
|
||||
initial_pin = create_password_hash(user_input)
|
||||
session_data = {
|
||||
'initial_pin': initial_pin
|
||||
}
|
||||
if ussd_session.get('session_data'):
|
||||
session_data = ussd_session.get('session_data')
|
||||
session_data['initial_pin'] = initial_pin
|
||||
else:
|
||||
session_data = {
|
||||
'initial_pin': initial_pin
|
||||
}
|
||||
|
||||
# create new in memory ussd session with current ussd session data
|
||||
create_or_update_session(
|
||||
@ -103,9 +107,8 @@ def pins_match(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
initial_pin = ussd_session.get('session_data').get('initial_pin')
|
||||
fernet = PasswordEncoder(PasswordEncoder.key)
|
||||
initial_pin = fernet.decrypt(initial_pin.encode())
|
||||
return bcrypt.checkpw(user_input.encode(), initial_pin)
|
||||
logg.debug(f'USSD SESSION: {ussd_session}')
|
||||
return check_password_hash(user_input, initial_pin)
|
||||
|
||||
|
||||
def complete_pin_change(state_machine_data: Tuple[str, dict, Account]):
|
||||
|
@ -11,7 +11,7 @@ from cic_ussd.balance import BalanceManager, compute_operational_balance
|
||||
from cic_ussd.chain import Chain
|
||||
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.phone_number import get_user_by_phone_number, process_phone_number
|
||||
from cic_ussd.processor import retrieve_token_symbol
|
||||
from cic_ussd.redis import create_cached_data_key, get_cached_data
|
||||
from cic_ussd.transactions import OutgoingTransactionProcessor
|
||||
@ -30,7 +30,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||
"""
|
||||
user_input, ussd_session, user = state_machine_data
|
||||
recipient = get_user_by_phone_number(phone_number=user_input)
|
||||
is_not_initiator = user_input != user.phone_number
|
||||
is_not_initiator = process_phone_number(user_input, 'KE') != user.phone_number
|
||||
has_active_account_status = user.get_account_status() == AccountStatus.ACTIVE.name
|
||||
return is_not_initiator and has_active_account_status and recipient is not None
|
||||
|
||||
|
@ -64,13 +64,17 @@ def process_gender_user_input(user: Account, user_input: str):
|
||||
if user.preferred_language == 'en':
|
||||
if user_input == '1':
|
||||
gender = 'Male'
|
||||
else:
|
||||
elif user_input == '2':
|
||||
gender = 'Female'
|
||||
elif user_input == '3':
|
||||
gender = 'Other'
|
||||
else:
|
||||
if user_input == '1':
|
||||
gender = 'Mwanaume'
|
||||
else:
|
||||
elif user_input == '2':
|
||||
gender = 'Mwanamke'
|
||||
elif user_input == '3':
|
||||
gender = 'Nyingine'
|
||||
return gender
|
||||
|
||||
|
||||
@ -88,14 +92,18 @@ def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict,
|
||||
key = ''
|
||||
if 'given_name' in current_state:
|
||||
key = 'given_name'
|
||||
elif 'family_name' in current_state:
|
||||
|
||||
if 'family_name' in current_state:
|
||||
key = 'family_name'
|
||||
elif 'gender' in current_state:
|
||||
|
||||
if 'gender' in current_state:
|
||||
key = 'gender'
|
||||
user_input = process_gender_user_input(user=user, user_input=user_input)
|
||||
elif 'location' in current_state:
|
||||
|
||||
if 'location' in current_state:
|
||||
key = 'location'
|
||||
elif 'products' in current_state:
|
||||
|
||||
if 'products' in current_state:
|
||||
key = 'products'
|
||||
|
||||
# check if there is existing session data
|
||||
@ -121,12 +129,20 @@ def format_user_metadata(metadata: dict, user: Account):
|
||||
gender = metadata.get('gender')
|
||||
given_name = metadata.get('given_name')
|
||||
family_name = metadata.get('family_name')
|
||||
location = {
|
||||
"area_name": metadata.get('location')
|
||||
}
|
||||
products = []
|
||||
if metadata.get('products'):
|
||||
|
||||
# check whether there's existing location data
|
||||
if isinstance(metadata.get('location'), dict):
|
||||
location = metadata.get('location')
|
||||
else:
|
||||
location = {
|
||||
"area_name": metadata.get('location')
|
||||
}
|
||||
# check whether it is a list
|
||||
if isinstance(metadata.get('products'), list):
|
||||
products = metadata.get('products')
|
||||
else:
|
||||
products = metadata.get('products').split(',')
|
||||
|
||||
phone_number = user.phone_number
|
||||
date_registered = int(user.created.replace().timestamp())
|
||||
blockchain_address = user.blockchain_address
|
||||
@ -192,28 +208,27 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
|
||||
# validate user metadata
|
||||
person = Person()
|
||||
user_metadata = json.loads(user_metadata)
|
||||
deserialized_person = person.deserialize(person_data=user_metadata)
|
||||
|
||||
# edit specific metadata attribute
|
||||
if given_name:
|
||||
deserialized_person.given_name = given_name
|
||||
elif family_name:
|
||||
deserialized_person.family_name = family_name
|
||||
elif gender:
|
||||
deserialized_person.gender = gender
|
||||
elif location:
|
||||
user_metadata['given_name'] = given_name
|
||||
if family_name:
|
||||
user_metadata['family_name'] = family_name
|
||||
if gender:
|
||||
user_metadata['gender'] = gender
|
||||
if location:
|
||||
# get existing location metadata:
|
||||
location_data = user_metadata.get('location')
|
||||
location_data['area_name'] = location
|
||||
deserialized_person.location = location_data
|
||||
elif products:
|
||||
deserialized_person.products = products
|
||||
user_metadata['location'] = location_data
|
||||
if products:
|
||||
user_metadata['products'] = products
|
||||
|
||||
edited_metadata = deserialized_person.serialize()
|
||||
user_metadata = format_user_metadata(metadata=user_metadata, user=user)
|
||||
|
||||
s_edit_person_metadata = celery.signature(
|
||||
'cic_ussd.tasks.metadata.edit_person_metadata',
|
||||
[blockchain_address, edited_metadata]
|
||||
'cic_ussd.tasks.metadata.create_person_metadata',
|
||||
[blockchain_address, user_metadata]
|
||||
)
|
||||
s_edit_person_metadata.apply_async(queue='cic-ussd')
|
||||
|
||||
|
@ -19,4 +19,6 @@ def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs
|
||||
"""
|
||||
if preferred_language:
|
||||
i18n.set('locale', preferred_language)
|
||||
else:
|
||||
i18n.set('locale', i18n.config.get('fallback'))
|
||||
return i18n.t(key, **kwargs)
|
||||
|
@ -1,4 +1,4 @@
|
||||
cic_base[full_graph]~=0.1.2b2
|
||||
cic-eth~=0.11.0b9
|
||||
cic-notify~=0.4.0a4
|
||||
cic_base[full_graph]~=0.1.2b15
|
||||
cic-eth~=0.11.0b16
|
||||
cic-notify~=0.4.0a5
|
||||
cic-types~=0.1.0a10
|
||||
|
@ -6,7 +6,10 @@
|
||||
"enter_new_pin",
|
||||
"new_pin_confirmation",
|
||||
"display_user_metadata",
|
||||
"standard_pin_authorization",
|
||||
"name_edit_pin_authorization",
|
||||
"gender_edit_pin_authorization",
|
||||
"location_edit_pin_authorization",
|
||||
"products_edit_pin_authorization",
|
||||
"account_balances_pin_authorization",
|
||||
"account_statement_pin_authorization",
|
||||
"account_balances"
|
||||
|
@ -1,7 +1,11 @@
|
||||
pytest==6.0.1
|
||||
Faker==8.1.2
|
||||
faker-e164==0.1.0
|
||||
pytest==6.2.4
|
||||
pytest-alembic==0.2.5
|
||||
pytest-celery==0.0.0a1
|
||||
pytest-cov==2.10.1
|
||||
pytest-mock==3.3.1
|
||||
pytest-ordering==0.6
|
||||
pytest-redis==2.0.0
|
||||
requests-mock==1.8.0
|
||||
requests-mock==1.8.0
|
||||
tavern==1.14.2
|
@ -6,6 +6,7 @@ from cic_types.pytest import *
|
||||
from tests.fixtures.config import *
|
||||
from tests.fixtures.db import *
|
||||
from tests.fixtures.celery import *
|
||||
from tests.fixtures.integration import *
|
||||
from tests.fixtures.user import *
|
||||
from tests.fixtures.ussd_session import *
|
||||
from tests.fixtures.redis import *
|
||||
|
249
apps/cic-ussd/tests/fixtures/integration.py
vendored
Normal file
249
apps/cic-ussd/tests/fixtures/integration.py
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
|
||||
import pytest
|
||||
from faker import Faker
|
||||
|
||||
# local imports
|
||||
|
||||
# test imports
|
||||
from tests.helpers.accounts import phone_number, pin_number, session_id
|
||||
|
||||
|
||||
fake = Faker()
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def generate_phone_number() -> str:
|
||||
return phone_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def generate_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_phone_number() -> str:
|
||||
return phone_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_phone_number() -> str:
|
||||
return phone_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_pin_number() -> str:
|
||||
return pin_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_pin_number() -> str:
|
||||
return pin_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_metadata_entry_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_metadata_entry_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_transaction_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_transaction_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_given_name() -> str:
|
||||
return fake.first_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_given_name() -> str:
|
||||
return fake.first_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_family_name() -> str:
|
||||
return fake.last_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_family_name() -> str:
|
||||
return fake.last_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_location() -> str:
|
||||
return fake.city()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_location() -> str:
|
||||
return fake.city()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_product() -> str:
|
||||
return fake.color_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_product() -> str:
|
||||
return fake.color_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_verify_balance_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_verify_balance_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_profile_management_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_profile_management_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_change_given_name() -> str:
|
||||
return fake.first_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_change_given_name() -> str:
|
||||
return fake.first_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_change_family_name() -> str:
|
||||
return fake.last_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_change_family_name() -> str:
|
||||
return fake.last_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_change_location() -> str:
|
||||
return fake.city()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_change_location() -> str:
|
||||
return fake.city()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_change_product() -> str:
|
||||
return fake.color_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_change_product() -> str:
|
||||
return fake.color_name()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_profile_management_session_id_1() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_profile_management_session_id_1() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_profile_management_session_id_2() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_profile_management_session_id_2() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_profile_management_session_id_3() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_profile_management_session_id_3() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_profile_management_session_id_4() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_profile_management_session_id_4() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_management_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_management_session_id() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_management_session_id_1() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_management_session_id_1() -> str:
|
||||
return session_id()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def first_account_new_pin_number() -> str:
|
||||
return pin_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def second_account_new_pin_number() -> str:
|
||||
return pin_number()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def gift_value(load_config):
|
||||
return load_config.get('TEST_GIFT_VALUE')
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def server_url(load_config):
|
||||
return load_config.get('TEST_SERVER_URL')
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def token_symbol(load_config):
|
||||
return load_config.get('TEST_TOKEN_SYMBOL')
|
26
apps/cic-ussd/tests/helpers/accounts.py
Normal file
26
apps/cic-ussd/tests/helpers/accounts.py
Normal file
@ -0,0 +1,26 @@
|
||||
# standard imports
|
||||
import random
|
||||
import uuid
|
||||
|
||||
# external imports
|
||||
from faker import Faker
|
||||
from faker_e164.providers import E164Provider
|
||||
|
||||
# local imports
|
||||
|
||||
# test imports
|
||||
|
||||
fake = Faker()
|
||||
fake.add_provider(E164Provider)
|
||||
|
||||
|
||||
def phone_number() -> str:
|
||||
return fake.e164('KE')
|
||||
|
||||
|
||||
def session_id() -> str:
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
def pin_number() -> int:
|
||||
return random.randint(1000, 9999)
|
11
apps/cic-ussd/tests/integration/ext/validator.py
Normal file
11
apps/cic-ussd/tests/integration/ext/validator.py
Normal file
@ -0,0 +1,11 @@
|
||||
import logging
|
||||
|
||||
|
||||
logg = logging.getLogger()
|
||||
logg.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def validate_response(response, expected_response):
|
||||
"""Makes sure that the response received matches the expected response"""
|
||||
logg.debug(f'RESPONSE: {response.content.decode("utf-8")}')
|
||||
assert response.content.decode('utf-8') == expected_response
|
2
apps/cic-ussd/tests/integration/run.sh
Normal file
2
apps/cic-ussd/tests/integration/run.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
PYTHONPATH=. py.test --debug -vv --log-level debug -s --log-cli-level debug
|
@ -0,0 +1,466 @@
|
||||
test_name: Test the creation of accounts through the cic_user_ussd_server entrypoint.
|
||||
marks:
|
||||
- usefixtures:
|
||||
- gift_value
|
||||
- server_url
|
||||
- token_symbol
|
||||
- generate_session_id
|
||||
- first_account_phone_number
|
||||
- second_account_phone_number
|
||||
- first_account_pin_number
|
||||
- second_account_pin_number
|
||||
- first_account_family_name
|
||||
- second_account_family_name
|
||||
- first_account_given_name
|
||||
- second_account_given_name
|
||||
- first_account_location
|
||||
- second_account_location
|
||||
- first_account_product
|
||||
- second_account_product
|
||||
- first_metadata_entry_session_id
|
||||
- second_metadata_entry_session_id
|
||||
- first
|
||||
|
||||
stages:
|
||||
- name: Initiate account creation process [first account].
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{generate_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '175'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n"
|
||||
|
||||
- name: Initiate account creation process [second account].
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{generate_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '175'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n"
|
||||
delay_after: 5
|
||||
|
||||
- name: Initaite account metadata entry [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '61'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Welcome to Sarafu Network\n1. English\n2. Kiswahili\n3. Help"
|
||||
|
||||
- name: Initaite account metadata entry [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '61'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Welcome to Sarafu Network\n1. English\n2. Kiswahili\n3. Help"
|
||||
|
||||
- name: Select preferred language [English]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '64'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Please enter a new four number PIN for your account.\n0. Back"
|
||||
|
||||
- name: Select preferred language [Kiswahili]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '71'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Tafadhali weka pin mpya yenye nambari nne kwa akaunti yako\n0. Nyuma"
|
||||
|
||||
- name: Enter pin number [{first_account_pin_number} - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '44'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter your four number PIN again\n0. Back"
|
||||
|
||||
- name: Enter pin number [second_account_pin_number - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '31'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka PIN yako tena\n0. Nyuma"
|
||||
|
||||
- name: Pin number confirmation [first_account_pin_number - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '28'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter first name\n0. Back"
|
||||
|
||||
- name: Pin number confirmation [{second_account_pin_number} - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '37'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka jina lako la kwanza\n0. Nyuma"
|
||||
|
||||
- name: Enter first name [first_account_given_name - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '29'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter family name\n0. Back"
|
||||
|
||||
- name: Enter first name [second_account_given_name - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '37'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka jina lako la mwisho\n0. Nyuma"
|
||||
|
||||
- name: Enter last name [first_account_family_name - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter gender\n1. Male\n2. Female\n3. Other\n0. Back"
|
||||
|
||||
- name: Enter last name [second_account_family_name - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '64'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka jinsia yako\n1. Mwanaume\n2. Mwanamke\n3. Nyngine\n0. Nyuma"
|
||||
|
||||
- name: Select gender [Male - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '31'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter your location\n0. Back"
|
||||
|
||||
- name: Select gender [Female - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '27'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka eneo lako\n0. Nyuma"
|
||||
|
||||
- name: Enter location [first_account_location - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1*{first_account_location}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '55'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Please enter a product or service you offer\n0. Back"
|
||||
|
||||
- name: Enter location [second_account_location - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2*{second_account_location}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '42'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka bidhaa ama huduma unauza\n0. Nyuma"
|
||||
|
||||
- name: Enter product [first_account_product - first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_metadata_entry_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1*{first_account_location}*{first_account_product}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Balance {gift_value} {token_symbol}\n1. Send\n2. My Account\n3. Help"
|
||||
delay_before: 10
|
||||
|
||||
- name: Enter product [second_account_product - second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_metadata_entry_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2*{second_account_location}*{second_account_product}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '56'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio {gift_value} {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
|
||||
delay_before: 10
|
@ -0,0 +1,587 @@
|
||||
test_name: Test performing account management operations.
|
||||
marks:
|
||||
- usefixtures:
|
||||
- server_url
|
||||
- token_symbol
|
||||
- first_account_pin_number
|
||||
- second_account_pin_number
|
||||
- first_account_phone_number
|
||||
- second_account_phone_number
|
||||
- first_account_management_session_id
|
||||
- second_account_management_session_id
|
||||
- first_account_management_session_id_1
|
||||
- second_account_management_session_id_1
|
||||
- first_account_new_pin_number
|
||||
- second_account_new_pin_number
|
||||
- fourth
|
||||
|
||||
stages:
|
||||
- name: Account management start menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Balance 58.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
|
||||
|
||||
- name: Account management start menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '56'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio 42.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
|
||||
|
||||
- name: Account management menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '105'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
|
||||
|
||||
- name: Account management menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '148'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
|
||||
|
||||
- name: Language change [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Choose language\n1. English\n2. Kiswahili\n0. Back"
|
||||
|
||||
- name: Language change [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Chagua lugha\n1. Kingereza\n2. Kiswahili\n0. Nyuma"
|
||||
|
||||
- name: Select language [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*2*2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '30'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "END Asante kwa kutumia huduma."
|
||||
|
||||
- name: Select language [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*2*1"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '36'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "END Thank you for using the service."
|
||||
|
||||
- name: Second account management start menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '56'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio 58.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
|
||||
|
||||
- name: Second account management start menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Balance 42.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
|
||||
|
||||
- name: Second account management menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '148'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
|
||||
|
||||
- name: Second account management menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '105'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
|
||||
|
||||
- name: Check balance [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '49'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Tafadhali weka PIN yako kuona salio.\n0. Nyuma"
|
||||
|
||||
- name: Check balance [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '50'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Please enter your PIN to view balances\n0. Back"
|
||||
|
||||
- name: Display balances [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio zako ni zifuatazo:\n salio: 58.00 {token_symbol}\n ushuru: {token_symbol}\n tuzo: {token_symbol}\n0. Nyuma"
|
||||
|
||||
- name: Display balances [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Your balances are as follows:\n balance: 42.00 {token_symbol}\n fees: {token_symbol}\n rewards: {token_symbol}\n0. Back"
|
||||
|
||||
- name: Resume account management menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}*0"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '148'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
|
||||
|
||||
- name: Resume account management menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}*0"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '105'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
|
||||
|
||||
- name: Change pin number [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}*0*5"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '34'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka nambari ya siri.\n0. Nyuma"
|
||||
|
||||
- name: Change pin number [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}*0*5"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '30'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter current PIN.\n0. Back"
|
||||
|
||||
- name: Enter old pin [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '38'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka nambari ya siri mpya\n0. Nyuma"
|
||||
|
||||
- name: Enter old pin [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '42'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter your new four number PIN\n0. Back"
|
||||
|
||||
- name: Enter new pin [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}*{first_account_new_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '31'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka PIN yako tena\n0. Nyuma"
|
||||
|
||||
- name: Enter new pin [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}*{second_account_new_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '48'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter your new four number PIN again\n0. Back"
|
||||
|
||||
- name: Enter new pin confirmation [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_management_session_id_1}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}*{first_account_new_pin_number}*{first_account_new_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '91'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi.\n00. Nyuma\n99. Ondoka"
|
||||
|
||||
- name: Enter new pin confirmation [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_management_session_id_1}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}*{second_account_new_pin_number}*{second_account_new_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '82'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Your request has been sent. You will receive an SMS shortly.\n00. Back\n99. Exit"
|
1573
apps/cic-ussd/tests/integration/test_profile_management.tavern.yaml
Normal file
1573
apps/cic-ussd/tests/integration/test_profile_management.tavern.yaml
Normal file
File diff suppressed because it is too large
Load Diff
282
apps/cic-ussd/tests/integration/test_transactions.tavern.yaml
Normal file
282
apps/cic-ussd/tests/integration/test_transactions.tavern.yaml
Normal file
@ -0,0 +1,282 @@
|
||||
test_name: Test that the two test accounts can trade with each other.
|
||||
marks:
|
||||
- usefixtures:
|
||||
- gift_value
|
||||
- server_url
|
||||
- token_symbol
|
||||
- first_account_family_name
|
||||
- second_account_family_name
|
||||
- first_account_given_name
|
||||
- second_account_given_name
|
||||
- first_account_phone_number
|
||||
- second_account_phone_number
|
||||
- first_account_pin_number
|
||||
- second_account_pin_number
|
||||
- first_transaction_session_id
|
||||
- second_transaction_session_id
|
||||
- first_account_verify_balance_session_id
|
||||
- second_account_verify_balance_session_id
|
||||
- second
|
||||
|
||||
stages:
|
||||
- name: Transactions start menu [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_transaction_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Balance {gift_value} {token_symbol}\n1. Send\n2. My Account\n3. Help"
|
||||
|
||||
- name: Transactions start menu [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_transaction_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '56'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio {gift_value} {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
|
||||
|
||||
- name: Initate transcation attempt [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_transaction_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '30'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter phone number\n0. Back"
|
||||
|
||||
- name: Initate transcation attempt [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_transaction_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "1"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '33'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka nambari ya simu\n0. Nyuma"
|
||||
|
||||
- name: Enter phone number [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_transaction_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{second_account_phone_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '24'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Enter amount\n0. Back"
|
||||
|
||||
- name: Enter phone number [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_transaction_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "1*{first_account_phone_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '25'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Weka kiwango\n0. Nyuma"
|
||||
|
||||
- name: Enter transcation amount [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_transaction_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{second_account_phone_number}*17"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON {second_account_given_name} {second_account_family_name} {second_account_phone_number} will receive 17.00 {token_symbol} from {first_account_given_name} {first_account_family_name} {first_account_phone_number}.\nPlease enter your PIN to confirm.\n0. Back"
|
||||
|
||||
- name: Enter transcation amount [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_transaction_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "1*{first_account_phone_number}*25"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON {first_account_given_name} {first_account_family_name} {first_account_phone_number} atapokea 25.00 {token_symbol} kutoka kwa {second_account_given_name} {second_account_family_name} {second_account_phone_number}.\nTafadhali weka nambari yako ya siri kudhibitisha.\n0. Nyuma"
|
||||
|
||||
- name: Pin to authorize transaction [first account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_transaction_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: "1*{second_account_phone_number}*17*{first_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Your request has been sent. {second_account_given_name} {second_account_family_name} {second_account_phone_number} will receive 17.00 {token_symbol} from {first_account_given_name} {first_account_family_name} {first_account_phone_number}.\n00. Back\n99. Exit"
|
||||
|
||||
- name: Pin to authorize transaction [second account]
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_transaction_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: "1*{first_account_phone_number}*25*{second_account_pin_number}"
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Ombi lako limetumwa. {first_account_given_name} {first_account_family_name} {first_account_phone_number} atapokea 25.00 {token_symbol} kutoka kwa {second_account_given_name} {second_account_family_name} {second_account_phone_number}.\n00. Nyuma\n99. Ondoka"
|
||||
|
||||
- name: Verify balance changes [first account]
|
||||
delay_before: 10
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{first_account_verify_balance_session_id}"
|
||||
phoneNumber: "{first_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '51'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Balance 58.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
|
||||
|
||||
- name: Verify balance changes [second account]
|
||||
delay_before: 10
|
||||
request:
|
||||
url: "{server_url}"
|
||||
data:
|
||||
serviceCode: "*483*46#"
|
||||
sessionId: "{second_account_verify_balance_session_id}"
|
||||
phoneNumber: "{second_account_phone_number}"
|
||||
text: ""
|
||||
headers:
|
||||
content-type: "application/x-www-form-urlencoded"
|
||||
method: POST
|
||||
response:
|
||||
status_code:
|
||||
- 200
|
||||
headers:
|
||||
Content-Length: '56'
|
||||
Content-Type: "text/plain"
|
||||
verify_response_with:
|
||||
function: ext.validator:validate_response
|
||||
extra_kwargs:
|
||||
expected_response: "CON Salio 42.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
|
@ -4,12 +4,13 @@
|
||||
"source": "enter_gender",
|
||||
"dest": "enter_location",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.is_valid_gender_selection"
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.is_valid_gender_selection",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_gender",
|
||||
"dest": "standard_pin_authorization",
|
||||
"dest": "gender_edit_pin_authorization",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"conditions": [
|
||||
"cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
|
||||
@ -18,15 +19,14 @@
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "gender_edit_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "gender_edit_pin_authorization",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
|
||||
},
|
||||
|
@ -3,26 +3,26 @@
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_location",
|
||||
"dest": "enter_products",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_location",
|
||||
"dest": "standard_pin_authorization",
|
||||
"dest": "location_edit_pin_authorization",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "location_edit_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "location_edit_pin_authorization",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
|
||||
}
|
||||
|
@ -7,49 +7,28 @@
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_given_name",
|
||||
"dest": "standard_pin_authorization",
|
||||
"source": "enter_family_name",
|
||||
"dest": "name_edit_pin_authorization",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_family_name",
|
||||
"dest": "enter_gender",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_family_name",
|
||||
"dest": "standard_pin_authorization",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "name_edit_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "name_edit_pin_authorization",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
|
||||
}
|
||||
|
@ -9,14 +9,14 @@
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_current_pin",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.menu.is_blocked_pin"
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_blocked_pin"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_new_pin",
|
||||
"dest": "new_pin_confirmation",
|
||||
"after": "cic_ussd.state_machine.logic.pin.save_initial_pin_to_session_data",
|
||||
"conditions": "cic_ussd.state_machine.logic.menu.is_valid_new_pin"
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_valid_new_pin"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
@ -28,7 +28,7 @@
|
||||
"source": "new_pin_confirmation",
|
||||
"dest": "complete",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
|
||||
"after": "cic_ussd.state_machine.logic.menu.complete_pin_change"
|
||||
"after": "cic_ussd.state_machine.logic.pin.complete_pin_change"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "enter_products",
|
||||
"dest": "standard_pin_authorization",
|
||||
"dest": "products_edit_pin_authorization",
|
||||
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
|
||||
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
|
||||
},
|
||||
@ -13,18 +13,19 @@
|
||||
"after": [
|
||||
"cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
|
||||
"cic_ussd.state_machine.logic.user.save_complete_user_metadata"
|
||||
]
|
||||
],
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "products_edit_pin_authorization",
|
||||
"dest": "exit",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
|
||||
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "standard_pin_authorization",
|
||||
"source": "products_edit_pin_authorization",
|
||||
"dest": "exit_pin_blocked",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
|
||||
}
|
||||
|
@ -36,26 +36,12 @@
|
||||
"source": "initial_pin_entry",
|
||||
"dest": "exit_invalid_pin"
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "initial_pin_confirmation",
|
||||
"dest": "start",
|
||||
"conditions": [
|
||||
"cic_ussd.state_machine.logic.pin.pins_match",
|
||||
"cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
||||
],
|
||||
"after": [
|
||||
"cic_ussd.state_machine.logic.pin.complete_pin_change",
|
||||
"cic_ussd.state_machine.logic.user.get_user_metadata",
|
||||
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
|
||||
]
|
||||
},
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "initial_pin_confirmation",
|
||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
|
||||
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
|
||||
"dest": "enter_given_name",
|
||||
"dest": "start",
|
||||
"after": [
|
||||
"cic_ussd.state_machine.logic.pin.complete_pin_change",
|
||||
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
|
||||
|
@ -8,7 +8,7 @@
|
||||
{
|
||||
"trigger": "scan_data",
|
||||
"source": "metadata_management",
|
||||
"dest": "enter_age",
|
||||
"dest": "enter_gender",
|
||||
"conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected"
|
||||
},
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ en:
|
||||
2. Kiswahili
|
||||
3. Help
|
||||
initial_pin_entry: |-
|
||||
CON Please enter a new four number PIN for your account.
|
||||
CON Please enter a new four number PIN for your account.
|
||||
0. Back
|
||||
initial_pin_confirmation: |-
|
||||
CON Enter your four number PIN again
|
||||
@ -76,7 +76,10 @@ en:
|
||||
CON Enter current PIN. You have %{remaining_attempts} attempts remaining.
|
||||
0. Back
|
||||
enter_new_pin: |-
|
||||
CON Enter new PIN again
|
||||
CON Enter your new four number PIN
|
||||
0. Back
|
||||
new_pin_confirmation: |-
|
||||
CON Enter your new four number PIN again
|
||||
0. Back
|
||||
transaction_pin_authorization:
|
||||
first: |-
|
||||
@ -107,6 +110,34 @@ en:
|
||||
retry: |-
|
||||
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
|
||||
0. Back
|
||||
name_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Please enter your PIN
|
||||
0. Back
|
||||
retry: |-
|
||||
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
|
||||
0. Back
|
||||
gender_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Please enter your PIN
|
||||
0. Back
|
||||
retry: |-
|
||||
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
|
||||
0. Back
|
||||
location_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Please enter your PIN
|
||||
0. Back
|
||||
retry: |-
|
||||
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
|
||||
0. Back
|
||||
products_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Please enter your PIN
|
||||
0. Back
|
||||
retry: |-
|
||||
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
|
||||
0. Back
|
||||
account_balances: |-
|
||||
CON Your balances are as follows:
|
||||
balance: %{operational_balance} %{token_symbol}
|
||||
|
@ -21,7 +21,7 @@ sw:
|
||||
CON Weka jinsia yako
|
||||
1. Mwanaume
|
||||
2. Mwanamke
|
||||
3. Nyngine
|
||||
3. Nyngine
|
||||
0. Nyuma
|
||||
enter_location: |-
|
||||
CON Weka eneo lako
|
||||
@ -61,7 +61,7 @@ sw:
|
||||
Jina: %{full_name}
|
||||
Jinsia: %{gender}
|
||||
Eneo: %{location}
|
||||
Unauza: %{user_bio}
|
||||
Unauza: %{products}
|
||||
0. Nyuma
|
||||
select_preferred_language: |-
|
||||
CON Chagua lugha
|
||||
@ -78,6 +78,9 @@ sw:
|
||||
enter_new_pin: |-
|
||||
CON Weka nambari ya siri mpya
|
||||
0. Nyuma
|
||||
new_pin_confirmation: |-
|
||||
CON Weka PIN yako tena
|
||||
0. Nyuma
|
||||
transaction_pin_authorization:
|
||||
first: |-
|
||||
CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||
@ -86,9 +89,9 @@ sw:
|
||||
retry: |-
|
||||
CON Weka nambari ya siri. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
standard_pin_authorization:
|
||||
display_metadata_pin_authorization:
|
||||
first: |-
|
||||
CON Tafadhali weka PIN yako.
|
||||
CON Tafadhali weka PIN yako
|
||||
0. Nyuma
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
@ -107,12 +110,40 @@ sw:
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
name_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Tafadhali weka PIN yako
|
||||
0. Nyuma
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
gender_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Tafadhali weka PIN yako
|
||||
0. Nyuma
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
location_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Tafadhali weka PIN yako
|
||||
0. Nyuma
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
products_edit_pin_authorization:
|
||||
first: |-
|
||||
CON Tafadhali weka PIN yako
|
||||
0. Nyuma
|
||||
retry: |-
|
||||
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Nyuma
|
||||
account_balances: |-
|
||||
CON Salio zako ni zifuatazo:
|
||||
salio: %{operational_balance}
|
||||
ushuru: %{tax}
|
||||
tuzo: %{bonus}
|
||||
0. Back
|
||||
salio: %{operational_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
tuzo: %{bonus} %{token_symbol}
|
||||
0. Nyuma
|
||||
first_transaction_set: |-
|
||||
CON %{first_transaction_set}
|
||||
1. Mbele
|
||||
|
@ -51,19 +51,13 @@ ENV PATH $NVM_DIR/versions/node//v$NODE_VERSION/bin:$PATH
|
||||
# WORKDIR /home/grassroots
|
||||
# USER grassroots
|
||||
|
||||
COPY contract-migration/requirements.txt .
|
||||
|
||||
ARG pip_extra_args=""
|
||||
ARG pip_index_url=https://pypi.org/simple
|
||||
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
|
||||
ARG cic_base_version=0.1.2b11
|
||||
ARG cic_eth_version=0.11.0b14
|
||||
ARG sarafu_token_version=0.0.1a8
|
||||
ARG sarafu_faucet_version=0.0.3a3
|
||||
RUN pip install --index-url https://pypi.org/simple --extra-index-url $pip_extra_index_url \
|
||||
cic-base[full_graph]==$cic_base_version \
|
||||
cic-eth==$cic_eth_version \
|
||||
sarafu-faucet==$sarafu_faucet_version \
|
||||
sarafu-token==$sarafu_token_version \
|
||||
cic-eth==$cic_eth_version
|
||||
RUN pip install --index-url https://pypi.org/simple \
|
||||
--extra-index-url $pip_extra_index_url -r requirements.txt
|
||||
|
||||
# -------------- begin runtime container ----------------
|
||||
FROM python:3.8.6-slim-buster as runtime-image
|
||||
|
4
apps/contract-migration/requirements.txt
Normal file
4
apps/contract-migration/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cic-base[full_graph]==0.1.2b15
|
||||
sarafu-faucet==0.0.3a3
|
||||
sarafu-token==0.0.1a8
|
||||
cic-eth==0.11.0b16
|
@ -47,7 +47,7 @@ EOF
|
||||
|
||||
>&2 echo "create account for gas gifter"
|
||||
old_gas_provider=$DEV_ETH_ACCOUNT_GAS_PROVIDER
|
||||
DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
|
||||
DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create --timeout 120 $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
|
||||
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
|
||||
|
||||
|
@ -89,6 +89,7 @@ After this step is run, you can find top-level ethereum addresses (like the cic
|
||||
|
||||
|
||||
#### Custodial provisions
|
||||
|
||||
This step is _only_ needed if you are importing using `cic_eth` or `cic_ussd`
|
||||
|
||||
`RUN_MASK=2 docker-compose up contract-migration`
|
||||
@ -129,7 +130,9 @@ If the contract migrations have been executed with the default "giftable" token
|
||||
#### Alternative 1 - Sovereign wallet import - `eth`
|
||||
|
||||
|
||||
First, make a note of the **block height** before running anything.
|
||||
First, make a note of the **block height** before running anything:
|
||||
|
||||
`eth-info -p http://localhost:63545`
|
||||
|
||||
To import, run to _completion_:
|
||||
|
||||
@ -147,7 +150,7 @@ Then run:
|
||||
|
||||
Run in sequence, in first terminal:
|
||||
|
||||
`python cic_eth/import_balance.py -v -c config -p <eth_provider> -r <cic_registry_address> --token-symbol <token_symbol> -y ../keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c --head out`
|
||||
`python cic_eth/import_balance.py -v -c config -p <eth_provider> -r <cic_registry_address> --token-symbol <token_symbol> -y ../contract-migration/keystore/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c --head out`
|
||||
|
||||
In another terminal:
|
||||
|
||||
@ -160,7 +163,7 @@ The `redis_hostname_in_docker` value is the hostname required to reach the redis
|
||||
|
||||
If you have previously run the `cic_ussd` import incompletely, it could be a good idea to purge the queue. If you have left docker-compose unchanged, `redis_url` should be `redis://localhost:63379`.
|
||||
|
||||
`celery -A cic_ussd.import_task purge -Q cic-import-ussd --broker redis://localhost:63379`
|
||||
`celery -A cic_ussd.import_task purge -Q cic-import-ussd --broker <redis_url>`
|
||||
|
||||
Then, in sequence, run in first terminal:
|
||||
|
||||
@ -197,6 +200,7 @@ If you imported using `cic_ussd`, the phone pointer is _already added_ and this
|
||||
|
||||
|
||||
##### Importing pins and ussd data (optional)
|
||||
|
||||
Once the user imports are complete the next step should be importing the user's pins and auxiliary ussd data. This can be done in 3 steps:
|
||||
|
||||
In one terminal run:
|
||||
@ -255,6 +259,8 @@ Should exit with code 0 if all input data is found in the respective services.
|
||||
|
||||
- Running the balance script should be _optional_ in all cases, but is currently required in the case of `cic_ussd` because it is needed to generate the metadata. An improvement would be moving the task to `import_users.py`, for a different queue than the balance tx handler.
|
||||
|
||||
- MacOS BigSur issue when installing psycopg2: ld: library not found for -lssl -> https://github.com/psycopg/psycopg2/issues/1115#issuecomment-831498953
|
||||
|
||||
- `cic_ussd` imports is poorly implemented, and consumes a lot of resources. Therefore it takes a long time to complete. Reducing the amount of polls for the phone pointer would go a long way to improve it.
|
||||
|
||||
- A strict constraint is maintained insistin the use of postgresql-12.
|
||||
|
@ -2,27 +2,24 @@
|
||||
|
||||
# standard imports
|
||||
import json
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
import logging
|
||||
import os
|
||||
import base64
|
||||
import hashlib
|
||||
import sys
|
||||
import argparse
|
||||
import random
|
||||
|
||||
# external imports
|
||||
import vobject
|
||||
import celery
|
||||
from faker import Faker
|
||||
from collections import OrderedDict
|
||||
import confini
|
||||
from cic_types.models.person import (
|
||||
Person,
|
||||
generate_vcard_from_contact_data,
|
||||
get_contact_data_from_vcard,
|
||||
)
|
||||
Person,
|
||||
generate_vcard_from_contact_data,
|
||||
get_contact_data_from_vcard,
|
||||
)
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
import phonenumbers
|
||||
|
||||
@ -32,17 +29,21 @@ logg = logging.getLogger()
|
||||
fake = Faker(['sl', 'en_US', 'no', 'de', 'ro'])
|
||||
|
||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||
#config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
|
||||
# config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
|
||||
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('--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')
|
||||
argparser.add_argument('--dir', default='out', type=str, help='path to users export dir tree')
|
||||
argparser.add_argument('user_count', type=int, help='amount of users to generate')
|
||||
argparser.add_argument('--dir', default='out', type=str,
|
||||
help='path to users export dir tree')
|
||||
argparser.add_argument('user_count', type=int,
|
||||
help='amount of users to generate')
|
||||
args = argparser.parse_args()
|
||||
|
||||
if args.v:
|
||||
@ -60,22 +61,23 @@ dt_then = dt_now - datetime.timedelta(weeks=150)
|
||||
ts_now = int(dt_now.timestamp())
|
||||
ts_then = int(dt_then.timestamp())
|
||||
|
||||
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
||||
celery_app = celery.Celery(broker=config.get(
|
||||
'CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
||||
|
||||
gift_max = args.gift_threshold or 0
|
||||
gift_factor = (10**6)
|
||||
|
||||
categories = [
|
||||
"food/water",
|
||||
"fuel/energy",
|
||||
"education",
|
||||
"health",
|
||||
"shop",
|
||||
"environment",
|
||||
"transport",
|
||||
"farming/labor",
|
||||
"savingsgroup",
|
||||
]
|
||||
"food/water",
|
||||
"fuel/energy",
|
||||
"education",
|
||||
"health",
|
||||
"shop",
|
||||
"environment",
|
||||
"transport",
|
||||
"farming/labor",
|
||||
"savingsgroup",
|
||||
]
|
||||
|
||||
phone_idx = []
|
||||
|
||||
@ -88,6 +90,7 @@ if tags == None or len(tags) == 0:
|
||||
|
||||
random.seed()
|
||||
|
||||
|
||||
def genPhoneIndex(phone):
|
||||
h = hashlib.new('sha256')
|
||||
h.update(phone.encode('utf-8'))
|
||||
@ -141,16 +144,16 @@ def genDob():
|
||||
if random.random() > 0.5:
|
||||
dob['month'] = dob_src.month
|
||||
dob['day'] = dob_src.day
|
||||
|
||||
|
||||
return dob
|
||||
|
||||
|
||||
def gen():
|
||||
old_blockchain_address = '0x' + os.urandom(20).hex()
|
||||
old_blockchain_checksum_address = to_checksum_address(old_blockchain_address)
|
||||
old_blockchain_checksum_address = to_checksum_address(
|
||||
old_blockchain_address)
|
||||
gender = random.choice(['female', 'male', 'other'])
|
||||
phone = genPhone()
|
||||
city = fake.city_name()
|
||||
v = genPersonal(phone)
|
||||
|
||||
contact_data = get_contact_data_from_vcard(v)
|
||||
@ -161,31 +164,63 @@ def gen():
|
||||
p.date_of_birth = genDob()
|
||||
p.gender = gender
|
||||
p.identities = {
|
||||
'evm': {
|
||||
'oldchain:1': [
|
||||
old_blockchain_checksum_address,
|
||||
],
|
||||
},
|
||||
}
|
||||
p.location['area_name'] = city
|
||||
'evm': {
|
||||
'oldchain:1': [
|
||||
old_blockchain_checksum_address,
|
||||
],
|
||||
},
|
||||
}
|
||||
p.products = [fake.random_element(elements=OrderedDict(
|
||||
[('fruit', 0.25),
|
||||
('maji', 0.05),
|
||||
('milk', 0.1),
|
||||
('teacher', 0.1),
|
||||
('doctor', 0.05),
|
||||
('boutique', 0.15),
|
||||
('recycling', 0.05),
|
||||
('farmer', 0.05),
|
||||
('oil', 0.05),
|
||||
('pastor', 0.1),
|
||||
('chama', 0.03),
|
||||
('pastor', 0.01),
|
||||
('bzrTsuZieaq', 0.01)
|
||||
]))]
|
||||
p.location['area_name'] = fake.random_element(elements=OrderedDict(
|
||||
[('mnarani', 0.05),
|
||||
('chilumani', 0.1),
|
||||
('madewani', 0.1),
|
||||
('kisauni', 0.05),
|
||||
('bangla', 0.1),
|
||||
('kabiro', 0.1),
|
||||
('manyani', 0.05),
|
||||
('ruben', 0.15),
|
||||
('makupa', 0.05),
|
||||
('kingston', 0.05),
|
||||
('rangala', 0.05),
|
||||
('homabay', 0.1),
|
||||
('nakuru', 0.03),
|
||||
('kajiado', 0.01),
|
||||
('zurtWicKtily', 0.01)
|
||||
]))
|
||||
if random.randint(0, 1):
|
||||
p.location['latitude'] = (random.random() + 180) - 90 #fake.local_latitude()
|
||||
p.location['longitude'] = (random.random() + 360) - 180 #fake.local_latitude()
|
||||
# fake.local_latitude()
|
||||
p.location['latitude'] = (random.random() + 180) - 90
|
||||
# fake.local_latitude()
|
||||
p.location['longitude'] = (random.random() + 360) - 180
|
||||
|
||||
|
||||
return (old_blockchain_checksum_address, phone, p)
|
||||
|
||||
|
||||
def prepareLocalFilePath(datadir, address):
|
||||
parts = [
|
||||
address[:2],
|
||||
address[2:4],
|
||||
]
|
||||
address[:2],
|
||||
address[2:4],
|
||||
]
|
||||
dirs = '{}/{}/{}'.format(
|
||||
datadir,
|
||||
parts[0],
|
||||
parts[1],
|
||||
)
|
||||
datadir,
|
||||
parts[0],
|
||||
parts[1],
|
||||
)
|
||||
os.makedirs(dirs, exist_ok=True)
|
||||
return dirs
|
||||
|
||||
@ -238,9 +273,10 @@ if __name__ == '__main__':
|
||||
|
||||
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))
|
||||
|
||||
fa.write('{},{}\n'.format(eth, amount))
|
||||
logg.debug('pidx {}, uid {}, eth {}, amount {}, phone {}'.format(
|
||||
pidx, uid, eth, amount, phone))
|
||||
|
||||
i += 1
|
||||
|
||||
ft.close()
|
||||
|
@ -1,5 +1,5 @@
|
||||
cic-base[full_graph]==0.1.2b11
|
||||
cic-base[full_graph]==0.1.2b15
|
||||
sarafu-faucet==0.0.3a3
|
||||
cic-eth==0.11.0b14
|
||||
cic-eth==0.11.0b16
|
||||
cic-types==0.1.0a11
|
||||
crypto-dev-signer==0.4.14b3
|
||||
|
@ -6,11 +6,7 @@ volumes:
|
||||
bee-data: {}
|
||||
signer-data: {}
|
||||
bloxberg-data: {}
|
||||
contract-config:
|
||||
driver_opts:
|
||||
type: local
|
||||
o: bind
|
||||
device: ./service-configs
|
||||
contract-config: {}
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user