Compare commits
154 Commits
lash/cic-c
...
lash/move-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e91d0a9f14 | ||
| 7027d77836 | |||
| d356f8167d | |||
|
|
5e11804e7f
|
||
|
|
2d1316f1b8
|
||
|
|
45c018d884
|
||
|
|
d83843a761
|
||
|
|
3e5dd44e0b | ||
|
|
e339cbbf70
|
||
|
1236d34d2c
|
|||
|
|
747ad4dcd1 | ||
|
|
e0394ebeba
|
||
| 753d21fe95 | |||
| 3b6e031746 | |||
| b1d5d45eef | |||
| 53317cb912 | |||
| 18382a1f35 | |||
| 29e91fafab | |||
| 5b20a9a24a | |||
| a252195bdc | |||
|
|
f1be3b633c | ||
|
|
e59a71188c
|
||
|
|
740a809506
|
||
| 1d0eb06f2f | |||
| 57127132b5 | |||
| 0bf2c35fcd | |||
| d046595764 | |||
|
9dd7ec88fd
|
|||
| 282fd2ff52 | |||
| 8f85598861 | |||
| 8529c349ca | |||
| 4368d2bf59 | |||
| da3c812bf5 | |||
| 82b1e87462 | |||
| e13c423daf | |||
| 56b3bd751d | |||
| 4f41c5bacf | |||
| 07583f0c3b | |||
| 0ae912082c | |||
| 094f4d4298 | |||
|
|
9471b1d8ab | ||
|
|
57100366d8 | ||
| 71e0973020 | |||
|
|
12ab5c2f66 | ||
| a804552620 | |||
| 0319fa6076 | |||
| 91dfc51d54 | |||
| 4fd861f080 | |||
|
|
28de7a4eac | ||
|
|
a31e79b0f7 | ||
|
eb2f71aee0
|
|||
|
|
e5b1352970 | ||
|
|
89b90da5d2 | ||
| 9607994c31 | |||
| 0da617d29e | |||
| 56bcad16a5 | |||
| 77d9936e39 | |||
|
|
72aeefc78b | ||
|
|
fab9b0c520
|
||
| 9566f8c8e2 | |||
|
007d7a5121
|
|||
| fc20849aff | |||
| 1605e53216 | |||
| 200fdf0e3c | |||
| 022db04198 | |||
| 1c17048981 | |||
|
|
04c0963f33 | ||
|
|
096ed9bc27 | ||
| 1a931eced4 | |||
| ed9e032890 | |||
|
|
69ae9b7c07 | ||
|
|
634d3fb401 | ||
|
|
65f722b291 | ||
|
|
0ad0f9981c | ||
|
|
5fb0f4a2e9 | ||
|
|
41a96b5584
|
||
|
|
d0f2bc0120 | ||
|
|
e2946052e0 | ||
|
|
546d69f1e9 | ||
|
|
fbf7351238 | ||
|
|
b886384fa8 | ||
|
|
277033f3b5 | ||
| 4ae094fd30 | |||
| cb239f112a | |||
|
|
d971a6eded | ||
|
|
b0a6df0177 | ||
|
|
92c9df4e19 | ||
|
|
9c49d568e0 | ||
|
|
d7113f3923 | ||
|
|
c569fe4b17 | ||
| 1c650df27d | |||
| a31b7bc9cd | |||
|
|
78ff58c1a2 | ||
| 1676addbeb | |||
| 1efc25ac15 | |||
|
|
db2ec0dcfa | ||
| 5148e6428b | |||
|
|
0c186ed968 | ||
|
|
c44439bd90 | ||
|
|
0411603078 | ||
| eee895ea71 | |||
|
|
a5ca898532 | ||
|
|
6d8508aebf | ||
|
|
f8f66984d2 | ||
|
|
0f02dd1b7c | ||
| 63a4a82ab0 | |||
|
949c1070a9
|
|||
| 5d9fbe9b64 | |||
| 873a3f082a | |||
| 7b408cf564 | |||
|
|
9dfbd7034c | ||
|
|
235f5cede8 | ||
|
|
0a59539f9a | ||
|
|
60b36945df | ||
| dae6526677 | |||
|
1e94a516c2
|
|||
| e8512ebbae | |||
| f2c955c60b | |||
| 17b3b27d81 | |||
| 1cb172b8bf | |||
|
|
9d47e4c764 | ||
|
|
c68cc318ab | ||
|
|
af99ac823a | ||
|
|
06652eb30f | ||
|
|
f66f913307 | ||
|
|
8bf1364864
|
||
| 0d6d7179eb | |||
|
e7f48f3ce0
|
|||
|
|
b252fab018 | ||
|
|
4667916d80
|
||
| 1f668384cc | |||
| 123dc55687 | |||
|
|
0b4d8d5937
|
||
|
|
ed6bef4052 | ||
|
|
6a8a356f09 | ||
| 5ec0b67496 | |||
| 7d935bcbc3 | |||
| fd69a3c6bb | |||
|
|
298bcf89e5 | ||
|
|
5d3d773f41 | ||
|
|
e71b2411d0 | ||
|
|
b4bfb76634 | ||
| aab5c8bf85 | |||
| e1564574f7 | |||
| 13253a2dcc | |||
| 9020fe1000 | |||
| a2e7d2973c | |||
| 82f650e81d | |||
| e77940d0de | |||
| 1df62717ef | |||
| c4919d56b1 | |||
| 6d44863a49 | |||
|
|
b02cdee1bd | ||
|
|
75bf8f15be |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,3 +8,8 @@ gmon.out
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
**/*sqlite
|
||||
**/.nyc_output
|
||||
**/coverage
|
||||
**/.venv
|
||||
.idea
|
||||
|
||||
@@ -6,6 +6,7 @@ include:
|
||||
- local: 'apps/cic-notify/.gitlab-ci.yml'
|
||||
- local: 'apps/cic-meta/.gitlab-ci.yml'
|
||||
- local: 'apps/cic-cache/.gitlab-ci.yml'
|
||||
- local: 'apps/data-seeding/.gitlab-ci.yml'
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
import moolb
|
||||
|
||||
# local imports
|
||||
from cic_cache.db import list_transactions_mined
|
||||
from cic_cache.db import list_transactions_account_mined
|
||||
from cic_cache.db.list import (
|
||||
list_transactions_mined,
|
||||
list_transactions_account_mined,
|
||||
list_transactions_mined_with_data,
|
||||
)
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class BloomCache:
|
||||
class Cache:
|
||||
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
|
||||
class BloomCache(Cache):
|
||||
|
||||
@staticmethod
|
||||
def __get_filter_size(n):
|
||||
n = 8192 * 8
|
||||
@@ -87,3 +93,44 @@ class BloomCache:
|
||||
f_blocktx.add(block + tx)
|
||||
logg.debug('added block {} tx {} lo {} hi {}'.format(r[0], r[1], lowest_block, highest_block))
|
||||
return (lowest_block, highest_block, f_block.to_bytes(), f_blocktx.to_bytes(),)
|
||||
|
||||
|
||||
class DataCache(Cache):
|
||||
|
||||
def load_transactions_with_data(self, offset, end):
|
||||
rows = list_transactions_mined_with_data(self.session, offset, end)
|
||||
tx_cache = []
|
||||
highest_block = -1;
|
||||
lowest_block = -1;
|
||||
date_is_str = None # stick this in startup
|
||||
for r in rows:
|
||||
if highest_block == -1:
|
||||
highest_block = r['block_number']
|
||||
lowest_block = r['block_number']
|
||||
tx_type = 'unknown'
|
||||
|
||||
if r['value'] != None:
|
||||
tx_type = '{}.{}'.format(r['domain'], r['value'])
|
||||
|
||||
if date_is_str == None:
|
||||
date_is_str = type(r['date_block']).__name__ == 'str'
|
||||
|
||||
o = {
|
||||
'block_number': r['block_number'],
|
||||
'tx_hash': r['tx_hash'],
|
||||
'date_block': r['date_block'],
|
||||
'sender': r['sender'],
|
||||
'recipient': r['recipient'],
|
||||
'from_value': int(r['from_value']),
|
||||
'to_value': int(r['to_value']),
|
||||
'source_token': r['source_token'],
|
||||
'destination_token': r['destination_token'],
|
||||
'success': r['success'],
|
||||
'tx_type': tx_type,
|
||||
}
|
||||
|
||||
if date_is_str:
|
||||
o['date_block'] = datetime.datetime.fromisoformat(r['date_block'])
|
||||
|
||||
tx_cache.append(o)
|
||||
return (lowest_block, highest_block, tx_cache)
|
||||
|
||||
@@ -28,6 +28,26 @@ def list_transactions_mined(
|
||||
return r
|
||||
|
||||
|
||||
def list_transactions_mined_with_data(
|
||||
session,
|
||||
offset,
|
||||
end,
|
||||
):
|
||||
"""Executes db query to return all confirmed transactions according to the specified offset and limit.
|
||||
|
||||
:param offset: Offset in data set to return transactions from
|
||||
:type offset: int
|
||||
:param limit: Max number of transactions to retrieve
|
||||
:type limit: int
|
||||
:result: Result set
|
||||
:rtype: SQLAlchemy.ResultProxy
|
||||
"""
|
||||
s = "SELECT tx_hash, block_number, date_block, sender, recipient, from_value, to_value, source_token, destination_token, success, domain, value FROM tx LEFT JOIN tag_tx_link ON tx.id = tag_tx_link.tx_id LEFT JOIN tag ON tag_tx_link.tag_id = tag.id WHERE block_number >= {} AND block_number <= {} ORDER BY block_number ASC, tx_index ASC".format(offset, end)
|
||||
|
||||
r = session.execute(s)
|
||||
return r
|
||||
|
||||
|
||||
def list_transactions_account_mined(
|
||||
session,
|
||||
address,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from .erc20 import *
|
||||
from .faucet import *
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.address import (
|
||||
to_checksum_address,
|
||||
)
|
||||
@@ -13,6 +12,7 @@ from cic_eth_registry.error import (
|
||||
NotAContractError,
|
||||
ContractMismatchError,
|
||||
)
|
||||
from eth_erc20 import ERC20
|
||||
|
||||
# local imports
|
||||
from .base import TagSyncFilter
|
||||
|
||||
73
apps/cic-cache/cic_cache/runnable/daemons/filters/faucet.py
Normal file
73
apps/cic-cache/cic_cache/runnable/daemons/filters/faucet.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from erc20_faucet import Faucet
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.status import Status
|
||||
from hexathon import strip_0x
|
||||
|
||||
# local imports
|
||||
import cic_cache.db as cic_cache_db
|
||||
from .base import TagSyncFilter
|
||||
|
||||
#logg = logging.getLogger().getChild(__name__)
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class FaucetFilter(TagSyncFilter):
|
||||
|
||||
def __init__(self, chain_spec, sender_address=ZERO_ADDRESS):
|
||||
super(FaucetFilter, self).__init__('give_to', domain='faucet')
|
||||
self.chain_spec = chain_spec
|
||||
self.sender_address = sender_address
|
||||
|
||||
|
||||
def filter(self, conn, block, tx, db_session=None):
|
||||
try:
|
||||
data = strip_0x(tx.payload)
|
||||
except ValueError:
|
||||
return False
|
||||
logg.debug('data {}'.format(data))
|
||||
if Faucet.method_for(data[:8]) == None:
|
||||
return False
|
||||
|
||||
token_sender = tx.inputs[0]
|
||||
token_recipient = data[64+8-40:]
|
||||
logg.debug('token recipient {}'.format(token_recipient))
|
||||
|
||||
f = Faucet(self.chain_spec)
|
||||
o = f.token(token_sender, sender_address=self.sender_address)
|
||||
r = conn.do(o)
|
||||
token = f.parse_token(r)
|
||||
|
||||
f = Faucet(self.chain_spec)
|
||||
o = f.token_amount(token_sender, sender_address=self.sender_address)
|
||||
r = conn.do(o)
|
||||
token_value = f.parse_token_amount(r)
|
||||
|
||||
cic_cache_db.add_transaction(
|
||||
db_session,
|
||||
tx.hash,
|
||||
block.number,
|
||||
tx.index,
|
||||
to_checksum_address(token_sender),
|
||||
to_checksum_address(token_recipient),
|
||||
token,
|
||||
token,
|
||||
token_value,
|
||||
token_value,
|
||||
tx.status == Status.SUCCESS,
|
||||
block.timestamp,
|
||||
)
|
||||
db_session.flush()
|
||||
cic_cache_db.tag_transaction(
|
||||
db_session,
|
||||
tx.hash,
|
||||
self.tag_name,
|
||||
domain=self.tag_domain,
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
return True
|
||||
110
apps/cic-cache/cic_cache/runnable/daemons/query.py
Normal file
110
apps/cic-cache/cic_cache/runnable/daemons/query.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import base64
|
||||
|
||||
# local imports
|
||||
from cic_cache.cache import (
|
||||
BloomCache,
|
||||
DataCache,
|
||||
)
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?'
|
||||
re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)/?(\d+)?/?(\d+)/?'
|
||||
re_transactions_all_data = r'/txa/(\d+)/(\d+)/?'
|
||||
|
||||
DEFAULT_LIMIT = 100
|
||||
|
||||
|
||||
def process_transactions_account_bloom(session, env):
|
||||
r = re.match(re_transactions_account_bloom, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
|
||||
address = r[1]
|
||||
if r[2] == None:
|
||||
address = '0x' + address
|
||||
offset = DEFAULT_LIMIT
|
||||
if r.lastindex > 2:
|
||||
offset = r[3]
|
||||
limit = 0
|
||||
if r.lastindex > 3:
|
||||
limit = r[4]
|
||||
|
||||
c = BloomCache(session)
|
||||
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit)
|
||||
|
||||
o = {
|
||||
'alg': 'sha256',
|
||||
'low': lowest_block,
|
||||
'high': highest_block,
|
||||
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
|
||||
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
|
||||
'filter_rounds': 3,
|
||||
}
|
||||
|
||||
j = json.dumps(o)
|
||||
|
||||
return ('application/json', j.encode('utf-8'),)
|
||||
|
||||
|
||||
def process_transactions_all_bloom(session, env):
|
||||
r = re.match(re_transactions_all_bloom, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
|
||||
offset = DEFAULT_LIMIT
|
||||
if r.lastindex > 0:
|
||||
offset = r[1]
|
||||
limit = 0
|
||||
if r.lastindex > 1:
|
||||
limit = r[2]
|
||||
|
||||
c = BloomCache(session)
|
||||
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions(offset, limit)
|
||||
|
||||
o = {
|
||||
'alg': 'sha256',
|
||||
'low': lowest_block,
|
||||
'high': highest_block,
|
||||
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
|
||||
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
|
||||
'filter_rounds': 3,
|
||||
}
|
||||
|
||||
j = json.dumps(o)
|
||||
|
||||
return ('application/json', j.encode('utf-8'),)
|
||||
|
||||
|
||||
def process_transactions_all_data(session, env):
|
||||
r = re.match(re_transactions_all_data, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
if env.get('HTTP_X_CIC_CACHE_MODE') != 'all':
|
||||
return None
|
||||
|
||||
offset = r[1]
|
||||
end = r[2]
|
||||
if int(r[2]) < int(r[1]):
|
||||
raise ValueError('cart before the horse, dude')
|
||||
|
||||
c = DataCache(session)
|
||||
(lowest_block, highest_block, tx_cache) = c.load_transactions_with_data(offset, end)
|
||||
|
||||
for r in tx_cache:
|
||||
r['date_block'] = r['date_block'].timestamp()
|
||||
|
||||
o = {
|
||||
'low': lowest_block,
|
||||
'high': highest_block,
|
||||
'data': tx_cache,
|
||||
}
|
||||
|
||||
|
||||
j = json.dumps(o)
|
||||
|
||||
return ('application/json', j.encode('utf-8'),)
|
||||
@@ -1,18 +1,20 @@
|
||||
# standard imports
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import argparse
|
||||
import json
|
||||
import base64
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
import confini
|
||||
|
||||
# local imports
|
||||
from cic_cache import BloomCache
|
||||
from cic_cache.db import dsn_from_config
|
||||
from cic_cache.db.models.base import SessionBase
|
||||
from cic_cache.runnable.daemons.query import (
|
||||
process_transactions_account_bloom,
|
||||
process_transactions_all_bloom,
|
||||
process_transactions_all_data,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
@@ -44,72 +46,6 @@ logg.debug('config:\n{}'.format(config))
|
||||
dsn = dsn_from_config(config)
|
||||
SessionBase.connect(dsn, config.true('DATABASE_DEBUG'))
|
||||
|
||||
re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?'
|
||||
re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)/?(\d+)?/?(\d+)/?'
|
||||
|
||||
DEFAULT_LIMIT = 100
|
||||
|
||||
|
||||
def process_transactions_account_bloom(session, env):
|
||||
r = re.match(re_transactions_account_bloom, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
|
||||
address = r[1]
|
||||
if r[2] == None:
|
||||
address = '0x' + address
|
||||
offset = DEFAULT_LIMIT
|
||||
if r.lastindex > 2:
|
||||
offset = r[3]
|
||||
limit = 0
|
||||
if r.lastindex > 3:
|
||||
limit = r[4]
|
||||
|
||||
c = BloomCache(session)
|
||||
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit)
|
||||
|
||||
o = {
|
||||
'alg': 'sha256',
|
||||
'low': lowest_block,
|
||||
'high': highest_block,
|
||||
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
|
||||
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
|
||||
'filter_rounds': 3,
|
||||
}
|
||||
|
||||
j = json.dumps(o)
|
||||
|
||||
return ('application/json', j.encode('utf-8'),)
|
||||
|
||||
|
||||
def process_transactions_all_bloom(session, env):
|
||||
r = re.match(re_transactions_all_bloom, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
|
||||
offset = DEFAULT_LIMIT
|
||||
if r.lastindex > 0:
|
||||
offset = r[1]
|
||||
limit = 0
|
||||
if r.lastindex > 1:
|
||||
limit = r[2]
|
||||
|
||||
c = BloomCache(session)
|
||||
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions(offset, limit)
|
||||
|
||||
o = {
|
||||
'alg': 'sha256',
|
||||
'low': lowest_block,
|
||||
'high': highest_block,
|
||||
'block_filter': base64.b64encode(bloom_filter_block).decode('utf-8'),
|
||||
'blocktx_filter': base64.b64encode(bloom_filter_tx).decode('utf-8'),
|
||||
'filter_rounds': 3,
|
||||
}
|
||||
|
||||
j = json.dumps(o)
|
||||
|
||||
return ('application/json', j.encode('utf-8'),)
|
||||
|
||||
|
||||
# uwsgi application
|
||||
def application(env, start_response):
|
||||
@@ -119,10 +55,16 @@ def application(env, start_response):
|
||||
|
||||
session = SessionBase.create_session()
|
||||
for handler in [
|
||||
process_transactions_all_data,
|
||||
process_transactions_all_bloom,
|
||||
process_transactions_account_bloom,
|
||||
]:
|
||||
r = handler(session, env)
|
||||
r = None
|
||||
try:
|
||||
r = handler(session, env)
|
||||
except ValueError as e:
|
||||
start_response('400 {}'.format(str(e)))
|
||||
return []
|
||||
if r != None:
|
||||
(mime_type, content) = r
|
||||
break
|
||||
|
||||
@@ -16,6 +16,7 @@ import cic_base.config
|
||||
import cic_base.log
|
||||
import cic_base.argparse
|
||||
import cic_base.rpc
|
||||
from cic_base.eth.syncer import chain_interface
|
||||
from cic_eth_registry import CICRegistry
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from chainlib.chain import ChainSpec
|
||||
@@ -28,10 +29,8 @@ from hexathon import (
|
||||
strip_0x,
|
||||
)
|
||||
from chainsyncer.backend.sql import SQLBackend
|
||||
from chainsyncer.driver import (
|
||||
HeadSyncer,
|
||||
HistorySyncer,
|
||||
)
|
||||
from chainsyncer.driver.head import HeadSyncer
|
||||
from chainsyncer.driver.history import HistorySyncer
|
||||
from chainsyncer.db.models.base import SessionBase
|
||||
|
||||
# local imports
|
||||
@@ -41,16 +40,26 @@ from cic_cache.db import (
|
||||
)
|
||||
from cic_cache.runnable.daemons.filters import (
|
||||
ERC20TransferFilter,
|
||||
FaucetFilter,
|
||||
)
|
||||
|
||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||
|
||||
def add_block_args(argparser):
|
||||
argparser.add_argument('--history-start', type=int, default=0, dest='history_start', help='Start block height for initial history sync')
|
||||
argparser.add_argument('--no-history', action='store_true', dest='no_history', help='Skip initial history sync')
|
||||
return argparser
|
||||
|
||||
|
||||
logg = cic_base.log.create()
|
||||
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
||||
#argparser = cic_base.argparse.add(argparser, add_traffic_args, 'traffic')
|
||||
argparser = cic_base.argparse.add(argparser, add_block_args, 'block')
|
||||
args = cic_base.argparse.parse(argparser, logg)
|
||||
config = cic_base.config.create(args.c, args, args.env_prefix)
|
||||
|
||||
config.add(args.history_start, 'SYNCER_HISTORY_START', True)
|
||||
config.add(args.no_history, '_NO_HISTORY', True)
|
||||
|
||||
cic_base.config.log(config)
|
||||
|
||||
dsn = dsn_from_config(config)
|
||||
@@ -59,7 +68,6 @@ SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||
|
||||
#RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
||||
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
||||
|
||||
|
||||
@@ -71,6 +79,7 @@ def register_filter_tags(filters, session):
|
||||
session.commit()
|
||||
logg.info('added tag name "{}" domain "{}"'.format(tag[0], tag[1]))
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
session.rollback()
|
||||
logg.debug('already have tag name "{}" domain "{}"'.format(tag[0], tag[1]))
|
||||
|
||||
|
||||
@@ -82,7 +91,7 @@ def main():
|
||||
r = rpc.do(o)
|
||||
block_offset = int(strip_0x(r), 16) + 1
|
||||
|
||||
logg.debug('starting at block {}'.format(block_offset))
|
||||
logg.debug('current block height {}'.format(block_offset))
|
||||
|
||||
syncers = []
|
||||
|
||||
@@ -91,17 +100,22 @@ def main():
|
||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
||||
|
||||
if len(syncer_backends) == 0:
|
||||
logg.info('found no backends to resume')
|
||||
syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
|
||||
initial_block_start = config.get('SYNCER_HISTORY_START')
|
||||
initial_block_offset = block_offset
|
||||
if config.get('_NO_HISTORY'):
|
||||
initial_block_start = block_offset
|
||||
initial_block_offset += 1
|
||||
syncer_backends.append(SQLBackend.initial(chain_spec, initial_block_offset, start_block_height=initial_block_start))
|
||||
logg.info('found no backends to resume, adding initial sync from history start {} end {}'.format(initial_block_start, initial_block_offset))
|
||||
else:
|
||||
for syncer_backend in syncer_backends:
|
||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||
|
||||
for syncer_backend in syncer_backends:
|
||||
syncers.append(HistorySyncer(syncer_backend))
|
||||
syncers.append(HistorySyncer(syncer_backend, chain_interface))
|
||||
|
||||
syncer_backend = SQLBackend.live(chain_spec, block_offset+1)
|
||||
syncers.append(HeadSyncer(syncer_backend))
|
||||
syncers.append(HeadSyncer(syncer_backend, chain_interface))
|
||||
|
||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||
if trusted_addresses_src == None:
|
||||
@@ -112,9 +126,11 @@ def main():
|
||||
logg.info('using trusted address {}'.format(address))
|
||||
|
||||
erc20_transfer_filter = ERC20TransferFilter(chain_spec)
|
||||
faucet_filter = FaucetFilter(chain_spec)
|
||||
|
||||
filters = [
|
||||
erc20_transfer_filter,
|
||||
faucet_filter,
|
||||
]
|
||||
|
||||
session = SessionBase.create_session()
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[eth]
|
||||
provider = ws://localhost:63546
|
||||
provider = http://localhost:63545
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[syncer]
|
||||
loop_interval = 1
|
||||
history_start = 0
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[syncer]
|
||||
loop_interval = 5
|
||||
history_start = 0
|
||||
|
||||
2
apps/cic-cache/config/test/syncer.ini
Normal file
2
apps/cic-cache/config/test/syncer.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[syncer]
|
||||
loop_interval = 1
|
||||
@@ -17,7 +17,7 @@ RUN apt-get update && \
|
||||
|
||||
# Copy shared requirements from top of mono-repo
|
||||
RUN echo "copying root req file ${root_requirement_file}"
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b9
|
||||
|
||||
COPY cic-cache/requirements.txt ./
|
||||
COPY cic-cache/setup.cfg \
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
cic-base~=0.1.2b6
|
||||
cic-base==0.1.3a3+build.4aa03607
|
||||
alembic==1.4.2
|
||||
confini~=0.3.6rc3
|
||||
uwsgi==2.0.19.1
|
||||
moolb~=0.1.0
|
||||
cic-eth-registry~=0.5.5a1
|
||||
cic-eth-registry~=0.5.6a1
|
||||
SQLAlchemy==1.3.20
|
||||
semver==2.13.0
|
||||
psycopg2==2.8.6
|
||||
celery==4.4.7
|
||||
redis==3.5.3
|
||||
chainsyncer[sql]~=0.0.2a2
|
||||
chainsyncer[sql]~=0.0.3a3
|
||||
erc20-faucet~=0.2.2a1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
import re
|
||||
|
||||
import alembic
|
||||
from alembic.config import Config as AlembicConfig
|
||||
@@ -23,6 +24,8 @@ argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('--migrations-dir', dest='migrations_dir', default=migrationsdir, type=str, help='path to alembic migrations directory')
|
||||
argparser.add_argument('--reset', action='store_true', help='downgrade before upgrading')
|
||||
argparser.add_argument('-f', action='store_true', help='force action')
|
||||
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||
args = argparser.parse_args()
|
||||
@@ -53,4 +56,10 @@ ac = AlembicConfig(os.path.join(migrations_dir, 'alembic.ini'))
|
||||
ac.set_main_option('sqlalchemy.url', dsn)
|
||||
ac.set_main_option('script_location', migrations_dir)
|
||||
|
||||
if args.reset:
|
||||
if not args.f:
|
||||
if not re.match(r'[yY][eE]?[sS]?', input('EEK! this will DELETE the existing db. are you sure??')):
|
||||
logg.error('user chickened out on requested reset, bailing')
|
||||
sys.exit(1)
|
||||
alembic.command.downgrade(ac, 'base')
|
||||
alembic.command.upgrade(ac, 'head')
|
||||
|
||||
@@ -6,8 +6,5 @@ sqlparse==0.4.1
|
||||
pytest-celery==0.0.0a1
|
||||
eth_tester==0.5.0b3
|
||||
py-evm==0.3.0a20
|
||||
web3==5.12.2
|
||||
cic-eth-registry~=0.5.5a3
|
||||
giftable-erc20-token~=0.0.8a10
|
||||
eth-address-index~=0.1.1a10
|
||||
sarafu-faucet~=0.0.3a1
|
||||
cic_base[full]==0.1.3a3+build.4aa03607
|
||||
sarafu-faucet~=0.0.4a1
|
||||
|
||||
@@ -88,3 +88,16 @@ def txs(
|
||||
tx_hash_first,
|
||||
tx_hash_second,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def tag_txs(
|
||||
init_database,
|
||||
txs,
|
||||
):
|
||||
|
||||
db.add_tag(init_database, 'taag', domain='test')
|
||||
init_database.commit()
|
||||
|
||||
db.tag_transaction(init_database, txs[1], 'taag', domain='test')
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from cic_cache.runnable.daemons.filters.erc20 import ERC20TransferFilter
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_cache(
|
||||
def test_erc20_filter(
|
||||
eth_rpc,
|
||||
foo_token,
|
||||
init_database,
|
||||
|
||||
71
apps/cic-cache/tests/filters/test_faucet.py
Normal file
71
apps/cic-cache/tests/filters/test_faucet.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.block import (
|
||||
block_by_hash,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
unpack,
|
||||
transaction,
|
||||
Tx,
|
||||
)
|
||||
from hexathon import strip_0x
|
||||
from erc20_faucet.faucet import SingleShotFaucet
|
||||
from sqlalchemy import text
|
||||
|
||||
# local imports
|
||||
from cic_cache.db import add_tag
|
||||
from cic_cache.runnable.daemons.filters.faucet import FaucetFilter
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def test_filter_faucet(
|
||||
eth_rpc,
|
||||
eth_signer,
|
||||
foo_token,
|
||||
faucet_noregistry,
|
||||
init_database,
|
||||
list_defaults,
|
||||
contract_roles,
|
||||
agent_roles,
|
||||
tags,
|
||||
):
|
||||
|
||||
chain_spec = ChainSpec('foo', 'bar', 42, 'baz')
|
||||
|
||||
fltr = FaucetFilter(chain_spec, contract_roles['CONTRACT_DEPLOYER'])
|
||||
|
||||
add_tag(init_database, fltr.tag_name, domain=fltr.tag_domain)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc)
|
||||
c = SingleShotFaucet(chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash_hex, o) = c.give_to(faucet_noregistry, agent_roles['ALICE'], agent_roles['ALICE'])
|
||||
r = eth_rpc.do(o)
|
||||
|
||||
tx_src = unpack(bytes.fromhex(strip_0x(o['params'][0])), chain_spec)
|
||||
|
||||
o = receipt(r)
|
||||
r = eth_rpc.do(o)
|
||||
rcpt = Tx.src_normalize(r)
|
||||
|
||||
assert r['status'] == 1
|
||||
|
||||
o = block_by_hash(r['block_hash'])
|
||||
r = eth_rpc.do(o)
|
||||
block_object = Block(r)
|
||||
|
||||
tx = Tx(tx_src, block_object)
|
||||
tx.apply_receipt(rcpt)
|
||||
|
||||
r = fltr.filter(eth_rpc, block_object, tx, init_database)
|
||||
assert r
|
||||
|
||||
s = text("SELECT x.tx_hash FROM tag a INNER JOIN tag_tx_link l ON l.tag_id = a.id INNER JOIN tx x ON x.id = l.tx_id WHERE a.domain = :a AND a.value = :b")
|
||||
r = init_database.execute(s, {'a': fltr.tag_domain, 'b': fltr.tag_name}).fetchone()
|
||||
assert r[0] == tx.hash
|
||||
31
apps/cic-cache/tests/test_api.py
Normal file
31
apps/cic-cache/tests/test_api.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_cache.runnable.daemons.query import process_transactions_all_data
|
||||
|
||||
|
||||
def test_api_all_data(
|
||||
init_database,
|
||||
txs,
|
||||
):
|
||||
|
||||
env = {
|
||||
'PATH_INFO': '/txa/410000/420000',
|
||||
'HTTP_X_CIC_CACHE_MODE': 'all',
|
||||
}
|
||||
j = process_transactions_all_data(init_database, env)
|
||||
o = json.loads(j[1])
|
||||
|
||||
assert len(o['data']) == 2
|
||||
|
||||
env = {
|
||||
'PATH_INFO': '/txa/420000/410000',
|
||||
'HTTP_X_CIC_CACHE_MODE': 'all',
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
j = process_transactions_all_data(init_database, env)
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
|
||||
# local imports
|
||||
from cic_cache import BloomCache
|
||||
from cic_cache.cache import DataCache
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
@@ -33,3 +34,23 @@ def test_cache(
|
||||
|
||||
assert b[0] == list_defaults['block'] - 1
|
||||
|
||||
|
||||
def test_cache_data(
|
||||
init_database,
|
||||
list_defaults,
|
||||
list_actors,
|
||||
list_tokens,
|
||||
txs,
|
||||
tag_txs,
|
||||
):
|
||||
|
||||
session = init_database
|
||||
|
||||
c = DataCache(session)
|
||||
b = c.load_transactions_with_data(410000, 420000)
|
||||
|
||||
assert len(b[2]) == 2
|
||||
assert b[2][0]['tx_hash'] == txs[1]
|
||||
assert b[2][1]['tx_type'] == 'unknown'
|
||||
assert b[2][0]['tx_type'] == 'test.taag'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,18 +5,29 @@
|
||||
|
||||
.cic_eth_changes_target:
|
||||
rules:
|
||||
- changes:
|
||||
- $CONTEXT/$APP_NAME/*
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
#changes:
|
||||
#- $CONTEXT/$APP_NAME/**/*
|
||||
when: always
|
||||
|
||||
build-mr-cic-eth:
|
||||
extends:
|
||||
- .cic_eth_changes_target
|
||||
- .py_build_merge_request
|
||||
- .cic_eth_variables
|
||||
- .cic_eth_changes_target
|
||||
- .py_build_target_test
|
||||
|
||||
test-mr-cic-eth:
|
||||
extends:
|
||||
- .cic_eth_variables
|
||||
- .cic_eth_changes_target
|
||||
stage: test
|
||||
image: $CI_REGISTRY_IMAGE/$APP_NAME-test:latest
|
||||
script:
|
||||
- cd apps/$APP_NAME/
|
||||
- pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests
|
||||
needs: ["build-mr-cic-eth"]
|
||||
|
||||
build-push-cic-eth:
|
||||
extends:
|
||||
- .py_build_push
|
||||
- .cic_eth_variables
|
||||
|
||||
|
||||
|
||||
2
apps/cic-eth/MANIFEST.in
Normal file
2
apps/cic-eth/MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
include *requirements.txt
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -16,4 +16,6 @@ def default_token(self):
|
||||
return {
|
||||
'symbol': self.default_token_symbol,
|
||||
'address': self.default_token_address,
|
||||
'name': self.default_token_name,
|
||||
'decimals': self.default_token_decimals,
|
||||
}
|
||||
|
||||
@@ -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(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(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')
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
from erc20_single_shot_faucet import SingleShotFaucet as Faucet
|
||||
from erc20_faucet import Faucet
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
)
|
||||
@@ -20,8 +20,9 @@ from chainlib.eth.tx import (
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.error import JSONRPCException
|
||||
from eth_accounts_index import AccountRegistry
|
||||
from sarafu_faucet import MinterFaucet as Faucet
|
||||
from eth_accounts_index.registry import AccountRegistry
|
||||
from eth_accounts_index import AccountsIndex
|
||||
from sarafu_faucet import MinterFaucet
|
||||
from chainqueue.db.models.tx import TxCache
|
||||
|
||||
# local import
|
||||
@@ -133,7 +134,7 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
|
||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||
nonce_oracle = CustodialTaskNonceOracle(writer_address, self.request.root_id, session=session) #, default_nonce)
|
||||
gas_oracle = self.create_gas_oracle(rpc, AccountRegistry.gas)
|
||||
account_registry = AccountRegistry(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
account_registry = AccountsIndex(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = account_registry.add(account_registry_address, writer_address, account_address, tx_format=TxFormat.RLP_SIGNED)
|
||||
rpc_signer.disconnect()
|
||||
|
||||
@@ -185,7 +186,7 @@ def gift(self, account_address, chain_spec_dict):
|
||||
# Generate and sign transaction
|
||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||
nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce)
|
||||
gas_oracle = self.create_gas_oracle(rpc, Faucet.gas)
|
||||
gas_oracle = self.create_gas_oracle(rpc, MinterFaucet.gas)
|
||||
faucet = Faucet(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = faucet.give_to(faucet_address, account_address, account_address, tx_format=TxFormat.RLP_SIGNED)
|
||||
rpc_signer.disconnect()
|
||||
@@ -338,7 +339,7 @@ def cache_account_data(
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
|
||||
tx = unpack(tx_signed_raw_bytes, chain_spec)
|
||||
tx_data = AccountRegistry.parse_add_request(tx['data'])
|
||||
tx_data = AccountsIndex.parse_add_request(tx['data'])
|
||||
|
||||
session = SessionBase.create_session()
|
||||
tx_cache = TxCache(
|
||||
|
||||
@@ -6,7 +6,6 @@ import celery
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.tx import (
|
||||
TxFormat,
|
||||
unpack,
|
||||
@@ -16,6 +15,7 @@ from cic_eth_registry.erc20 import ERC20Token
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.db.models.tx import TxCache
|
||||
from chainqueue.error import NotLocalTxError
|
||||
from eth_erc20 import ERC20
|
||||
|
||||
# local imports
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -7,7 +7,7 @@ from chainlib.chain import ChainSpec
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from cic_eth_registry import CICRegistry
|
||||
from eth_address_declarator import AddressDeclarator
|
||||
from eth_address_declarator import Declarator
|
||||
|
||||
# local imports
|
||||
from cic_eth.task import BaseTask
|
||||
@@ -23,12 +23,12 @@ def translate_address(address, trusted_addresses, chain_spec, sender_address=ZER
|
||||
registry = CICRegistry(chain_spec, rpc)
|
||||
|
||||
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
||||
c = AddressDeclarator(chain_spec)
|
||||
c = Declarator(chain_spec)
|
||||
|
||||
for trusted_address in trusted_addresses:
|
||||
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
|
||||
r = rpc.do(o)
|
||||
declaration_hex = AddressDeclarator.parse_declaration(r)
|
||||
declaration_hex = Declarator.parse_declaration(r)
|
||||
declaration_hex = declaration_hex[0].rstrip('0')
|
||||
declaration_bytes = bytes.fromhex(declaration_hex)
|
||||
declaration = None
|
||||
|
||||
@@ -14,13 +14,13 @@ from chainlib.eth.tx import (
|
||||
)
|
||||
from chainlib.eth.block import block_by_number
|
||||
from chainlib.eth.contract import abi_decode_single
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from hexathon import strip_0x
|
||||
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
|
||||
from cic_eth.queue.time import tx_times
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,19 +3,20 @@ import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from cic_eth_registry.error import (
|
||||
UnknownContractError,
|
||||
NotAContractError,
|
||||
)
|
||||
from chainlib.status import Status as TxStatus
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.error import RequestMismatchException
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
)
|
||||
# TODO: use sarafu_Faucet for both when inheritance has been implemented
|
||||
from erc20_single_shot_faucet import SingleShotFaucet
|
||||
from sarafu_faucet import MinterFaucet as Faucet
|
||||
from eth_erc20 import ERC20
|
||||
from erc20_faucet import Faucet
|
||||
|
||||
# local imports
|
||||
from .base import SyncFilter
|
||||
@@ -71,14 +72,15 @@ class CallbackFilter(SyncFilter):
|
||||
#transfer_data['token_address'] = tx.inputs[0]
|
||||
faucet_contract = tx.inputs[0]
|
||||
|
||||
c = SingleShotFaucet(self.chain_spec)
|
||||
c = Faucet(self.chain_spec)
|
||||
|
||||
o = c.token(faucet_contract, sender_address=self.caller_address)
|
||||
r = conn.do(o)
|
||||
transfer_data['token_address'] = add_0x(c.parse_token(r))
|
||||
|
||||
o = c.amount(faucet_contract, sender_address=self.caller_address)
|
||||
o = c.token_amount(faucet_contract, sender_address=self.caller_address)
|
||||
r = conn.do(o)
|
||||
transfer_data['value'] = c.parse_amount(r)
|
||||
transfer_data['value'] = c.parse_token_amount(r)
|
||||
|
||||
return ('tokengift', transfer_data)
|
||||
|
||||
@@ -127,8 +129,7 @@ class CallbackFilter(SyncFilter):
|
||||
(transfer_type, transfer_data) = parser(tx, conn)
|
||||
if transfer_type == None:
|
||||
continue
|
||||
else:
|
||||
pass
|
||||
break
|
||||
except RequestMismatchException:
|
||||
continue
|
||||
|
||||
@@ -171,7 +172,9 @@ class CallbackFilter(SyncFilter):
|
||||
t = self.call_back(transfer_type, result)
|
||||
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
|
||||
except UnknownContractError:
|
||||
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tx.queue, tx.method, transfer_data['to'], tx.hash))
|
||||
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(self.queue, self.method, transfer_data['to'], tx.hash))
|
||||
except NotAContractError:
|
||||
logg.debug('callback filter {}:{} skipping "transfer" on non-contract address {} tx {}'.format(self.queue, self.method, transfer_data['to'], tx.hash))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -14,7 +14,7 @@ from .base import SyncFilter
|
||||
|
||||
logg = logging.getLogger().getChild(__name__)
|
||||
|
||||
account_registry_add_log_hash = '0x5ed3bdd47b9af629827a8d129aa39c870b10c03f0153fe9ddb8e84b665061acd'
|
||||
account_registry_add_log_hash = '0x9cc987676e7d63379f176ea50df0ae8d2d9d1141d1231d4ce15b5965f73c9430'
|
||||
|
||||
|
||||
class RegistrationFilter(SyncFilter):
|
||||
@@ -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):
|
||||
|
||||
@@ -30,7 +30,7 @@ class TxFilter(SyncFilter):
|
||||
if otx == None:
|
||||
logg.debug('tx {} not found locally, skipping'.format(tx_hash_hex))
|
||||
return None
|
||||
logg.info('tx filter match on {}'.format(otx.tx_hash))
|
||||
logg.debug('otx filter match on {}'.format(otx.tx_hash))
|
||||
db_session.flush()
|
||||
SessionBase.release_session(db_session)
|
||||
s_final_state = celery.signature(
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import argparse
|
||||
import json
|
||||
|
||||
# third-party imports
|
||||
import web3
|
||||
import confini
|
||||
import celery
|
||||
from json.decoder import JSONDecodeError
|
||||
from cic_registry.chain import ChainSpec
|
||||
|
||||
# local imports
|
||||
from cic_eth.db import dsn_from_config
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
from cic_eth.eth.util import unpack_signed_raw_tx
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
dbdir = os.path.join(rootdir, 'cic_eth', 'db')
|
||||
migrationsdir = os.path.join(dbdir, 'migrations')
|
||||
|
||||
config_dir = os.path.join('/usr/local/etc/cic-eth')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
||||
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||
args = argparser.parse_args()
|
||||
|
||||
if args.vv:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
config = confini.Config(args.c, args.env_prefix)
|
||||
config.process()
|
||||
args_override = {
|
||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
||||
}
|
||||
config.censor('PASSWORD', 'DATABASE')
|
||||
config.censor('PASSWORD', 'SSL')
|
||||
logg.debug('config:\n{}'.format(config))
|
||||
|
||||
dsn = dsn_from_config(config)
|
||||
SessionBase.connect(dsn)
|
||||
|
||||
celery_app = celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY_BROKER_URL'))
|
||||
queue = args.q
|
||||
|
||||
re_something = r'^/something/?'
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||
|
||||
|
||||
def process_something(session, env):
|
||||
r = re.match(re_something, env.get('PATH_INFO'))
|
||||
if not r:
|
||||
return None
|
||||
|
||||
#if env.get('CONTENT_TYPE') != 'application/json':
|
||||
# raise AttributeError('content type')
|
||||
|
||||
#if env.get('REQUEST_METHOD') != 'POST':
|
||||
# raise AttributeError('method')
|
||||
|
||||
#post_data = json.load(env.get('wsgi.input'))
|
||||
|
||||
#return ('text/plain', 'foo'.encode('utf-8'),)
|
||||
|
||||
|
||||
# uwsgi application
|
||||
def application(env, start_response):
|
||||
|
||||
for k in env.keys():
|
||||
logg.debug('env {} {}'.format(k, env[k]))
|
||||
|
||||
headers = []
|
||||
content = b''
|
||||
err = None
|
||||
|
||||
session = SessionBase.create_session()
|
||||
for handler in [
|
||||
process_something,
|
||||
]:
|
||||
try:
|
||||
r = handler(session, env)
|
||||
except AttributeError as e:
|
||||
logg.error('handler fail attribute {}'.format(e))
|
||||
err = '400 Impertinent request'
|
||||
break
|
||||
except JSONDecodeError as e:
|
||||
logg.error('handler fail json {}'.format(e))
|
||||
err = '400 Invalid data format'
|
||||
break
|
||||
except KeyError as e:
|
||||
logg.error('handler fail key {}'.format(e))
|
||||
err = '400 Invalid JSON'
|
||||
break
|
||||
except ValueError as e:
|
||||
logg.error('handler fail value {}'.format(e))
|
||||
err = '400 Invalid data'
|
||||
break
|
||||
except RuntimeError as e:
|
||||
logg.error('task fail value {}'.format(e))
|
||||
err = '500 Task failed, sorry I cannot tell you more'
|
||||
break
|
||||
if r != None:
|
||||
(mime_type, content) = r
|
||||
break
|
||||
session.close()
|
||||
|
||||
if err != None:
|
||||
headers.append(('Content-Type', 'text/plain, charset=UTF-8',))
|
||||
start_response(err, headers)
|
||||
session.close()
|
||||
return [content]
|
||||
|
||||
headers.append(('Content-Length', str(len(content))),)
|
||||
headers.append(('Access-Control-Allow-Origin', '*',));
|
||||
|
||||
if len(content) == 0:
|
||||
headers.append(('Content-Type', 'text/plain, charset=UTF-8',))
|
||||
start_response('404 Looked everywhere, sorry', headers)
|
||||
else:
|
||||
headers.append(('Content-Type', mime_type,))
|
||||
start_response('200 OK', headers)
|
||||
|
||||
return [content]
|
||||
@@ -22,6 +22,7 @@ from chainlib.eth.connection import (
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainqueue.db.models.otx import Otx
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
import liveness.linux
|
||||
|
||||
|
||||
@@ -36,6 +37,7 @@ from cic_eth.eth import (
|
||||
from cic_eth.admin import (
|
||||
debug,
|
||||
ctrl,
|
||||
token,
|
||||
)
|
||||
from cic_eth.queue import (
|
||||
query,
|
||||
@@ -74,7 +76,6 @@ argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
||||
argparser.add_argument('-r', type=str, help='CIC registry address')
|
||||
argparser.add_argument('--default-token-symbol', dest='default_token_symbol', type=str, help='Symbol of default token to use')
|
||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi')
|
||||
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
@@ -120,20 +121,25 @@ broker = config.get('CELERY_BROKER_URL')
|
||||
if broker[:4] == 'file':
|
||||
bq = tempfile.mkdtemp()
|
||||
bp = tempfile.mkdtemp()
|
||||
current_app.conf.update({
|
||||
conf_update = {
|
||||
'broker_url': broker,
|
||||
'broker_transport_options': {
|
||||
'data_folder_in': bq,
|
||||
'data_folder_out': bq,
|
||||
'data_folder_processed': bp,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
if config.true('CELERY_DEBUG'):
|
||||
conf_update['result_extended'] = True
|
||||
current_app.conf.update(conf_update)
|
||||
logg.warning('celery broker dirs queue i/o {} processed {}, will NOT be deleted on shutdown'.format(bq, bp))
|
||||
else:
|
||||
current_app.conf.update({
|
||||
'broker_url': broker,
|
||||
})
|
||||
conf_update = {
|
||||
'broker_url': broker,
|
||||
}
|
||||
if config.true('CELERY_DEBUG'):
|
||||
conf_update['result_extended'] = True
|
||||
current_app.conf.update(conf_update)
|
||||
|
||||
result = config.get('CELERY_RESULT_URL')
|
||||
if result[:4] == 'file':
|
||||
@@ -188,6 +194,7 @@ def main():
|
||||
except UnknownContractError as e:
|
||||
logg.exception('Registry contract connection failed for {}: {}'.format(config.get('CIC_REGISTRY_ADDRESS'), e))
|
||||
sys.exit(1)
|
||||
logg.info('connected contract registry {}'.format(config.get('CIC_REGISTRY_ADDRESS')))
|
||||
|
||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||
if trusted_addresses_src == None:
|
||||
@@ -202,6 +209,11 @@ def main():
|
||||
|
||||
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
|
||||
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
|
||||
default_token = ERC20Token(chain_spec, rpc, BaseTask.default_token_address)
|
||||
default_token.load(rpc)
|
||||
BaseTask.default_token_decimals = default_token.decimals
|
||||
BaseTask.default_token_name = default_token.name
|
||||
|
||||
BaseTask.run_dir = config.get('CIC_RUN_DIR')
|
||||
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import cic_base.config
|
||||
import cic_base.log
|
||||
import cic_base.argparse
|
||||
import cic_base.rpc
|
||||
from cic_base.eth.syncer import chain_interface
|
||||
from cic_eth_registry.error import UnknownContractError
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
@@ -26,10 +27,8 @@ from hexathon import (
|
||||
strip_0x,
|
||||
)
|
||||
from chainsyncer.backend.sql import SQLBackend
|
||||
from chainsyncer.driver import (
|
||||
HeadSyncer,
|
||||
HistorySyncer,
|
||||
)
|
||||
from chainsyncer.driver.head import HeadSyncer
|
||||
from chainsyncer.driver.history import HistorySyncer
|
||||
from chainsyncer.db.models.base import SessionBase
|
||||
|
||||
# local imports
|
||||
@@ -51,15 +50,23 @@ from cic_eth.registry import (
|
||||
|
||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||
|
||||
def add_block_args(argparser):
|
||||
argparser.add_argument('--history-start', type=int, default=0, dest='history_start', help='Start block height for initial history sync')
|
||||
argparser.add_argument('--no-history', action='store_true', dest='no_history', help='Skip initial history sync')
|
||||
return argparser
|
||||
|
||||
|
||||
logg = cic_base.log.create()
|
||||
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
||||
#argparser = cic_base.argparse.add(argparser, add_traffic_args, 'traffic')
|
||||
argparser = cic_base.argparse.add(argparser, add_block_args, 'block')
|
||||
args = cic_base.argparse.parse(argparser, logg)
|
||||
|
||||
config = cic_base.config.create(args.c, args, args.env_prefix)
|
||||
|
||||
config.add(args.y, '_KEYSTORE_FILE', True)
|
||||
|
||||
config.add(args.q, '_CELERY_QUEUE', True)
|
||||
config.add(args.history_start, 'SYNCER_HISTORY_START', True)
|
||||
config.add(args.no_history, '_NO_HISTORY', True)
|
||||
|
||||
cic_base.config.log(config)
|
||||
|
||||
@@ -69,9 +76,10 @@ SessionBase.connect(dsn, pool_size=16, debug=config.true('DATABASE_DEBUG'))
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||
|
||||
#RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
||||
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
# connect to celery
|
||||
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
||||
@@ -89,7 +97,7 @@ def main():
|
||||
stat = init_chain_stat(rpc, block_start=block_current)
|
||||
loop_interval = stat.block_average()
|
||||
|
||||
logg.debug('starting at block {}'.format(block_offset))
|
||||
logg.debug('current block height {}'.format(block_offset))
|
||||
|
||||
syncers = []
|
||||
|
||||
@@ -98,8 +106,13 @@ def main():
|
||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
||||
|
||||
if len(syncer_backends) == 0:
|
||||
logg.info('found no backends to resume')
|
||||
syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
|
||||
initial_block_start = config.get('SYNCER_HISTORY_START')
|
||||
initial_block_offset = block_offset
|
||||
if config.get('_NO_HISTORY'):
|
||||
initial_block_start = block_offset
|
||||
initial_block_offset += 1
|
||||
syncer_backends.append(SQLBackend.initial(chain_spec, initial_block_offset, start_block_height=initial_block_start))
|
||||
logg.info('found no backends to resume, adding initial sync from history start {} end {}'.format(initial_block_start, initial_block_offset))
|
||||
else:
|
||||
for syncer_backend in syncer_backends:
|
||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||
@@ -108,11 +121,11 @@ def main():
|
||||
|
||||
for syncer_backend in syncer_backends:
|
||||
try:
|
||||
syncers.append(HistorySyncer(syncer_backend))
|
||||
syncers.append(HistorySyncer(syncer_backend, chain_interface))
|
||||
logg.info('Initializing HISTORY syncer on backend {}'.format(syncer_backend))
|
||||
except AttributeError:
|
||||
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
||||
syncers.append(HeadSyncer(syncer_backend))
|
||||
syncers.append(HeadSyncer(syncer_backend, chain_interface))
|
||||
|
||||
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
||||
|
||||
@@ -155,7 +168,6 @@ def main():
|
||||
for cf in callback_filters:
|
||||
syncer.add_filter(cf)
|
||||
|
||||
#r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
||||
r = syncer.loop(int(loop_interval), rpc)
|
||||
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ import confini
|
||||
import celery
|
||||
|
||||
# local imports
|
||||
from cic_eth.api import Api
|
||||
from cic_eth.api import (
|
||||
Api,
|
||||
AdminApi,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
@@ -53,13 +56,20 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
|
||||
queue = args.q
|
||||
|
||||
api = Api(config.get('CIC_CHAIN_SPEC'), queue=queue)
|
||||
admin_api = AdminApi(None)
|
||||
|
||||
def main():
|
||||
t = admin_api.registry()
|
||||
registry_address = t.get()
|
||||
print('Registry: {}'.format(registry_address))
|
||||
|
||||
t = api.default_token()
|
||||
token_info = t.get()
|
||||
print('Default token symbol: {}'.format(token_info['symbol']))
|
||||
print('Default token address: {}'.format(token_info['address']))
|
||||
logg.debug('Default token name: {}'.format(token_info['name']))
|
||||
logg.debug('Default token decimals: {}'.format(token_info['decimals']))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,6 +34,8 @@ class BaseTask(celery.Task):
|
||||
create_gas_oracle = RPCGasOracle
|
||||
default_token_address = None
|
||||
default_token_symbol = None
|
||||
default_token_name = None
|
||||
default_token_decimals = None
|
||||
run_dir = '/run'
|
||||
|
||||
def create_session(self):
|
||||
@@ -116,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,))
|
||||
@@ -129,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()
|
||||
|
||||
@@ -9,8 +9,8 @@ import semver
|
||||
version = (
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
'beta.11',
|
||||
1,
|
||||
'alpha.2',
|
||||
)
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[celery]
|
||||
broker_url = redis://
|
||||
result_url = redis://
|
||||
debug = 0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[celery]
|
||||
broker_url = redis://localhost:63379
|
||||
result_url = redis://localhost:63379
|
||||
debug = 0
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[SYNCER]
|
||||
loop_interval =
|
||||
history_start = 0
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[SYNCER]
|
||||
loop_interval =
|
||||
history_start = 0
|
||||
|
||||
22
apps/cic-eth/doc/texinfo/accounts.texi
Normal file
22
apps/cic-eth/doc/texinfo/accounts.texi
Normal file
@@ -0,0 +1,22 @@
|
||||
@node cic-eth-accounts
|
||||
@section Accounts
|
||||
|
||||
Accounts are private keys in the signer component keyed by "addresses," a one-way transformation of a public key. Data can be signed by using the account as identifier for corresponding RPC requests.
|
||||
|
||||
Any account to be managed by @code{cic-eth} must be created by the corresponding task. This is because @code{cic-eth} creates a @code{nonce} entry for each newly created account, and guarantees that every nonce will only be used once in its threaded environment.
|
||||
|
||||
The calling code receives the account address upon creation. It never receives or has access to the private key.
|
||||
|
||||
|
||||
@subsection Signer RPC
|
||||
|
||||
The signer is expected to handle a subset of the standard JSON-RPC:
|
||||
|
||||
@table @code
|
||||
@item personal_newAccount(password)
|
||||
Creates a new account, returning the account address.
|
||||
@item eth_signTransactions(tx_dict)
|
||||
Sign the transaction represented as a dictionary.
|
||||
@item eth_sign(address, message)
|
||||
Signs an arbtirary message with the standard Ethereum prefix.
|
||||
@end table
|
||||
60
apps/cic-eth/doc/texinfo/admin.texi
Normal file
60
apps/cic-eth/doc/texinfo/admin.texi
Normal file
@@ -0,0 +1,60 @@
|
||||
@node cic-eth system maintenance
|
||||
@appendix Admin API
|
||||
|
||||
The admin API is still in an early stage of refinement. User friendliness can be considerably improved.
|
||||
|
||||
All of the API calls are celery task proxies, and return @code{Celery.AsyncResult} unless otherwise noted.
|
||||
|
||||
In contrast to the client API module, this API does not currently implement a pluggable callback.
|
||||
|
||||
@appendixsection registry
|
||||
|
||||
Returns the @code{ContractRegistry} this instance of @code{cic-eth-tasker} is running on.
|
||||
|
||||
@appendixsection proxy-do
|
||||
|
||||
Execute an arbitary JSON-RPC request using the @code{cic-eth-tasker} blockchain node RPC connection.
|
||||
|
||||
@appendixsection default_token
|
||||
|
||||
Returns the default token symbol and address.
|
||||
|
||||
@appendixsection lock
|
||||
|
||||
Set lock bits, globally or per address
|
||||
|
||||
@appendixsection unlock
|
||||
|
||||
Opposite of lock
|
||||
|
||||
@appendixsection get_lock
|
||||
|
||||
Get the current state of a lock
|
||||
|
||||
@appendixsection tag_account
|
||||
|
||||
Associate an identifier with an account address (@xref{cic-eth system accounts})
|
||||
|
||||
@appendixsection have_account
|
||||
|
||||
Check whether a private key exists in the keystore able to sign on behalf of the given account (it actually performs a signature).
|
||||
|
||||
@appendixsection resend
|
||||
|
||||
Clone or resend a transaction
|
||||
|
||||
@appendixsection check_nonce
|
||||
|
||||
Returns diagnostics for nonce sequences per account, e.g. detect nonce gaps that block execution of further transactions.
|
||||
|
||||
@appendixsection fix_nonce
|
||||
|
||||
Re-orders all nonces by shifting all transaction nonces after the given transaction down by one. This has the additional effect of obsoleting the given transaction. Can be used to close gaps in the nonce sequencing. Use with care!
|
||||
|
||||
@appendixsection account
|
||||
|
||||
Return brief transaction info lists per account
|
||||
|
||||
@appendixsection tx
|
||||
|
||||
Return a complex transaction metadata object for a single transaction. The object assembles state from both the blockchain node and the custodial queue system.
|
||||
18
apps/cic-eth/doc/texinfo/all.texi
Normal file
18
apps/cic-eth/doc/texinfo/all.texi
Normal file
@@ -0,0 +1,18 @@
|
||||
\input texinfo
|
||||
@setfilename index.html
|
||||
@settitle CIC custodial services reference deployment
|
||||
|
||||
@copying
|
||||
Released 2021 under GPL3
|
||||
@end copying
|
||||
|
||||
@titlepage
|
||||
@title CIC custodial services reference deployment
|
||||
@author Louis Holbrook
|
||||
@end titlepage
|
||||
|
||||
@c
|
||||
@contents
|
||||
|
||||
@include index.texi
|
||||
|
||||
4
apps/cic-eth/doc/texinfo/chains.texi
Normal file
4
apps/cic-eth/doc/texinfo/chains.texi
Normal file
@@ -0,0 +1,4 @@
|
||||
@node cic-eth Appendix Task chains
|
||||
@appendix Task chains
|
||||
|
||||
TBC - explain here how to generate these chain diagrams
|
||||
108
apps/cic-eth/doc/texinfo/configuration.texi
Normal file
108
apps/cic-eth/doc/texinfo/configuration.texi
Normal file
@@ -0,0 +1,108 @@
|
||||
@node cic-eth configuration
|
||||
@section Configuration
|
||||
|
||||
(refer to @code{cic-base} for a general overview of the config pipeline)
|
||||
|
||||
Configuration parameters are grouped by configuration filename.
|
||||
|
||||
|
||||
@subsection cic
|
||||
|
||||
@table @var
|
||||
@item registry_address
|
||||
Ethereum address of the @var{ContractRegistry} contract
|
||||
@item chain_spec
|
||||
String representation of the connected blockchain according to the @var{chainlib} @var{ChainSpec} format.
|
||||
@item tx_retry_delay
|
||||
Minimum time in seconds to wait before retrying a transaction
|
||||
@item trust_address
|
||||
Comma-separated list of one or more ethereum addresses regarded as trusted for describing other resources, Used by @var{cic-eth-registry} in the context of the @var{AddressDeclarator}.
|
||||
@item defalt_token_symbol
|
||||
Fallback token to operate on when no other context is given.
|
||||
@item health_modules
|
||||
Comma-separated list of methods to execute liveness tests against. (see ...)
|
||||
@item run_dir
|
||||
Directory to use for session-scoped variables for @var{cic-eth} daemon parent processes.
|
||||
@end table
|
||||
|
||||
|
||||
@subsection celery
|
||||
|
||||
@table @var
|
||||
@item broker_url
|
||||
Message broker URL
|
||||
@item result_url
|
||||
Result backend URL
|
||||
@item debug
|
||||
Boolean value. If set, the amount of available context for a task in the result backend will be maximized@footnote{This is a @emph{required} setting for the task graph documenter to enabled it to display task names in the graph}.
|
||||
@end table
|
||||
|
||||
|
||||
@subsection database
|
||||
|
||||
See ref cic-base when ready
|
||||
|
||||
|
||||
@subsection eth
|
||||
|
||||
@table @var
|
||||
@item provider
|
||||
Address of default RPC endpoint for transactions and state queries.
|
||||
@item gas_gifter_minimum_balance
|
||||
The minimum gas balance that must be held by the @code{GAS GIFTER} token before the queue processing shuts down@footnote{You should really make sure that this threshold is never hit}
|
||||
@end table
|
||||
|
||||
|
||||
@subsection redis
|
||||
|
||||
Defines connection to the redis server used outside of the context of @var{celery}. This is usually the same server, but should be a different db.
|
||||
|
||||
@table @var
|
||||
@item host
|
||||
Redis hostname
|
||||
@item port
|
||||
Redis port
|
||||
@item db
|
||||
Redis db
|
||||
@end table
|
||||
|
||||
|
||||
@subsection signer
|
||||
|
||||
Parameters
|
||||
|
||||
@table @var
|
||||
@item socket_path
|
||||
The connection string for the signer JSON-RPC service.@footnote{The @var{crypto-dev-signer} supports UNIX socket or a HTTP(S) connections}
|
||||
@item secret
|
||||
If set, this password is used to add obfuscation on top of the encryption already applied by the signer for the keystore.
|
||||
@end table
|
||||
|
||||
|
||||
|
||||
@subsection ssl
|
||||
|
||||
Certificate information for https api callbacks.
|
||||
|
||||
@table @var
|
||||
@item enable_client
|
||||
Boolean value. If set, client certificate will be used to authenticate the callback request.
|
||||
@item cert_file
|
||||
Client certificate file in PEM or DER format
|
||||
@item key_file
|
||||
Client key file in PEM or DER format
|
||||
@item password
|
||||
Password for unlocking the client key
|
||||
@item ca_file
|
||||
Certificate authority bundle, to verify the certificate sent by the callback server.
|
||||
@end table
|
||||
|
||||
|
||||
@subsection syncer
|
||||
|
||||
@table @var
|
||||
@item loop_interval
|
||||
Seconds to pause before each execution of the @var{chainsyncer} poll loop.
|
||||
@end table
|
||||
|
||||
|
||||
46
apps/cic-eth/doc/texinfo/dependencies.texi
Normal file
46
apps/cic-eth/doc/texinfo/dependencies.texi
Normal file
@@ -0,0 +1,46 @@
|
||||
@node cic-eth-dependencies
|
||||
@section Dependencies
|
||||
|
||||
This application is written in Python 3.8. It is tightly coupled with @code{python-celery}, which provides the task worker ecosystem. It also uses @code{SQLAlchemy} which provides useful abstractions for persistent storage though SQL, and @code{alembic} for database schema migrations.
|
||||
|
||||
There is currently also a somewhat explicit coupling with @code{Redis}, which is used as message broker for @code{python-celery}. @code{Redis} is also explicitly used by some CLI tools to retrieve results from command execution. This coupling may be relaxed in the future to allow other key-value pubsub solutions instead.
|
||||
|
||||
|
||||
@subsection Generalized project dependencies
|
||||
|
||||
The core features are built around four main independent components that have been developed for the purpose of this project, but are separated and maintained as general-purpose libraries.
|
||||
|
||||
@table @samp
|
||||
@item chainlib
|
||||
A cross-chain library prototype that can provide encodings for transactions on a Solidity-based EVM contract network.
|
||||
@item chainqueue
|
||||
Queue manager that guarantees delivery of outgoing blockchain transactions.
|
||||
@item chainsyncer
|
||||
Monitors blockchains and guarantees execution of an arbitrary count of pluggable code objects for each block transaction.
|
||||
@item crypto-dev-signer
|
||||
An keystore capable of signing for the EVM chain through a standard Ethereum JSON-RPC interface.
|
||||
@end table
|
||||
|
||||
@anchor{cic-eth-dependencies-smart-contracts}
|
||||
@subsection Smart contract dependencies
|
||||
|
||||
The Smart contracts needed by the network must be discoverable through a single entry point called the Contract Registry. The contract registry is expected to reference itself in its records. The authenticity of the contract registry must be guaranteed by external sources of trust.
|
||||
|
||||
The contract registry maps contract addresses to well-known identifiers. The contracts are as follows:
|
||||
|
||||
@table @code
|
||||
@item ContractRegistry (points to self)
|
||||
Resolves plaintext identifiers to contract addresses.
|
||||
@item AccountRegistry
|
||||
An append-only store of accounts hosted by the custodial system
|
||||
@item TokenRegistry
|
||||
Unique symbol-to-address mappings for token contracts
|
||||
@item AddressDeclarator
|
||||
Reverse address to resource lookup
|
||||
@item TokenAuthorization
|
||||
Escrow contract for external spending on behalf of custodial users
|
||||
@item Faucet
|
||||
Called by newly created accounts to receive initial token balance
|
||||
@end table
|
||||
|
||||
The dependency @code{cic-eth-registry} abstracts and facilitates lookups of resources on the blockchain network. In its current state it resolves tokens by symbol or address, and contracts by common-name identifiers. In the @code{cic-eth} code all lookups for EVM network resources will be performed through this dependency.
|
||||
49
apps/cic-eth/doc/texinfo/incoming.texi
Normal file
49
apps/cic-eth/doc/texinfo/incoming.texi
Normal file
@@ -0,0 +1,49 @@
|
||||
@node cic-eth-incoming
|
||||
@section Incoming transactions
|
||||
|
||||
All transactions in mined blocks will be passed to a selection of plugin filters to the @code{chainsyncer} component. Each of these filters are individual python module files in @code{cic_eth.runnable.daemons.filters}. This section describes their function.
|
||||
|
||||
The status bits refer to the bits definining the @code{chainqueue} state.
|
||||
|
||||
|
||||
@subsection tx
|
||||
|
||||
Looks up the transaction in the local queue, and if found it sets the @code{FINAL} state bit. If the contract code execution was unsuccessful, the @code{NETWORK ERROR} state bit is also set.
|
||||
|
||||
|
||||
@subsection gas
|
||||
|
||||
If the transaction is a gas token transfer, it checks if the recipient is a custodial account awaiting gas refill to execute a transaction (the queue item will have the @code{GAS ISSUES} bit set). If this is the case, the transaction will be activated by setting the @code{QUEUED} bit.
|
||||
|
||||
|
||||
@subsection register
|
||||
|
||||
If the transaction is an account registration@footnote{The contract keyed by @var{AccountRegistry} in the @var{ContractRegistry} contract}, a Faucet transaction will be triggered for the registered account@footnote{The faucet contract used in the reference implementation will verify whether the account calling it is registered in the @var{AccountRegistry}. Thus it cannot be called before the account registration has succeeded.}
|
||||
|
||||
|
||||
@subsection callback
|
||||
|
||||
Executes, in order, Celery tasks defined in the configuration variable @var{TASKS_TRANSFER_CALLBACKS}. Each of these tasks are registered as individual filters in the @code{chainsyncer} component, with the corresponding execution guarantees.
|
||||
|
||||
The callbacks will receive the following arguments
|
||||
|
||||
@enumerate
|
||||
@item @strong{result}
|
||||
A complex representation of the transaction (see section ?)
|
||||
@item @strong{transfertype}
|
||||
A string describing the type of transaction found@footnote{See appendix ? for an overview of possible values}
|
||||
@item @strong{status}
|
||||
0 if contract code executed successfully. Any other value is an error@footnote{The values 1-1024 are reserved for system specific errors. In the current implementation only a general error state with value 1 is defined. See appendix ?.}
|
||||
@end enumerate
|
||||
|
||||
|
||||
@subsection transferauth
|
||||
If a valid transfer authorization request has been made, a token @emph{allowance}@footnote{@code{approve} for ERC20 tokens} transaction is executed on behalf of the custodial account, with the @var{TransferAuthorization} contract as spender.
|
||||
|
||||
|
||||
|
||||
@subsection convert
|
||||
If the transaction is a token conversion, @emph{and} there is a pending transfer registered for the conversion, the corresponding token transfer transaction will be executed. Not currently implemented
|
||||
|
||||
|
||||
|
||||
14
apps/cic-eth/doc/texinfo/index.texi
Normal file
14
apps/cic-eth/doc/texinfo/index.texi
Normal file
@@ -0,0 +1,14 @@
|
||||
@top cic-eth
|
||||
|
||||
@include intro.texi
|
||||
@include dependencies.texi
|
||||
@include configuration.texi
|
||||
@include system.texi
|
||||
@include interacting.texi
|
||||
@include outgoing.texi
|
||||
@include incoming.texi
|
||||
@include services.texi
|
||||
@include tools.texi
|
||||
@include admin.texi
|
||||
@include chains.texi
|
||||
@include transfertypes.texi
|
||||
109
apps/cic-eth/doc/texinfo/interacting.texi
Normal file
109
apps/cic-eth/doc/texinfo/interacting.texi
Normal file
@@ -0,0 +1,109 @@
|
||||
@node cic-eth-interacting
|
||||
@section Interacting with the system
|
||||
|
||||
The API to the @var{cic-eth} component is a proxy for executing @emph{chains of Celery tasks}. The tasks that compose individual chains are documented in @ref{cic-eth Appendix Task chains,the Task Chain appendix}, which also describes a CLI tool that can generate graph representationso of them.
|
||||
|
||||
There are two API classes, @var{Api} and @var{AdminApi}. The former is described later in this section, the latter described in @ref{cic-eth system maintenance,the Admin API appendix}.
|
||||
|
||||
|
||||
@subsection Interface
|
||||
|
||||
API calls are constructed by creating @emph{Celery task signatures} and linking them together, sequentially and/or in parallell. In turn, the tasks themselves may spawn other asynchronous tasks. This means that the code in @file{cic_eth.api.*} does not necessarily specify the full task graph that will be executed for any one command.
|
||||
|
||||
The operational guarantee that tasks will be executed, not forgotten, and retried under certain circumstances is deferred to @var{Celery}. On top of this, the @var{chainqueue} component ensures that semantic state changes that the @code{Celery} tasks ask of it are valid.
|
||||
|
||||
|
||||
@anchor{cic-eth-locking}
|
||||
@subsection Locking
|
||||
|
||||
All methods that make a change to the blockchain network must pass @emph{locking layer checks}. Locks may be applied on a global or per-address basis. Lock states are defined by a combination of bit flags. The implemented lock bits are:
|
||||
|
||||
@table @var
|
||||
@item INIT
|
||||
The system has not yet been initialized. In this state, writes are limited to creating unregistered accounts only.
|
||||
@item QUEUE
|
||||
Items may not be added to the queue
|
||||
@item SEND
|
||||
Queued items may not be attempted sent to the network
|
||||
@item CREATE (global-only)
|
||||
New accounts may not be created
|
||||
@item STICKY
|
||||
Until reset, no other part of the locking state can be reset
|
||||
@end table
|
||||
|
||||
|
||||
@subsection Callback
|
||||
|
||||
All API calls provide the option to attach a callback to the end of the task chain. This callback will be executed regardless of whether task chain execution succeeded or not.
|
||||
|
||||
Refer to @file{cic-eth.callbacks.noop.noop} for the expected callback signature.
|
||||
|
||||
|
||||
@subsection API Methods that change state
|
||||
|
||||
|
||||
@subsubsection create_account
|
||||
|
||||
Creates a new account in the keystore, optionally registering the account with the @var{AccountRegistry} contract.
|
||||
|
||||
|
||||
@subsubsection transfer
|
||||
|
||||
Attempts to execute a token transaction between two addresses. It is the caller's responsibility to check whether the token balance is sufficient for the transactions.
|
||||
|
||||
|
||||
@subsubsection refill_gas
|
||||
|
||||
Executes a gas token transfer to a custodial address from the @var{GAS GIFTER} system account.
|
||||
|
||||
|
||||
@subsubsection convert
|
||||
|
||||
Converts a token to another token for the given custodial account. Currently not implemented.
|
||||
|
||||
|
||||
@anchor{cic-eth-convert-and-transfer}
|
||||
@subsubsection convert_and_transfer
|
||||
|
||||
Same as convert, but will automatically execute a token transfer to another custodial account when conversion has been completed. Currently not implemented.
|
||||
|
||||
|
||||
@subsection Read-only API methods
|
||||
|
||||
@subsubsection balance
|
||||
|
||||
Retrieves a complex balance statement of a single account, including:
|
||||
|
||||
@itemize
|
||||
@item The network balance at the current block height
|
||||
@item Value reductions due to by pending outgoing transactions
|
||||
@item Value increments due to by pending incoming transactions
|
||||
@end itemize
|
||||
|
||||
Only the first of these balance items has guaranteed finality. The reduction by outgoing transaction can be reasonably be assumed to eventually become final. The same applies for the increment by incoming transaction, @emph{unless} the transfer is part of a multiple-transaction operation. For example, a @ref{cic-eth-convert-and-transfer,convert_and_transfer} operation may fail in the convert stage and/or may yield less tokens then expected after conversion.
|
||||
|
||||
|
||||
@subsubsection list
|
||||
|
||||
Returns an aggregate iist of all token value changes for a given address. As not all value transfers are a result of literal value transfer contract calls (e.g. @var{transfer} and @var{transferFrom} in @var{ERC20}), this data may come from a number of sources, including:
|
||||
|
||||
@itemize
|
||||
@item Literal value transfers within the custodial system
|
||||
@item Literal value transfers from or to an external address
|
||||
@item Faucet invocations (token minting)
|
||||
@item Demurrage and redistribution built into the token contract
|
||||
@end itemize
|
||||
|
||||
|
||||
@subsubsection default_token
|
||||
|
||||
Return the symbol and address of the token used by default in the network.
|
||||
|
||||
|
||||
@subsubsection ping
|
||||
|
||||
Convenience method for the caller to check whether the @var{cic-eth} engine is alive.
|
||||
|
||||
|
||||
|
||||
|
||||
74
apps/cic-eth/doc/texinfo/outgoing.texi
Normal file
74
apps/cic-eth/doc/texinfo/outgoing.texi
Normal file
@@ -0,0 +1,74 @@
|
||||
@node cic-eth-outgoing
|
||||
@section Outgoing transactions
|
||||
|
||||
@strong{Important! A pre-requisite for proper functioning of the component is that no other agent is sending transactions to the network for any of the keys in the keystore.}
|
||||
|
||||
The term @var{state bit} refers to the bits definining the @code{chainqueue} state.
|
||||
|
||||
@subsection Lock
|
||||
|
||||
Any task that changes blockchain state @strong{must} apply a @code{QUEUE} lock for the address it operates on. This is to ensure that transactions are sent to the network in order.@footnote{If too many transactions arrive out of order to the blockchain node, it may arbitrarily prune those that cannot directly be included in a block. This puts unnecessary strain (and reliance) on the transaction retry mechanism.}
|
||||
|
||||
This lock will be released once the blockchain node confirms handover of the transaction.@footnote{This is the responsibility of the @var{dispatcher} service}
|
||||
|
||||
|
||||
@subsection Nonce
|
||||
|
||||
A separate task step is executed for binding a transaction nonce to a Celery task root id, which uniquely identifies the task chain. This provides atomicity of the nonce across the parallell task environment, and also recoverability in case unexpected program interruption.
|
||||
|
||||
The nonce of a permanently failed task must be @emph{manually} unlocked. Celery tasks that involve nonces who permanently fail are to be considered @emph{critical anomalies} and should not happen. The queue locking mechanism is designed to prevent the amount of out-of-sequence transactions for an account to escalate.
|
||||
|
||||
|
||||
@subsection Choosing fee prices
|
||||
|
||||
@code{cic-eth} uses the @code{chainlib} module to resolve gas price lookups.
|
||||
|
||||
Optimizing gas price discovery should be the responsibility of the chainlib layer. It already accommodates using an separate RPC for the @code{eth_gasPrice} call.@footnote{A sample implementation of a gas price tracker speaking JSON-RPC (also built using chainlib/chainsyncer) can be found at @url{https://gitlab.com/nolash/eth-stat-syncer}.}
|
||||
|
||||
|
||||
@subsection Choosing gas limits
|
||||
|
||||
To determine the gas limit of a transaction, normally the EVM node will be used to perform a dry-run exection of the inputs against the current chain state.
|
||||
|
||||
As the current state of the custodial system should only rely on known, trusted contract bytecode, there is no real need for this mechanism. The @code{chainlib}-based contract interfaces are expected to provide a method call that return safe gas limit values for contract interactions.@footnote{Of course, this method call may in turn conceal more sophisticated gas limit heuristics.}
|
||||
|
||||
Note that it is still the responsibility of @code{cic-eth} to make sure that the gas limit of the network is sufficient to allow execution of all needed contracts.
|
||||
|
||||
|
||||
@subsection Gas refills
|
||||
|
||||
If the gas balance of a custodial account is below a certain threshold, a gas refill task will be spawned. The gas will be transferred from the @code{GAS GIFTER} system account.
|
||||
|
||||
In the event that the balance is insufficient even for the imminent transaction@footnote{This will of course be the case when an account is first created, whereupon it has a balance of 0. The subsequent faucet call will spawn a gas refill task.}, execution of the transaction will be deferred until the gas refill transaction is completed. In this case the transaction will be marked with the @code{GAS ISSUES} state bit.
|
||||
|
||||
The value chosen for the gas refill threshold should ideally allow enough of a margin to avoid the need of deferring transactions in the future.
|
||||
|
||||
|
||||
@subsection Queueing transactions
|
||||
|
||||
Once the lock, nonce and gas processing parts has been completed, the transaction will be queued for sending. This means that the @code{QUEUED} state bit is set. From here the @ref{cic-eth-services-dispatcher,dispatcher service} takes over responsibility.
|
||||
|
||||
|
||||
@subsection Retrying transactions
|
||||
|
||||
There are three conditions create the need to defer and retry transactions.
|
||||
|
||||
The first is communication problems with the blockchain node itself, for example if it is overloaded or being restarted. As far as possible, retries of this nature will be left to the Celery task workers. There may be cases, however, where it is appropriate to hand the responsibility to the @code{chainqueue} instead. In this case, the queue item will have the @code{NODE ERROR} state bit set.
|
||||
|
||||
The second condition occurs when transactions take too long to be confirmed by the network. In this case, the transaction will be re-submitted, but with a higher gas price.
|
||||
|
||||
The third condition occurs when the blockchain node purges the transaction from the mempool before it is sent to the network. @code{cic-eth} does not distinguish this case from the second, as the issue is solved using the same mechanism.
|
||||
|
||||
|
||||
@subsubsection Transaction obsoletion
|
||||
|
||||
"Re-submitting" a transaction means creating a transaction with a previously used nonce for an account address.
|
||||
|
||||
When this happens, The @code{chainqueue} will still contain all previous transactions with the same nonce. The transaction being superseded will have the @code{OBSOLETED} state bit set.
|
||||
|
||||
Once a transaction has been mined, all other transactions with the same node will have the @code{OBSOLETED} and @code{FINAL} state bits set.
|
||||
|
||||
|
||||
@subsection Unexpected conditions
|
||||
|
||||
Any unexpected condition exposing the need for urgent code improvement and/or manual intervention will be signalled by marking the transaction with the @code{FUBAR} state bit set.
|
||||
@@ -1,24 +0,0 @@
|
||||
@node cic-eth
|
||||
@chapter cic-eth
|
||||
|
||||
@section Overview
|
||||
|
||||
@code{cic-eth} is the heart of the custodial account component. It is a combination of python-celery task queues and daemons that sign, dispatch and monitor blockchain transactions, aswell as triggering tasks contingent on other transactions.
|
||||
|
||||
@subsection Dependencies
|
||||
|
||||
The @code{cic-registry} module is used as a cache for contracts and tokens on the network.
|
||||
|
||||
A web3 JSON-RPC service that transparently proxies a keystore and provides transaction and message signing. The current development version uses the python web3 middleware feature to route methodsi involving the keystore to the module @code{crypto-dev-signer}, which is hosted on @file{pypi.org}.
|
||||
|
||||
@subsection What does it do
|
||||
|
||||
|
||||
|
||||
@subsection Tasks
|
||||
|
||||
Two main categories exist for tasks, @code{eth} and @code{queue}.
|
||||
|
||||
The @code{eth} tasks provide means to construct and decode Ethereum transactions, as well as interfacing the underlying key store.
|
||||
|
||||
Tasks in the @code{queue} module operate on the state of transactions queued for processing by @code{cic-eth}.
|
||||
50
apps/cic-eth/doc/texinfo/services.texi
Normal file
50
apps/cic-eth/doc/texinfo/services.texi
Normal file
@@ -0,0 +1,50 @@
|
||||
@node cic-eth-services
|
||||
@section Services
|
||||
|
||||
There are four daemons that together orchestrate all of the aforementioned recipes. This section will provide a high level description of them.
|
||||
|
||||
Each of them have their own set of command line flags. These are available in the CLI help text provided by @kbd{-h} @kbd{--help} and are not recited here.
|
||||
|
||||
Daemon executable scripts are located in the @file{cic_eth.runnable.daemons} package. If @var{cic-eth} is installed as a python package, they are installed as executables in @var{PATH}.
|
||||
|
||||
|
||||
@subsection tasker
|
||||
|
||||
This is the heart of the custodial system. Tasker is the parent process for the celery workers executing all tasks interacting with and changing the state of the queue and the chain. It is also the only service that interfaces with the signer/keystore.
|
||||
|
||||
The other @var{cic-eth} daemons all interface with this component, along with any client adapter bridging an end-user gateway (e.g. @var{cic-ussd}). However, the service itself does not have to be actively running for the other services to run; @var{Celery} handles queueing up the incoming tasks until the @var{tasker} comes back online.@footnote{Whereas this is true, there is currently no fail-safe implemented to handles the event of task backlog overflow in Celery. Furthermore, no targeted testing has yet been performed to asses the stability of the system over time if a sudden, sustained surge of resumed task executions occurs. It may be advisable to suspend activity that adds new queue items to the system if volume is high and/or the @var{cic-eth} outage endures. However, there is no panacea for this condition, as every usage scenario is different}
|
||||
|
||||
The tasker has a set of pre-requisites that must be fulfilled before it will start
|
||||
|
||||
@itemize
|
||||
@item It must be given a valid @var{ContractRegistry} address, which must include valid references to all contracts specified in @ref{cic-eth-dependencies-smart-contracts,Smart contract dependencies}
|
||||
@item The gas gifter balance must be above the minimum threshold (See "eth" section in configurations).
|
||||
@item There must be a valid alembic migration record in the storage database
|
||||
@item The redis backend must be reachable and writable
|
||||
@item There must be a reachable JSON-RPC server at the other end of the signer socket path (see "signer" section in configurations)
|
||||
@end itemize
|
||||
|
||||
|
||||
@subsection tracker
|
||||
|
||||
Implements the @var{chainsyncer}, and registers the filters described in @ref{cic-eth-incoming,Incoming Transactions} to be executed for every transaction. It consumes the appropriate @var{TASKS_TRANSFER_CALLBACKS} configuration setting to add externally defined filters at without having to change the daemon code.
|
||||
|
||||
The @var{tracker} has the same requisities for the @var{ContractRegistry} as the @var{tasker}.
|
||||
|
||||
@strong{Important! Guarantees of filter executions has some caveats. Refer to the @var{chainsyncer} documentation for more details.}
|
||||
|
||||
|
||||
@anchor{cic-eth-services-dispatcher}
|
||||
@subsection dispatcher
|
||||
|
||||
Uses the @code{get_upcoming_tx} method call from @var{chainqueue} to receive batches of queued transactions that are ready to send to the blockchain node. Every batch will only contain a single transaction by any one address, which will be the transaction with the next nonce not previously seen by the network. There is no limit currently set to how many transactions that will be included in a single batch.
|
||||
|
||||
|
||||
@subsection retrier
|
||||
|
||||
The responsibility of the @var{retrier} is to re-queue transactions that failed to be sent to the blockchain node, as well as create @emph{replacements} for transactions whose processing by the network has been delayed. @strong{[refer transaction obolestion]}.
|
||||
|
||||
It is in turn the responsiblity of the @var{dispatcher} to send these (re-)queued transactions to the blockchain node.
|
||||
|
||||
|
||||
|
||||
17
apps/cic-eth/doc/texinfo/system.texi
Normal file
17
apps/cic-eth/doc/texinfo/system.texi
Normal file
@@ -0,0 +1,17 @@
|
||||
@node cic-eth system accounts
|
||||
@section System initialization
|
||||
|
||||
When the system starts for the first time, it is locked for any state change request other than account creation@footnote{Specifically, the @code{INIT}, @code{SEND} and @code{QUEUE} lock bits are set.}. These locks should be @emph{reset} once system initialization has been completed. Currently, system initialization only involves creating and tagging required system accounts, as specified below.
|
||||
|
||||
See @ref{cic-eth-locking,Locking} and @ref{cic-eth-tools-ctrl,ctrl in Tools} for details on locking.
|
||||
|
||||
@subsection System accounts
|
||||
|
||||
Certain accounts in the system have special roles. These are defined by @emph{tagging} certain accounts addresses with well-known identifiers.
|
||||
|
||||
@table @var
|
||||
@item GAS_GIFTER
|
||||
This account @strong{must} at all times have enough gas token to fund any custodial account address in need.
|
||||
@item ACCOUNT_REGISTRY_WRITER
|
||||
This account @strong{must} have access to add newly created account addresses to the (@xref{cic-eth-dependencies-smart-contracts,Smart contract dependencies})
|
||||
@end table
|
||||
51
apps/cic-eth/doc/texinfo/tools.texi
Normal file
51
apps/cic-eth/doc/texinfo/tools.texi
Normal file
@@ -0,0 +1,51 @@
|
||||
@node cic-eth-tools
|
||||
@section Tools
|
||||
|
||||
A collection of CLI tools have been provided to help with diagnostics and other administrative tasks. These use the same configuration infrastructure as the daemons.
|
||||
|
||||
Tool scripts are located in the @file{cic_eth.runnable} package. If @var{cic-eth} is installed as a python package, they are installed as executables in @var{PATH}.
|
||||
|
||||
|
||||
@subsection info (cic-eth-info)
|
||||
|
||||
Returns self-explanatory metadata for the blockchain network, and optionally an address.
|
||||
|
||||
|
||||
@subsection inspect (cic-eth-inspect)
|
||||
|
||||
Returns information about a specific resource related to the tranasaction queue. The results returned depend on the type of the argument.
|
||||
|
||||
@table @var
|
||||
@item lock
|
||||
If the argument is the literal string @kbd{lock}, it will list all active lock settings currently in effect. (@xref{cic-eth-locking})
|
||||
@item <address>
|
||||
If the argument is a 0x-prefixed hex string of 42 characters, it returns all transactions where the specified address is a sender or recipient@footnote{If the address is the gas gifter or the accounts index writer, this may be a @emph{lot} of transactions. Use with care!}
|
||||
@item <tx_hash>
|
||||
If the argument is a 0x-prefixed hex string of 66 characters, it returns data from the custodial queueing system aswell as the network for a single transaction whose hash matches the input. Fails if the transaction does not exist in the queue
|
||||
@item <code>
|
||||
If the argument is a 0x-prefixed hex string longer than 66 bytes, the argument will be interpreted as raw RLP serialized transaction data, and attempt to match this with an entry in the queue. If a match is found, the result is the same as for @var{<tx_hash>}
|
||||
@end table
|
||||
|
||||
|
||||
@subsection create (cic-eth-create)
|
||||
|
||||
Create a new account, optionally registering the account in the accounts registry, and optionally receiving the newly created address through a redis subscription.
|
||||
|
||||
@subsection transfer (cic-eth-transfer)
|
||||
|
||||
Execute a token transfer on behalf of a custodial account.
|
||||
|
||||
@subsection tag (cic-eth-tag)
|
||||
|
||||
Associate an account address with a string identifier. @xref{cic-eth system accounts}
|
||||
|
||||
|
||||
@anchor{cic-eth-tools-ctrl}
|
||||
@subsection ctrl (cic-eth-ctrl)
|
||||
|
||||
Set or reset lock bits, globally or per account address.
|
||||
|
||||
@subsection resend (cic-eth-resend)
|
||||
|
||||
Resend a transaction. This can either be done "in-place," which means increasing the gas price and re-queueing@footnote{this is the same thing that the retrier does}. It can also be used to @emph{clone} a transaction, which obviously will duplicate the effect of the cloned transaction on the blockchain network.
|
||||
|
||||
11
apps/cic-eth/doc/texinfo/transfertypes.texi
Normal file
11
apps/cic-eth/doc/texinfo/transfertypes.texi
Normal file
@@ -0,0 +1,11 @@
|
||||
@node cic-eth Appendix Transaction types
|
||||
@appendix Transfer types
|
||||
|
||||
@table @var
|
||||
@item transfer
|
||||
A regular token transfer, e.g. ERC20 @code{transfer}
|
||||
@item transferfrom
|
||||
A token transfer performed on behalf of another party, e.g. ERC20 @code{transferFrom}
|
||||
@item tokengift
|
||||
Result of a successful faucet request.
|
||||
@end table
|
||||
@@ -1,48 +1,62 @@
|
||||
# FROM grassrootseconomics:cic
|
||||
|
||||
#FROM python:3.8.6-alpine
|
||||
FROM python:3.8.6-slim-buster
|
||||
|
||||
#COPY --from=0 /usr/local/share/cic/solidity/ /usr/local/share/cic/solidity/
|
||||
FROM python:3.8.6-slim-buster as compile
|
||||
|
||||
WORKDIR /usr/src/cic-eth
|
||||
|
||||
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||
ARG root_requirement_file='requirements.txt'
|
||||
|
||||
#RUN apk update && \
|
||||
# apk add gcc musl-dev gnupg libpq
|
||||
#RUN apk add postgresql-dev
|
||||
#RUN apk add linux-headers
|
||||
#RUN apk add libffi-dev
|
||||
RUN apt-get update && \
|
||||
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
|
||||
|
||||
# Copy shared requirements from top of mono-repo
|
||||
RUN echo "copying root req file ${root_requirement_file}"
|
||||
#COPY $root_requirement_file .
|
||||
#RUN pip install -r $root_requirement_file $pip_extra_index_url_flag
|
||||
RUN /usr/local/bin/python -m pip install --upgrade pip
|
||||
#RUN git clone https://gitlab.com/grassrootseconomics/cic-base.git && \
|
||||
# cd cic-base && \
|
||||
# git checkout 7ae1f02efc206b13a65873567b0f6d1c3b7f9bc0 && \
|
||||
# python merge_requirements.py | tee merged_requirements.txt
|
||||
#RUN cd cic-base && \
|
||||
# pip install $pip_extra_index_url_flag -r ./merged_requirements.txt
|
||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a77
|
||||
#RUN python -m venv venv && . venv/bin/activate
|
||||
|
||||
COPY cic-eth/scripts/ scripts/
|
||||
COPY cic-eth/setup.cfg cic-eth/setup.py ./
|
||||
COPY cic-eth/cic_eth/ cic_eth/
|
||||
# Copy app specific requirements
|
||||
COPY cic-eth/requirements.txt .
|
||||
COPY cic-eth/test_requirements.txt .
|
||||
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||
RUN /usr/local/bin/python -m pip install --upgrade pip
|
||||
RUN pip install semver
|
||||
|
||||
# TODO use a packaging style that lets us copy requirments only ie. pip-tools
|
||||
COPY cic-eth/ .
|
||||
RUN pip install $pip_extra_index_url_flag .
|
||||
|
||||
# --- TEST IMAGE ---
|
||||
FROM python:3.8.6-slim-buster as test
|
||||
|
||||
RUN apt-get update && \
|
||||
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
|
||||
|
||||
WORKDIR /usr/src/cic-eth
|
||||
|
||||
RUN /usr/local/bin/python -m pip install --upgrade pip
|
||||
|
||||
COPY --from=compile /usr/local/bin/ /usr/local/bin/
|
||||
COPY --from=compile /usr/local/lib/python3.8/site-packages/ \
|
||||
/usr/local/lib/python3.8/site-packages/
|
||||
# TODO we could use venv inside container to isolate the system and app deps further
|
||||
# COPY --from=compile /usr/src/cic-eth/ .
|
||||
# RUN . venv/bin/activate
|
||||
|
||||
COPY cic-eth/test_requirements.txt .
|
||||
RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
|
||||
|
||||
COPY cic-eth .
|
||||
|
||||
ENV PYTHONPATH .
|
||||
|
||||
ENTRYPOINT ["pytest"]
|
||||
|
||||
# --- RUNTIME ---
|
||||
FROM python:3.8.6-slim-buster as runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt install -y gnupg libpq-dev procps
|
||||
|
||||
WORKDIR /usr/src/cic-eth
|
||||
|
||||
COPY --from=compile /usr/local/bin/ /usr/local/bin/
|
||||
COPY --from=compile /usr/local/lib/python3.8/site-packages/ \
|
||||
/usr/local/lib/python3.8/site-packages/
|
||||
|
||||
COPY cic-eth/docker/* ./
|
||||
RUN chmod 755 *.sh
|
||||
COPY cic-eth/tests/ tests/
|
||||
|
||||
COPY cic-eth/scripts/ scripts/
|
||||
# # ini files in config directory defines the configurable parameters for the application
|
||||
# # they can all be overridden by environment variables
|
||||
# # to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
|
||||
@@ -51,3 +65,4 @@ COPY cic-eth/cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
|
||||
COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
|
||||
|
||||
COPY util/liveness/health.sh /usr/local/bin/health.sh
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
cic-base==0.1.2b5
|
||||
cic-base==0.1.3a3+build.4aa03607
|
||||
celery==4.4.7
|
||||
crypto-dev-signer~=0.4.14b3
|
||||
crypto-dev-signer~=0.4.14b6
|
||||
confini~=0.3.6rc3
|
||||
cic-eth-registry~=0.5.4a16
|
||||
#cic-bancor~=0.0.6
|
||||
cic-eth-registry~=0.5.6a1
|
||||
redis==3.5.3
|
||||
alembic==1.4.2
|
||||
websockets==8.1
|
||||
requests~=2.24.0
|
||||
eth_accounts_index~=0.0.11a9
|
||||
erc20-transfer-authorization~=0.3.1a5
|
||||
eth_accounts_index~=0.0.12a1
|
||||
erc20-transfer-authorization~=0.3.2a1
|
||||
uWSGI==2.0.19.1
|
||||
semver==2.13.0
|
||||
websocket-client==0.57.0
|
||||
moolb~=0.1.1b2
|
||||
eth-address-index~=0.1.1a9
|
||||
chainlib~=0.0.2a20
|
||||
eth-address-index~=0.1.2a1
|
||||
chainlib-eth~=0.0.5a1
|
||||
hexathon~=0.0.1a7
|
||||
chainsyncer[sql]~=0.0.2a2
|
||||
chainqueue~=0.0.2a2
|
||||
pysha3==1.0.2
|
||||
chainsyncer[sql]~=0.0.3a3
|
||||
chainqueue~=0.0.2b5
|
||||
sarafu-faucet~=0.0.4a1
|
||||
erc20-faucet~=0.2.2a1
|
||||
coincurve==15.0.0
|
||||
sarafu-faucet==0.0.2a28
|
||||
potaahto~=0.0.1a1
|
||||
potaahto~=0.0.1a2
|
||||
pycryptodome==3.10.1
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
import alembic
|
||||
from alembic.config import Config as AlembicConfig
|
||||
@@ -23,6 +25,8 @@ argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('--migrations-dir', dest='migrations_dir', default=migrationsdir, type=str, help='path to alembic migrations directory')
|
||||
argparser.add_argument('--reset', action='store_true', help='downgrade before upgrading')
|
||||
argparser.add_argument('-f', action='store_true', help='force action')
|
||||
argparser.add_argument('-v', action='store_true', help='be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||
args = argparser.parse_args()
|
||||
@@ -53,4 +57,10 @@ ac = AlembicConfig(os.path.join(migrations_dir, 'alembic.ini'))
|
||||
ac.set_main_option('sqlalchemy.url', dsn)
|
||||
ac.set_main_option('script_location', migrations_dir)
|
||||
|
||||
if args.reset:
|
||||
if not args.f:
|
||||
if not re.match(r'[yY][eE]?[sS]?', input('EEK! this will DELETE the existing db. are you sure??')):
|
||||
logg.error('user chickened out on requested reset, bailing')
|
||||
sys.exit(1)
|
||||
alembic.command.downgrade(ac, 'base')
|
||||
alembic.command.upgrade(ac, 'head')
|
||||
|
||||
@@ -11,17 +11,6 @@ while True:
|
||||
requirements.append(l.rstrip())
|
||||
f.close()
|
||||
|
||||
test_requirements = []
|
||||
f = open('test_requirements.txt', 'r')
|
||||
while True:
|
||||
l = f.readline()
|
||||
if l == '':
|
||||
break
|
||||
test_requirements.append(l.rstrip())
|
||||
f.close()
|
||||
|
||||
|
||||
setup(
|
||||
install_requires=requirements,
|
||||
tests_require=test_requirements,
|
||||
install_requires=requirements
|
||||
)
|
||||
|
||||
@@ -4,4 +4,3 @@ pytest-mock==3.3.1
|
||||
pytest-cov==2.10.1
|
||||
eth-tester==0.5.0b3
|
||||
py-evm==0.3.0a20
|
||||
giftable-erc20-token==0.0.8a9
|
||||
|
||||
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 chainlib.eth.erc20 import ERC20
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ from chainlib.eth.tx import (
|
||||
Tx,
|
||||
)
|
||||
from chainlib.eth.block import Block
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from eth_erc20 import ERC20
|
||||
from sarafu_faucet import MinterFaucet
|
||||
from eth_accounts_index import AccountRegistry
|
||||
from eth_accounts_index.registry import AccountRegistry
|
||||
from potaahto.symbols import snake_and_camel
|
||||
from hexathon import add_0x
|
||||
|
||||
@@ -26,7 +26,6 @@ from cic_eth.runnable.daemons.filters.callback import CallbackFilter
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transfer_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
@@ -66,7 +65,6 @@ def test_transfer_tx(
|
||||
assert transfer_type == 'transfer'
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transfer_from_tx(
|
||||
default_chain_spec,
|
||||
init_database,
|
||||
|
||||
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
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
# external imports
|
||||
import pytest
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from eth_erc20 import ERC20
|
||||
from chainlib.eth.tx import receipt
|
||||
|
||||
# local imports
|
||||
|
||||
@@ -9,7 +9,7 @@ import celery
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from eth_accounts_index import AccountRegistry
|
||||
from eth_accounts_index.registry import AccountRegistry
|
||||
from hexathon import strip_0x
|
||||
from chainqueue.db.enum import StatusEnum
|
||||
from chainqueue.db.models.otx import Otx
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user