Compare commits
3 Commits
lash/allow
...
lash/exter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5305b332
|
||
|
|
2101a23ccb
|
||
|
|
af97b7799f
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,15 +1,2 @@
|
|||||||
service-configs/*
|
service-configs/*
|
||||||
!service-configs/.gitkeep
|
!service-configs/.gitkeep
|
||||||
**/node_modules/
|
|
||||||
__pycache__
|
|
||||||
*.pyc
|
|
||||||
*.o
|
|
||||||
gmon.out
|
|
||||||
*.egg-info
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
**/*sqlite
|
|
||||||
**/.nyc_output
|
|
||||||
**/coverage
|
|
||||||
**/.venv
|
|
||||||
.idea
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ include:
|
|||||||
- local: 'apps/cic-notify/.gitlab-ci.yml'
|
- local: 'apps/cic-notify/.gitlab-ci.yml'
|
||||||
- local: 'apps/cic-meta/.gitlab-ci.yml'
|
- local: 'apps/cic-meta/.gitlab-ci.yml'
|
||||||
- local: 'apps/cic-cache/.gitlab-ci.yml'
|
- local: 'apps/cic-cache/.gitlab-ci.yml'
|
||||||
- local: 'apps/data-seeding/.gitlab-ci.yml'
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
DEBUG=
|
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=sqlite
|
ENGINE=sqlite
|
||||||
DRIVER=pysqlite
|
DRIVER=pysqlite
|
||||||
DEBUG=
|
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import moolb
|
import moolb
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_cache.db.list import (
|
from cic_cache.db import list_transactions_mined
|
||||||
list_transactions_mined,
|
from cic_cache.db import list_transactions_account_mined
|
||||||
list_transactions_account_mined,
|
|
||||||
list_transactions_mined_with_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
class Cache:
|
class BloomCache:
|
||||||
|
|
||||||
def __init__(self, session):
|
def __init__(self, session):
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
|
|
||||||
class BloomCache(Cache):
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_filter_size(n):
|
def __get_filter_size(n):
|
||||||
n = 8192 * 8
|
n = 8192 * 8
|
||||||
@@ -93,44 +87,3 @@ class BloomCache(Cache):
|
|||||||
f_blocktx.add(block + tx)
|
f_blocktx.add(block + tx)
|
||||||
logg.debug('added block {} tx {} lo {} hi {}'.format(r[0], r[1], lowest_block, highest_block))
|
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(),)
|
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)
|
|
||||||
|
|||||||
@@ -2,14 +2,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from .list import (
|
from .list import list_transactions_mined
|
||||||
list_transactions_mined,
|
from .list import list_transactions_account_mined
|
||||||
list_transactions_account_mined,
|
from .list import add_transaction
|
||||||
add_transaction,
|
|
||||||
tag_transaction,
|
|
||||||
add_tag,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
from cic_cache.db.models.base import SessionBase
|
from cic_cache.db.models.base import SessionBase
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
@@ -28,26 +27,6 @@ def list_transactions_mined(
|
|||||||
return r
|
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(
|
def list_transactions_account_mined(
|
||||||
session,
|
session,
|
||||||
address,
|
address,
|
||||||
@@ -71,8 +50,7 @@ def list_transactions_account_mined(
|
|||||||
|
|
||||||
|
|
||||||
def add_transaction(
|
def add_transaction(
|
||||||
session,
|
session, tx_hash,
|
||||||
tx_hash,
|
|
||||||
block_number,
|
block_number,
|
||||||
tx_index,
|
tx_index,
|
||||||
sender,
|
sender,
|
||||||
@@ -84,33 +62,6 @@ def add_transaction(
|
|||||||
success,
|
success,
|
||||||
timestamp,
|
timestamp,
|
||||||
):
|
):
|
||||||
"""Adds a single transaction to the cache persistent storage. Sensible interpretation of all fields is the responsibility of the caller.
|
|
||||||
|
|
||||||
:param session: Persistent storage session object
|
|
||||||
:type session: SQLAlchemy session
|
|
||||||
:param tx_hash: Transaction hash
|
|
||||||
:type tx_hash: str, 0x-hex
|
|
||||||
:param block_number: Block number
|
|
||||||
:type block_number: int
|
|
||||||
:param tx_index: Transaction index in block
|
|
||||||
:type tx_index: int
|
|
||||||
:param sender: Ethereum address of effective sender
|
|
||||||
:type sender: str, 0x-hex
|
|
||||||
:param receiver: Ethereum address of effective recipient
|
|
||||||
:type receiver: str, 0x-hex
|
|
||||||
:param source_token: Ethereum address of token used by sender
|
|
||||||
:type source_token: str, 0x-hex
|
|
||||||
:param destination_token: Ethereum address of token received by recipient
|
|
||||||
:type destination_token: str, 0x-hex
|
|
||||||
:param from_value: Source token value spent in transaction
|
|
||||||
:type from_value: int
|
|
||||||
:param to_value: Destination token value received in transaction
|
|
||||||
:type to_value: int
|
|
||||||
:param success: True if code execution on network was successful
|
|
||||||
:type success: bool
|
|
||||||
:param date_block: Block timestamp
|
|
||||||
:type date_block: datetime
|
|
||||||
"""
|
|
||||||
date_block = datetime.datetime.fromtimestamp(timestamp)
|
date_block = datetime.datetime.fromtimestamp(timestamp)
|
||||||
s = "INSERT INTO tx (tx_hash, block_number, tx_index, sender, recipient, source_token, destination_token, from_value, to_value, success, date_block) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', {}, {}, {}, '{}')".format(
|
s = "INSERT INTO tx (tx_hash, block_number, tx_index, sender, recipient, source_token, destination_token, from_value, to_value, success, date_block) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', {}, {}, {}, '{}')".format(
|
||||||
tx_hash,
|
tx_hash,
|
||||||
@@ -126,74 +77,3 @@ def add_transaction(
|
|||||||
date_block,
|
date_block,
|
||||||
)
|
)
|
||||||
session.execute(s)
|
session.execute(s)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def tag_transaction(
|
|
||||||
session,
|
|
||||||
tx_hash,
|
|
||||||
name,
|
|
||||||
domain=None,
|
|
||||||
):
|
|
||||||
"""Tag a single transaction with a single tag.
|
|
||||||
|
|
||||||
Tag must already exist in storage.
|
|
||||||
|
|
||||||
:param session: Persistent storage session object
|
|
||||||
:type session: SQLAlchemy session
|
|
||||||
:param tx_hash: Transaction hash
|
|
||||||
:type tx_hash: str, 0x-hex
|
|
||||||
:param name: Tag value
|
|
||||||
:type name: str
|
|
||||||
:param domain: Tag domain
|
|
||||||
:type domain: str
|
|
||||||
:raises ValueError: Unknown tag or transaction hash
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
s = text("SELECT id from tx where tx_hash = :a")
|
|
||||||
r = session.execute(s, {'a': tx_hash}).fetchall()
|
|
||||||
tx_id = r[0].values()[0]
|
|
||||||
|
|
||||||
if tx_id == None:
|
|
||||||
raise ValueError('unknown tx hash {}'.format(tx_hash))
|
|
||||||
|
|
||||||
#s = text("SELECT id from tag where value = :a and domain = :b")
|
|
||||||
if domain == None:
|
|
||||||
s = text("SELECT id from tag where value = :a")
|
|
||||||
else:
|
|
||||||
s = text("SELECT id from tag where value = :a and domain = :b")
|
|
||||||
r = session.execute(s, {'a': name, 'b': domain}).fetchall()
|
|
||||||
tag_id = r[0].values()[0]
|
|
||||||
|
|
||||||
logg.debug('type {} {}'.format(type(tag_id), type(tx_id)))
|
|
||||||
|
|
||||||
if tag_id == None:
|
|
||||||
raise ValueError('unknown tag name {} domain {}'.format(name, domain))
|
|
||||||
|
|
||||||
s = text("INSERT INTO tag_tx_link (tag_id, tx_id) VALUES (:a, :b)")
|
|
||||||
r = session.execute(s, {'a': int(tag_id), 'b': int(tx_id)})
|
|
||||||
|
|
||||||
|
|
||||||
def add_tag(
|
|
||||||
session,
|
|
||||||
name,
|
|
||||||
domain=None,
|
|
||||||
):
|
|
||||||
"""Add a single tag to storage.
|
|
||||||
|
|
||||||
:param session: Persistent storage session object
|
|
||||||
:type session: SQLAlchemy session
|
|
||||||
:param name: Tag value
|
|
||||||
:type name: str
|
|
||||||
:param domain: Tag domain
|
|
||||||
:type domain: str
|
|
||||||
:raises sqlalchemy.exc.IntegrityError: Tag already exists
|
|
||||||
"""
|
|
||||||
|
|
||||||
s = None
|
|
||||||
if domain == None:
|
|
||||||
s = text("INSERT INTO tag (value) VALUES (:b)")
|
|
||||||
else:
|
|
||||||
s = text("INSERT INTO tag (domain, value) VALUES (:a, :b)")
|
|
||||||
session.execute(s, {'a': domain, 'b': name})
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
"""Transaction tags
|
|
||||||
|
|
||||||
Revision ID: aaf2bdce7d6e
|
|
||||||
Revises: 6604de4203e2
|
|
||||||
Create Date: 2021-05-01 09:20:20.775082
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'aaf2bdce7d6e'
|
|
||||||
down_revision = '6604de4203e2'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
op.create_table(
|
|
||||||
'tag',
|
|
||||||
sa.Column('id', sa.Integer, primary_key=True),
|
|
||||||
sa.Column('domain', sa.String(), nullable=True),
|
|
||||||
sa.Column('value', sa.String(), nullable=False),
|
|
||||||
)
|
|
||||||
op.create_index('idx_tag_domain_value', 'tag', ['domain', 'value'], unique=True)
|
|
||||||
|
|
||||||
op.create_table(
|
|
||||||
'tag_tx_link',
|
|
||||||
sa.Column('id', sa.Integer, primary_key=True),
|
|
||||||
sa.Column('tag_id', sa.Integer, sa.ForeignKey('tag.id'), nullable=False),
|
|
||||||
sa.Column('tx_id', sa.Integer, sa.ForeignKey('tx.id'), nullable=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
op.drop_table('tag_tx_link')
|
|
||||||
op.drop_index('idx_tag_domain_value')
|
|
||||||
op.drop_table('tag')
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
from .erc20 import *
|
from .erc20 import *
|
||||||
from .faucet import *
|
|
||||||
|
|||||||
@@ -1,27 +1,2 @@
|
|||||||
class TagSyncFilter:
|
class SyncFilter:
|
||||||
"""Holds tag name and domain for an implementing filter.
|
pass
|
||||||
|
|
||||||
:param name: Tag value
|
|
||||||
:type name: str
|
|
||||||
:param domain: Tag domain
|
|
||||||
:type domain: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, domain=None):
|
|
||||||
self.tag_name = name
|
|
||||||
self.tag_domain = domain
|
|
||||||
|
|
||||||
|
|
||||||
def tag(self):
|
|
||||||
"""Return tag value/domain.
|
|
||||||
|
|
||||||
:rtype: Tuple
|
|
||||||
:returns: tag value/domain.
|
|
||||||
"""
|
|
||||||
return (self.tag_name, self.tag_domain)
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.tag_domain == None:
|
|
||||||
return self.tag_name
|
|
||||||
return '{}.{}'.format(self.tag_domain, self.tag_name)
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.address import (
|
from chainlib.eth.address import (
|
||||||
to_checksum_address,
|
to_checksum_address,
|
||||||
)
|
)
|
||||||
@@ -12,19 +13,17 @@ from cic_eth_registry.error import (
|
|||||||
NotAContractError,
|
NotAContractError,
|
||||||
ContractMismatchError,
|
ContractMismatchError,
|
||||||
)
|
)
|
||||||
from eth_erc20 import ERC20
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from .base import TagSyncFilter
|
from .base import SyncFilter
|
||||||
from cic_cache import db as cic_cache_db
|
from cic_cache import db as cic_cache_db
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger().getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ERC20TransferFilter(TagSyncFilter):
|
class ERC20TransferFilter(SyncFilter):
|
||||||
|
|
||||||
def __init__(self, chain_spec):
|
def __init__(self, chain_spec):
|
||||||
super(ERC20TransferFilter, self).__init__('transfer', domain='erc20')
|
|
||||||
self.chain_spec = chain_spec
|
self.chain_spec = chain_spec
|
||||||
|
|
||||||
|
|
||||||
@@ -47,9 +46,6 @@ class ERC20TransferFilter(TagSyncFilter):
|
|||||||
except RequestMismatchException:
|
except RequestMismatchException:
|
||||||
logg.debug('erc20 match but not a transfer, skipping')
|
logg.debug('erc20 match but not a transfer, skipping')
|
||||||
return False
|
return False
|
||||||
except ValueError:
|
|
||||||
logg.debug('erc20 match but bogus data, skipping')
|
|
||||||
return False
|
|
||||||
|
|
||||||
token_sender = tx.outputs[0]
|
token_sender = tx.outputs[0]
|
||||||
token_recipient = transfer_data[0]
|
token_recipient = transfer_data[0]
|
||||||
@@ -72,12 +68,5 @@ class ERC20TransferFilter(TagSyncFilter):
|
|||||||
block.timestamp,
|
block.timestamp,
|
||||||
)
|
)
|
||||||
db_session.flush()
|
db_session.flush()
|
||||||
cic_cache_db.tag_transaction(
|
|
||||||
db_session,
|
|
||||||
tx.hash,
|
|
||||||
self.tag_name,
|
|
||||||
domain=self.tag_domain,
|
|
||||||
)
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
# 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,20 +1,18 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import confini
|
import confini
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
|
from cic_cache import BloomCache
|
||||||
from cic_cache.db import dsn_from_config
|
from cic_cache.db import dsn_from_config
|
||||||
from cic_cache.db.models.base import SessionBase
|
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)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -46,6 +44,72 @@ logg.debug('config:\n{}'.format(config))
|
|||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
SessionBase.connect(dsn, config.true('DATABASE_DEBUG'))
|
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
|
# uwsgi application
|
||||||
def application(env, start_response):
|
def application(env, start_response):
|
||||||
@@ -55,16 +119,10 @@ def application(env, start_response):
|
|||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
for handler in [
|
for handler in [
|
||||||
process_transactions_all_data,
|
|
||||||
process_transactions_all_bloom,
|
process_transactions_all_bloom,
|
||||||
process_transactions_account_bloom,
|
process_transactions_account_bloom,
|
||||||
]:
|
]:
|
||||||
r = None
|
r = handler(session, env)
|
||||||
try:
|
|
||||||
r = handler(session, env)
|
|
||||||
except ValueError as e:
|
|
||||||
start_response('400 {}'.format(str(e)))
|
|
||||||
return []
|
|
||||||
if r != None:
|
if r != None:
|
||||||
(mime_type, content) = r
|
(mime_type, content) = r
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import confini
|
import confini
|
||||||
import celery
|
import celery
|
||||||
import sqlalchemy
|
|
||||||
import rlp
|
import rlp
|
||||||
import cic_base.config
|
import cic_base.config
|
||||||
import cic_base.log
|
import cic_base.log
|
||||||
import cic_base.argparse
|
import cic_base.argparse
|
||||||
import cic_base.rpc
|
import cic_base.rpc
|
||||||
from cic_base.eth.syncer import chain_interface
|
|
||||||
from cic_eth_registry import CICRegistry
|
from cic_eth_registry import CICRegistry
|
||||||
from cic_eth_registry.error import UnknownContractError
|
from cic_eth_registry.error import UnknownContractError
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
@@ -28,38 +26,26 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend.sql import SQLBackend
|
from chainsyncer.backend import SyncerBackend
|
||||||
from chainsyncer.driver.head import HeadSyncer
|
from chainsyncer.driver import (
|
||||||
from chainsyncer.driver.history import HistorySyncer
|
HeadSyncer,
|
||||||
|
)
|
||||||
from chainsyncer.db.models.base import SessionBase
|
from chainsyncer.db.models.base import SessionBase
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_cache.db import (
|
from cic_cache.db import dsn_from_config
|
||||||
dsn_from_config,
|
|
||||||
add_tag,
|
|
||||||
)
|
|
||||||
from cic_cache.runnable.daemons.filters import (
|
from cic_cache.runnable.daemons.filters import (
|
||||||
ERC20TransferFilter,
|
ERC20TransferFilter,
|
||||||
FaucetFilter,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
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()
|
logg = cic_base.log.create()
|
||||||
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
||||||
argparser = cic_base.argparse.add(argparser, add_block_args, 'block')
|
#argparser = cic_base.argparse.add(argparser, add_traffic_args, 'traffic')
|
||||||
args = cic_base.argparse.parse(argparser, logg)
|
args = cic_base.argparse.parse(argparser, logg)
|
||||||
config = cic_base.config.create(args.c, args, args.env_prefix)
|
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)
|
cic_base.config.log(config)
|
||||||
|
|
||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
@@ -68,21 +54,10 @@ SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
|||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
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'))
|
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
||||||
|
|
||||||
|
|
||||||
def register_filter_tags(filters, session):
|
|
||||||
for f in filters:
|
|
||||||
tag = f.tag()
|
|
||||||
try:
|
|
||||||
add_tag(session, tag[0], domain=tag[1])
|
|
||||||
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]))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Connect to blockchain with chainlib
|
# Connect to blockchain with chainlib
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||||
@@ -91,31 +66,24 @@ def main():
|
|||||||
r = rpc.do(o)
|
r = rpc.do(o)
|
||||||
block_offset = int(strip_0x(r), 16) + 1
|
block_offset = int(strip_0x(r), 16) + 1
|
||||||
|
|
||||||
logg.debug('current block height {}'.format(block_offset))
|
logg.debug('starting at block {}'.format(block_offset))
|
||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SQLBackend.first(chain_spec):
|
#if SyncerBackend.first(chain_spec):
|
||||||
# backend = SQLBackend.initial(chain_spec, block_offset)
|
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
syncer_backends = SyncerBackend.resume(chain_spec, block_offset)
|
||||||
|
|
||||||
if len(syncer_backends) == 0:
|
if len(syncer_backends) == 0:
|
||||||
initial_block_start = config.get('SYNCER_HISTORY_START')
|
logg.info('found no backends to resume')
|
||||||
initial_block_offset = block_offset
|
syncer_backends.append(SyncerBackend.initial(chain_spec, 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:
|
else:
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||||
|
|
||||||
for syncer_backend in syncer_backends:
|
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1))
|
||||||
syncers.append(HistorySyncer(syncer_backend, 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')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
if trusted_addresses_src == None:
|
if trusted_addresses_src == None:
|
||||||
@@ -126,22 +94,11 @@ def main():
|
|||||||
logg.info('using trusted address {}'.format(address))
|
logg.info('using trusted address {}'.format(address))
|
||||||
|
|
||||||
erc20_transfer_filter = ERC20TransferFilter(chain_spec)
|
erc20_transfer_filter = ERC20TransferFilter(chain_spec)
|
||||||
faucet_filter = FaucetFilter(chain_spec)
|
|
||||||
|
|
||||||
filters = [
|
|
||||||
erc20_transfer_filter,
|
|
||||||
faucet_filter,
|
|
||||||
]
|
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
register_filter_tags(filters, session)
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for syncer in syncers:
|
for syncer in syncers:
|
||||||
logg.debug('running syncer index {}'.format(i))
|
logg.debug('running syncer index {}'.format(i))
|
||||||
for f in filters:
|
syncer.add_filter(erc20_transfer_filter)
|
||||||
syncer.add_filter(f)
|
|
||||||
r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
||||||
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
DEBUG=0
|
DEBUG=
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[eth]
|
[eth]
|
||||||
provider = http://localhost:63545
|
provider = ws://localhost:63546
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
[syncer]
|
[syncer]
|
||||||
loop_interval = 1
|
loop_interval = 1
|
||||||
history_start = 0
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
[syncer]
|
[syncer]
|
||||||
loop_interval = 5
|
loop_interval = 5
|
||||||
history_start = 0
|
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
[cic]
|
[cic]
|
||||||
registry_address =
|
registry_address =
|
||||||
chain_spec =
|
|
||||||
trust_address =
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=sqlite
|
ENGINE=sqlite
|
||||||
DRIVER=pysqlite
|
DRIVER=pysqlite
|
||||||
DEBUG=1
|
DEBUG=
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[syncer]
|
|
||||||
loop_interval = 1
|
|
||||||
@@ -17,7 +17,7 @@ RUN apt-get update && \
|
|||||||
|
|
||||||
# Copy shared requirements from top of mono-repo
|
# Copy shared requirements from top of mono-repo
|
||||||
RUN echo "copying root req file ${root_requirement_file}"
|
RUN echo "copying root req file ${root_requirement_file}"
|
||||||
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b9
|
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58
|
||||||
|
|
||||||
COPY cic-cache/requirements.txt ./
|
COPY cic-cache/requirements.txt ./
|
||||||
COPY cic-cache/setup.cfg \
|
COPY cic-cache/setup.cfg \
|
||||||
@@ -43,9 +43,10 @@ COPY cic-cache/config/ /usr/local/etc/cic-cache/
|
|||||||
RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/
|
RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/
|
||||||
COPY cic-cache/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
|
COPY cic-cache/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
|
||||||
|
|
||||||
COPY cic-cache/docker/start_tracker.sh ./start_tracker.sh
|
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
|
||||||
COPY cic-cache/docker/db.sh ./db.sh
|
mkdir -p /usr/local/share/cic/solidity && \
|
||||||
RUN chmod 755 ./*.sh
|
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
|
||||||
|
|
||||||
# Tracker
|
# Tracker
|
||||||
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
||||||
# Server
|
# Server
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
>&2 echo executing database migration
|
|
||||||
python scripts/migrate.py -c /usr/local/etc/cic-cache --migrations-dir /usr/local/share/cic-cache/alembic -vv
|
|
||||||
set +e
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
. ./db.sh
|
|
||||||
|
|
||||||
if [ $? -ne "0" ]; then
|
|
||||||
>&2 echo db migrate fail
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
/usr/local/bin/cic-cache-trackerd $@
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
cic-base==0.1.3a3+build.984b5cff
|
cic-base~=0.1.2a58
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
confini~=0.3.6rc3
|
confini~=0.3.6rc3
|
||||||
uwsgi==2.0.19.1
|
uwsgi==2.0.19.1
|
||||||
moolb~=0.1.0
|
moolb~=0.1.0
|
||||||
cic-eth-registry~=0.5.6a1
|
cic-eth-registry~=0.5.4a10
|
||||||
SQLAlchemy==1.3.20
|
SQLAlchemy==1.3.20
|
||||||
semver==2.13.0
|
semver==2.13.0
|
||||||
psycopg2==2.8.6
|
psycopg2==2.8.6
|
||||||
celery==4.4.7
|
celery==4.4.7
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
chainsyncer[sql]~=0.0.3a3
|
chainlib~=0.0.2a2
|
||||||
erc20-faucet~=0.2.2a1
|
chainsyncer~=0.0.1a21
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
|
|
||||||
import alembic
|
import alembic
|
||||||
from alembic.config import Config as AlembicConfig
|
from alembic.config import Config as AlembicConfig
|
||||||
@@ -24,8 +23,6 @@ argparser = argparse.ArgumentParser()
|
|||||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||||
argparser.add_argument('--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('--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('--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('-v', action='store_true', help='be verbose')
|
||||||
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
argparser.add_argument('-vv', action='store_true', help='be more verbose')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
@@ -56,10 +53,4 @@ ac = AlembicConfig(os.path.join(migrations_dir, 'alembic.ini'))
|
|||||||
ac.set_main_option('sqlalchemy.url', dsn)
|
ac.set_main_option('sqlalchemy.url', dsn)
|
||||||
ac.set_main_option('script_location', migrations_dir)
|
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')
|
alembic.command.upgrade(ac, 'head')
|
||||||
|
|||||||
@@ -4,7 +4,3 @@ pytest-mock==3.3.1
|
|||||||
pysqlite3==0.4.3
|
pysqlite3==0.4.3
|
||||||
sqlparse==0.4.1
|
sqlparse==0.4.1
|
||||||
pytest-celery==0.0.0a1
|
pytest-celery==0.0.0a1
|
||||||
eth_tester==0.5.0b3
|
|
||||||
py-evm==0.3.0a20
|
|
||||||
cic_base[full]==0.1.3a3+build.984b5cff
|
|
||||||
sarafu-faucet~=0.0.4a1
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@@ -84,20 +84,3 @@ def txs(
|
|||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
return [
|
|
||||||
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')
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from chainlib.eth.pytest import *
|
|
||||||
from cic_eth_registry.pytest.fixtures_tokens import *
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import pytest
|
|
||||||
from sqlalchemy import text
|
|
||||||
from chainlib.eth.tx import Tx
|
|
||||||
from chainlib.eth.block import Block
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
from hexathon import (
|
|
||||||
strip_0x,
|
|
||||||
add_0x,
|
|
||||||
)
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_cache.db import add_tag
|
|
||||||
from cic_cache.runnable.daemons.filters.erc20 import ERC20TransferFilter
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def test_erc20_filter(
|
|
||||||
eth_rpc,
|
|
||||||
foo_token,
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
tags,
|
|
||||||
):
|
|
||||||
|
|
||||||
chain_spec = ChainSpec('foo', 'bar', 42, 'baz')
|
|
||||||
|
|
||||||
fltr = ERC20TransferFilter(chain_spec)
|
|
||||||
|
|
||||||
add_tag(init_database, fltr.tag_name, domain=fltr.tag_domain)
|
|
||||||
|
|
||||||
data = 'a9059cbb'
|
|
||||||
data += strip_0x(list_actors['alice'])
|
|
||||||
data += '1000'.ljust(64, '0')
|
|
||||||
|
|
||||||
block = Block({
|
|
||||||
'hash': os.urandom(32).hex(),
|
|
||||||
'number': 42,
|
|
||||||
'timestamp': datetime.datetime.utcnow().timestamp(),
|
|
||||||
'transactions': [],
|
|
||||||
})
|
|
||||||
|
|
||||||
tx = Tx({
|
|
||||||
'to': foo_token,
|
|
||||||
'from': list_actors['bob'],
|
|
||||||
'data': data,
|
|
||||||
'value': 0,
|
|
||||||
'hash': os.urandom(32).hex(),
|
|
||||||
'nonce': 13,
|
|
||||||
'gasPrice': 10000000,
|
|
||||||
'gas': 123456,
|
|
||||||
})
|
|
||||||
block.txs.append(tx)
|
|
||||||
tx.block = block
|
|
||||||
|
|
||||||
r = fltr.filter(eth_rpc, block, tx, db_session=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
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import pytest
|
import pytest
|
||||||
import confini
|
import confini
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ logg = logging.getLogger(__file__)
|
|||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def load_config():
|
def load_config():
|
||||||
config_dir = os.path.join(root_dir, 'config/test')
|
config_dir = os.path.join(root_dir, '.config/test')
|
||||||
conf = confini.Config(config_dir, 'CICTEST')
|
conf = confini.Config(config_dir, 'CICTEST')
|
||||||
conf.process()
|
conf.process()
|
||||||
logg.debug('config {}'.format(conf))
|
logg.debug('config {}'.format(conf))
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import pytest
|
import pytest
|
||||||
import sqlparse
|
import sqlparse
|
||||||
import alembic
|
|
||||||
from alembic.config import Config as AlembicConfig
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_cache.db.models.base import SessionBase
|
from cic_cache.db.models.base import SessionBase
|
||||||
from cic_cache.db import dsn_from_config
|
from cic_cache.db import dsn_from_config
|
||||||
from cic_cache.db import add_tag
|
|
||||||
|
|
||||||
logg = logging.getLogger(__file__)
|
logg = logging.getLogger(__file__)
|
||||||
|
|
||||||
@@ -29,10 +26,11 @@ def database_engine(
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
dsn = dsn_from_config(load_config)
|
dsn = dsn_from_config(load_config)
|
||||||
SessionBase.connect(dsn, debug=load_config.true('DATABASE_DEBUG'))
|
SessionBase.connect(dsn)
|
||||||
return dsn
|
return dsn
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: use alembic instead to migrate db, here we have to keep separate schema than migration script in script/migrate.py
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def init_database(
|
def init_database(
|
||||||
load_config,
|
load_config,
|
||||||
@@ -40,23 +38,52 @@ def init_database(
|
|||||||
):
|
):
|
||||||
|
|
||||||
rootdir = os.path.dirname(os.path.dirname(__file__))
|
rootdir = os.path.dirname(os.path.dirname(__file__))
|
||||||
dbdir = os.path.join(rootdir, 'cic_cache', 'db')
|
schemadir = os.path.join(rootdir, 'db', load_config.get('DATABASE_DRIVER'))
|
||||||
migrationsdir = os.path.join(dbdir, 'migrations', load_config.get('DATABASE_ENGINE'))
|
|
||||||
if not os.path.isdir(migrationsdir):
|
if load_config.get('DATABASE_ENGINE') == 'sqlite':
|
||||||
migrationsdir = os.path.join(dbdir, 'migrations', 'default')
|
rconn = SessionBase.engine.raw_connection()
|
||||||
logg.info('using migrations directory {}'.format(migrationsdir))
|
f = open(os.path.join(schemadir, 'db.sql'))
|
||||||
|
s = f.read()
|
||||||
|
f.close()
|
||||||
|
rconn.executescript(s)
|
||||||
|
|
||||||
|
else:
|
||||||
|
rconn = SessionBase.engine.raw_connection()
|
||||||
|
rcursor = rconn.cursor()
|
||||||
|
|
||||||
|
#rcursor.execute('DROP FUNCTION IF EXISTS public.transaction_list')
|
||||||
|
#rcursor.execute('DROP FUNCTION IF EXISTS public.balances')
|
||||||
|
|
||||||
|
f = open(os.path.join(schemadir, 'db.sql'))
|
||||||
|
s = f.read()
|
||||||
|
f.close()
|
||||||
|
r = re.compile(r'^[A-Z]', re.MULTILINE)
|
||||||
|
for l in sqlparse.parse(s):
|
||||||
|
strl = str(l)
|
||||||
|
# we need to check for empty query lines, as sqlparse doesn't do that on its own (and psycopg complains when it gets them)
|
||||||
|
if not re.search(r, strl):
|
||||||
|
logg.warning('skipping parsed query line {}'.format(strl))
|
||||||
|
continue
|
||||||
|
rcursor.execute(strl)
|
||||||
|
rconn.commit()
|
||||||
|
|
||||||
|
rcursor.execute('SET search_path TO public')
|
||||||
|
|
||||||
|
# this doesn't work when run separately, no idea why
|
||||||
|
# functions have been manually added to original schema from cic-eth
|
||||||
|
# f = open(os.path.join(schemadir, 'proc_transaction_list.sql'))
|
||||||
|
# s = f.read()
|
||||||
|
# f.close()
|
||||||
|
# rcursor.execute(s)
|
||||||
|
#
|
||||||
|
# f = open(os.path.join(schemadir, 'proc_balances.sql'))
|
||||||
|
# s = f.read()
|
||||||
|
# f.close()
|
||||||
|
# rcursor.execute(s)
|
||||||
|
|
||||||
|
rcursor.close()
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
|
|
||||||
ac = AlembicConfig(os.path.join(migrationsdir, 'alembic.ini'))
|
|
||||||
ac.set_main_option('sqlalchemy.url', database_engine)
|
|
||||||
ac.set_main_option('script_location', migrationsdir)
|
|
||||||
|
|
||||||
alembic.command.downgrade(ac, 'base')
|
|
||||||
alembic.command.upgrade(ac, 'head')
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
yield session
|
yield session
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
@@ -89,14 +116,3 @@ def list_defaults(
|
|||||||
return {
|
return {
|
||||||
'block': 420000,
|
'block': 420000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def tags(
|
|
||||||
init_database,
|
|
||||||
):
|
|
||||||
|
|
||||||
add_tag(init_database, 'foo')
|
|
||||||
add_tag(init_database, 'baz', domain='bar')
|
|
||||||
add_tag(init_database, 'xyzzy', domain='bar')
|
|
||||||
init_database.commit()
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -4,12 +4,11 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_cache import BloomCache
|
from cic_cache import BloomCache
|
||||||
from cic_cache.cache import DataCache
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
@@ -34,23 +33,3 @@ def test_cache(
|
|||||||
|
|
||||||
assert b[0] == list_defaults['block'] - 1
|
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'
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import os
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_cache.db import tag_transaction
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def test_cache(
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
list_tokens,
|
|
||||||
txs,
|
|
||||||
tags,
|
|
||||||
):
|
|
||||||
|
|
||||||
tag_transaction(init_database, txs[0], 'foo')
|
|
||||||
tag_transaction(init_database, txs[0], 'baz', domain='bar')
|
|
||||||
tag_transaction(init_database, txs[1], 'xyzzy', domain='bar')
|
|
||||||
|
|
||||||
r = init_database.execute("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.value = 'foo'").fetchall()
|
|
||||||
assert r[0][0] == txs[0]
|
|
||||||
|
|
||||||
|
|
||||||
r = init_database.execute("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 = 'bar' AND a.value = 'baz'").fetchall()
|
|
||||||
assert r[0][0] == txs[0]
|
|
||||||
|
|
||||||
|
|
||||||
r = init_database.execute("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 = 'bar' AND a.value = 'xyzzy'").fetchall()
|
|
||||||
assert r[0][0] == txs[1]
|
|
||||||
@@ -5,5 +5,3 @@ omit =
|
|||||||
cic_eth/db/migrations/*
|
cic_eth/db/migrations/*
|
||||||
cic_eth/sync/head.py
|
cic_eth/sync/head.py
|
||||||
cic_eth/sync/mempool.py
|
cic_eth/sync/mempool.py
|
||||||
cic_eth/queue/state.py
|
|
||||||
*redis*.py
|
|
||||||
|
|||||||
@@ -5,29 +5,18 @@
|
|||||||
|
|
||||||
.cic_eth_changes_target:
|
.cic_eth_changes_target:
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
- changes:
|
||||||
#changes:
|
- $CONTEXT/$APP_NAME/*
|
||||||
#- $CONTEXT/$APP_NAME/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
build-mr-cic-eth:
|
build-mr-cic-eth:
|
||||||
extends:
|
extends:
|
||||||
- .cic_eth_variables
|
|
||||||
- .cic_eth_changes_target
|
- .cic_eth_changes_target
|
||||||
- .py_build_target_test
|
- .py_build_merge_request
|
||||||
|
|
||||||
test-mr-cic-eth:
|
|
||||||
extends:
|
|
||||||
- .cic_eth_variables
|
- .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:
|
build-push-cic-eth:
|
||||||
extends:
|
extends:
|
||||||
- .py_build_push
|
- .py_build_push
|
||||||
- .cic_eth_variables
|
- .cic_eth_variables
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
include *requirements.txt
|
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import celery
|
import celery
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
@@ -32,9 +32,7 @@ def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.AL
|
|||||||
:returns: New lock state for address
|
:returns: New lock state for address
|
||||||
:rtype: number
|
:rtype: number
|
||||||
"""
|
"""
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
|
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
|
||||||
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
|
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
|
||||||
return chained_input
|
return chained_input
|
||||||
@@ -53,9 +51,7 @@ def unlock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.
|
|||||||
:returns: New lock state for address
|
:returns: New lock state for address
|
||||||
:rtype: number
|
:rtype: number
|
||||||
"""
|
"""
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
r = Lock.reset(chain_str, flags, address=address)
|
r = Lock.reset(chain_str, flags, address=address)
|
||||||
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
|
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
|
||||||
return chained_input
|
return chained_input
|
||||||
@@ -131,9 +127,7 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
|
|||||||
|
|
||||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
@celery_app.task(base=CriticalSQLAlchemyTask)
|
||||||
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
||||||
chain_str = '::'
|
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
||||||
if chain_spec_dict != None:
|
|
||||||
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
|
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
|
||||||
if address != None:
|
if address != None:
|
||||||
@@ -145,9 +139,3 @@ def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
|
|||||||
session.flush()
|
session.flush()
|
||||||
session.close()
|
session.close()
|
||||||
return chained_input
|
return chained_input
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
|
||||||
def shutdown(message):
|
|
||||||
logg.critical('shutdown called: {}'.format(message))
|
|
||||||
celery_app.control.shutdown() #broadcast('shutdown')
|
|
||||||
|
|||||||
@@ -4,18 +4,11 @@ import logging
|
|||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.eth.tx import unpack
|
||||||
from chainlib.eth.tx import (
|
from chainqueue.query import get_tx
|
||||||
unpack,
|
from chainqueue.state import set_cancel
|
||||||
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.otx import Otx
|
||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
from hexathon import strip_0x
|
|
||||||
from potaahto.symbols import snake_and_camel
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
@@ -28,14 +21,13 @@ from cic_eth.admin.ctrl import (
|
|||||||
)
|
)
|
||||||
from cic_eth.queue.tx import queue_create
|
from cic_eth.queue.tx import queue_create
|
||||||
from cic_eth.eth.gas import create_check_gas_task
|
from cic_eth.eth.gas import create_check_gas_task
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
celery_app = celery.current_app
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=BaseTask)
|
@celery_app.task(bind=True)
|
||||||
def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
|
def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
|
||||||
"""Shift all transactions with nonces higher than the offset by the provided position delta.
|
"""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.
|
Transactions who are replaced by transactions that move nonces will be marked as OVERRIDDEN.
|
||||||
@@ -46,29 +38,25 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
|
|||||||
:type tx_hash_orig_hex: str, 0x-hex
|
:type tx_hash_orig_hex: str, 0x-hex
|
||||||
:param delta: Amount
|
: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
|
queue = None
|
||||||
try:
|
try:
|
||||||
queue = self.request.delivery_info.get('routing_key')
|
queue = self.request.delivery_info.get('routing_key')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
session = BaseTask.session_func()
|
chain_spec = ChainSpec.from_chain_str(chain_str)
|
||||||
tx_brief = get_tx(chain_spec, tx_hash_orig_hex, session=session)
|
tx_brief = get_tx(tx_hash_orig_hex)
|
||||||
tx_raw = bytes.fromhex(strip_0x(tx_brief['signed_tx']))
|
tx_raw = bytes.fromhex(strip_0x(tx_brief['signed_tx'][2:]))
|
||||||
tx = unpack(tx_raw, chain_spec)
|
tx = unpack(tx_raw, chain_spec)
|
||||||
nonce = tx_brief['nonce']
|
nonce = tx_brief['nonce']
|
||||||
address = tx['from']
|
address = tx['from']
|
||||||
|
|
||||||
logg.debug('shifting nonce {} position(s) for address {}, offset {}, hash {}'.format(delta, address, nonce, tx['hash']))
|
logg.debug('shifting nonce {} position(s) for address {}, offset {}'.format(delta, address, nonce))
|
||||||
|
|
||||||
lock_queue(None, chain_spec.asdict(), address=address)
|
lock_queue(None, chain_str, address)
|
||||||
lock_send(None, chain_spec.asdict(), address=address)
|
lock_send(None, chain_str, address)
|
||||||
|
|
||||||
set_cancel(chain_spec, strip_0x(tx['hash']), manual=True, session=session)
|
|
||||||
|
|
||||||
|
session = SessionBase.create_session()
|
||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
q = q.join(TxCache)
|
q = q.join(TxCache)
|
||||||
q = q.filter(TxCache.sender==address)
|
q = q.filter(TxCache.sender==address)
|
||||||
@@ -81,57 +69,49 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
|
|||||||
for otx in otxs:
|
for otx in otxs:
|
||||||
tx_raw = bytes.fromhex(strip_0x(otx.signed_tx))
|
tx_raw = bytes.fromhex(strip_0x(otx.signed_tx))
|
||||||
tx_new = unpack(tx_raw, chain_spec)
|
tx_new = unpack(tx_raw, chain_spec)
|
||||||
tx_new = snake_and_camel(tx_new)
|
|
||||||
|
|
||||||
tx_previous_hash_hex = tx_new['hash']
|
tx_previous_hash_hex = tx_new['hash']
|
||||||
tx_previous_nonce = tx_new['nonce']
|
tx_previous_nonce = tx_new['nonce']
|
||||||
|
|
||||||
tx_new['gas_price'] += 1
|
|
||||||
tx_new['gasPrice'] = tx_new['gas_price']
|
|
||||||
tx_new['nonce'] -= delta
|
|
||||||
|
|
||||||
logg.debug('tx_new {}'.format(tx_new))
|
|
||||||
|
|
||||||
del(tx_new['hash'])
|
del(tx_new['hash'])
|
||||||
del(tx_new['hash_unsigned'])
|
del(tx_new['hash_unsigned'])
|
||||||
del(tx_new['hashUnsigned'])
|
tx_new['nonce'] -= delta
|
||||||
|
|
||||||
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)
|
(tx_hash_hex, tx_signed_raw_hex) = sign_tx(tx_new, chain_str)
|
||||||
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']))
|
logg.debug('tx {} -> {} nonce {} -> {}'.format(tx_previous_hash_hex, tx_hash_hex, tx_previous_nonce, tx_new['nonce']))
|
||||||
|
|
||||||
otx = Otx(
|
otx = Otx(
|
||||||
tx_new['nonce'],
|
nonce=tx_new['nonce'],
|
||||||
tx_hash_hex,
|
address=tx_new['from'],
|
||||||
tx_signed_raw_hex,
|
tx_hash=tx_hash_hex,
|
||||||
)
|
signed_tx=tx_signed_raw_hex,
|
||||||
|
)
|
||||||
session.add(otx)
|
session.add(otx)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# TODO: cancel all first, then replace. Otherwise we risk two non-locked states for two different nonces.
|
# TODO: cancel all first, then replace. Otherwise we risk two non-locked states for two different nonces.
|
||||||
set_cancel(chain_spec, strip_0x(tx_previous_hash_hex), manual=True, session=session)
|
set_cancel(tx_previous_hash_hex, True)
|
||||||
|
|
||||||
TxCache.clone(tx_previous_hash_hex, tx_hash_hex, session=session)
|
TxCache.clone(tx_previous_hash_hex, tx_hash_hex)
|
||||||
|
|
||||||
tx_hashes.append(tx_hash_hex)
|
tx_hashes.append(tx_hash_hex)
|
||||||
txs.append(tx_signed_raw_hex)
|
txs.append(tx_signed_raw_hex)
|
||||||
session.commit()
|
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
s = create_check_gas_task(
|
s = create_check_gas_and_send_task(
|
||||||
txs,
|
txs,
|
||||||
chain_spec,
|
chain_str,
|
||||||
tx_new['from'],
|
tx_new['from'],
|
||||||
gas=tx_new['gas'],
|
tx_new['gas'],
|
||||||
tx_hashes_hex=tx_hashes,
|
tx_hashes,
|
||||||
queue=queue,
|
queue,
|
||||||
)
|
)
|
||||||
|
|
||||||
s_unlock_send = celery.signature(
|
s_unlock_send = celery.signature(
|
||||||
'cic_eth.admin.ctrl.unlock_send',
|
'cic_eth.admin.ctrl.unlock_send',
|
||||||
[
|
[
|
||||||
chain_spec.asdict(),
|
chain_str,
|
||||||
tx_new['from'],
|
tx_new['from'],
|
||||||
],
|
],
|
||||||
queue=queue,
|
queue=queue,
|
||||||
@@ -139,7 +119,7 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
|
|||||||
s_unlock_direct = celery.signature(
|
s_unlock_direct = celery.signature(
|
||||||
'cic_eth.admin.ctrl.unlock_queue',
|
'cic_eth.admin.ctrl.unlock_queue',
|
||||||
[
|
[
|
||||||
chain_spec.asdict(),
|
chain_str,
|
||||||
tx_new['from'],
|
tx_new['from'],
|
||||||
],
|
],
|
||||||
queue=queue,
|
queue=queue,
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=BaseTask)
|
|
||||||
def default_token(self):
|
|
||||||
return {
|
|
||||||
'symbol': self.default_token_symbol,
|
|
||||||
'address': self.default_token_address,
|
|
||||||
'name': self.default_token_name,
|
|
||||||
'decimals': self.default_token_decimals,
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ from chainlib.eth.constant import (
|
|||||||
ZERO_ADDRESS,
|
ZERO_ADDRESS,
|
||||||
)
|
)
|
||||||
from cic_eth_registry import CICRegistry
|
from cic_eth_registry import CICRegistry
|
||||||
from cic_eth_registry.erc20 import ERC20Token
|
|
||||||
from cic_eth_registry.error import UnknownContractError
|
from cic_eth_registry.error import UnknownContractError
|
||||||
from chainlib.eth.address import to_checksum_address
|
from chainlib.eth.address import to_checksum_address
|
||||||
from chainlib.eth.contract import code
|
from chainlib.eth.contract import code
|
||||||
@@ -31,14 +30,13 @@ from chainqueue.db.enum import (
|
|||||||
status_str,
|
status_str,
|
||||||
)
|
)
|
||||||
from chainqueue.error import TxStateChangeError
|
from chainqueue.error import TxStateChangeError
|
||||||
from chainqueue.sql.query import get_tx
|
|
||||||
from eth_erc20 import ERC20
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
from cic_eth.db.models.role import AccountRole
|
from cic_eth.db.models.role import AccountRole
|
||||||
from cic_eth.db.models.nonce import Nonce
|
from cic_eth.db.models.nonce import Nonce
|
||||||
from cic_eth.error import InitializationError
|
from cic_eth.error import InitializationError
|
||||||
|
from cic_eth.queue.query import get_tx
|
||||||
|
|
||||||
app = celery.current_app
|
app = celery.current_app
|
||||||
|
|
||||||
@@ -62,29 +60,6 @@ class AdminApi:
|
|||||||
self.call_address = call_address
|
self.call_address = call_address
|
||||||
|
|
||||||
|
|
||||||
def proxy_do(self, chain_spec, o):
|
|
||||||
s_proxy = celery.signature(
|
|
||||||
'cic_eth.task.rpc_proxy',
|
|
||||||
[
|
|
||||||
chain_spec.asdict(),
|
|
||||||
o,
|
|
||||||
'default',
|
|
||||||
],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
return s_proxy.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def registry(self):
|
|
||||||
s_registry = celery.signature(
|
|
||||||
'cic_eth.task.registry',
|
|
||||||
[],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
return s_registry.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
def unlock(self, chain_spec, address, flags=None):
|
def unlock(self, chain_spec, address, flags=None):
|
||||||
s_unlock = celery.signature(
|
s_unlock = celery.signature(
|
||||||
'cic_eth.admin.ctrl.unlock',
|
'cic_eth.admin.ctrl.unlock',
|
||||||
@@ -171,6 +146,7 @@ class AdminApi:
|
|||||||
|
|
||||||
# TODO: This check should most likely be in resend task itself
|
# TODO: This check should most likely be in resend task itself
|
||||||
tx_dict = s_get_tx_cache.apply_async().get()
|
tx_dict = s_get_tx_cache.apply_async().get()
|
||||||
|
#if tx_dict['status'] in [StatusEnum.REVERTED, StatusEnum.SUCCESS, StatusEnum.CANCELLED, StatusEnum.OBSOLETED]:
|
||||||
if not is_alive(getattr(StatusEnum, tx_dict['status']).value):
|
if not is_alive(getattr(StatusEnum, tx_dict['status']).value):
|
||||||
raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex))
|
raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex))
|
||||||
|
|
||||||
@@ -190,7 +166,6 @@ class AdminApi:
|
|||||||
s_manual = celery.signature(
|
s_manual = celery.signature(
|
||||||
'cic_eth.queue.state.set_manual',
|
'cic_eth.queue.state.set_manual',
|
||||||
[
|
[
|
||||||
chain_spec.asdict(),
|
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
@@ -209,9 +184,8 @@ class AdminApi:
|
|||||||
s.link(s_gas)
|
s.link(s_gas)
|
||||||
|
|
||||||
return s_manual.apply_async()
|
return s_manual.apply_async()
|
||||||
|
|
||||||
|
def check_nonce(self, address):
|
||||||
def check_nonce(self, chain_spec, address):
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.queue.query.get_account_tx',
|
'cic_eth.queue.query.get_account_tx',
|
||||||
[
|
[
|
||||||
@@ -232,12 +206,13 @@ class AdminApi:
|
|||||||
s_get_tx = celery.signature(
|
s_get_tx = celery.signature(
|
||||||
'cic_eth.queue.query.get_tx',
|
'cic_eth.queue.query.get_tx',
|
||||||
[
|
[
|
||||||
chain_spec.asdict(),
|
chain_spec.asdict(),
|
||||||
k,
|
k,
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
tx = s_get_tx.apply_async().get()
|
tx = s_get_tx.apply_async().get()
|
||||||
|
#tx = get_tx(k)
|
||||||
logg.debug('checking nonce {} (previous {})'.format(tx['nonce'], last_nonce))
|
logg.debug('checking nonce {} (previous {})'.format(tx['nonce'], last_nonce))
|
||||||
nonce_otx = tx['nonce']
|
nonce_otx = tx['nonce']
|
||||||
if not is_alive(tx['status']) and tx['status'] & local_fail > 0:
|
if not is_alive(tx['status']) and tx['status'] & local_fail > 0:
|
||||||
@@ -245,14 +220,15 @@ class AdminApi:
|
|||||||
blocking_tx = k
|
blocking_tx = k
|
||||||
blocking_nonce = nonce_otx
|
blocking_nonce = nonce_otx
|
||||||
elif nonce_otx - last_nonce > 1:
|
elif nonce_otx - last_nonce > 1:
|
||||||
logg.debug('tx {}'.format(tx))
|
logg.error('nonce gap; {} followed {} for account {}'.format(nonce_otx, last_nonce, tx['from']))
|
||||||
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_tx = k
|
||||||
blocking_nonce = nonce_otx
|
blocking_nonce = nonce_otx
|
||||||
break
|
break
|
||||||
last_nonce = nonce_otx
|
last_nonce = nonce_otx
|
||||||
|
|
||||||
|
#nonce_cache = Nonce.get(address)
|
||||||
|
#nonce_w3 = self.w3.eth.getTransactionCount(address, 'pending')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'nonce': {
|
'nonce': {
|
||||||
#'network': nonce_cache,
|
#'network': nonce_cache,
|
||||||
@@ -261,13 +237,12 @@ class AdminApi:
|
|||||||
'blocking': blocking_nonce,
|
'blocking': blocking_nonce,
|
||||||
},
|
},
|
||||||
'tx': {
|
'tx': {
|
||||||
'blocking': add_0x(blocking_tx),
|
'blocking': blocking_tx,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: is risky since it does not validate that there is actually a nonce problem?
|
def fix_nonce(self, address, nonce, chain_spec):
|
||||||
def fix_nonce(self, chain_spec, address, nonce):
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.queue.query.get_account_tx',
|
'cic_eth.queue.query.get_account_tx',
|
||||||
[
|
[
|
||||||
@@ -281,17 +256,15 @@ class AdminApi:
|
|||||||
txs = s.apply_async().get()
|
txs = s.apply_async().get()
|
||||||
|
|
||||||
tx_hash_hex = None
|
tx_hash_hex = None
|
||||||
session = SessionBase.create_session()
|
|
||||||
for k in txs.keys():
|
for k in txs.keys():
|
||||||
tx_dict = get_tx(chain_spec, k, session=session)
|
tx_dict = get_tx(k)
|
||||||
if tx_dict['nonce'] == nonce:
|
if tx_dict['nonce'] == nonce:
|
||||||
tx_hash_hex = k
|
tx_hash_hex = k
|
||||||
session.close()
|
|
||||||
|
|
||||||
s_nonce = celery.signature(
|
s_nonce = celery.signature(
|
||||||
'cic_eth.admin.nonce.shift_nonce',
|
'cic_eth.admin.nonce.shift_nonce',
|
||||||
[
|
[
|
||||||
chain_spec.asdict(),
|
self.rpc.chain_spec.asdict(),
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
],
|
],
|
||||||
queue=self.queue
|
queue=self.queue
|
||||||
@@ -299,6 +272,20 @@ class AdminApi:
|
|||||||
return s_nonce.apply_async()
|
return s_nonce.apply_async()
|
||||||
|
|
||||||
|
|
||||||
|
# # TODO: this is a stub, complete all checks
|
||||||
|
# def ready(self):
|
||||||
|
# """Checks whether all required initializations have been performed.
|
||||||
|
#
|
||||||
|
# :raises cic_eth.error.InitializationError: At least one setting pre-requisite has not been met.
|
||||||
|
# :raises KeyError: An address provided for initialization is not known by the keystore.
|
||||||
|
# """
|
||||||
|
# addr = AccountRole.get_address('ETH_GAS_PROVIDER_ADDRESS')
|
||||||
|
# if addr == ZERO_ADDRESS:
|
||||||
|
# raise InitializationError('missing account ETH_GAS_PROVIDER_ADDRESS')
|
||||||
|
#
|
||||||
|
# self.w3.eth.sign(addr, text='666f6f')
|
||||||
|
|
||||||
|
|
||||||
def account(self, chain_spec, address, include_sender=True, include_recipient=True, renderer=None, w=sys.stdout):
|
def account(self, chain_spec, address, include_sender=True, include_recipient=True, renderer=None, w=sys.stdout):
|
||||||
"""Lists locally originated transactions for the given Ethereum address.
|
"""Lists locally originated transactions for the given Ethereum address.
|
||||||
|
|
||||||
@@ -361,7 +348,6 @@ class AdminApi:
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Add exception upon non-existent tx aswell as invalid tx data to docstring
|
# TODO: Add exception upon non-existent tx aswell as invalid tx data to docstring
|
||||||
# TODO: This method is WAY too long
|
|
||||||
def tx(self, chain_spec, tx_hash=None, tx_raw=None, registry=None, renderer=None, w=sys.stdout):
|
def tx(self, chain_spec, tx_hash=None, tx_raw=None, registry=None, renderer=None, w=sys.stdout):
|
||||||
"""Output local and network details about a given transaction with local origin.
|
"""Output local and network details about a given transaction with local origin.
|
||||||
|
|
||||||
@@ -384,6 +370,7 @@ class AdminApi:
|
|||||||
|
|
||||||
if tx_raw != None:
|
if tx_raw != None:
|
||||||
tx_hash = add_0x(keccak256_hex_to_hex(tx_raw))
|
tx_hash = add_0x(keccak256_hex_to_hex(tx_raw))
|
||||||
|
#tx_hash = self.w3.keccak(hexstr=tx_raw).hex()
|
||||||
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.queue.query.get_tx_cache',
|
'cic_eth.queue.query.get_tx_cache',
|
||||||
@@ -396,87 +383,41 @@ class AdminApi:
|
|||||||
|
|
||||||
t = s.apply_async()
|
t = s.apply_async()
|
||||||
tx = t.get()
|
tx = t.get()
|
||||||
|
|
||||||
source_token = None
|
source_token = None
|
||||||
if tx['source_token'] != ZERO_ADDRESS:
|
if tx['source_token'] != ZERO_ADDRESS:
|
||||||
source_token_declaration = None
|
try:
|
||||||
if registry != None:
|
source_token = registry.by_address(tx['source_token'])
|
||||||
try:
|
#source_token = CICRegistry.get_address(chain_spec, tx['source_token']).contract
|
||||||
source_token_declaration = registry.by_address(tx['source_token'], sender_address=self.call_address)
|
except UnknownContractError:
|
||||||
except UnknownContractError:
|
#source_token_contract = self.w3.eth.contract(abi=CICRegistry.abi('ERC20'), address=tx['source_token'])
|
||||||
logg.warning('unknown source token contract {} (direct)'.format(tx['source_token']))
|
#source_token = CICRegistry.add_token(chain_spec, source_token_contract)
|
||||||
else:
|
logg.warning('unknown source token contract {}'.format(tx['source_token']))
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.task.registry_address_lookup',
|
|
||||||
[
|
|
||||||
chain_spec.asdict(),
|
|
||||||
tx['source_token'],
|
|
||||||
],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
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
|
destination_token = None
|
||||||
if tx['destination_token'] != ZERO_ADDRESS:
|
if tx['source_token'] != ZERO_ADDRESS:
|
||||||
destination_token_declaration = None
|
try:
|
||||||
if registry != None:
|
#destination_token = CICRegistry.get_address(chain_spec, tx['destination_token'])
|
||||||
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:
|
||||||
except UnknownContractError:
|
#destination_token_contract = self.w3.eth.contract(abi=CICRegistry.abi('ERC20'), address=tx['source_token'])
|
||||||
logg.warning('unknown destination token contract {}'.format(tx['destination_token']))
|
#destination_token = CICRegistry.add_token(chain_spec, destination_token_contract)
|
||||||
else:
|
logg.warning('unknown destination token contract {}'.format(tx['destination_token']))
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.task.registry_address_lookup',
|
|
||||||
[
|
|
||||||
chain_spec.asdict(),
|
|
||||||
tx['destination_token'],
|
|
||||||
],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
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['sender_description'] = 'Custodial account'
|
||||||
tx['recipient_description'] = 'Custodial account'
|
tx['recipient_description'] = 'Custodial account'
|
||||||
|
|
||||||
o = code(tx['sender'])
|
o = code(tx['sender'])
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
if len(strip_0x(r, allow_empty=True)) > 0:
|
if len(strip_0x(r, allow_empty=True)) > 0:
|
||||||
if registry != None:
|
try:
|
||||||
try:
|
#sender_contract = CICRegistry.get_address(chain_spec, tx['sender'])
|
||||||
sender_contract = registry.by_address(tx['sender'], sender_address=self.call_address)
|
sender_contract = registry.by_address(tx['sender'], sender_address=self.call_address)
|
||||||
tx['sender_description'] = 'Contract at {}'.format(tx['sender'])
|
tx['sender_description'] = 'Contract at {}'.format(tx['sender']) #sender_contract)
|
||||||
except UnknownContractError:
|
except UnknownContractError:
|
||||||
tx['sender_description'] = 'Unknown contract'
|
tx['sender_description'] = 'Unknown contract'
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
tx['sender_description'] = 'Unknown contract'
|
tx['sender_description'] = 'Unknown contract'
|
||||||
else:
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.task.registry_address_lookup',
|
|
||||||
[
|
|
||||||
chain_spec.asdict(),
|
|
||||||
tx['sender'],
|
|
||||||
],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
tx['sender_description'] = t.get()
|
|
||||||
if tx['sender_description'] == None:
|
|
||||||
tx['sender_description'] = 'Unknown contract'
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.eth.account.have',
|
'cic_eth.eth.account.have',
|
||||||
@@ -505,31 +446,16 @@ class AdminApi:
|
|||||||
tx['sender_description'] = role
|
tx['sender_description'] = role
|
||||||
|
|
||||||
o = code(tx['recipient'])
|
o = code(tx['recipient'])
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
if len(strip_0x(r, allow_empty=True)) > 0:
|
if len(strip_0x(r, allow_empty=True)) > 0:
|
||||||
if registry != None:
|
try:
|
||||||
try:
|
#recipient_contract = CICRegistry.by_address(tx['recipient'])
|
||||||
recipient_contract = registry.by_address(tx['recipient'])
|
recipient_contract = registry.by_address(tx['recipient'])
|
||||||
tx['recipient_description'] = 'Contract at {}'.format(tx['recipient'])
|
tx['recipient_description'] = 'Contract at {}'.format(tx['recipient']) #recipient_contract)
|
||||||
except UnknownContractError as e:
|
except UnknownContractError as e:
|
||||||
tx['recipient_description'] = 'Unknown contract'
|
tx['recipient_description'] = 'Unknown contract'
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
tx['recipient_description'] = 'Unknown contract'
|
tx['recipient_description'] = 'Unknown contract'
|
||||||
else:
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.task.registry_address_lookup',
|
|
||||||
[
|
|
||||||
chain_spec.asdict(),
|
|
||||||
tx['recipient'],
|
|
||||||
],
|
|
||||||
queue=self.queue
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
tx['recipient_description'] = t.get()
|
|
||||||
if tx['recipient_description'] == None:
|
|
||||||
tx['recipient_description'] = 'Unknown contract'
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.eth.account.have',
|
'cic_eth.eth.account.have',
|
||||||
@@ -557,19 +483,13 @@ class AdminApi:
|
|||||||
if role != None:
|
if role != None:
|
||||||
tx['recipient_description'] = role
|
tx['recipient_description'] = role
|
||||||
|
|
||||||
erc20_c = ERC20(chain_spec)
|
|
||||||
if source_token != None:
|
if source_token != None:
|
||||||
tx['source_token_symbol'] = source_token.symbol
|
tx['source_token_symbol'] = source_token.symbol()
|
||||||
o = erc20_c.balance_of(tx['source_token'], tx['sender'], sender_address=self.call_address)
|
tx['sender_token_balance'] = source_token.function('balanceOf')(tx['sender']).call()
|
||||||
r = self.rpc.do(o)
|
|
||||||
tx['sender_token_balance'] = erc20_c.parse_balance(r)
|
|
||||||
|
|
||||||
if destination_token != None:
|
if destination_token != None:
|
||||||
tx['destination_token_symbol'] = destination_token.symbol
|
tx['destination_token_symbol'] = destination_token.symbol()
|
||||||
o = erc20_c.balance_of(tx['destination_token'], tx['recipient'], sender_address=self.call_address)
|
tx['recipient_token_balance'] = source_token.function('balanceOf')(tx['recipient']).call()
|
||||||
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
|
# 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'
|
tx['network_status'] = 'Not in node'
|
||||||
@@ -577,8 +497,7 @@ class AdminApi:
|
|||||||
r = None
|
r = None
|
||||||
try:
|
try:
|
||||||
o = transaction(tx_hash)
|
o = transaction(tx_hash)
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
if r != None:
|
if r != None:
|
||||||
tx['network_status'] = 'Mempool'
|
tx['network_status'] = 'Mempool'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -587,8 +506,7 @@ class AdminApi:
|
|||||||
if r != None:
|
if r != None:
|
||||||
try:
|
try:
|
||||||
o = receipt(tx_hash)
|
o = receipt(tx_hash)
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
logg.debug('h {} o {}'.format(tx_hash, o))
|
logg.debug('h {} o {}'.format(tx_hash, o))
|
||||||
if int(strip_0x(r['status'])) == 1:
|
if int(strip_0x(r['status'])) == 1:
|
||||||
tx['network_status'] = 'Confirmed'
|
tx['network_status'] = 'Confirmed'
|
||||||
@@ -603,13 +521,11 @@ class AdminApi:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
o = balance(tx['sender'])
|
o = balance(tx['sender'])
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
tx['sender_gas_balance'] = r
|
tx['sender_gas_balance'] = r
|
||||||
|
|
||||||
o = balance(tx['recipient'])
|
o = balance(tx['recipient'])
|
||||||
t = self.proxy_do(chain_spec, o)
|
r = self.rpc.do(o)
|
||||||
r = t.get()
|
|
||||||
tx['recipient_gas_balance'] = r
|
tx['recipient_gas_balance'] = r
|
||||||
|
|
||||||
tx_unpacked = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec)
|
tx_unpacked = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec)
|
||||||
|
|||||||
@@ -62,168 +62,29 @@ class Api:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_token(self):
|
def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
||||||
s_token = celery.signature(
|
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
||||||
'cic_eth.admin.token.default_token',
|
|
||||||
[],
|
|
||||||
queue=self.queue,
|
|
||||||
)
|
|
||||||
if self.callback_param != None:
|
|
||||||
s_token.link(self.callback_success)
|
|
||||||
|
|
||||||
return s_token.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
# def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
|
||||||
# """Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
|
||||||
#
|
|
||||||
# :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_from(self, from_address, to_address, value, token_symbol, spender_address):
|
|
||||||
"""Executes a chain of celery tasks that performs a transfer of ERC20 tokens by one address on behalf of another address to a third party.
|
|
||||||
|
|
||||||
:param from_address: Ethereum address of sender
|
:param from_address: Ethereum address of sender
|
||||||
:type from_address: str, 0x-hex
|
:type from_address: str, 0x-hex
|
||||||
:param to_address: Ethereum address of recipient
|
:param to_address: Ethereum address of receipient
|
||||||
:type to_address: str, 0x-hex
|
:type to_address: str, 0x-hex
|
||||||
:param value: Estimated return from conversion
|
:param target_return: Estimated return from conversion
|
||||||
:type value: int
|
:type target_return: int
|
||||||
:param token_symbol: ERC20 token symbol of token to send
|
:param minimum_return: The least value of destination token return to allow
|
||||||
:type token_symbol: str
|
:type minimum_return: int
|
||||||
:param spender_address: Ethereum address of recipient
|
:param from_token_symbol: ERC20 token symbol of token being converted
|
||||||
:type spender_address: str, 0x-hex
|
: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
|
:returns: uuid of root task
|
||||||
:rtype: celery.Task
|
:rtype: celery.Task
|
||||||
"""
|
"""
|
||||||
|
raise NotImplementedError('out of service until new DEX migration is done')
|
||||||
s_check = celery.signature(
|
s_check = celery.signature(
|
||||||
'cic_eth.admin.ctrl.check_lock',
|
'cic_eth.admin.ctrl.check_lock',
|
||||||
[
|
[
|
||||||
[token_symbol],
|
[from_token_symbol, to_token_symbol],
|
||||||
self.chain_spec.asdict(),
|
self.chain_spec.asdict(),
|
||||||
LockEnum.QUEUE,
|
LockEnum.QUEUE,
|
||||||
from_address,
|
from_address,
|
||||||
@@ -234,7 +95,70 @@ class Api:
|
|||||||
'cic_eth.eth.nonce.reserve_nonce',
|
'cic_eth.eth.nonce.reserve_nonce',
|
||||||
[
|
[
|
||||||
self.chain_spec.asdict(),
|
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,
|
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,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
@@ -245,41 +169,29 @@ class Api:
|
|||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
s_allow = celery.signature(
|
s_convert = celery.signature(
|
||||||
'cic_eth.eth.erc20.check_allowance',
|
'cic_eth.eth.bancor.convert_with_default_reserve',
|
||||||
[
|
[
|
||||||
from_address,
|
from_address,
|
||||||
value,
|
target_return,
|
||||||
|
minimum_return,
|
||||||
|
from_address,
|
||||||
self.chain_spec.asdict(),
|
self.chain_spec.asdict(),
|
||||||
spender_address,
|
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
s_transfer = celery.signature(
|
|
||||||
'cic_eth.eth.erc20.transfer_from',
|
|
||||||
[
|
|
||||||
from_address,
|
|
||||||
to_address,
|
|
||||||
value,
|
|
||||||
self.chain_spec.asdict(),
|
|
||||||
spender_address,
|
|
||||||
],
|
|
||||||
queue=self.queue,
|
|
||||||
)
|
|
||||||
s_tokens.link(s_allow)
|
|
||||||
s_nonce.link(s_tokens)
|
s_nonce.link(s_tokens)
|
||||||
s_check.link(s_nonce)
|
s_check.link(s_nonce)
|
||||||
if self.callback_param != None:
|
if self.callback_param != None:
|
||||||
s_transfer.link(self.callback_success)
|
s_convert.link(self.callback_success)
|
||||||
s_allow.link(s_transfer).on_error(self.callback_error)
|
s_tokens.link(s_convert).on_error(self.callback_error)
|
||||||
else:
|
else:
|
||||||
s_allow.link(s_transfer)
|
s_tokens.link(s_convert)
|
||||||
|
|
||||||
t = s_check.apply_async(queue=self.queue)
|
t = s_check.apply_async(queue=self.queue)
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def transfer(self, from_address, to_address, value, token_symbol):
|
def transfer(self, from_address, to_address, value, token_symbol):
|
||||||
"""Executes a chain of celery tasks that performs a transfer of ERC20 tokens from one address to another.
|
"""Executes a chain of celery tasks that performs a transfer of ERC20 tokens from one address to another.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
from cic_eth.db.models.base import SessionBase
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
session.execute('SELECT count(*) from alembic_version')
|
|
||||||
session.close()
|
|
||||||
return True
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
from chainlib.eth.gas import balance
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.db.models.role import AccountRole
|
|
||||||
from cic_eth.db.models.base import SessionBase
|
|
||||||
from cic_eth.db.enum import LockEnum
|
|
||||||
from cic_eth.error import LockedError
|
|
||||||
from cic_eth.admin.ctrl import check_lock
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
|
|
||||||
config = kwargs['config']
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|
||||||
logg.debug('check gas balance of gas gifter for chain {}'.format(chain_spec))
|
|
||||||
|
|
||||||
try:
|
|
||||||
check_lock(None, None, LockEnum.INIT)
|
|
||||||
except LockedError:
|
|
||||||
logg.warning('INIT lock is set, skipping GAS GIFTER balance check.')
|
|
||||||
return True
|
|
||||||
|
|
||||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
|
||||||
o = balance(gas_provider)
|
|
||||||
r = rpc.do(o)
|
|
||||||
try:
|
|
||||||
r = int(r, 16)
|
|
||||||
except TypeError:
|
|
||||||
r = int(r)
|
|
||||||
gas_min = int(config.get('ETH_GAS_GIFTER_MINIMUM_BALANCE'))
|
|
||||||
if r < gas_min:
|
|
||||||
logg.error('EEK! gas gifter has balance {}, below minimum {}'.format(r, gas_min))
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# external imports
|
|
||||||
import redis
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
r = redis.Redis(
|
|
||||||
host=kwargs['config'].get('REDIS_HOST'),
|
|
||||||
port=kwargs['config'].get('REDIS_PORT'),
|
|
||||||
db=kwargs['config'].get('REDIS_DB'),
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
r.set(kwargs['unit'], os.getpid())
|
|
||||||
except redis.connection.ConnectionError:
|
|
||||||
return False
|
|
||||||
except redis.connection.ResponseError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from urllib.error import URLError
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from chainlib.eth.sign import sign_message
|
|
||||||
from chainlib.error import JSONRPCException
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def health(*args, **kwargs):
|
|
||||||
blocked = True
|
|
||||||
max_attempts = 5
|
|
||||||
conn = RPCConnection.connect(kwargs['config'].get('CIC_CHAIN_SPEC'), tag='signer')
|
|
||||||
for i in range(max_attempts):
|
|
||||||
idx = i + 1
|
|
||||||
logg.debug('attempt signer connection check {}/{}'.format(idx, max_attempts))
|
|
||||||
try:
|
|
||||||
conn.do(sign_message(ZERO_ADDRESS, '0x2a'))
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except ConnectionError:
|
|
||||||
pass
|
|
||||||
except URLError:
|
|
||||||
pass
|
|
||||||
except JSONRPCException:
|
|
||||||
logg.debug('signer connection succeeded')
|
|
||||||
return True
|
|
||||||
|
|
||||||
if idx < max_attempts:
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
return False
|
|
||||||
@@ -74,11 +74,10 @@ class LockEnum(enum.IntEnum):
|
|||||||
QUEUE: Disable queueing new or modified transactions
|
QUEUE: Disable queueing new or modified transactions
|
||||||
"""
|
"""
|
||||||
STICKY=1
|
STICKY=1
|
||||||
INIT=2
|
CREATE=2
|
||||||
CREATE=4
|
SEND=4
|
||||||
SEND=8
|
QUEUE=8
|
||||||
QUEUE=16
|
QUERY=16
|
||||||
QUERY=32
|
|
||||||
ALL=int(0xfffffffffffffffe)
|
ALL=int(0xfffffffffffffffe)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ Revises: 1f1b3b641d08
|
|||||||
Create Date: 2021-04-02 18:41:20.864265
|
Create Date: 2021-04-02 18:41:20.864265
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from cic_eth.db.enum import LockEnum
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -26,11 +23,10 @@ def upgrade():
|
|||||||
sa.Column("address", sa.String(42), nullable=True),
|
sa.Column("address", sa.String(42), nullable=True),
|
||||||
sa.Column('blockchain', sa.String),
|
sa.Column('blockchain', sa.String),
|
||||||
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
|
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
|
||||||
sa.Column("date_created", sa.DateTime, nullable=False, default=datetime.datetime.utcnow),
|
sa.Column("date_created", sa.DateTime, nullable=False),
|
||||||
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True),
|
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True),
|
||||||
)
|
)
|
||||||
op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True)
|
op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True)
|
||||||
op.execute("INSERT INTO lock (address, date_created, blockchain, flags) VALUES('{}', '{}', '::', {})".format(ZERO_ADDRESS, datetime.datetime.utcnow(), LockEnum.INIT | LockEnum.SEND | LockEnum.QUEUE))
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from sqlalchemy.pool import (
|
|||||||
StaticPool,
|
StaticPool,
|
||||||
QueuePool,
|
QueuePool,
|
||||||
AssertionPool,
|
AssertionPool,
|
||||||
NullPool,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -65,7 +64,6 @@ class SessionBase(Model):
|
|||||||
if SessionBase.poolable:
|
if SessionBase.poolable:
|
||||||
poolclass = QueuePool
|
poolclass = QueuePool
|
||||||
if pool_size > 1:
|
if pool_size > 1:
|
||||||
logg.info('db using queue pool')
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
max_overflow=pool_size*3,
|
max_overflow=pool_size*3,
|
||||||
@@ -76,22 +74,17 @@ class SessionBase(Model):
|
|||||||
echo=debug,
|
echo=debug,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if pool_size == 0:
|
if debug:
|
||||||
logg.info('db using nullpool')
|
|
||||||
poolclass = NullPool
|
|
||||||
elif debug:
|
|
||||||
logg.info('db using assertion pool')
|
|
||||||
poolclass = AssertionPool
|
poolclass = AssertionPool
|
||||||
else:
|
else:
|
||||||
logg.info('db using static pool')
|
|
||||||
poolclass = StaticPool
|
poolclass = StaticPool
|
||||||
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
poolclass=poolclass,
|
poolclass=poolclass,
|
||||||
echo=debug,
|
echo=debug,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logg.info('db not poolable')
|
|
||||||
e = create_engine(
|
e = create_engine(
|
||||||
dsn,
|
dsn,
|
||||||
echo=debug,
|
echo=debug,
|
||||||
|
|||||||
8
apps/cic-eth/cic_eth/db/util.py
Normal file
8
apps/cic-eth/cic_eth/db/util.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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')
|
||||||
@@ -48,8 +48,6 @@ class RoleMissingError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IntegrityError(Exception):
|
class IntegrityError(Exception):
|
||||||
"""Exception raised to signal irregularities with deduplication and ordering of tasks
|
"""Exception raised to signal irregularities with deduplication and ordering of tasks
|
||||||
|
|
||||||
@@ -64,24 +62,15 @@ class LockedError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SeppukuError(Exception):
|
class SignerError(Exception):
|
||||||
"""Exception base class for all errors that should cause system shutdown
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SignerError(SeppukuError):
|
|
||||||
"""Exception raised when signer is unavailable or generates an error
|
"""Exception raised when signer is unavailable or generates an error
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RoleAgencyError(SeppukuError):
|
class EthError(Exception):
|
||||||
"""Exception raise when a role cannot perform its function. This is a critical exception
|
"""Exception raised when unspecified error from evm node is encountered
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class YouAreBrokeError(Exception):
|
|
||||||
"""Exception raised when a value transfer is attempted without access to sufficient funds
|
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import logging
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
from erc20_faucet import Faucet
|
from erc20_single_shot_faucet import SingleShotFaucet as Faucet
|
||||||
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
from chainlib.eth.sign import (
|
from chainlib.eth.sign import (
|
||||||
new_account,
|
new_account,
|
||||||
@@ -19,10 +19,8 @@ from chainlib.eth.tx import (
|
|||||||
unpack,
|
unpack,
|
||||||
)
|
)
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.error import JSONRPCException
|
from eth_accounts_index import AccountRegistry
|
||||||
from eth_accounts_index.registry import AccountRegistry
|
from sarafu_faucet import MinterFaucet as Faucet
|
||||||
from eth_accounts_index import AccountsIndex
|
|
||||||
from sarafu_faucet import MinterFaucet
|
|
||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
|
|
||||||
# local import
|
# local import
|
||||||
@@ -72,18 +70,11 @@ def create(self, password, chain_spec_dict):
|
|||||||
a = None
|
a = None
|
||||||
conn = RPCConnection.connect(chain_spec, 'signer')
|
conn = RPCConnection.connect(chain_spec, 'signer')
|
||||||
o = new_account()
|
o = new_account()
|
||||||
try:
|
a = conn.do(o)
|
||||||
a = conn.do(o)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
|
|
||||||
# TODO: It seems infeasible that a can be None in any case, verify
|
|
||||||
if a == None:
|
if a == None:
|
||||||
raise SignerError('create account')
|
raise SignerError('create account')
|
||||||
|
|
||||||
logg.debug('created account {}'.format(a))
|
logg.debug('created account {}'.format(a))
|
||||||
|
|
||||||
# Initialize nonce provider record for account
|
# Initialize nonce provider record for account
|
||||||
@@ -134,7 +125,7 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
|
|||||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||||
nonce_oracle = CustodialTaskNonceOracle(writer_address, self.request.root_id, session=session) #, default_nonce)
|
nonce_oracle = CustodialTaskNonceOracle(writer_address, self.request.root_id, session=session) #, default_nonce)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, AccountRegistry.gas)
|
gas_oracle = self.create_gas_oracle(rpc, AccountRegistry.gas)
|
||||||
account_registry = AccountsIndex(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
account_registry = AccountRegistry(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)
|
(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()
|
rpc_signer.disconnect()
|
||||||
|
|
||||||
@@ -186,7 +177,7 @@ def gift(self, account_address, chain_spec_dict):
|
|||||||
# Generate and sign transaction
|
# Generate and sign transaction
|
||||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||||
nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce)
|
nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MinterFaucet.gas)
|
gas_oracle = self.create_gas_oracle(rpc, Faucet.gas)
|
||||||
faucet = Faucet(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
|
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)
|
(tx_hash_hex, tx_signed_raw_hex) = faucet.give_to(faucet_address, account_address, account_address, tx_format=TxFormat.RLP_SIGNED)
|
||||||
rpc_signer.disconnect()
|
rpc_signer.disconnect()
|
||||||
@@ -228,22 +219,21 @@ def have(self, account, chain_spec_dict):
|
|||||||
"""
|
"""
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
o = sign_message(account, '0x2a')
|
o = sign_message(account, '0x2a')
|
||||||
conn = RPCConnection.connect(chain_spec, 'signer')
|
try:
|
||||||
|
conn = RPCConnection.connect(chain_spec, 'signer')
|
||||||
|
except Exception as e:
|
||||||
|
logg.debug('cannot sign with {}: {}'.format(account, e))
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
except ConnectionError as e:
|
conn.disconnect()
|
||||||
raise SignerError(e)
|
return account
|
||||||
except FileNotFoundError as e:
|
except Exception as e:
|
||||||
raise SignerError(e)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
logg.debug('cannot sign with {}: {}'.format(account, e))
|
logg.debug('cannot sign with {}: {}'.format(account, e))
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
conn.disconnect()
|
|
||||||
return account
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
|
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
|
||||||
def set_role(self, tag, address, chain_spec_dict):
|
def set_role(self, tag, address, chain_spec_dict):
|
||||||
@@ -339,7 +329,7 @@ def cache_account_data(
|
|||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
|
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
|
||||||
tx = unpack(tx_signed_raw_bytes, chain_spec)
|
tx = unpack(tx_signed_raw_bytes, chain_spec)
|
||||||
tx_data = AccountsIndex.parse_add_request(tx['data'])
|
tx_data = AccountRegistry.parse_add_request(tx['data'])
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
tx_cache = TxCache(
|
tx_cache = TxCache(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import celery
|
|||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
|
from chainlib.eth.erc20 import ERC20
|
||||||
from chainlib.eth.tx import (
|
from chainlib.eth.tx import (
|
||||||
TxFormat,
|
TxFormat,
|
||||||
unpack,
|
unpack,
|
||||||
@@ -15,7 +16,6 @@ from cic_eth_registry.erc20 import ERC20Token
|
|||||||
from hexathon import strip_0x
|
from hexathon import strip_0x
|
||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
from chainqueue.error import NotLocalTxError
|
from chainqueue.error import NotLocalTxError
|
||||||
from eth_erc20 import ERC20
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
@@ -24,7 +24,6 @@ from cic_eth.error import (
|
|||||||
TokenCountError,
|
TokenCountError,
|
||||||
PermanentTxError,
|
PermanentTxError,
|
||||||
OutOfGasError,
|
OutOfGasError,
|
||||||
YouAreBrokeError,
|
|
||||||
)
|
)
|
||||||
from cic_eth.queue.tx import register_tx
|
from cic_eth.queue.tx import register_tx
|
||||||
from cic_eth.eth.gas import (
|
from cic_eth.eth.gas import (
|
||||||
@@ -72,117 +71,6 @@ def balance(tokens, holder_address, chain_spec_dict):
|
|||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True)
|
|
||||||
def check_allowance(self, tokens, holder_address, value, chain_spec_dict, spender_address):
|
|
||||||
"""Best-effort verification that the allowance for a transfer from spend is sufficient.
|
|
||||||
|
|
||||||
:raises YouAreBrokeError: If allowance is insufficient
|
|
||||||
|
|
||||||
:param tokens: Token addresses
|
|
||||||
:type tokens: list of str, 0x-hex
|
|
||||||
:param holder_address: Token holder address
|
|
||||||
:type holder_address: str, 0x-hex
|
|
||||||
:param value: Amount of token, in 'wei'
|
|
||||||
:type value: int
|
|
||||||
:param chain_str: Chain spec string representation
|
|
||||||
:type chain_str: str
|
|
||||||
:param spender_address: Address of account spending on behalf of holder
|
|
||||||
:type spender_address: str, 0x-hex
|
|
||||||
:return: Token list as passed to task
|
|
||||||
:rtype: dict
|
|
||||||
"""
|
|
||||||
logg.debug('tokens {}'.format(tokens))
|
|
||||||
if len(tokens) != 1:
|
|
||||||
raise TokenCountError
|
|
||||||
t = tokens[0]
|
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
|
||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
|
||||||
|
|
||||||
caller_address = ERC20Token.caller_address
|
|
||||||
c = ERC20(chain_spec)
|
|
||||||
o = c.allowance(t['address'], holder_address, spender_address, sender_address=caller_address)
|
|
||||||
r = rpc.do(o)
|
|
||||||
allowance = c.parse_allowance(r)
|
|
||||||
if allowance < value:
|
|
||||||
errstr = 'allowance {} insufficent to transfer {} {} by {} on behalf of {}'.format(allowance, value, t['symbol'], spender_address, holder_address)
|
|
||||||
logg.error(errstr)
|
|
||||||
raise YouAreBrokeError(errstr)
|
|
||||||
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask)
|
|
||||||
def transfer_from(self, tokens, holder_address, receiver_address, value, chain_spec_dict, spender_address):
|
|
||||||
"""Transfer ERC20 tokens between addresses
|
|
||||||
|
|
||||||
First argument is a list of tokens, to enable the task to be chained to the symbol to token address resolver function. However, it accepts only one token as argument.
|
|
||||||
|
|
||||||
:param tokens: Token addresses
|
|
||||||
:type tokens: list of str, 0x-hex
|
|
||||||
:param holder_address: Token holder address
|
|
||||||
:type holder_address: str, 0x-hex
|
|
||||||
:param receiver_address: Token receiver address
|
|
||||||
:type receiver_address: str, 0x-hex
|
|
||||||
:param value: Amount of token, in 'wei'
|
|
||||||
:type value: int
|
|
||||||
:param chain_str: Chain spec string representation
|
|
||||||
:type chain_str: str
|
|
||||||
:param spender_address: Address of account spending on behalf of holder
|
|
||||||
:type spender_address: str, 0x-hex
|
|
||||||
:raises TokenCountError: Either none or more then one tokens have been passed as tokens argument
|
|
||||||
:return: Transaction hash for tranfer operation
|
|
||||||
:rtype: str, 0x-hex
|
|
||||||
"""
|
|
||||||
# we only allow one token, one transfer
|
|
||||||
logg.debug('tokens {}'.format(tokens))
|
|
||||||
if len(tokens) != 1:
|
|
||||||
raise TokenCountError
|
|
||||||
t = tokens[0]
|
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
|
||||||
queue = self.request.delivery_info.get('routing_key')
|
|
||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
|
||||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
|
||||||
|
|
||||||
session = self.create_session()
|
|
||||||
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
|
||||||
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
|
||||||
try:
|
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.transfer_from(t['address'], spender_address, holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
|
|
||||||
|
|
||||||
rpc_signer.disconnect()
|
|
||||||
rpc.disconnect()
|
|
||||||
|
|
||||||
cache_task = 'cic_eth.eth.erc20.cache_transfer_from_data'
|
|
||||||
|
|
||||||
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
|
||||||
session.commit()
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
gas_pair = gas_oracle.get_gas(tx_signed_raw_hex)
|
|
||||||
gas_budget = gas_pair[0] * gas_pair[1]
|
|
||||||
logg.debug('transfer tx {} {} {}'.format(tx_hash_hex, queue, gas_budget))
|
|
||||||
|
|
||||||
s = create_check_gas_task(
|
|
||||||
[tx_signed_raw_hex],
|
|
||||||
chain_spec,
|
|
||||||
holder_address,
|
|
||||||
gas_budget,
|
|
||||||
[tx_hash_hex],
|
|
||||||
queue,
|
|
||||||
)
|
|
||||||
s.apply_async()
|
|
||||||
return tx_hash_hex
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask)
|
@celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask)
|
||||||
def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_dict):
|
def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_dict):
|
||||||
"""Transfer ERC20 tokens between addresses
|
"""Transfer ERC20 tokens between addresses
|
||||||
@@ -220,13 +108,7 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_d
|
|||||||
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
||||||
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
|
|
||||||
|
|
||||||
rpc_signer.disconnect()
|
rpc_signer.disconnect()
|
||||||
rpc.disconnect()
|
rpc.disconnect()
|
||||||
@@ -289,12 +171,7 @@ def approve(self, tokens, holder_address, spender_address, value, chain_spec_dic
|
|||||||
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
|
||||||
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
|
||||||
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
|
|
||||||
rpc_signer.disconnect()
|
rpc_signer.disconnect()
|
||||||
rpc.disconnect()
|
rpc.disconnect()
|
||||||
@@ -344,7 +221,6 @@ def resolve_tokens_by_symbol(self, token_symbols, chain_spec_dict):
|
|||||||
logg.debug('token {}'.format(token_address))
|
logg.debug('token {}'.format(token_address))
|
||||||
tokens.append({
|
tokens.append({
|
||||||
'address': token_address,
|
'address': token_address,
|
||||||
'symbol': token_symbol,
|
|
||||||
'converters': [],
|
'converters': [],
|
||||||
})
|
})
|
||||||
rpc.disconnect()
|
rpc.disconnect()
|
||||||
@@ -392,48 +268,6 @@ def cache_transfer_data(
|
|||||||
return (tx_hash_hex, cache_id)
|
return (tx_hash_hex, cache_id)
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
|
||||||
def cache_transfer_from_data(
|
|
||||||
tx_hash_hex,
|
|
||||||
tx_signed_raw_hex,
|
|
||||||
chain_spec_dict,
|
|
||||||
):
|
|
||||||
"""Helper function for otx_cache_transfer_from
|
|
||||||
|
|
||||||
:param tx_hash_hex: Transaction hash
|
|
||||||
:type tx_hash_hex: str, 0x-hex
|
|
||||||
:param tx: Signed raw transaction
|
|
||||||
:type tx: str, 0x-hex
|
|
||||||
:returns: Transaction hash and id of cache element in storage backend, respectively
|
|
||||||
:rtype: tuple
|
|
||||||
"""
|
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
|
||||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
|
|
||||||
tx = unpack(tx_signed_raw_bytes, chain_spec)
|
|
||||||
|
|
||||||
tx_data = ERC20.parse_transfer_from_request(tx['data'])
|
|
||||||
spender_address = tx_data[0]
|
|
||||||
recipient_address = tx_data[1]
|
|
||||||
token_value = tx_data[2]
|
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
|
||||||
tx_cache = TxCache(
|
|
||||||
tx_hash_hex,
|
|
||||||
tx['from'],
|
|
||||||
recipient_address,
|
|
||||||
tx['to'],
|
|
||||||
tx['to'],
|
|
||||||
token_value,
|
|
||||||
token_value,
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
session.add(tx_cache)
|
|
||||||
session.commit()
|
|
||||||
cache_id = tx_cache.id
|
|
||||||
session.close()
|
|
||||||
return (tx_hash_hex, cache_id)
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
@celery_app.task(base=CriticalSQLAlchemyTask)
|
||||||
def cache_approve_data(
|
def cache_approve_data(
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
|
|||||||
@@ -57,12 +57,10 @@ celery_app = celery.current_app
|
|||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
MAXIMUM_FEE_UNITS = 8000000
|
|
||||||
|
|
||||||
class MaxGasOracle:
|
class MaxGasOracle:
|
||||||
|
|
||||||
def gas(code=None):
|
def gas(code=None):
|
||||||
return MAXIMUM_FEE_UNITS
|
return 8000000
|
||||||
|
|
||||||
|
|
||||||
def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None):
|
def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None):
|
||||||
@@ -152,7 +150,7 @@ def cache_gas_data(
|
|||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, throws=(OutOfGasError), base=CriticalSQLAlchemyAndWeb3Task)
|
@celery_app.task(bind=True, throws=(OutOfGasError), base=CriticalSQLAlchemyAndWeb3Task)
|
||||||
def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_required=MAXIMUM_FEE_UNITS):
|
def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_required=None):
|
||||||
"""Check the gas level of the sender address of a transaction.
|
"""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.
|
If the account balance is not sufficient for the required gas, gas refill is requested and OutOfGasError raiser.
|
||||||
@@ -172,30 +170,24 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
|||||||
:return: Signed raw transaction data list
|
:return: Signed raw transaction data list
|
||||||
:rtype: param txs, unchanged
|
:rtype: param txs, unchanged
|
||||||
"""
|
"""
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
|
||||||
logg.debug('txs {} tx_hashes {}'.format(txs, tx_hashes))
|
|
||||||
|
|
||||||
addresspass = None
|
|
||||||
if len(txs) == 0:
|
if len(txs) == 0:
|
||||||
addresspass = []
|
|
||||||
for i in range(len(tx_hashes)):
|
for i in range(len(tx_hashes)):
|
||||||
o = get_tx(chain_spec_dict, tx_hashes[i])
|
o = get_tx(tx_hashes[i])
|
||||||
txs.append(o['signed_tx'])
|
txs.append(o['signed_tx'])
|
||||||
logg.debug('sender {}'.format(o))
|
|
||||||
tx = unpack(bytes.fromhex(strip_0x(o['signed_tx'])), chain_spec)
|
|
||||||
if address == None:
|
if address == None:
|
||||||
address = tx['from']
|
address = o['address']
|
||||||
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):
|
if not is_checksum_address(address):
|
||||||
raise ValueError('invalid address {}'.format(address))
|
raise ValueError('invalid address {}'.format(address))
|
||||||
|
|
||||||
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
|
|
||||||
queue = self.request.delivery_info.get('routing_key')
|
queue = self.request.delivery_info.get('routing_key')
|
||||||
|
|
||||||
conn = RPCConnection.connect(chain_spec)
|
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
|
gas_balance = 0
|
||||||
try:
|
try:
|
||||||
o = balance(address)
|
o = balance(address)
|
||||||
@@ -206,9 +198,6 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
|||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
raise EthError('gas_balance call for {}: {}'.format(address, e))
|
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))
|
logg.debug('address {} has gas {} needs {}'.format(address, gas_balance, gas_required))
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
||||||
@@ -279,8 +268,7 @@ def check_gas(self, tx_hashes, chain_spec_dict, txs=[], address=None, gas_requir
|
|||||||
queue=queue,
|
queue=queue,
|
||||||
)
|
)
|
||||||
ready_tasks.append(s)
|
ready_tasks.append(s)
|
||||||
t = celery.group(ready_tasks)()
|
celery.group(ready_tasks)()
|
||||||
logg.debug('group {}'.format(t))
|
|
||||||
|
|
||||||
return txs
|
return txs
|
||||||
|
|
||||||
@@ -340,12 +328,7 @@ def refill_gas(self, recipient_address, chain_spec_dict):
|
|||||||
|
|
||||||
# build and add transaction
|
# build and add transaction
|
||||||
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
|
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
|
||||||
cache_task = 'cic_eth.eth.gas.cache_gas_data'
|
cache_task = 'cic_eth.eth.gas.cache_gas_data'
|
||||||
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
||||||
@@ -421,12 +404,7 @@ def resend_with_higher_gas(self, txold_hash_hex, chain_spec_dict, gas=None, defa
|
|||||||
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
|
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
|
||||||
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
|
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
|
||||||
tx['gasPrice'] = new_gas_price
|
tx['gasPrice'] = new_gas_price
|
||||||
try:
|
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
|
|
||||||
except ConnectionError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise SignerError(e)
|
|
||||||
queue_create(
|
queue_create(
|
||||||
chain_spec,
|
chain_spec,
|
||||||
tx['nonce'],
|
tx['nonce'],
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
# external imports
|
# extended imports
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.status import Status as TxStatus
|
from chainlib.status import Status as TxStatus
|
||||||
from cic_eth_registry.erc20 import ERC20Token
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.ext.address import translate_address
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedTx:
|
class ExtendedTx:
|
||||||
@@ -31,12 +27,12 @@ class ExtendedTx:
|
|||||||
self.status_code = TxStatus.PENDING.value
|
self.status_code = TxStatus.PENDING.value
|
||||||
|
|
||||||
|
|
||||||
def set_actors(self, sender, recipient, trusted_declarator_addresses=None, caller_address=ZERO_ADDRESS):
|
def set_actors(self, sender, recipient, trusted_declarator_addresses=None):
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.recipient = recipient
|
self.recipient = recipient
|
||||||
if trusted_declarator_addresses != None:
|
if trusted_declarator_addresses != None:
|
||||||
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
|
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec)
|
||||||
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
|
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec)
|
||||||
|
|
||||||
|
|
||||||
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
def set_tokens(self, source, source_value, destination=None, destination_value=None):
|
||||||
@@ -44,8 +40,8 @@ class ExtendedTx:
|
|||||||
destination = source
|
destination = source
|
||||||
if destination_value == None:
|
if destination_value == None:
|
||||||
destination_value = source_value
|
destination_value = source_value
|
||||||
st = ERC20Token(self.chain_spec, self.rpc, source)
|
st = ERC20Token(self.rpc, source)
|
||||||
dt = ERC20Token(self.chain_spec, self.rpc, destination)
|
dt = ERC20Token(self.rpc, destination)
|
||||||
self.source_token = source
|
self.source_token = source
|
||||||
self.source_token_symbol = st.symbol
|
self.source_token_symbol = st.symbol
|
||||||
self.source_token_name = st.name
|
self.source_token_name = st.name
|
||||||
@@ -66,10 +62,10 @@ class ExtendedTx:
|
|||||||
self.status_code = n
|
self.status_code = n
|
||||||
|
|
||||||
|
|
||||||
def asdict(self):
|
def to_dict(self):
|
||||||
o = {}
|
o = {}
|
||||||
for attr in dir(self):
|
for attr in dir(self):
|
||||||
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'asdict', 'rpc']:
|
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'to_dict']:
|
||||||
continue
|
continue
|
||||||
o[attr] = getattr(self, attr)
|
o[attr] = getattr(self, attr)
|
||||||
return o
|
return o
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from chainqueue.db.models.tx import Otx
|
|||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
from chainqueue.db.enum import StatusBits
|
from chainqueue.db.enum import StatusBits
|
||||||
from chainqueue.error import NotLocalTxError
|
from chainqueue.error import NotLocalTxError
|
||||||
from potaahto.symbols import snake_and_camel
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db import SessionBase
|
from cic_eth.db import SessionBase
|
||||||
@@ -59,9 +58,6 @@ def hashes_to_txs(self, tx_hashes):
|
|||||||
if len(tx_hashes) == 0:
|
if len(tx_hashes) == 0:
|
||||||
raise ValueError('no transaction to send')
|
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']
|
queue = self.request.delivery_info['routing_key']
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
@@ -152,7 +148,7 @@ def send(self, txs, chain_spec_dict):
|
|||||||
|
|
||||||
@celery_app.task(bind=True, throws=(NotFoundEthException,), base=CriticalWeb3Task)
|
@celery_app.task(bind=True, throws=(NotFoundEthException,), base=CriticalWeb3Task)
|
||||||
def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
||||||
"""Force update of network status of a single transaction
|
"""Force update of network status of a simgle transaction
|
||||||
|
|
||||||
:param tx_hash_hex: Transaction hash
|
:param tx_hash_hex: Transaction hash
|
||||||
:type tx_hash_hex: str, 0x-hex
|
:type tx_hash_hex: str, 0x-hex
|
||||||
@@ -177,14 +173,12 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
|||||||
|
|
||||||
# TODO: apply receipt in tx object to validate and normalize input
|
# TODO: apply receipt in tx object to validate and normalize input
|
||||||
if rcpt != None:
|
if rcpt != None:
|
||||||
rcpt = snake_and_camel(rcpt)
|
|
||||||
success = rcpt['status'] == 1
|
success = rcpt['status'] == 1
|
||||||
logg.debug('sync tx {} mined block {} tx index {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], rcpt['transactionIndex'], success))
|
logg.debug('sync tx {} mined block {} success {}'.format(tx_hash_hex, rcpt['blockNumber'], success))
|
||||||
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.queue.state.set_final',
|
'cic_eth.queue.state.set_final',
|
||||||
[
|
[
|
||||||
chain_spec_dict,
|
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
rcpt['blockNumber'],
|
rcpt['blockNumber'],
|
||||||
rcpt['transactionIndex'],
|
rcpt['transactionIndex'],
|
||||||
@@ -192,14 +186,12 @@ def sync_tx(self, tx_hash_hex, chain_spec_dict):
|
|||||||
],
|
],
|
||||||
queue=queue,
|
queue=queue,
|
||||||
)
|
)
|
||||||
# TODO: it's not entirely clear how we can reliable determine that its in mempool without explicitly checking
|
|
||||||
else:
|
else:
|
||||||
logg.debug('sync tx {} mempool'.format(tx_hash_hex))
|
logg.debug('sync tx {} mempool'.format(tx_hash_hex))
|
||||||
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
'cic_eth.queue.state.set_sent',
|
'cic_eth.queue.state.set_sent',
|
||||||
[
|
[
|
||||||
chain_spec_dict,
|
|
||||||
tx_hash_hex,
|
tx_hash_hex,
|
||||||
],
|
],
|
||||||
queue=queue,
|
queue=queue,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from chainlib.chain import ChainSpec
|
|||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from cic_eth_registry import CICRegistry
|
from cic_eth_registry import CICRegistry
|
||||||
from eth_address_declarator import Declarator
|
from eth_address_declarator import AddressDeclarator
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.task import BaseTask
|
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)
|
registry = CICRegistry(chain_spec, rpc)
|
||||||
|
|
||||||
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
||||||
c = Declarator(chain_spec)
|
c = AddressDeclarator(chain_spec)
|
||||||
|
|
||||||
for trusted_address in trusted_addresses:
|
for trusted_address in trusted_addresses:
|
||||||
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
|
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
|
||||||
r = rpc.do(o)
|
r = rpc.do(o)
|
||||||
declaration_hex = Declarator.parse_declaration(r)
|
declaration_hex = AddressDeclarator.parse_declaration(r)
|
||||||
declaration_hex = declaration_hex[0].rstrip('0')
|
declaration_hex = declaration_hex[0].rstrip('0')
|
||||||
declaration_bytes = bytes.fromhex(declaration_hex)
|
declaration_bytes = bytes.fromhex(declaration_hex)
|
||||||
declaration = None
|
declaration = None
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ from chainlib.eth.tx import (
|
|||||||
)
|
)
|
||||||
from chainlib.eth.block import block_by_number
|
from chainlib.eth.block import block_by_number
|
||||||
from chainlib.eth.contract import abi_decode_single
|
from chainlib.eth.contract import abi_decode_single
|
||||||
|
from chainlib.eth.erc20 import ERC20
|
||||||
from hexathon import strip_0x
|
from hexathon import strip_0x
|
||||||
from cic_eth_registry import CICRegistry
|
from cic_eth_registry import CICRegistry
|
||||||
from cic_eth_registry.erc20 import ERC20Token
|
from cic_eth_registry.erc20 import ERC20Token
|
||||||
from chainqueue.db.models.otx import Otx
|
from chainqueue.db.models.otx import Otx
|
||||||
from chainqueue.db.enum import StatusEnum
|
from chainqueue.db.enum import StatusEnum
|
||||||
from chainqueue.sql.query import get_tx_cache
|
from chainqueue.query import get_tx_cache
|
||||||
from eth_erc20 import ERC20
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.queue.time import tx_times
|
from cic_eth.queue.time import tx_times
|
||||||
@@ -114,7 +114,7 @@ def list_tx_by_bloom(self, bloomspec, address, chain_spec_dict):
|
|||||||
|
|
||||||
# TODO: pass through registry to validate declarator entry of token
|
# TODO: pass through registry to validate declarator entry of token
|
||||||
#token = registry.by_address(tx['to'], sender_address=self.call_address)
|
#token = registry.by_address(tx['to'], sender_address=self.call_address)
|
||||||
token = ERC20Token(chain_spec, rpc, tx['to'])
|
token = ERC20Token(rpc, tx['to'])
|
||||||
token_symbol = token.symbol
|
token_symbol = token.symbol
|
||||||
token_decimals = token.decimals
|
token_decimals = token.decimals
|
||||||
times = tx_times(tx['hash'], chain_spec)
|
times = tx_times(tx['hash'], chain_spec)
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
60806040526000805534801561001457600080fd5b50610181806100246000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c0100000000000000000000000000000000000000000000000000000000900480630dbe671f146100585780631817835814610076575b600080fd5b610060610080565b60405161006d91906100ae565b60405180910390f35b61007e610086565b005b60005481565b600080815480929190610098906100d3565b9190505550565b6100a8816100c9565b82525050565b60006020820190506100c3600083018461009f565b92915050565b6000819050919050565b60006100de826100c9565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101115761011061011c565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea264697066735822122034ad8e91e864f030d47f5b93e281869206c1b203c36dc79a209ac9c9c16e577564736f6c63430008040033
|
|
||||||
10
apps/cic-eth/cic_eth/pytest/testdata/Bogus.sol
vendored
10
apps/cic-eth/cic_eth/pytest/testdata/Bogus.sol
vendored
@@ -1,10 +0,0 @@
|
|||||||
pragma solidity ^0.8.0;
|
|
||||||
|
|
||||||
contract Bogus {
|
|
||||||
|
|
||||||
uint256 public a = 0;
|
|
||||||
|
|
||||||
function poke() public {
|
|
||||||
a++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import datetime
|
|||||||
import celery
|
import celery
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
import chainqueue.sql.query
|
import chainqueue.query
|
||||||
from chainqueue.db.enum import (
|
from chainqueue.db.enum import (
|
||||||
StatusEnum,
|
StatusEnum,
|
||||||
is_alive,
|
is_alive,
|
||||||
@@ -28,7 +28,7 @@ celery_app = celery.current_app
|
|||||||
def get_tx_cache(chain_spec_dict, tx_hash):
|
def get_tx_cache(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.query.get_tx_cache(chain_spec, tx_hash, session=session)
|
r = chainqueue.query.get_tx_cache(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ def get_tx_cache(chain_spec_dict, tx_hash):
|
|||||||
def get_tx(chain_spec_dict, tx_hash):
|
def get_tx(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.query.get_tx(chain_spec, tx_hash, session=session)
|
r = chainqueue.query.get_tx(chain_spec, tx_hash)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
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):
|
def get_account_tx(chain_spec_dict, address, as_sender=True, as_recipient=True, counterpart=None):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
|
r = chainqueue.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
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):
|
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)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
|
r = chainqueue.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def get_status_tx(chain_spec, status, not_status=None, before=None, exact=False, limit=0, session=None):
|
def get_status_tx(chain_spec, status, not_status=None, before=None, exact=False, limit=0, session=None):
|
||||||
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)
|
return chainqueue.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):
|
def get_paused_tx(chain_spec, status=None, sender=None, session=None, decoder=None):
|
||||||
return chainqueue.sql.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
|
return chainqueue.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
|
||||||
|
|
||||||
|
|
||||||
def get_nonce_tx(chain_spec, nonce, sender):
|
def get_nonce_tx(chain_spec, nonce, sender):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# external imports
|
# external imports
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
import chainqueue.sql.state
|
import chainqueue.state
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
import celery
|
import celery
|
||||||
@@ -14,7 +14,7 @@ celery_app = celery.current_app
|
|||||||
def set_sent(chain_spec_dict, tx_hash, fail=False):
|
def set_sent(chain_spec_dict, tx_hash, fail=False):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_sent(chain_spec, tx_hash, fail, session=session)
|
r = chainqueue.state.set_sent(chain_spec, tx_hash, fail, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
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):
|
def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
|
r = chainqueue.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
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):
|
def set_cancel(chain_spec_dict, tx_hash, manual=False):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_cancel(chain_spec, tx_hash, manual, session=session)
|
r = chainqueue.state.set_cancel(chain_spec, tx_hash, manual, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ def set_cancel(chain_spec_dict, tx_hash, manual=False):
|
|||||||
def set_rejected(chain_spec_dict, tx_hash):
|
def set_rejected(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_rejected(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_rejected(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ def set_rejected(chain_spec_dict, tx_hash):
|
|||||||
def set_fubar(chain_spec_dict, tx_hash):
|
def set_fubar(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_fubar(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_fubar(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ def set_fubar(chain_spec_dict, tx_hash):
|
|||||||
def set_manual(chain_spec_dict, tx_hash):
|
def set_manual(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_manual(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_manual(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ def set_manual(chain_spec_dict, tx_hash):
|
|||||||
def set_ready(chain_spec_dict, tx_hash):
|
def set_ready(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_ready(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_ready(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ def set_ready(chain_spec_dict, tx_hash):
|
|||||||
def set_reserved(chain_spec_dict, tx_hash):
|
def set_reserved(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_reserved(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_reserved(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ def set_reserved(chain_spec_dict, tx_hash):
|
|||||||
def set_waitforgas(chain_spec_dict, tx_hash):
|
def set_waitforgas(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.set_waitforgas(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.set_waitforgas(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ def set_waitforgas(chain_spec_dict, tx_hash):
|
|||||||
def get_state_log(chain_spec_dict, tx_hash):
|
def get_state_log(chain_spec_dict, tx_hash):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.get_state_log(chain_spec, tx_hash, session=session)
|
r = chainqueue.state.get_state_log(chain_spec, tx_hash, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -104,6 +104,6 @@ def get_state_log(chain_spec_dict, tx_hash):
|
|||||||
def obsolete(chain_spec_dict, tx_hash, final):
|
def obsolete(chain_spec_dict, tx_hash, final):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
r = chainqueue.sql.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
|
r = chainqueue.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
|
||||||
session.close()
|
session.close()
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from chainqueue.error import NotLocalTxError
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.task import CriticalSQLAlchemyAndWeb3Task
|
from cic_eth.task import CriticalSQLAlchemyAndWeb3Task
|
||||||
from cic_eth.db.models.base import SessionBase
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
celery_app = celery.current_app
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ from sqlalchemy import tuple_
|
|||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
import chainqueue.sql.state
|
import chainqueue.state
|
||||||
from chainqueue.db.enum import (
|
from chainqueue.db.enum import (
|
||||||
StatusEnum,
|
StatusEnum,
|
||||||
StatusBits,
|
StatusBits,
|
||||||
is_alive,
|
is_alive,
|
||||||
dead,
|
dead,
|
||||||
)
|
)
|
||||||
from chainqueue.sql.tx import create
|
from chainqueue.tx import create
|
||||||
from chainqueue.error import NotLocalTxError
|
from chainqueue.error import NotLocalTxError
|
||||||
from chainqueue.db.enum import status_str
|
from chainqueue.db.enum import status_str
|
||||||
|
|
||||||
|
|||||||
@@ -5,30 +5,29 @@ import logging
|
|||||||
from cic_eth_registry import CICRegistry
|
from cic_eth_registry import CICRegistry
|
||||||
from cic_eth_registry.lookup.declarator import AddressDeclaratorLookup
|
from cic_eth_registry.lookup.declarator import AddressDeclaratorLookup
|
||||||
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
|
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
def connect_token_registry(rpc, chain_spec, sender_address=ZERO_ADDRESS):
|
def connect_token_registry(rpc, chain_spec):
|
||||||
registry = CICRegistry(chain_spec, rpc)
|
registry = CICRegistry(chain_spec, rpc)
|
||||||
token_registry_address = registry.by_name('TokenRegistry', sender_address=sender_address)
|
token_registry_address = registry.by_name('TokenRegistry')
|
||||||
logg.debug('using token registry address {}'.format(token_registry_address))
|
logg.debug('using token registry address {}'.format(token_registry_address))
|
||||||
lookup = TokenIndexLookup(chain_spec, token_registry_address)
|
lookup = TokenIndexLookup(chain_spec, token_registry_address)
|
||||||
CICRegistry.add_lookup(lookup)
|
CICRegistry.add_lookup(lookup)
|
||||||
|
|
||||||
|
|
||||||
def connect_declarator(rpc, chain_spec, trusted_addresses, sender_address=ZERO_ADDRESS):
|
def connect_declarator(rpc, chain_spec, trusted_addresses):
|
||||||
registry = CICRegistry(chain_spec, rpc)
|
registry = CICRegistry(chain_spec, rpc)
|
||||||
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
|
declarator_address = registry.by_name('AddressDeclarator')
|
||||||
logg.debug('using declarator address {}'.format(declarator_address))
|
logg.debug('using declarator address {}'.format(declarator_address))
|
||||||
lookup = AddressDeclaratorLookup(chain_spec, declarator_address, trusted_addresses)
|
lookup = AddressDeclaratorLookup(chain_spec, declarator_address, trusted_addresses)
|
||||||
CICRegistry.add_lookup(lookup)
|
CICRegistry.add_lookup(lookup)
|
||||||
|
|
||||||
|
|
||||||
def connect(rpc, chain_spec, registry_address, sender_address=ZERO_ADDRESS):
|
def connect(rpc, chain_spec, registry_address):
|
||||||
CICRegistry.address = registry_address
|
CICRegistry.address = registry_address
|
||||||
registry = CICRegistry(chain_spec, rpc)
|
registry = CICRegistry(chain_spec, rpc)
|
||||||
registry_address = registry.by_name('ContractRegistry', sender_address=sender_address)
|
registry_address = registry.by_name('ContractRegistry')
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
|
|||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
||||||
|
argparser.add_argument('-r', '--registry-address', type=str, help='CIC registry address')
|
||||||
argparser.add_argument('-f', '--format', dest='f', default=default_format, type=str, help='Output format')
|
argparser.add_argument('-f', '--format', dest='f', default=default_format, type=str, help='Output format')
|
||||||
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
|
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
||||||
@@ -58,7 +59,6 @@ args_override = {
|
|||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
||||||
}
|
}
|
||||||
# override args
|
# override args
|
||||||
config.dict_override(args_override, 'cli')
|
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
config.censor('PASSWORD', 'DATABASE')
|
||||||
config.censor('PASSWORD', 'SSL')
|
config.censor('PASSWORD', 'SSL')
|
||||||
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
||||||
@@ -67,9 +67,7 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
|
|||||||
|
|
||||||
queue = args.q
|
queue = args.q
|
||||||
|
|
||||||
chain_spec = None
|
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||||
if config.get('CIC_CHAIN_SPEC') != None and config.get('CIC_CHAIN_SPEC') != '::':
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|
||||||
admin_api = AdminApi(None)
|
admin_api = AdminApi(None)
|
||||||
|
|
||||||
|
|
||||||
@@ -84,9 +82,6 @@ def lock_names_to_flag(s):
|
|||||||
|
|
||||||
# TODO: move each command to submodule
|
# TODO: move each command to submodule
|
||||||
def main():
|
def main():
|
||||||
chain_spec_dict = None
|
|
||||||
if chain_spec != None:
|
|
||||||
chain_spec_dict = chain_spec.asdict()
|
|
||||||
if args.command == 'unlock':
|
if args.command == 'unlock':
|
||||||
flags = lock_names_to_flag(args.flags)
|
flags = lock_names_to_flag(args.flags)
|
||||||
if not is_checksum_address(args.address):
|
if not is_checksum_address(args.address):
|
||||||
@@ -96,7 +91,7 @@ def main():
|
|||||||
'cic_eth.admin.ctrl.unlock',
|
'cic_eth.admin.ctrl.unlock',
|
||||||
[
|
[
|
||||||
None,
|
None,
|
||||||
chain_spec_dict,
|
chain_spec.asdict(),
|
||||||
args.address,
|
args.address,
|
||||||
flags,
|
flags,
|
||||||
],
|
],
|
||||||
@@ -115,7 +110,7 @@ def main():
|
|||||||
'cic_eth.admin.ctrl.lock',
|
'cic_eth.admin.ctrl.lock',
|
||||||
[
|
[
|
||||||
None,
|
None,
|
||||||
chain_spec_dict,
|
chain_spec.asdict(),
|
||||||
args.address,
|
args.address,
|
||||||
flags,
|
flags,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ from cic_eth_registry import CICRegistry
|
|||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
from chainlib.connection import RPCConnection
|
from chainlib.connection import RPCConnection
|
||||||
|
from chainsyncer.error import SyncDone
|
||||||
from hexathon import strip_0x
|
from hexathon import strip_0x
|
||||||
from chainqueue.db.enum import (
|
from chainqueue.db.enum import (
|
||||||
StatusEnum,
|
StatusEnum,
|
||||||
StatusBits,
|
StatusBits,
|
||||||
)
|
)
|
||||||
from chainqueue.error import NotLocalTxError
|
from chainqueue.error import NotLocalTxError
|
||||||
from chainqueue.sql.state import set_reserved
|
from chainqueue.state import set_reserved
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
import cic_eth
|
import cic_eth
|
||||||
@@ -152,7 +153,10 @@ class DispatchSyncer:
|
|||||||
def main():
|
def main():
|
||||||
syncer = DispatchSyncer(chain_spec)
|
syncer = DispatchSyncer(chain_spec)
|
||||||
conn = RPCConnection.connect(chain_spec, 'default')
|
conn = RPCConnection.connect(chain_spec, 'default')
|
||||||
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
|
try:
|
||||||
|
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
|
||||||
|
except SyncDone as e:
|
||||||
|
sys.stderr.write("dispatcher done at block {}\n".format(e))
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import celery
|
import celery
|
||||||
from cic_eth_registry.error import (
|
from cic_eth_registry.error import UnknownContractError
|
||||||
UnknownContractError,
|
|
||||||
NotAContractError,
|
|
||||||
)
|
|
||||||
from chainlib.status import Status as TxStatus
|
from chainlib.status import Status as TxStatus
|
||||||
from chainlib.eth.address import to_checksum_address
|
from chainlib.eth.address import to_checksum_address
|
||||||
from chainlib.eth.error import RequestMismatchException
|
from chainlib.eth.error import RequestMismatchException
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from hexathon import (
|
from chainlib.eth.erc20 import ERC20
|
||||||
strip_0x,
|
from hexathon import strip_0x
|
||||||
add_0x,
|
|
||||||
)
|
|
||||||
from eth_erc20 import ERC20
|
|
||||||
from erc20_faucet import Faucet
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from .base import SyncFilter
|
from .base import SyncFilter
|
||||||
@@ -25,74 +18,65 @@ from cic_eth.eth.meta import ExtendedTx
|
|||||||
logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger().getChild(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_transfer(tx):
|
||||||
|
r = ERC20.parse_transfer_request(tx.payload)
|
||||||
|
transfer_data = {}
|
||||||
|
transfer_data['to'] = r[0]
|
||||||
|
transfer_data['value'] = r[1]
|
||||||
|
transfer_data['from'] = tx['from']
|
||||||
|
transfer_data['token_address'] = tx['to']
|
||||||
|
return ('transfer', transfer_data)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_transferfrom(tx):
|
||||||
|
r = ERC20.parse_transfer_request(tx.payload)
|
||||||
|
transfer_data = unpack_transferfrom(tx.payload)
|
||||||
|
transfer_data['from'] = r[0]
|
||||||
|
transfer_data['to'] = r[1]
|
||||||
|
transfer_data['value'] = r[2]
|
||||||
|
transfer_data['token_address'] = tx['to']
|
||||||
|
return ('transferfrom', transfer_data)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_giftto(tx):
|
||||||
|
# TODO: broken
|
||||||
|
logg.error('broken')
|
||||||
|
return
|
||||||
|
transfer_data = unpack_gift(tx.payload)
|
||||||
|
transfer_data['from'] = tx.inputs[0]
|
||||||
|
transfer_data['value'] = 0
|
||||||
|
transfer_data['token_address'] = ZERO_ADDRESS
|
||||||
|
# TODO: would be better to query the gift amount from the block state
|
||||||
|
for l in tx.logs:
|
||||||
|
topics = l['topics']
|
||||||
|
logg.debug('topixx {}'.format(topics))
|
||||||
|
if strip_0x(topics[0]) == '45c201a59ac545000ead84f30b2db67da23353aa1d58ac522c48505412143ffa':
|
||||||
|
#transfer_data['value'] = web3.Web3.toInt(hexstr=strip_0x(l['data']))
|
||||||
|
transfer_data['value'] = int.from_bytes(bytes.fromhex(strip_0x(l_data)))
|
||||||
|
#token_address_bytes = topics[2][32-20:]
|
||||||
|
token_address = strip_0x(topics[2])[64-40:]
|
||||||
|
transfer_data['token_address'] = to_checksum_address(token_address)
|
||||||
|
return ('tokengift', transfer_data)
|
||||||
|
|
||||||
|
|
||||||
class CallbackFilter(SyncFilter):
|
class CallbackFilter(SyncFilter):
|
||||||
|
|
||||||
trusted_addresses = []
|
trusted_addresses = []
|
||||||
|
|
||||||
def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS):
|
def __init__(self, chain_spec, method, queue):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.method = method
|
self.method = method
|
||||||
self.chain_spec = chain_spec
|
self.chain_spec = chain_spec
|
||||||
self.caller_address = caller_address
|
|
||||||
|
|
||||||
|
|
||||||
def parse_transfer(self, tx, conn):
|
|
||||||
if not tx.payload:
|
|
||||||
return (None, None)
|
|
||||||
r = ERC20.parse_transfer_request(tx.payload)
|
|
||||||
transfer_data = {}
|
|
||||||
transfer_data['to'] = r[0]
|
|
||||||
transfer_data['value'] = r[1]
|
|
||||||
transfer_data['from'] = tx.outputs[0]
|
|
||||||
transfer_data['token_address'] = tx.inputs[0]
|
|
||||||
return ('transfer', transfer_data)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_transferfrom(self, tx, conn):
|
|
||||||
if not tx.payload:
|
|
||||||
return (None, None)
|
|
||||||
r = ERC20.parse_transfer_from_request(tx.payload)
|
|
||||||
transfer_data = {}
|
|
||||||
transfer_data['from'] = r[0]
|
|
||||||
transfer_data['to'] = r[1]
|
|
||||||
transfer_data['value'] = r[2]
|
|
||||||
transfer_data['token_address'] = tx.inputs[0]
|
|
||||||
return ('transferfrom', transfer_data)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_giftto(self, tx, conn):
|
|
||||||
if not tx.payload:
|
|
||||||
return (None, None)
|
|
||||||
r = Faucet.parse_give_to_request(tx.payload)
|
|
||||||
transfer_data = {}
|
|
||||||
transfer_data['to'] = r[0]
|
|
||||||
transfer_data['value'] = tx.value
|
|
||||||
transfer_data['from'] = tx.outputs[0]
|
|
||||||
#transfer_data['token_address'] = tx.inputs[0]
|
|
||||||
faucet_contract = tx.inputs[0]
|
|
||||||
|
|
||||||
c = 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.token_amount(faucet_contract, sender_address=self.caller_address)
|
|
||||||
r = conn.do(o)
|
|
||||||
transfer_data['value'] = c.parse_token_amount(r)
|
|
||||||
|
|
||||||
return ('tokengift', transfer_data)
|
|
||||||
|
|
||||||
|
|
||||||
def call_back(self, transfer_type, result):
|
def call_back(self, transfer_type, result):
|
||||||
result['chain_spec'] = result['chain_spec'].asdict()
|
logg.debug('result {}'.format(result))
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
self.method,
|
self.method,
|
||||||
[
|
[
|
||||||
result,
|
result,
|
||||||
transfer_type,
|
transfer_type,
|
||||||
int(result['status_code'] != 0),
|
int(result['status_code'] == 0),
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
@@ -108,27 +92,25 @@ class CallbackFilter(SyncFilter):
|
|||||||
# s_translate.link(s)
|
# s_translate.link(s)
|
||||||
# s_translate.apply_async()
|
# s_translate.apply_async()
|
||||||
t = s.apply_async()
|
t = s.apply_async()
|
||||||
return t
|
return s
|
||||||
|
|
||||||
|
|
||||||
def parse_data(self, tx, conn):
|
def parse_data(self, tx):
|
||||||
transfer_type = None
|
transfer_type = None
|
||||||
transfer_data = None
|
transfer_data = None
|
||||||
# TODO: what's with the mix of attributes and dict keys
|
# TODO: what's with the mix of attributes and dict keys
|
||||||
logg.debug('have payload {}'.format(tx.payload))
|
logg.debug('have payload {}'.format(tx.payload))
|
||||||
|
method_signature = tx.payload[:8]
|
||||||
|
|
||||||
logg.debug('tx status {}'.format(tx.status))
|
logg.debug('tx status {}'.format(tx.status))
|
||||||
|
|
||||||
for parser in [
|
for parser in [
|
||||||
self.parse_transfer,
|
parse_transfer,
|
||||||
self.parse_transferfrom,
|
parse_transferfrom,
|
||||||
self.parse_giftto,
|
parse_giftto,
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
if tx:
|
(transfer_type, transfer_data) = parser(tx)
|
||||||
(transfer_type, transfer_data) = parser(tx, conn)
|
|
||||||
if transfer_type == None:
|
|
||||||
continue
|
|
||||||
break
|
break
|
||||||
except RequestMismatchException:
|
except RequestMismatchException:
|
||||||
continue
|
continue
|
||||||
@@ -146,7 +128,7 @@ class CallbackFilter(SyncFilter):
|
|||||||
transfer_data = None
|
transfer_data = None
|
||||||
transfer_type = None
|
transfer_type = None
|
||||||
try:
|
try:
|
||||||
(transfer_type, transfer_data) = self.parse_data(tx, conn)
|
(transfer_type, transfer_data) = self.parse_data(tx)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logg.debug('invalid method data length for tx {}'.format(tx.hash))
|
logg.debug('invalid method data length for tx {}'.format(tx.hash))
|
||||||
return
|
return
|
||||||
@@ -162,19 +144,16 @@ class CallbackFilter(SyncFilter):
|
|||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
|
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
|
||||||
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses, caller_address=self.caller_address)
|
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses)
|
||||||
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
|
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
|
||||||
if transfer_data['status'] == 0:
|
if transfer_data['status'] == 0:
|
||||||
tokentx.set_status(1)
|
tokentx.set_status(1)
|
||||||
else:
|
else:
|
||||||
tokentx.set_status(0)
|
tokentx.set_status(0)
|
||||||
result = tokentx.asdict()
|
t = self.call_back(transfer_type, tokentx.to_dict())
|
||||||
t = self.call_back(transfer_type, result)
|
logg.info('callback success task id {} tx {}'.format(t, tx.hash))
|
||||||
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
|
|
||||||
except UnknownContractError:
|
except UnknownContractError:
|
||||||
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(self.queue, self.method, transfer_data['to'], tx.hash))
|
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tc.queue, tc.method, transfer_data['to'], tx.hash))
|
||||||
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):
|
def __str__(self):
|
||||||
|
|||||||
@@ -10,15 +10,14 @@ from chainlib.eth.tx import unpack
|
|||||||
from chainqueue.db.enum import StatusBits
|
from chainqueue.db.enum import StatusBits
|
||||||
from chainqueue.db.models.tx import TxCache
|
from chainqueue.db.models.tx import TxCache
|
||||||
from chainqueue.db.models.otx import Otx
|
from chainqueue.db.models.otx import Otx
|
||||||
from chainqueue.sql.query import get_paused_tx_cache as get_paused_tx
|
from chainqueue.query import get_paused_tx_cache as get_paused_tx
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
from cic_eth.eth.gas import create_check_gas_task
|
from cic_eth.eth.gas import create_check_gas_task
|
||||||
from .base import SyncFilter
|
from .base import SyncFilter
|
||||||
|
|
||||||
#logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger().getChild(__name__)
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
class GasFilter(SyncFilter):
|
class GasFilter(SyncFilter):
|
||||||
@@ -28,11 +27,11 @@ class GasFilter(SyncFilter):
|
|||||||
self.chain_spec = chain_spec
|
self.chain_spec = chain_spec
|
||||||
|
|
||||||
|
|
||||||
def filter(self, conn, block, tx, db_session):
|
def filter(self, conn, block, tx, session):
|
||||||
if tx.value > 0:
|
if tx.value > 0:
|
||||||
tx_hash_hex = add_0x(tx.hash)
|
tx_hash_hex = add_0x(tx.hash)
|
||||||
logg.debug('gas refill tx {}'.format(tx_hash_hex))
|
logg.debug('gas refill tx {}'.format(tx_hash_hex))
|
||||||
session = SessionBase.bind_session(db_session)
|
session = SessionBase.bind_session(session)
|
||||||
q = session.query(TxCache.recipient)
|
q = session.query(TxCache.recipient)
|
||||||
q = q.join(Otx)
|
q = q.join(Otx)
|
||||||
q = q.filter(Otx.tx_hash==strip_0x(tx_hash_hex))
|
q = q.filter(Otx.tx_hash==strip_0x(tx_hash_hex))
|
||||||
@@ -57,7 +56,7 @@ class GasFilter(SyncFilter):
|
|||||||
tx_hashes_hex=list(txs.keys()),
|
tx_hashes_hex=list(txs.keys()),
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
return s.apply_async()
|
s.apply_async()
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from .base import SyncFilter
|
|||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger().getChild(__name__)
|
||||||
|
|
||||||
account_registry_add_log_hash = '0x9cc987676e7d63379f176ea50df0ae8d2d9d1141d1231d4ce15b5965f73c9430'
|
account_registry_add_log_hash = '0x5ed3bdd47b9af629827a8d129aa39c870b10c03f0153fe9ddb8e84b665061acd'
|
||||||
|
|
||||||
|
|
||||||
class RegistrationFilter(SyncFilter):
|
class RegistrationFilter(SyncFilter):
|
||||||
@@ -50,8 +50,7 @@ class RegistrationFilter(SyncFilter):
|
|||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
)
|
)
|
||||||
s_nonce.link(s_gift)
|
s_nonce.link(s_gift)
|
||||||
t = s_nonce.apply_async()
|
s_nonce.apply_async()
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
from chainqueue.sql.state import obsolete_by_cache
|
from chainqueue.state import obsolete_by_cache
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class TransferAuthFilter(SyncFilter):
|
|||||||
self.transfer_request_contract = registry.by_name('TransferAuthorization', sender_address=call_address)
|
self.transfer_request_contract = registry.by_name('TransferAuthorization', sender_address=call_address)
|
||||||
|
|
||||||
|
|
||||||
def filter(self, conn, block, tx, db_session): #rcpt, chain_str, session=None):
|
def filter(self, conn, block, tx, session): #rcpt, chain_str, session=None):
|
||||||
|
|
||||||
if tx.payload == None:
|
if tx.payload == None:
|
||||||
logg.debug('no payload')
|
logg.debug('no payload')
|
||||||
@@ -45,17 +45,16 @@ class TransferAuthFilter(SyncFilter):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
recipient = tx.inputs[0]
|
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))
|
logg.debug('not our transfer auth contract address {}'.format(recipient))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
r = TransferAuthorization.parse_create_request_request(tx.payload)
|
r = TransferAuthorization.parse_create_request_request(tx.payload)
|
||||||
|
|
||||||
sender = r[0]
|
sender = abi_decode_single(ABIContractType.ADDRESS, r[0])
|
||||||
recipient = r[1]
|
recipient = abi_decode_single(ABIContractType.ADDRESS, r[1])
|
||||||
token = r[2]
|
token = abi_decode_single(ABIContractType.ADDRESS, r[2])
|
||||||
value = r[3]
|
value = abi_decode_single(ABIContractType.UINT256, r[3])
|
||||||
|
|
||||||
token_data = {
|
token_data = {
|
||||||
'address': token,
|
'address': token,
|
||||||
@@ -65,7 +64,6 @@ class TransferAuthFilter(SyncFilter):
|
|||||||
'cic_eth.eth.nonce.reserve_nonce',
|
'cic_eth.eth.nonce.reserve_nonce',
|
||||||
[
|
[
|
||||||
[token_data],
|
[token_data],
|
||||||
self.chain_spec.asdict(),
|
|
||||||
sender,
|
sender,
|
||||||
],
|
],
|
||||||
queue=self.queue,
|
queue=self.queue,
|
||||||
@@ -82,7 +80,7 @@ class TransferAuthFilter(SyncFilter):
|
|||||||
)
|
)
|
||||||
s_nonce.link(s_approve)
|
s_nonce.link(s_approve)
|
||||||
t = s_nonce.apply_async()
|
t = s_nonce.apply_async()
|
||||||
return t
|
return True
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class TxFilter(SyncFilter):
|
|||||||
if otx == None:
|
if otx == None:
|
||||||
logg.debug('tx {} not found locally, skipping'.format(tx_hash_hex))
|
logg.debug('tx {} not found locally, skipping'.format(tx_hash_hex))
|
||||||
return None
|
return None
|
||||||
logg.debug('otx filter match on {}'.format(otx.tx_hash))
|
logg.info('tx filter match on {}'.format(otx.tx_hash))
|
||||||
db_session.flush()
|
db_session.flush()
|
||||||
SessionBase.release_session(db_session)
|
SessionBase.release_session(db_session)
|
||||||
s_final_state = celery.signature(
|
s_final_state = celery.signature(
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from cic_eth.admin.ctrl import lock_send
|
|||||||
from cic_eth.db.enum import LockEnum
|
from cic_eth.db.enum import LockEnum
|
||||||
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
||||||
from cic_eth.sync.retry import RetrySyncer
|
from cic_eth.sync.retry import RetrySyncer
|
||||||
from cic_eth.stat import init_chain_stat
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -72,21 +71,57 @@ RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, tag='def
|
|||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
||||||
|
|
||||||
|
straggler_delay = int(config.get('CIC_TX_RETRY_DELAY'))
|
||||||
|
|
||||||
|
## TODO: we already have the signed raw tx in get, so its a waste of cycles to get_tx here
|
||||||
|
#def sendfail_filter(w3, tx_hash, rcpt, chain_spec):
|
||||||
|
# tx_dict = get_tx(tx_hash)
|
||||||
|
# tx = unpack(tx_dict['signed_tx'], chain_spec)
|
||||||
|
# logg.debug('submitting tx {} for retry'.format(tx_hash))
|
||||||
|
# s_check = celery.signature(
|
||||||
|
# 'cic_eth.admin.ctrl.check_lock',
|
||||||
|
# [
|
||||||
|
# tx_hash,
|
||||||
|
# chain_str,
|
||||||
|
# LockEnum.QUEUE,
|
||||||
|
# tx['from'],
|
||||||
|
# ],
|
||||||
|
# queue=queue,
|
||||||
|
# )
|
||||||
|
## s_resume = celery.signature(
|
||||||
|
## 'cic_eth.eth.tx.resume_tx',
|
||||||
|
## [
|
||||||
|
## chain_str,
|
||||||
|
## ],
|
||||||
|
## queue=queue,
|
||||||
|
## )
|
||||||
|
#
|
||||||
|
## s_retry_status = celery.signature(
|
||||||
|
## 'cic_eth.queue.state.set_ready',
|
||||||
|
## [],
|
||||||
|
## queue=queue,
|
||||||
|
## )
|
||||||
|
# s_resend = celery.signature(
|
||||||
|
# 'cic_eth.eth.gas.resend_with_higher_gas',
|
||||||
|
# [
|
||||||
|
# chain_str,
|
||||||
|
# ],
|
||||||
|
# queue=queue,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# #s_resume.link(s_retry_status)
|
||||||
|
# #s_check.link(s_resume)
|
||||||
|
# s_check.link(s_resend)
|
||||||
|
# s_check.apply_async()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conn = RPCConnection.connect(chain_spec, 'default')
|
conn = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
straggler_delay = int(config.get('CIC_TX_RETRY_DELAY'))
|
|
||||||
loop_interval = config.get('SYNCER_LOOP_INTERVAL')
|
|
||||||
if loop_interval == None:
|
|
||||||
stat = init_chain_stat(conn)
|
|
||||||
loop_interval = stat.block_average()
|
|
||||||
|
|
||||||
syncer = RetrySyncer(conn, chain_spec, straggler_delay, batch_size=config.get('_BATCH_SIZE'))
|
syncer = RetrySyncer(conn, chain_spec, straggler_delay, batch_size=config.get('_BATCH_SIZE'))
|
||||||
syncer.backend.set(0, 0)
|
syncer.backend.set(0, 0)
|
||||||
fltr = StragglerFilter(chain_spec, queue=queue)
|
fltr = StragglerFilter(chain_spec, queue=queue)
|
||||||
syncer.add_filter(fltr)
|
syncer.add_filter(fltr)
|
||||||
syncer.loop(int(loop_interval), conn)
|
syncer.loop(float(straggler_delay), conn)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
136
apps/cic-eth/cic_eth/runnable/daemons/server.py
Normal file
136
apps/cic-eth/cic_eth/runnable/daemons/server.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# 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]
|
||||||
@@ -11,20 +11,10 @@ import websocket
|
|||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
import confini
|
import confini
|
||||||
from chainlib.connection import (
|
from chainlib.connection import RPCConnection
|
||||||
RPCConnection,
|
from chainlib.eth.connection import EthUnixSignerConnection
|
||||||
ConnType,
|
|
||||||
)
|
|
||||||
from chainlib.eth.connection import (
|
|
||||||
EthUnixSignerConnection,
|
|
||||||
EthHTTPSignerConnection,
|
|
||||||
)
|
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainqueue.db.models.otx import Otx
|
from chainqueue.db.models.otx import Otx
|
||||||
from cic_eth_registry.error import UnknownContractError
|
|
||||||
from cic_eth_registry.erc20 import ERC20Token
|
|
||||||
import liveness.linux
|
|
||||||
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.eth import (
|
from cic_eth.eth import (
|
||||||
@@ -37,7 +27,6 @@ from cic_eth.eth import (
|
|||||||
from cic_eth.admin import (
|
from cic_eth.admin import (
|
||||||
debug,
|
debug,
|
||||||
ctrl,
|
ctrl,
|
||||||
token,
|
|
||||||
)
|
)
|
||||||
from cic_eth.queue import (
|
from cic_eth.queue import (
|
||||||
query,
|
query,
|
||||||
@@ -50,7 +39,6 @@ from cic_eth.queue import (
|
|||||||
from cic_eth.callbacks import (
|
from cic_eth.callbacks import (
|
||||||
Callback,
|
Callback,
|
||||||
http,
|
http,
|
||||||
noop,
|
|
||||||
#tcp,
|
#tcp,
|
||||||
redis,
|
redis,
|
||||||
)
|
)
|
||||||
@@ -62,8 +50,6 @@ from cic_eth.registry import (
|
|||||||
connect_declarator,
|
connect_declarator,
|
||||||
connect_token_registry,
|
connect_token_registry,
|
||||||
)
|
)
|
||||||
from cic_eth.task import BaseTask
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@@ -75,7 +61,7 @@ argparser.add_argument('-p', '--provider', dest='p', type=str, help='rpc provide
|
|||||||
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
|
||||||
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
|
||||||
argparser.add_argument('-r', type=str, help='CIC registry address')
|
argparser.add_argument('-r', type=str, help='CIC registry address')
|
||||||
argparser.add_argument('--default-token-symbol', dest='default_token_symbol', type=str, help='Symbol of default token to use')
|
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi')
|
||||||
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
|
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
||||||
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('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||||
@@ -94,7 +80,6 @@ config.process()
|
|||||||
args_override = {
|
args_override = {
|
||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
||||||
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
|
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
|
||||||
'CIC_DEFAULT_TOKEN_SYMBOL': getattr(args, 'default_token_symbol'),
|
|
||||||
'ETH_PROVIDER': getattr(args, 'p'),
|
'ETH_PROVIDER': getattr(args, 'p'),
|
||||||
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
|
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
|
||||||
}
|
}
|
||||||
@@ -104,15 +89,14 @@ config.censor('PASSWORD', 'DATABASE')
|
|||||||
config.censor('PASSWORD', 'SSL')
|
config.censor('PASSWORD', 'SSL')
|
||||||
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
|
||||||
|
|
||||||
health_modules = config.get('CIC_HEALTH_MODULES', [])
|
|
||||||
if len(health_modules) != 0:
|
|
||||||
health_modules = health_modules.split(',')
|
|
||||||
logg.debug('health mods {}'.format(health_modules))
|
|
||||||
|
|
||||||
# connect to database
|
# connect to database
|
||||||
dsn = dsn_from_config(config)
|
dsn = dsn_from_config(config)
|
||||||
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
|
SessionBase.connect(dsn, pool_size=50, debug=config.true('DATABASE_DEBUG'))
|
||||||
|
|
||||||
|
# verify database connection with minimal sanity query
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
session.execute('select version_num from alembic_version')
|
||||||
|
session.close()
|
||||||
|
|
||||||
# set up celery
|
# set up celery
|
||||||
current_app = celery.Celery(__name__)
|
current_app = celery.Celery(__name__)
|
||||||
@@ -121,25 +105,20 @@ broker = config.get('CELERY_BROKER_URL')
|
|||||||
if broker[:4] == 'file':
|
if broker[:4] == 'file':
|
||||||
bq = tempfile.mkdtemp()
|
bq = tempfile.mkdtemp()
|
||||||
bp = tempfile.mkdtemp()
|
bp = tempfile.mkdtemp()
|
||||||
conf_update = {
|
current_app.conf.update({
|
||||||
'broker_url': broker,
|
'broker_url': broker,
|
||||||
'broker_transport_options': {
|
'broker_transport_options': {
|
||||||
'data_folder_in': bq,
|
'data_folder_in': bq,
|
||||||
'data_folder_out': bq,
|
'data_folder_out': bq,
|
||||||
'data_folder_processed': bp,
|
'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))
|
logg.warning('celery broker dirs queue i/o {} processed {}, will NOT be deleted on shutdown'.format(bq, bp))
|
||||||
else:
|
else:
|
||||||
conf_update = {
|
current_app.conf.update({
|
||||||
'broker_url': broker,
|
'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')
|
result = config.get('CELERY_RESULT_URL')
|
||||||
if result[:4] == 'file':
|
if result[:4] == 'file':
|
||||||
@@ -154,18 +133,11 @@ else:
|
|||||||
})
|
})
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
||||||
RPCConnection.register_constructor(ConnType.UNIX, EthUnixSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_constructor(ConnType.HTTP, EthHTTPSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_constructor(ConnType.HTTP_SSL, EthHTTPSignerConnection, 'signer')
|
|
||||||
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
|
||||||
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer')
|
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection)
|
||||||
|
|
||||||
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
|
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
|
||||||
|
|
||||||
#import cic_eth.checks.gas
|
|
||||||
#if not cic_eth.checks.gas.health(config=config):
|
|
||||||
# raise RuntimeError()
|
|
||||||
liveness.linux.load(health_modules, rundir=config.get('CIC_RUN_DIR'), config=config, unit='cic-eth-tasker')
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argv = ['worker']
|
argv = ['worker']
|
||||||
@@ -189,12 +161,7 @@ def main():
|
|||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
try:
|
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
||||||
registry = connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
|
||||||
except UnknownContractError as e:
|
|
||||||
logg.exception('Registry contract connection failed for {}: {}'.format(config.get('CIC_REGISTRY_ADDRESS'), e))
|
|
||||||
sys.exit(1)
|
|
||||||
logg.info('connected contract registry {}'.format(config.get('CIC_REGISTRY_ADDRESS')))
|
|
||||||
|
|
||||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
if trusted_addresses_src == None:
|
if trusted_addresses_src == None:
|
||||||
@@ -203,23 +170,10 @@ def main():
|
|||||||
trusted_addresses = trusted_addresses_src.split(',')
|
trusted_addresses = trusted_addresses_src.split(',')
|
||||||
for address in trusted_addresses:
|
for address in trusted_addresses:
|
||||||
logg.info('using trusted address {}'.format(address))
|
logg.info('using trusted address {}'.format(address))
|
||||||
|
|
||||||
connect_declarator(rpc, chain_spec, trusted_addresses)
|
connect_declarator(rpc, chain_spec, trusted_addresses)
|
||||||
connect_token_registry(rpc, chain_spec)
|
connect_token_registry(rpc, chain_spec)
|
||||||
|
|
||||||
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
|
|
||||||
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
|
|
||||||
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))
|
|
||||||
|
|
||||||
liveness.linux.set(rundir=config.get('CIC_RUN_DIR'))
|
|
||||||
current_app.worker_main(argv)
|
current_app.worker_main(argv)
|
||||||
liveness.linux.reset(rundir=config.get('CIC_RUN_DIR'))
|
|
||||||
|
|
||||||
|
|
||||||
@celery.signals.eventlet_pool_postshutdown.connect
|
@celery.signals.eventlet_pool_postshutdown.connect
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import confini
|
import confini
|
||||||
import celery
|
import celery
|
||||||
import rlp
|
import rlp
|
||||||
@@ -15,7 +15,7 @@ import cic_base.config
|
|||||||
import cic_base.log
|
import cic_base.log
|
||||||
import cic_base.argparse
|
import cic_base.argparse
|
||||||
import cic_base.rpc
|
import cic_base.rpc
|
||||||
from cic_base.eth.syncer import chain_interface
|
from cic_eth_registry import CICRegistry
|
||||||
from cic_eth_registry.error import UnknownContractError
|
from cic_eth_registry.error import UnknownContractError
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
@@ -26,9 +26,11 @@ from chainlib.eth.block import (
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
from chainsyncer.backend.sql import SQLBackend
|
from chainsyncer.backend import SyncerBackend
|
||||||
from chainsyncer.driver.head import HeadSyncer
|
from chainsyncer.driver import (
|
||||||
from chainsyncer.driver.history import HistorySyncer
|
HeadSyncer,
|
||||||
|
HistorySyncer,
|
||||||
|
)
|
||||||
from chainsyncer.db.models.base import SessionBase
|
from chainsyncer.db.models.base import SessionBase
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
@@ -40,33 +42,18 @@ from cic_eth.runnable.daemons.filters import (
|
|||||||
RegistrationFilter,
|
RegistrationFilter,
|
||||||
TransferAuthFilter,
|
TransferAuthFilter,
|
||||||
)
|
)
|
||||||
from cic_eth.stat import init_chain_stat
|
|
||||||
from cic_eth.registry import (
|
|
||||||
connect as connect_registry,
|
|
||||||
connect_declarator,
|
|
||||||
connect_token_registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
script_dir = os.path.realpath(os.path.dirname(__file__))
|
script_dir = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
|
||||||
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()
|
logg = cic_base.log.create()
|
||||||
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
argparser = cic_base.argparse.create(script_dir, cic_base.argparse.full_template)
|
||||||
argparser = cic_base.argparse.add(argparser, add_block_args, 'block')
|
#argparser = cic_base.argparse.add(argparser, add_traffic_args, 'traffic')
|
||||||
args = cic_base.argparse.parse(argparser, logg)
|
args = cic_base.argparse.parse(argparser, logg)
|
||||||
|
|
||||||
config = cic_base.config.create(args.c, args, args.env_prefix)
|
config = cic_base.config.create(args.c, args, args.env_prefix)
|
||||||
|
|
||||||
config.add(args.y, '_KEYSTORE_FILE', True)
|
config.add(args.y, '_KEYSTORE_FILE', True)
|
||||||
|
|
||||||
config.add(args.q, '_CELERY_QUEUE', 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)
|
cic_base.config.log(config)
|
||||||
|
|
||||||
@@ -76,10 +63,9 @@ SessionBase.connect(dsn, pool_size=16, debug=config.true('DATABASE_DEBUG'))
|
|||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
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'))
|
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# connect to celery
|
# connect to celery
|
||||||
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
||||||
@@ -92,42 +78,32 @@ def main():
|
|||||||
block_current = int(r, 16)
|
block_current = int(r, 16)
|
||||||
block_offset = block_current + 1
|
block_offset = block_current + 1
|
||||||
|
|
||||||
loop_interval = config.get('SYNCER_LOOP_INTERVAL')
|
stat = init_chain_stat(rpc, block_current)
|
||||||
if loop_interval == None:
|
|
||||||
stat = init_chain_stat(rpc, block_start=block_current)
|
|
||||||
loop_interval = stat.block_average()
|
|
||||||
|
|
||||||
logg.debug('current block height {}'.format(block_offset))
|
logg.debug('starting at block {}'.format(block_offset))
|
||||||
|
|
||||||
syncers = []
|
syncers = []
|
||||||
|
|
||||||
#if SQLBackend.first(chain_spec):
|
#if SyncerBackend.first(chain_spec):
|
||||||
# backend = SQLBackend.initial(chain_spec, block_offset)
|
# backend = SyncerBackend.initial(chain_spec, block_offset)
|
||||||
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
|
syncer_backends = SyncerBackend.resume(chain_spec, block_offset)
|
||||||
|
|
||||||
if len(syncer_backends) == 0:
|
if len(syncer_backends) == 0:
|
||||||
initial_block_start = config.get('SYNCER_HISTORY_START')
|
logg.info('found no backends to resume')
|
||||||
initial_block_offset = block_offset
|
syncer_backends.append(SyncerBackend.initial(chain_spec, 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:
|
else:
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
logg.info('resuming sync session {}'.format(syncer_backend))
|
logg.info('resuming sync session {}'.format(syncer_backend))
|
||||||
|
|
||||||
syncer_backends.append(SQLBackend.live(chain_spec, block_offset+1))
|
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1))
|
||||||
|
|
||||||
for syncer_backend in syncer_backends:
|
for syncer_backend in syncer_backends:
|
||||||
try:
|
try:
|
||||||
syncers.append(HistorySyncer(syncer_backend, chain_interface))
|
syncers.append(HistorySyncer(syncer_backend))
|
||||||
logg.info('Initializing HISTORY syncer on backend {}'.format(syncer_backend))
|
logg.info('Initializing HISTORY syncer on backend {}'.format(syncer_backend))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
|
||||||
syncers.append(HeadSyncer(syncer_backend, chain_interface))
|
syncers.append(HeadSyncer(syncer_backend))
|
||||||
|
|
||||||
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
|
||||||
|
|
||||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
if trusted_addresses_src == None:
|
if trusted_addresses_src == None:
|
||||||
@@ -136,8 +112,6 @@ def main():
|
|||||||
trusted_addresses = trusted_addresses_src.split(',')
|
trusted_addresses = trusted_addresses_src.split(',')
|
||||||
for address in trusted_addresses:
|
for address in trusted_addresses:
|
||||||
logg.info('using trusted address {}'.format(address))
|
logg.info('using trusted address {}'.format(address))
|
||||||
connect_declarator(rpc, chain_spec, trusted_addresses)
|
|
||||||
connect_token_registry(rpc, chain_spec)
|
|
||||||
CallbackFilter.trusted_addresses = trusted_addresses
|
CallbackFilter.trusted_addresses = trusted_addresses
|
||||||
|
|
||||||
callback_filters = []
|
callback_filters = []
|
||||||
@@ -168,7 +142,7 @@ def main():
|
|||||||
for cf in callback_filters:
|
for cf in callback_filters:
|
||||||
syncer.add_filter(cf)
|
syncer.add_filter(cf)
|
||||||
|
|
||||||
r = syncer.loop(int(loop_interval), rpc)
|
r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
|
||||||
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
sys.stderr.write("sync {} done at block {}\n".format(syncer, r))
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import confini
|
|
||||||
import celery
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.api import (
|
|
||||||
Api,
|
|
||||||
AdminApi,
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
default_format = 'terminal'
|
|
||||||
default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
|
|
||||||
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
|
|
||||||
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
|
|
||||||
argparser.add_argument('-q', type=str, default='cic-eth', help='celery queue to submit transaction tasks to')
|
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
|
||||||
argparser.add_argument('-vv', help='be more verbose', action='store_true')
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
if args.v == True:
|
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
|
||||||
elif args.vv == True:
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
config_dir = os.path.join(args.c)
|
|
||||||
os.makedirs(config_dir, 0o777, True)
|
|
||||||
config = confini.Config(config_dir, args.env_prefix)
|
|
||||||
config.process()
|
|
||||||
args_override = {
|
|
||||||
'CIC_CHAIN_SPEC': getattr(args, 'i'),
|
|
||||||
}
|
|
||||||
config.dict_override(args_override, 'cli args')
|
|
||||||
config.censor('PASSWORD', 'DATABASE')
|
|
||||||
config.censor('PASSWORD', 'SSL')
|
|
||||||
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
|
|
||||||
|
|
||||||
|
|
||||||
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
|
|
||||||
|
|
||||||
queue = args.q
|
|
||||||
|
|
||||||
api = Api(config.get('CIC_CHAIN_SPEC'), queue=queue)
|
|
||||||
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()
|
|
||||||
@@ -85,6 +85,9 @@ def main():
|
|||||||
callback_queue=args.q,
|
callback_queue=args.q,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#register = not args.no_register
|
||||||
|
#logg.debug('register {}'.format(register))
|
||||||
|
#t = api.create_account(register=register)
|
||||||
t = api.transfer(config.get('_SENDER'), config.get('_RECIPIENT'), config.get('_VALUE'), config.get('_SYMBOL'))
|
t = api.transfer(config.get('_SENDER'), config.get('_RECIPIENT'), config.get('_VALUE'), config.get('_SYMBOL'))
|
||||||
|
|
||||||
ps.get_message()
|
ps.get_message()
|
||||||
|
|||||||
@@ -81,14 +81,10 @@ chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
|
|||||||
|
|
||||||
rpc = EthHTTPConnection(args.p)
|
rpc = EthHTTPConnection(args.p)
|
||||||
|
|
||||||
#registry_address = config.get('CIC_REGISTRY_ADDRESS')
|
registry_address = config.get('CIC_REGISTRY_ADDRESS')
|
||||||
|
|
||||||
admin_api = AdminApi(rpc)
|
admin_api = AdminApi(rpc)
|
||||||
|
|
||||||
t = admin_api.registry()
|
|
||||||
registry_address = t.get()
|
|
||||||
logg.info('got registry address from task pool: {}'.format(registry_address))
|
|
||||||
|
|
||||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
if trusted_addresses_src == None:
|
if trusted_addresses_src == None:
|
||||||
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
||||||
@@ -155,16 +151,14 @@ def main():
|
|||||||
txs = []
|
txs = []
|
||||||
renderer = render_tx
|
renderer = render_tx
|
||||||
if len(config.get('_QUERY')) > 66:
|
if len(config.get('_QUERY')) > 66:
|
||||||
#registry = connect_registry(rpc, chain_spec, registry_address)
|
registry = connect_registry(rpc, chain_spec, registry_address)
|
||||||
#admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), registry=registry, renderer=renderer)
|
admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), registry=registry, renderer=renderer)
|
||||||
admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), renderer=renderer)
|
|
||||||
elif len(config.get('_QUERY')) > 42:
|
elif len(config.get('_QUERY')) > 42:
|
||||||
#registry = connect_registry(rpc, chain_spec, registry_address)
|
registry = connect_registry(rpc, chain_spec, registry_address)
|
||||||
#admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), registry=registry, renderer=renderer)
|
admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), registry=registry, renderer=renderer)
|
||||||
admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), renderer=renderer)
|
|
||||||
|
|
||||||
elif len(config.get('_QUERY')) == 42:
|
elif len(config.get('_QUERY')) == 42:
|
||||||
#registry = connect_registry(rpc, chain_spec, registry_address)
|
registry = connect_registry(rpc, chain_spec, registry_address)
|
||||||
txs = admin_api.account(chain_spec, config.get('_QUERY'), include_recipient=False, renderer=render_account)
|
txs = admin_api.account(chain_spec, config.get('_QUERY'), include_recipient=False, renderer=render_account)
|
||||||
renderer = render_account
|
renderer = render_account
|
||||||
elif len(config.get('_QUERY')) >= 4 and config.get('_QUERY')[:4] == 'lock':
|
elif len(config.get('_QUERY')) >= 4 and config.get('_QUERY')[:4] == 'lock':
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from chainlib.stat import ChainStat
|
|
||||||
from chainlib.eth.block import (
|
|
||||||
block_latest,
|
|
||||||
block_by_number,
|
|
||||||
Block,
|
|
||||||
)
|
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
|
||||||
|
|
||||||
BLOCK_SAMPLES = 10
|
|
||||||
|
|
||||||
|
|
||||||
def init_chain_stat(rpc, block_start=0):
|
|
||||||
stat = ChainStat()
|
|
||||||
|
|
||||||
if block_start == 0:
|
|
||||||
o = block_latest()
|
|
||||||
r = rpc.do(o)
|
|
||||||
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)
|
|
||||||
block_src = rpc.do(o)
|
|
||||||
logg.debug('block {}'.format(block_src))
|
|
||||||
block = Block(block_src)
|
|
||||||
stat.block_apply(block)
|
|
||||||
|
|
||||||
logg.debug('calculated block time {} from {} block samples'.format(stat.block_average(), BLOCK_SAMPLES))
|
|
||||||
return stat
|
|
||||||
@@ -4,7 +4,7 @@ import datetime
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
from chainsyncer.driver import HeadSyncer
|
from chainsyncer.driver import HeadSyncer
|
||||||
from chainsyncer.backend.memory import MemBackend
|
from chainsyncer.backend import MemBackend
|
||||||
from chainsyncer.error import NoBlockForYou
|
from chainsyncer.error import NoBlockForYou
|
||||||
from chainlib.eth.block import (
|
from chainlib.eth.block import (
|
||||||
block_by_number,
|
block_by_number,
|
||||||
|
|||||||
@@ -7,21 +7,18 @@ import uuid
|
|||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
from chainlib.eth.nonce import RPCNonceOracle
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
from chainlib.eth.gas import RPCGasOracle
|
from chainlib.eth.gas import RPCGasOracle
|
||||||
from cic_eth_registry import CICRegistry
|
|
||||||
from cic_eth_registry.error import UnknownContractError
|
|
||||||
import liveness.linux
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.error import SeppukuError
|
from cic_eth.error import (
|
||||||
|
SignerError,
|
||||||
|
EthError,
|
||||||
|
)
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
|
|
||||||
#logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
celery_app = celery.current_app
|
||||||
|
|
||||||
@@ -32,11 +29,6 @@ class BaseTask(celery.Task):
|
|||||||
call_address = ZERO_ADDRESS
|
call_address = ZERO_ADDRESS
|
||||||
create_nonce_oracle = RPCNonceOracle
|
create_nonce_oracle = RPCNonceOracle
|
||||||
create_gas_oracle = RPCGasOracle
|
create_gas_oracle = RPCGasOracle
|
||||||
default_token_address = None
|
|
||||||
default_token_symbol = None
|
|
||||||
default_token_name = None
|
|
||||||
default_token_decimals = None
|
|
||||||
run_dir = '/run'
|
|
||||||
|
|
||||||
def create_session(self):
|
def create_session(self):
|
||||||
return BaseTask.session_func()
|
return BaseTask.session_func()
|
||||||
@@ -46,19 +38,6 @@ class BaseTask(celery.Task):
|
|||||||
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
|
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
|
||||||
if isinstance(exc, SeppukuError):
|
|
||||||
liveness.linux.reset(rundir=self.run_dir)
|
|
||||||
logg.critical(einfo)
|
|
||||||
msg = 'received critical exception {}, calling shutdown'.format(str(exc))
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.admin.ctrl.shutdown',
|
|
||||||
[msg],
|
|
||||||
queue=self.request.delivery_info.get('routing_key'),
|
|
||||||
)
|
|
||||||
s.apply_async()
|
|
||||||
|
|
||||||
|
|
||||||
class CriticalTask(BaseTask):
|
class CriticalTask(BaseTask):
|
||||||
retry_jitter = True
|
retry_jitter = True
|
||||||
@@ -88,6 +67,7 @@ class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
|
|||||||
sqlalchemy.exc.TimeoutError,
|
sqlalchemy.exc.TimeoutError,
|
||||||
requests.exceptions.ConnectionError,
|
requests.exceptions.ConnectionError,
|
||||||
sqlalchemy.exc.ResourceClosedError,
|
sqlalchemy.exc.ResourceClosedError,
|
||||||
|
EthError,
|
||||||
)
|
)
|
||||||
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
||||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||||
@@ -98,46 +78,19 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
|
|||||||
sqlalchemy.exc.DatabaseError,
|
sqlalchemy.exc.DatabaseError,
|
||||||
sqlalchemy.exc.TimeoutError,
|
sqlalchemy.exc.TimeoutError,
|
||||||
sqlalchemy.exc.ResourceClosedError,
|
sqlalchemy.exc.ResourceClosedError,
|
||||||
|
SignerError,
|
||||||
)
|
)
|
||||||
|
|
||||||
class CriticalWeb3AndSignerTask(CriticalTask):
|
class CriticalWeb3AndSignerTask(CriticalTask):
|
||||||
autoretry_for = (
|
autoretry_for = (
|
||||||
requests.exceptions.ConnectionError,
|
requests.exceptions.ConnectionError,
|
||||||
|
SignerError,
|
||||||
)
|
)
|
||||||
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
safe_gas_threshold_amount = 2000000000 * 60000 * 3
|
||||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
|
||||||
def check_health(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: registry / rpc methods should perhaps be moved to better named module
|
|
||||||
@celery_app.task()
|
|
||||||
def registry():
|
|
||||||
return CICRegistry.address
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=BaseTask)
|
@celery_app.task(bind=True, base=BaseTask)
|
||||||
def registry_address_lookup(self, chain_spec_dict, address, connection_tag='default'):
|
def hello(self):
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
time.sleep(0.1)
|
||||||
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
|
return id(SessionBase.create_session)
|
||||||
registry = CICRegistry(chain_spec, conn)
|
|
||||||
r = registry.by_address(address, sender_address=self.call_address)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(throws=(UnknownContractError,))
|
|
||||||
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, sender_address=self.call_address)
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
|
||||||
def rpc_proxy(chain_spec_dict, o, connection_tag='default'):
|
|
||||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
|
||||||
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
|
|
||||||
return conn.do(o)
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import semver
|
|||||||
version = (
|
version = (
|
||||||
0,
|
0,
|
||||||
11,
|
11,
|
||||||
1,
|
0,
|
||||||
'alpha.3',
|
'beta.1',
|
||||||
)
|
)
|
||||||
|
|
||||||
version_object = semver.VersionInfo(
|
version_object = semver.VersionInfo(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
[celery]
|
[celery]
|
||||||
broker_url = redis://
|
broker_url = redis://
|
||||||
result_url = redis://
|
result_url = redis://
|
||||||
debug = 0
|
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ registry_address =
|
|||||||
chain_spec = evm:bloxberg:8996
|
chain_spec = evm:bloxberg:8996
|
||||||
tx_retry_delay =
|
tx_retry_delay =
|
||||||
trust_address =
|
trust_address =
|
||||||
default_token_symbol = GFT
|
|
||||||
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
|
|
||||||
run_dir = /run
|
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ HOST=localhost
|
|||||||
PORT=5432
|
PORT=5432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
POOL_SIZE=50
|
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
[celery]
|
[celery]
|
||||||
broker_url = redis://localhost:63379
|
broker_url = redis://localhost:63379
|
||||||
result_url = redis://localhost:63379
|
result_url = redis://localhost:63379
|
||||||
debug = 0
|
|
||||||
|
|||||||
@@ -2,7 +2,3 @@
|
|||||||
registry_address =
|
registry_address =
|
||||||
chain_spec = evm:bloxberg:8996
|
chain_spec = evm:bloxberg:8996
|
||||||
trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
|
trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
|
||||||
tx_retry_delay = 20
|
|
||||||
default_token_symbol = GFT
|
|
||||||
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
|
|
||||||
run_dir = /run
|
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ HOST=localhost
|
|||||||
PORT=63432
|
PORT=63432
|
||||||
ENGINE=postgresql
|
ENGINE=postgresql
|
||||||
DRIVER=psycopg2
|
DRIVER=psycopg2
|
||||||
POOL_SIZE=50
|
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
[eth]
|
[eth]
|
||||||
|
#ws_provider = ws://localhost:8546
|
||||||
|
#ttp_provider = http://localhost:8545
|
||||||
provider = http://localhost:63545
|
provider = http://localhost:63545
|
||||||
gas_gifter_minimum_balance = 10000000000000000000000
|
gas_provider_address =
|
||||||
|
#chain_id =
|
||||||
|
abi_dir = /home/lash/src/ext/cic/grassrootseconomics/cic-contracts/abis
|
||||||
|
account_accounts_index_writer =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[signer]
|
[signer]
|
||||||
socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc
|
socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc
|
||||||
secret = deedbeef
|
secret = deedbeef
|
||||||
database_name = signer_test
|
database_name = signer_test
|
||||||
dev_keys_path =
|
dev_keys_path =
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
[SYNCER]
|
[SYNCER]
|
||||||
loop_interval =
|
loop_interval = 1
|
||||||
history_start = 0
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
[eth]
|
[eth]
|
||||||
|
#ws_provider = ws://localhost:8546
|
||||||
|
#ttp_provider = http://localhost:8545
|
||||||
provider = http://localhost:8545
|
provider = http://localhost:8545
|
||||||
gas_gifter_minimum_balance = 10000000000000000000000
|
gas_provider_address =
|
||||||
|
#chain_id =
|
||||||
|
abi_dir = /usr/local/share/cic/solidity/abi
|
||||||
|
account_accounts_index_writer =
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
[SYNCER]
|
[SYNCER]
|
||||||
loop_interval =
|
loop_interval = 1
|
||||||
history_start = 0
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
@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
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user