Compare commits
8 Commits
lash/defau
...
tmp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fb380a54d
|
||
|
|
0663059a1d
|
||
|
|
2b0b94c501
|
||
|
|
8ea195e481
|
||
|
|
c067c031a2
|
||
|
|
6febf32bb7
|
||
|
|
3ca7ca881d | ||
|
|
c78aad90c8
|
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,16 +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
|
|
||||||
**/.vim
|
|
||||||
|
|||||||
@@ -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=
|
|
||||||
|
|||||||
@@ -2,5 +2,4 @@
|
|||||||
omit =
|
omit =
|
||||||
.venv/*
|
.venv/*
|
||||||
scripts/*
|
scripts/*
|
||||||
cic_cache/db/migrations/*
|
cic_cache/db/postgres/*
|
||||||
cic_cache/version.py
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
.git
|
|
||||||
.cache
|
|
||||||
.dot
|
|
||||||
**/doc
|
|
||||||
@@ -1,52 +1,22 @@
|
|||||||
.cic_cache_variables:
|
.cic_cache_variables:
|
||||||
variables:
|
variables:
|
||||||
APP_NAME: cic-cache
|
APP_NAME: cic-cache
|
||||||
DOCKERFILE_PATH: docker/Dockerfile_ci
|
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
|
||||||
CONTEXT: apps/$APP_NAME
|
|
||||||
|
.cic_cache_changes_target:
|
||||||
|
rules:
|
||||||
|
- changes:
|
||||||
|
- $CONTEXT/$APP_NAME/*
|
||||||
|
|
||||||
build-mr-cic-cache:
|
build-mr-cic-cache:
|
||||||
extends:
|
extends:
|
||||||
|
- .cic_cache_changes_target
|
||||||
- .py_build_merge_request
|
- .py_build_merge_request
|
||||||
- .cic_cache_variables
|
- .cic_cache_variables
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
changes:
|
|
||||||
- apps/cic-cache/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
test-mr-cic-cache:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .cic_cache_variables
|
|
||||||
cache:
|
|
||||||
key:
|
|
||||||
files:
|
|
||||||
- test_requirements.txt
|
|
||||||
paths:
|
|
||||||
- /root/.cache/pip
|
|
||||||
image: $MR_IMAGE_TAG
|
|
||||||
script:
|
|
||||||
- cd apps/$APP_NAME/
|
|
||||||
- >
|
|
||||||
pip install --extra-index-url https://pip.grassrootseconomics.net:8433
|
|
||||||
--extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple
|
|
||||||
-r test_requirements.txt
|
|
||||||
- export PYTHONPATH=. && pytest -x --cov=cic_cache --cov-fail-under=90 --cov-report term-missing tests
|
|
||||||
needs: ["build-mr-cic-cache"]
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
changes:
|
|
||||||
- apps/$APP_NAME/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
build-push-cic-cache:
|
build-push-cic-cache:
|
||||||
extends:
|
extends:
|
||||||
- .py_build_push
|
- .py_build_push
|
||||||
- .cic_cache_variables
|
- .cic_cache_variables
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "master"
|
|
||||||
changes:
|
|
||||||
- apps/cic-cache/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
# 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()
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_FILTER_SIZE = 8192 * 8
|
class BloomCache:
|
||||||
|
|
||||||
class Cache:
|
|
||||||
|
|
||||||
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 = DEFAULT_FILTER_SIZE
|
n = 8192 * 8
|
||||||
logg.warning('filter size hardcoded to {}'.format(n))
|
logg.warning('filter size hardcoded to {}'.format(n))
|
||||||
return n
|
return n
|
||||||
|
|
||||||
@@ -95,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,114 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import base64
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
from hexathon import add_0x
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_cache.cache import (
|
|
||||||
BloomCache,
|
|
||||||
DataCache,
|
|
||||||
)
|
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
|
||||||
#logg = logging.getLogger()
|
|
||||||
|
|
||||||
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 = add_0x(address)
|
|
||||||
offset = 0
|
|
||||||
if r.lastindex > 2:
|
|
||||||
offset = r[4]
|
|
||||||
limit = DEFAULT_LIMIT
|
|
||||||
if r.lastindex > 4:
|
|
||||||
limit = r[6]
|
|
||||||
|
|
||||||
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,27 @@ 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
|
||||||
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 +55,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 +67,26 @@ 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
|
syncers.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:
|
for syncer_backend in syncer_backends:
|
||||||
syncers.append(HistorySyncer(syncer_backend, chain_interface))
|
syncers.append(HistorySyncer(syncer_backend))
|
||||||
|
|
||||||
syncer_backend = SQLBackend.live(chain_spec, block_offset+1)
|
syncer_backend = SyncerBackend.live(chain_spec, block_offset+1)
|
||||||
syncers.append(HeadSyncer(syncer_backend, chain_interface))
|
syncers.append(HeadSyncer(syncer_backend))
|
||||||
|
|
||||||
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 +97,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
|
|
||||||
@@ -1,38 +1,53 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
FROM python:3.8.6-slim-buster
|
||||||
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
|
|
||||||
|
|
||||||
# RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b9
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
#COPY --from=0 /usr/local/share/cic/solidity/ /usr/local/share/cic/solidity/
|
||||||
#RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
|
|
||||||
#RUN pip install $pip_extra_index_url_flag .
|
|
||||||
#RUN pip install .[server]
|
|
||||||
|
|
||||||
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
|
WORKDIR /usr/src/cic-cache
|
||||||
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
|
|
||||||
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
|
|
||||||
pip install --index-url https://pypi.org/simple \
|
|
||||||
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
|
|
||||||
-r requirements.txt
|
|
||||||
|
|
||||||
COPY . .
|
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||||
|
ARG root_requirement_file='requirements.txt'
|
||||||
|
|
||||||
RUN python setup.py install
|
#RUN apk update && \
|
||||||
|
# apk add gcc musl-dev gnupg libpq
|
||||||
|
#RUN apk add postgresql-dev
|
||||||
|
#RUN apk add linux-headers
|
||||||
|
#RUN apk add libffi-dev
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
|
||||||
|
|
||||||
|
# Copy shared requirements from top of mono-repo
|
||||||
|
RUN echo "copying root req file ${root_requirement_file}"
|
||||||
|
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58
|
||||||
|
|
||||||
|
COPY cic-cache/requirements.txt ./
|
||||||
|
COPY cic-cache/setup.cfg \
|
||||||
|
cic-cache/setup.py \
|
||||||
|
./
|
||||||
|
COPY cic-cache/cic_cache/ ./cic_cache/
|
||||||
|
COPY cic-cache/scripts/ ./scripts/
|
||||||
|
COPY cic-cache/test_requirements.txt ./
|
||||||
|
RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
|
||||||
|
RUN pip install $pip_extra_index_url_flag .
|
||||||
|
RUN pip install .[server]
|
||||||
|
|
||||||
|
COPY cic-cache/tests/ ./tests/
|
||||||
|
#COPY db/ cic-cache/db
|
||||||
|
#RUN apk add postgresql-client
|
||||||
|
|
||||||
# ini files in config directory defines the configurable parameters for the application
|
# ini files in config directory defines the configurable parameters for the application
|
||||||
# they can all be overridden by environment variables
|
# they can all be overridden by environment variables
|
||||||
# to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
|
# to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
|
||||||
COPY config/ /usr/local/etc/cic-cache/
|
COPY cic-cache/config/ /usr/local/etc/cic-cache/
|
||||||
|
|
||||||
# for db migrations
|
# for db migrations
|
||||||
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/db/migrations/ /usr/local/share/cic-cache/alembic/
|
COPY cic-cache/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
|
||||||
|
|
||||||
|
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
|
||||||
|
mkdir -p /usr/local/share/cic/solidity && \
|
||||||
|
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
|
||||||
|
|
||||||
COPY /docker/start_tracker.sh ./start_tracker.sh
|
|
||||||
COPY /docker/db.sh ./db.sh
|
|
||||||
RUN chmod 755 ./*.sh
|
|
||||||
# Tracker
|
# Tracker
|
||||||
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
||||||
# Server
|
# Server
|
||||||
# ENTRYPOINT [ "/usr/local/bin/uwsgi", "--wsgi-file", "/usr/local/lib/python3.8/site-packages/cic_cache/runnable/server.py", "--http", ":80", "--pyargv", "-vv" ]
|
# ENTRYPOINT [ "/usr/local/bin/uwsgi", "--wsgi-file", "/usr/local/lib/python3.8/site-packages/cic_cache/runnable/server.py", "--http", ":80", "--pyargv", "-vv" ]
|
||||||
ENTRYPOINT []
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
|
||||||
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
|
|
||||||
|
|
||||||
# RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b9
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
#RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
|
|
||||||
#RUN pip install $pip_extra_index_url_flag .
|
|
||||||
#RUN pip install .[server]
|
|
||||||
|
|
||||||
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
|
|
||||||
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
|
|
||||||
RUN pip install --index-url https://pypi.org/simple \
|
|
||||||
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
|
|
||||||
-r requirements.txt
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN python setup.py install
|
|
||||||
|
|
||||||
# ini files in config directory defines the configurable parameters for the application
|
|
||||||
# they can all be overridden by environment variables
|
|
||||||
# to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
|
|
||||||
COPY config/ /usr/local/etc/cic-cache/
|
|
||||||
|
|
||||||
# for db migrations
|
|
||||||
RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/
|
|
||||||
COPY cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
|
|
||||||
|
|
||||||
COPY /docker/start_tracker.sh ./start_tracker.sh
|
|
||||||
COPY /docker/db.sh ./db.sh
|
|
||||||
RUN chmod 755 ./*.sh
|
|
||||||
# Tracker
|
|
||||||
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
|
|
||||||
# Server
|
|
||||||
# ENTRYPOINT [ "/usr/local/bin/uwsgi", "--wsgi-file", "/usr/local/lib/python3.8/site-packages/cic_cache/runnable/server.py", "--http", ":80", "--pyargv", "-vv" ]
|
|
||||||
ENTRYPOINT []
|
|
||||||
@@ -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.2a66
|
||||||
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.4a13
|
||||||
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.2a10
|
||||||
erc20-faucet~=0.2.2a1
|
chainsyncer[sql]~=0.0.2a1
|
||||||
|
|||||||
@@ -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,14 +3,11 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# external imports
|
# third-party imports
|
||||||
import pytest
|
import pytest
|
||||||
import moolb
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_cache import db
|
from cic_cache import db
|
||||||
from cic_cache import BloomCache
|
|
||||||
from cic_cache.cache import DEFAULT_FILTER_SIZE
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
root_dir = os.path.dirname(script_dir)
|
root_dir = os.path.dirname(script_dir)
|
||||||
@@ -87,24 +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')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
|
||||||
def zero_filter():
|
|
||||||
return moolb.Bloom(DEFAULT_FILTER_SIZE, 3)
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from chainlib.eth.pytest import *
|
|
||||||
from cic_eth_registry.pytest.fixtures_tokens import *
|
|
||||||
|
|
||||||
@@ -1,173 +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 chainlib.eth.error import RequestMismatchException
|
|
||||||
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
|
|
||||||
from cic_cache.runnable.daemons.filters.base import TagSyncFilter
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def test_base_filter_str(
|
|
||||||
init_database,
|
|
||||||
):
|
|
||||||
f = TagSyncFilter('foo')
|
|
||||||
assert 'foo' == str(f)
|
|
||||||
f = TagSyncFilter('foo', domain='bar')
|
|
||||||
assert 'bar.foo' == str(f)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def test_erc20_filter_nocontract(
|
|
||||||
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)
|
|
||||||
|
|
||||||
# incomplete args
|
|
||||||
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': os.urandom(20).hex(),
|
|
||||||
'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
|
|
||||||
|
|
||||||
assert not fltr.filter(eth_rpc, block, tx, db_session=init_database)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'contract_method,contract_input,expected_exception',
|
|
||||||
[
|
|
||||||
('a9059cbb', os.urandom(32).hex(), ValueError), # not enough args
|
|
||||||
('a9059cbb', os.urandom(31).hex(), ValueError), # wrong arg boundary
|
|
||||||
('a9059cbc', os.urandom(64).hex(), RequestMismatchException), # wrong method
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_erc20_filter_bogus(
|
|
||||||
eth_rpc,
|
|
||||||
foo_token,
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
tags,
|
|
||||||
contract_method,
|
|
||||||
contract_input,
|
|
||||||
expected_exception,
|
|
||||||
):
|
|
||||||
|
|
||||||
chain_spec = ChainSpec('foo', 'bar', 42, 'baz')
|
|
||||||
|
|
||||||
fltr = ERC20TransferFilter(chain_spec)
|
|
||||||
add_tag(init_database, fltr.tag_name, domain=fltr.tag_domain)
|
|
||||||
|
|
||||||
# incomplete args
|
|
||||||
data = contract_method
|
|
||||||
data += contract_input
|
|
||||||
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
|
|
||||||
|
|
||||||
assert not fltr.filter(eth_rpc, block, tx, db_session=init_database)
|
|
||||||
@@ -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,230 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
import copy
|
|
||||||
import re
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import pytest
|
|
||||||
from hexathon import strip_0x
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_cache.runnable.daemons.query import *
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'query_path_prefix, query_role, query_address_index, query_offset, query_offset_index, query_limit, query_limit_index, match_re',
|
|
||||||
[
|
|
||||||
('/tx/user/', 'alice', 0, None, 3, None, 5, re_transactions_account_bloom),
|
|
||||||
('/tx/user/', 'alice', 0, 42, 3, None, 5, re_transactions_account_bloom),
|
|
||||||
('/tx/user/', 'alice', 0, 42, 3, 13, 5, re_transactions_account_bloom),
|
|
||||||
('/tx/', None, 0, None, 3, None, 5, re_transactions_all_bloom),
|
|
||||||
('/tx/', None, 0, 42, 3, None, 5, re_transactions_all_bloom),
|
|
||||||
('/tx/', None, 0, 42, 3, 13, 5, re_transactions_all_bloom),
|
|
||||||
('/txa/', None, 0, None, 3, None, 5, re_transactions_all_data),
|
|
||||||
('/txa/', None, 0, 42, 3, None, 5, re_transactions_all_data),
|
|
||||||
('/txa/', None, 0, 42, 3, 13, 5, re_transactions_all_data),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_query_regex(
|
|
||||||
list_actors,
|
|
||||||
query_path_prefix,
|
|
||||||
query_role,
|
|
||||||
query_address_index,
|
|
||||||
query_offset,
|
|
||||||
query_offset_index,
|
|
||||||
query_limit,
|
|
||||||
query_limit_index,
|
|
||||||
match_re,
|
|
||||||
):
|
|
||||||
|
|
||||||
paths = []
|
|
||||||
path = query_path_prefix
|
|
||||||
query_address = None
|
|
||||||
if query_role != None:
|
|
||||||
query_address = strip_0x(list_actors[query_role])
|
|
||||||
paths.append(path + '0x' + query_address)
|
|
||||||
paths.append(path + query_address)
|
|
||||||
if query_offset != None:
|
|
||||||
if query_limit != None:
|
|
||||||
for i in range(len(paths)-1):
|
|
||||||
paths[i] += '/{}/{}'.format(query_offset, query_limit)
|
|
||||||
else:
|
|
||||||
for i in range(len(paths)-1):
|
|
||||||
paths[i] += '/' + str(query_offset)
|
|
||||||
|
|
||||||
for i in range(len(paths)):
|
|
||||||
paths.append(paths[i] + '/')
|
|
||||||
|
|
||||||
for p in paths:
|
|
||||||
logg.debug('testing path {} against {}'.format(p, match_re))
|
|
||||||
m = re.match(match_re, p)
|
|
||||||
l = len(m.groups())
|
|
||||||
logg.debug('laast index match {} groups {}'.format(m.lastindex, l))
|
|
||||||
for i in range(l+1):
|
|
||||||
logg.debug('group {} {}'.format(i, m[i]))
|
|
||||||
if m.lastindex >= query_offset_index:
|
|
||||||
assert query_offset == int(m[query_offset_index + 1])
|
|
||||||
if m.lastindex >= query_limit_index:
|
|
||||||
assert query_limit == int(m[query_limit_index + 1])
|
|
||||||
if query_address_index != None:
|
|
||||||
match_address = strip_0x(m[query_address_index + 1])
|
|
||||||
assert query_address == match_address
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'role_name, query_offset, query_limit, query_match',
|
|
||||||
[
|
|
||||||
('alice', None, None, [(420000, 13), (419999, 42)]),
|
|
||||||
('alice', None, 1, [(420000, 13)]),
|
|
||||||
('alice', 1, None, [(419999, 42)]), # 420000 == list_defaults['block']
|
|
||||||
('alice', 2, None, []), # 420000 == list_defaults['block']
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_query_process_txs_account(
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
list_tokens,
|
|
||||||
txs,
|
|
||||||
zero_filter,
|
|
||||||
role_name,
|
|
||||||
query_offset,
|
|
||||||
query_limit,
|
|
||||||
query_match,
|
|
||||||
):
|
|
||||||
|
|
||||||
actor = None
|
|
||||||
try:
|
|
||||||
actor = list_actors[role_name]
|
|
||||||
except KeyError:
|
|
||||||
actor = os.urandom(20).hex()
|
|
||||||
path_info = '/tx/user/0x' + strip_0x(actor)
|
|
||||||
if query_offset != None:
|
|
||||||
path_info += '/' + str(query_offset)
|
|
||||||
if query_limit != None:
|
|
||||||
if query_offset == None:
|
|
||||||
path_info += '/0'
|
|
||||||
path_info += '/' + str(query_limit)
|
|
||||||
env = {
|
|
||||||
'PATH_INFO': path_info,
|
|
||||||
}
|
|
||||||
logg.debug('using path {}'.format(path_info))
|
|
||||||
r = process_transactions_account_bloom(init_database, env)
|
|
||||||
assert r != None
|
|
||||||
|
|
||||||
o = json.loads(r[1])
|
|
||||||
block_filter_data = base64.b64decode(o['block_filter'].encode('utf-8'))
|
|
||||||
zero_filter_data = zero_filter.to_bytes()
|
|
||||||
if len(query_match) == 0:
|
|
||||||
assert block_filter_data == zero_filter_data
|
|
||||||
return
|
|
||||||
|
|
||||||
assert block_filter_data != zero_filter_data
|
|
||||||
block_filter = copy.copy(zero_filter)
|
|
||||||
block_filter.merge(block_filter_data)
|
|
||||||
block_filter_data = block_filter.to_bytes()
|
|
||||||
assert block_filter_data != zero_filter_data
|
|
||||||
|
|
||||||
for (block, tx) in query_match:
|
|
||||||
block = block.to_bytes(4, byteorder='big')
|
|
||||||
assert block_filter.check(block)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'query_offset, query_limit, query_match',
|
|
||||||
[
|
|
||||||
(None, 2, [(420000, 13), (419999, 42)]),
|
|
||||||
(0, 1, [(420000, 13)]),
|
|
||||||
(1, 1, [(419999, 42)]),
|
|
||||||
(2, 0, []),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_query_process_txs_bloom(
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
list_tokens,
|
|
||||||
txs,
|
|
||||||
zero_filter,
|
|
||||||
query_offset,
|
|
||||||
query_limit,
|
|
||||||
query_match,
|
|
||||||
):
|
|
||||||
|
|
||||||
path_info = '/tx'
|
|
||||||
if query_offset != None:
|
|
||||||
path_info += '/' + str(query_offset)
|
|
||||||
if query_limit != None:
|
|
||||||
if query_offset == None:
|
|
||||||
path_info += '/0'
|
|
||||||
path_info += '/' + str(query_limit)
|
|
||||||
env = {
|
|
||||||
'PATH_INFO': path_info,
|
|
||||||
}
|
|
||||||
logg.debug('using path {}'.format(path_info))
|
|
||||||
r = process_transactions_all_bloom(init_database, env)
|
|
||||||
assert r != None
|
|
||||||
|
|
||||||
o = json.loads(r[1])
|
|
||||||
block_filter_data = base64.b64decode(o['block_filter'].encode('utf-8'))
|
|
||||||
zero_filter_data = zero_filter.to_bytes()
|
|
||||||
if len(query_match) == 0:
|
|
||||||
assert block_filter_data == zero_filter_data
|
|
||||||
return
|
|
||||||
|
|
||||||
assert block_filter_data != zero_filter_data
|
|
||||||
block_filter = copy.copy(zero_filter)
|
|
||||||
block_filter.merge(block_filter_data)
|
|
||||||
block_filter_data = block_filter.to_bytes()
|
|
||||||
assert block_filter_data != zero_filter_data
|
|
||||||
|
|
||||||
for (block, tx) in query_match:
|
|
||||||
block = block.to_bytes(4, byteorder='big')
|
|
||||||
assert block_filter.check(block)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'query_block_start, query_block_end, query_match_count',
|
|
||||||
[
|
|
||||||
(None, 42, 0),
|
|
||||||
(420000, 420001, 1),
|
|
||||||
(419999, 419999, 1), # matches are inclusive
|
|
||||||
(419999, 420000, 2),
|
|
||||||
(419999, 420001, 2),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_query_process_txs_data(
|
|
||||||
init_database,
|
|
||||||
list_defaults,
|
|
||||||
list_actors,
|
|
||||||
list_tokens,
|
|
||||||
txs,
|
|
||||||
zero_filter,
|
|
||||||
query_block_start,
|
|
||||||
query_block_end,
|
|
||||||
query_match_count,
|
|
||||||
):
|
|
||||||
|
|
||||||
path_info = '/txa'
|
|
||||||
if query_block_start != None:
|
|
||||||
path_info += '/' + str(query_block_start)
|
|
||||||
if query_block_end != None:
|
|
||||||
if query_block_start == None:
|
|
||||||
path_info += '/0'
|
|
||||||
path_info += '/' + str(query_block_end)
|
|
||||||
env = {
|
|
||||||
'PATH_INFO': path_info,
|
|
||||||
'HTTP_X_CIC_CACHE_MODE': 'all',
|
|
||||||
}
|
|
||||||
logg.debug('using path {}'.format(path_info))
|
|
||||||
r = process_transactions_all_data(init_database, env)
|
|
||||||
assert r != None
|
|
||||||
|
|
||||||
o = json.loads(r[1])
|
|
||||||
assert len(o['data']) == query_match_count
|
|
||||||
@@ -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]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
include *requirements.txt
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
from erc20_demurrage_token.demurrage import DemurrageCalculator
|
|
||||||
from chainlib.connection import RPCConnection
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
|
||||||
from cic_eth_registry import CICRegistry
|
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
celery_app = celery.current_app
|
|
||||||
|
|
||||||
|
|
||||||
class NoopCalculator:
|
|
||||||
|
|
||||||
def amount_since(self, amount, timestamp):
|
|
||||||
logg.debug('noopcalculator amount {} timestamp {}'.format(amount, timestamp))
|
|
||||||
return amount
|
|
||||||
|
|
||||||
|
|
||||||
class DemurrageCalculationTask(celery.Task):
|
|
||||||
|
|
||||||
demurrage_token_calcs = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register_token(cls, rpc, chain_spec, token_symbol, sender_address=ZERO_ADDRESS):
|
|
||||||
registry = CICRegistry(chain_spec, rpc)
|
|
||||||
token_address = registry.by_name(token_symbol, sender_address=sender_address)
|
|
||||||
try:
|
|
||||||
c = DemurrageCalculator.from_contract(rpc, chain_spec, token_address, sender_address=sender_address)
|
|
||||||
logg.info('found demurrage calculator for ERC20 {} @ {}'.format(token_symbol, token_address))
|
|
||||||
except:
|
|
||||||
logg.warning('Token {} at address {} does not appear to be a demurrage contract. Calls to balance adjust for this token will always return the same amount'.format(token_symbol, token_address))
|
|
||||||
c = NoopCalculator()
|
|
||||||
|
|
||||||
cls.demurrage_token_calcs[token_symbol] = c
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, base=DemurrageCalculationTask)
|
|
||||||
def get_adjusted_balance(self, token_symbol, amount, timestamp):
|
|
||||||
c = self.demurrage_token_calcs[token_symbol]
|
|
||||||
return c.amount_since(amount, timestamp)
|
|
||||||
|
|
||||||
|
|
||||||
def aux_setup(rpc, config, sender_address=ZERO_ADDRESS):
|
|
||||||
chain_spec_str = config.get('CIC_CHAIN_SPEC')
|
|
||||||
chain_spec = ChainSpec.from_chain_str(chain_spec_str)
|
|
||||||
token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
|
|
||||||
|
|
||||||
DemurrageCalculationTask.register_token(rpc, chain_spec, token_symbol, sender_address=sender_address)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
from cic_eth.api.base import ApiBase
|
|
||||||
|
|
||||||
app = celery.current_app
|
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Api(ApiBase):
|
|
||||||
|
|
||||||
def get_adjusted_balance(self, token_symbol, balance, timestamp):
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth_aux.erc20_demurrage_token.get_adjusted_balance',
|
|
||||||
[
|
|
||||||
token_symbol,
|
|
||||||
balance,
|
|
||||||
timestamp,
|
|
||||||
],
|
|
||||||
queue=None,
|
|
||||||
)
|
|
||||||
if self.callback_param != None:
|
|
||||||
s.link(self.callback_success)
|
|
||||||
s.link.on_error(self.callback_error)
|
|
||||||
|
|
||||||
t = s.apply_async(queue=self.queue)
|
|
||||||
return t
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
celery==4.4.7
|
|
||||||
erc20-demurrage-token~=0.0.2a3
|
|
||||||
cic-eth-registry~=0.5.6a1
|
|
||||||
chainlib~=0.0.5a1
|
|
||||||
cic_eth~=0.12.0a2
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = cic-eth-aux-erc20-demurrage-token
|
|
||||||
version = 0.0.2a4
|
|
||||||
description = cic-eth tasks supporting erc20 demurrage token
|
|
||||||
author = Louis Holbrook
|
|
||||||
author_email = dev@holbrook.no
|
|
||||||
url = https://gitlab.com/ccicnet/erc20-demurrage-token
|
|
||||||
keywords =
|
|
||||||
ethereum
|
|
||||||
blockchain
|
|
||||||
cryptocurrency
|
|
||||||
erc20
|
|
||||||
classifiers =
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Operating System :: OS Independent
|
|
||||||
Development Status :: 3 - Alpha
|
|
||||||
Environment :: No Input/Output (Daemon)
|
|
||||||
Intended Audience :: Developers
|
|
||||||
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
||||||
Topic :: Internet
|
|
||||||
#Topic :: Blockchain :: EVM
|
|
||||||
license = GPL3
|
|
||||||
licence_files =
|
|
||||||
LICENSE
|
|
||||||
|
|
||||||
[options]
|
|
||||||
include_package_data = True
|
|
||||||
python_requires = >= 3.6
|
|
||||||
packages =
|
|
||||||
cic_eth_aux.erc20_demurrage_token
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
from setuptools import setup
|
|
||||||
|
|
||||||
requirements = []
|
|
||||||
f = open('requirements.txt', 'r')
|
|
||||||
while True:
|
|
||||||
l = f.readline()
|
|
||||||
if l == '':
|
|
||||||
break
|
|
||||||
requirements.append(l.rstrip())
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
test_requirements = []
|
|
||||||
f = open('test_requirements.txt', 'r')
|
|
||||||
while True:
|
|
||||||
l = f.readline()
|
|
||||||
if l == '':
|
|
||||||
break
|
|
||||||
test_requirements.append(l.rstrip())
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
setup(
|
|
||||||
install_requires=requirements,
|
|
||||||
tests_require=test_requirements,
|
|
||||||
)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
pytest==6.0.1
|
|
||||||
pytest-celery==0.0.0a1
|
|
||||||
pytest-mock==3.3.1
|
|
||||||
pytest-cov==2.10.1
|
|
||||||
eth-tester==0.5.0b3
|
|
||||||
py-evm==0.3.0a20
|
|
||||||
SQLAlchemy==1.3.20
|
|
||||||
cic-eth~=0.12.0a1
|
|
||||||
liveness~=0.0.1a7
|
|
||||||
eth-accounts-index==0.0.12a1
|
|
||||||
eth-contract-registry==0.5.6a1
|
|
||||||
eth-address-index==0.1.2a1
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# external imports
|
|
||||||
import celery
|
|
||||||
from chainlib.eth.pytest.fixtures_chain import *
|
|
||||||
from chainlib.eth.pytest.fixtures_ethtester import *
|
|
||||||
from cic_eth_registry.pytest.fixtures_contracts import *
|
|
||||||
from cic_eth_registry.pytest.fixtures_tokens import *
|
|
||||||
from erc20_demurrage_token.unittest.base import TestTokenDeploy
|
|
||||||
from erc20_demurrage_token.token import DemurrageToken
|
|
||||||
from eth_token_index.index import TokenUniqueSymbolIndex
|
|
||||||
from eth_address_declarator.declarator import AddressDeclarator
|
|
||||||
|
|
||||||
# cic-eth imports
|
|
||||||
from cic_eth.pytest.fixtures_celery import *
|
|
||||||
from cic_eth.pytest.fixtures_token import *
|
|
||||||
from cic_eth.pytest.fixtures_config import *
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def demurrage_token(
|
|
||||||
default_chain_spec,
|
|
||||||
eth_rpc,
|
|
||||||
token_registry,
|
|
||||||
contract_roles,
|
|
||||||
eth_signer,
|
|
||||||
):
|
|
||||||
d = TestTokenDeploy(eth_rpc, token_symbol='BAR', token_name='Bar Token')
|
|
||||||
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], conn=eth_rpc)
|
|
||||||
c = DemurrageToken(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
|
||||||
token_address = d.deploy(eth_rpc, contract_roles['CONTRACT_DEPLOYER'], c, 'SingleNocap')
|
|
||||||
logg.debug('demurrage token contract "BAR" deployed to {}'.format(token_address))
|
|
||||||
|
|
||||||
return token_address
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def demurrage_token_symbol(
|
|
||||||
default_chain_spec,
|
|
||||||
eth_rpc,
|
|
||||||
demurrage_token,
|
|
||||||
contract_roles,
|
|
||||||
):
|
|
||||||
|
|
||||||
c = DemurrageToken(default_chain_spec)
|
|
||||||
o = c.symbol(demurrage_token, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
|
||||||
r = eth_rpc.do(o)
|
|
||||||
return c.parse_symbol(r)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def demurrage_token_declaration(
|
|
||||||
foo_token_declaration,
|
|
||||||
):
|
|
||||||
return foo_token_declaration
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def register_demurrage_token(
|
|
||||||
default_chain_spec,
|
|
||||||
token_registry,
|
|
||||||
eth_rpc,
|
|
||||||
eth_signer,
|
|
||||||
register_lookups,
|
|
||||||
contract_roles,
|
|
||||||
demurrage_token_declaration,
|
|
||||||
demurrage_token,
|
|
||||||
address_declarator,
|
|
||||||
):
|
|
||||||
|
|
||||||
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc)
|
|
||||||
|
|
||||||
c = TokenUniqueSymbolIndex(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
|
||||||
(tx_hash_hex, o) = c.register(token_registry, contract_roles['CONTRACT_DEPLOYER'], demurrage_token)
|
|
||||||
eth_rpc.do(o)
|
|
||||||
o = receipt(tx_hash_hex)
|
|
||||||
r = eth_rpc.do(o)
|
|
||||||
assert r['status'] == 1
|
|
||||||
|
|
||||||
nonce_oracle = RPCNonceOracle(contract_roles['TRUSTED_DECLARATOR'], eth_rpc)
|
|
||||||
c = AddressDeclarator(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
|
|
||||||
(tx_hash_hex, o) = c.add_declaration(address_declarator, contract_roles['TRUSTED_DECLARATOR'], demurrage_token, demurrage_token_declaration)
|
|
||||||
|
|
||||||
eth_rpc.do(o)
|
|
||||||
o = receipt(tx_hash_hex)
|
|
||||||
r = eth_rpc.do(o)
|
|
||||||
assert r['status'] == 1
|
|
||||||
|
|
||||||
return token_registry
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
import copy
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
|
|
||||||
# cic-eth imports
|
|
||||||
from cic_eth_aux.erc20_demurrage_token import (
|
|
||||||
DemurrageCalculationTask,
|
|
||||||
aux_setup,
|
|
||||||
)
|
|
||||||
from cic_eth_aux.erc20_demurrage_token.api import Api as AuxApi
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def test_demurrage_calulate_task(
|
|
||||||
default_chain_spec,
|
|
||||||
eth_rpc,
|
|
||||||
cic_registry,
|
|
||||||
celery_session_worker,
|
|
||||||
register_demurrage_token,
|
|
||||||
demurrage_token_symbol,
|
|
||||||
contract_roles,
|
|
||||||
load_config,
|
|
||||||
):
|
|
||||||
|
|
||||||
config = copy.copy(load_config)
|
|
||||||
config.add(str(default_chain_spec), 'CIC_CHAIN_SPEC', exists_ok=True)
|
|
||||||
config.add(demurrage_token_symbol, 'CIC_DEFAULT_TOKEN_SYMBOL', exists_ok=True)
|
|
||||||
aux_setup(eth_rpc, load_config, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
|
||||||
|
|
||||||
since = datetime.datetime.utcnow() - datetime.timedelta(minutes=1)
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth_aux.erc20_demurrage_token.get_adjusted_balance',
|
|
||||||
[
|
|
||||||
demurrage_token_symbol,
|
|
||||||
1000,
|
|
||||||
since.timestamp(),
|
|
||||||
],
|
|
||||||
queue=None,
|
|
||||||
)
|
|
||||||
t = s.apply_async()
|
|
||||||
r = t.get_leaf()
|
|
||||||
assert t.successful()
|
|
||||||
assert r == 980
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_demurrage_calculate_api(
|
|
||||||
default_chain_spec,
|
|
||||||
eth_rpc,
|
|
||||||
cic_registry,
|
|
||||||
celery_session_worker,
|
|
||||||
register_demurrage_token,
|
|
||||||
demurrage_token_symbol,
|
|
||||||
contract_roles,
|
|
||||||
load_config,
|
|
||||||
):
|
|
||||||
|
|
||||||
api = AuxApi(str(default_chain_spec), queue=None)
|
|
||||||
since = datetime.datetime.utcnow() - datetime.timedelta(minutes=1)
|
|
||||||
t = api.get_adjusted_balance(demurrage_token_symbol, 1000, since.timestamp())
|
|
||||||
r = t.get_leaf()
|
|
||||||
assert t.successful()
|
|
||||||
assert r == 980
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.git
|
|
||||||
.cache
|
|
||||||
.dot
|
|
||||||
**/doc
|
|
||||||
**/.venv
|
|
||||||
**/venv
|
|
||||||
@@ -1,52 +1,22 @@
|
|||||||
.cic_eth_variables:
|
.cic_eth_variables:
|
||||||
variables:
|
variables:
|
||||||
APP_NAME: cic-eth
|
APP_NAME: cic-eth
|
||||||
DOCKERFILE_PATH: docker/Dockerfile_ci
|
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
|
||||||
CONTEXT: apps/$APP_NAME
|
|
||||||
|
.cic_eth_changes_target:
|
||||||
|
rules:
|
||||||
|
- changes:
|
||||||
|
- $CONTEXT/$APP_NAME/*
|
||||||
|
|
||||||
build-mr-cic-eth:
|
build-mr-cic-eth:
|
||||||
extends:
|
extends:
|
||||||
|
- .cic_eth_changes_target
|
||||||
|
- .py_build_merge_request
|
||||||
- .cic_eth_variables
|
- .cic_eth_variables
|
||||||
- .py_build_target_dev
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
changes:
|
|
||||||
- apps/cic-eth/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
test-mr-cic-eth:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .cic_eth_variables
|
|
||||||
cache:
|
|
||||||
key:
|
|
||||||
files:
|
|
||||||
- test_requirements.txt
|
|
||||||
paths:
|
|
||||||
- /root/.cache/pip
|
|
||||||
image: $MR_IMAGE_TAG
|
|
||||||
script:
|
|
||||||
- cd apps/$APP_NAME/
|
|
||||||
- >
|
|
||||||
pip install --extra-index-url https://pip.grassrootseconomics.net:8433
|
|
||||||
--extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple
|
|
||||||
-r admin_requirements.txt
|
|
||||||
-r services_requirements.txt
|
|
||||||
-r test_requirements.txt
|
|
||||||
- export PYTHONPATH=. && pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests
|
|
||||||
needs: ["build-mr-cic-eth"]
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
changes:
|
|
||||||
- apps/cic-eth/**/*
|
|
||||||
when: always
|
|
||||||
|
|
||||||
build-push-cic-eth:
|
build-push-cic-eth:
|
||||||
extends:
|
extends:
|
||||||
- .py_build_push
|
- .py_build_push
|
||||||
- .cic_eth_variables
|
- .cic_eth_variables
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "master"
|
|
||||||
changes:
|
|
||||||
- apps/cic-eth/**/*
|
|
||||||
when: always
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
include *requirements.txt config/test/*
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
SQLAlchemy==1.3.20
|
|
||||||
cic-eth-registry~=0.5.6a1
|
|
||||||
hexathon~=0.0.1a7
|
|
||||||
chainqueue~=0.0.2b5
|
|
||||||
eth-erc20==0.0.10a2
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -5,3 +5,4 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .api_task import Api
|
from .api_task import Api
|
||||||
|
from .api_admin import AdminApi
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -8,182 +8,83 @@ import logging
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
import celery
|
import celery
|
||||||
|
from cic_eth_registry import CICRegistry
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.api.base import ApiBase
|
from cic_eth.db.enum import LockEnum
|
||||||
from cic_eth.enum import LockEnum
|
|
||||||
|
|
||||||
app = celery.current_app
|
app = celery.current_app
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Api(ApiBase):
|
class Api:
|
||||||
|
"""Creates task chains to perform well-known CIC operations.
|
||||||
|
|
||||||
def default_token(self):
|
Each method that sends tasks returns details about the root task. The root task uuid can be provided in the callback, to enable to caller to correlate the result with individual calls. It can also be used to independently poll the completion of a task chain.
|
||||||
s_token = celery.signature(
|
|
||||||
'cic_eth.admin.token.default_token',
|
|
||||||
[],
|
|
||||||
queue=self.queue,
|
|
||||||
)
|
|
||||||
if self.callback_param != None:
|
|
||||||
s_token.link(self.callback_success)
|
|
||||||
|
|
||||||
return s_token.apply_async()
|
:param callback_param: Static value to pass to callback
|
||||||
|
:type callback_param: str
|
||||||
|
:param callback_task: Callback task that executes callback_param call. (Must be included by the celery worker)
|
||||||
|
:type callback_task: string
|
||||||
|
:param queue: Name of worker queue to submit tasks to
|
||||||
|
:type queue: str
|
||||||
|
"""
|
||||||
|
def __init__(self, chain_str, queue='cic-eth', callback_param=None, callback_task='cic_eth.callbacks.noop.noop', callback_queue=None):
|
||||||
|
self.chain_str = chain_str
|
||||||
|
self.chain_spec = ChainSpec.from_chain_str(chain_str)
|
||||||
|
self.callback_param = callback_param
|
||||||
|
self.callback_task = callback_task
|
||||||
|
self.queue = queue
|
||||||
|
logg.debug('api using queue {}'.format(self.queue))
|
||||||
|
self.callback_success = None
|
||||||
|
self.callback_error = None
|
||||||
|
if callback_queue == None:
|
||||||
|
callback_queue=self.queue
|
||||||
|
|
||||||
|
if callback_param != None:
|
||||||
|
self.callback_success = celery.signature(
|
||||||
|
callback_task,
|
||||||
|
[
|
||||||
|
callback_param,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
queue=callback_queue,
|
||||||
|
)
|
||||||
|
self.callback_error = celery.signature(
|
||||||
|
callback_task,
|
||||||
|
[
|
||||||
|
callback_param,
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
queue=callback_queue,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
|
||||||
# """Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.
|
||||||
#
|
|
||||||
# :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,
|
||||||
@@ -194,7 +95,70 @@ class Api(ApiBase):
|
|||||||
'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,
|
||||||
)
|
)
|
||||||
@@ -205,41 +169,29 @@ class Api(ApiBase):
|
|||||||
],
|
],
|
||||||
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,52 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# external imports
|
|
||||||
import celery
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
|
|
||||||
logg = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class ApiBase:
|
|
||||||
"""Creates task chains to perform well-known CIC operations.
|
|
||||||
|
|
||||||
Each method that sends tasks returns details about the root task. The root task uuid can be provided in the callback, to enable to caller to correlate the result with individual calls. It can also be used to independently poll the completion of a task chain.
|
|
||||||
|
|
||||||
:param callback_param: Static value to pass to callback
|
|
||||||
:type callback_param: str
|
|
||||||
:param callback_task: Callback task that executes callback_param call. (Must be included by the celery worker)
|
|
||||||
:type callback_task: string
|
|
||||||
:param queue: Name of worker queue to submit tasks to
|
|
||||||
:type queue: str
|
|
||||||
"""
|
|
||||||
def __init__(self, chain_str, queue='cic-eth', callback_param=None, callback_task='cic_eth.callbacks.noop.noop', callback_queue=None):
|
|
||||||
self.chain_str = chain_str
|
|
||||||
self.chain_spec = ChainSpec.from_chain_str(chain_str)
|
|
||||||
self.callback_param = callback_param
|
|
||||||
self.callback_task = callback_task
|
|
||||||
self.queue = queue
|
|
||||||
logg.debug('api using queue {}'.format(self.queue))
|
|
||||||
self.callback_success = None
|
|
||||||
self.callback_error = None
|
|
||||||
if callback_queue == None:
|
|
||||||
callback_queue=self.queue
|
|
||||||
|
|
||||||
if callback_param != None:
|
|
||||||
self.callback_success = celery.signature(
|
|
||||||
callback_task,
|
|
||||||
[
|
|
||||||
callback_param,
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
queue=callback_queue,
|
|
||||||
)
|
|
||||||
self.callback_error = celery.signature(
|
|
||||||
callback_task,
|
|
||||||
[
|
|
||||||
callback_param,
|
|
||||||
1,
|
|
||||||
],
|
|
||||||
queue=callback_queue,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -1 +1,157 @@
|
|||||||
from cic_eth.enum import *
|
# standard imports
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
|
class StatusBits(enum.IntEnum):
|
||||||
|
"""Individual bit flags that are combined to define the state and legacy of a queued transaction
|
||||||
|
|
||||||
|
"""
|
||||||
|
QUEUED = 0x01 # transaction should be sent to network
|
||||||
|
IN_NETWORK = 0x08 # transaction is in network
|
||||||
|
|
||||||
|
DEFERRED = 0x10 # an attempt to send the transaction to network has failed
|
||||||
|
GAS_ISSUES = 0x20 # transaction is pending sender account gas funding
|
||||||
|
|
||||||
|
LOCAL_ERROR = 0x100 # errors that originate internally from the component
|
||||||
|
NODE_ERROR = 0x200 # errors originating in the node (invalid RLP input...)
|
||||||
|
NETWORK_ERROR = 0x400 # errors that originate from the network (REVERT)
|
||||||
|
UNKNOWN_ERROR = 0x800 # unclassified errors (the should not occur)
|
||||||
|
|
||||||
|
FINAL = 0x1000 # transaction processing has completed
|
||||||
|
OBSOLETE = 0x2000 # transaction has been replaced by a different transaction with higher fee
|
||||||
|
MANUAL = 0x8000 # transaction processing has been manually overridden
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
|
class StatusEnum(enum.IntEnum):
|
||||||
|
"""
|
||||||
|
|
||||||
|
- Inactive, not finalized. (<0)
|
||||||
|
* PENDING: The initial state of a newly added transaction record. No action has been performed on this transaction yet.
|
||||||
|
* SENDFAIL: The transaction was not received by the node.
|
||||||
|
* RETRY: The transaction is queued for a new send attempt after previously failing.
|
||||||
|
* READYSEND: The transaction is queued for its first send attempt
|
||||||
|
* OBSOLETED: A new transaction with the same nonce and higher gas has been sent to network.
|
||||||
|
* WAITFORGAS: The transaction is on hold pending gas funding.
|
||||||
|
- Active state: (==0)
|
||||||
|
* SENT: The transaction has been sent to the mempool.
|
||||||
|
- Inactive, finalized. (>0)
|
||||||
|
* FUBAR: Unknown error occurred and transaction is abandoned. Manual intervention needed.
|
||||||
|
* CANCELLED: The transaction was sent, but was not mined and has disappered from the mempool. This usually follows a transaction being obsoleted.
|
||||||
|
* OVERRIDDEN: Transaction has been manually overriden.
|
||||||
|
* REJECTED: The transaction was rejected by the node.
|
||||||
|
* REVERTED: The transaction was mined, but exception occurred during EVM execution. (Block number will be set)
|
||||||
|
* SUCCESS: THe transaction was successfully mined. (Block number will be set)
|
||||||
|
|
||||||
|
"""
|
||||||
|
PENDING = 0
|
||||||
|
|
||||||
|
SENDFAIL = StatusBits.DEFERRED | StatusBits.LOCAL_ERROR
|
||||||
|
RETRY = StatusBits.QUEUED | StatusBits.DEFERRED
|
||||||
|
READYSEND = StatusBits.QUEUED
|
||||||
|
|
||||||
|
OBSOLETED = StatusBits.OBSOLETE | StatusBits.IN_NETWORK
|
||||||
|
|
||||||
|
WAITFORGAS = StatusBits.GAS_ISSUES
|
||||||
|
|
||||||
|
SENT = StatusBits.IN_NETWORK
|
||||||
|
FUBAR = StatusBits.FINAL | StatusBits.UNKNOWN_ERROR
|
||||||
|
CANCELLED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.OBSOLETE
|
||||||
|
OVERRIDDEN = StatusBits.FINAL | StatusBits.OBSOLETE | StatusBits.MANUAL
|
||||||
|
|
||||||
|
REJECTED = StatusBits.NODE_ERROR | StatusBits.FINAL
|
||||||
|
REVERTED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.NETWORK_ERROR
|
||||||
|
SUCCESS = StatusBits.IN_NETWORK | StatusBits.FINAL
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
|
class LockEnum(enum.IntEnum):
|
||||||
|
"""
|
||||||
|
STICKY: When set, reset is not possible
|
||||||
|
CREATE: Disable creation of accounts
|
||||||
|
SEND: Disable sending to network
|
||||||
|
QUEUE: Disable queueing new or modified transactions
|
||||||
|
"""
|
||||||
|
STICKY=1
|
||||||
|
CREATE=2
|
||||||
|
SEND=4
|
||||||
|
QUEUE=8
|
||||||
|
QUERY=16
|
||||||
|
ALL=int(0xfffffffffffffffe)
|
||||||
|
|
||||||
|
|
||||||
|
def status_str(v, bits_only=False):
|
||||||
|
"""Render a human-readable string describing the status
|
||||||
|
|
||||||
|
If the bit field exactly matches a StatusEnum value, the StatusEnum label will be returned.
|
||||||
|
|
||||||
|
If a StatusEnum cannot be matched, the string will be postfixed with "*", unless explicitly instructed to return bit field labels only.
|
||||||
|
|
||||||
|
:param v: Status bit field
|
||||||
|
:type v: number
|
||||||
|
:param bits_only: Only render individual bit labels.
|
||||||
|
:type bits_only: bool
|
||||||
|
:returns: Status string
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
s = ''
|
||||||
|
if not bits_only:
|
||||||
|
try:
|
||||||
|
s = StatusEnum(v).name
|
||||||
|
return s
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if v == 0:
|
||||||
|
return 'NONE'
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
b = (1 << i)
|
||||||
|
if (b & 0xffff) & v:
|
||||||
|
n = StatusBits(b).name
|
||||||
|
if len(s) > 0:
|
||||||
|
s += ','
|
||||||
|
s += n
|
||||||
|
if not bits_only:
|
||||||
|
s += '*'
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def all_errors():
|
||||||
|
"""Bit mask of all error states
|
||||||
|
|
||||||
|
:returns: Error flags
|
||||||
|
:rtype: number
|
||||||
|
"""
|
||||||
|
return StatusBits.LOCAL_ERROR | StatusBits.NODE_ERROR | StatusBits.NETWORK_ERROR | StatusBits.UNKNOWN_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
def is_error_status(v):
|
||||||
|
"""Check if value is an error state
|
||||||
|
|
||||||
|
:param v: Status bit field
|
||||||
|
:type v: number
|
||||||
|
:returns: True if error
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return bool(v & all_errors())
|
||||||
|
|
||||||
|
|
||||||
|
def dead():
|
||||||
|
"""Bit mask defining whether a transaction is still likely to be processed on the network.
|
||||||
|
|
||||||
|
:returns: Bit mask
|
||||||
|
:rtype: number
|
||||||
|
"""
|
||||||
|
return StatusBits.FINAL | StatusBits.OBSOLETE
|
||||||
|
|
||||||
|
|
||||||
|
def is_alive(v):
|
||||||
|
"""Check if transaction is still likely to be processed on the network.
|
||||||
|
|
||||||
|
The contingency of "likely" refers to the case a transaction has been obsoleted after sent to the network, but the network still confirms the obsoleted transaction. The return value of this method will not change as a result of this, BUT the state itself will (as the FINAL bit will be set).
|
||||||
|
|
||||||
|
:returns:
|
||||||
|
"""
|
||||||
|
return bool(v & dead() == 0)
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
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')
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import enum
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
|
||||||
class StatusBits(enum.IntEnum):
|
|
||||||
"""Individual bit flags that are combined to define the state and legacy of a queued transaction
|
|
||||||
|
|
||||||
"""
|
|
||||||
QUEUED = 0x01 # transaction should be sent to network
|
|
||||||
IN_NETWORK = 0x08 # transaction is in network
|
|
||||||
|
|
||||||
DEFERRED = 0x10 # an attempt to send the transaction to network has failed
|
|
||||||
GAS_ISSUES = 0x20 # transaction is pending sender account gas funding
|
|
||||||
|
|
||||||
LOCAL_ERROR = 0x100 # errors that originate internally from the component
|
|
||||||
NODE_ERROR = 0x200 # errors originating in the node (invalid RLP input...)
|
|
||||||
NETWORK_ERROR = 0x400 # errors that originate from the network (REVERT)
|
|
||||||
UNKNOWN_ERROR = 0x800 # unclassified errors (the should not occur)
|
|
||||||
|
|
||||||
FINAL = 0x1000 # transaction processing has completed
|
|
||||||
OBSOLETE = 0x2000 # transaction has been replaced by a different transaction with higher fee
|
|
||||||
MANUAL = 0x8000 # transaction processing has been manually overridden
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
|
||||||
class StatusEnum(enum.IntEnum):
|
|
||||||
"""
|
|
||||||
|
|
||||||
- Inactive, not finalized. (<0)
|
|
||||||
* PENDING: The initial state of a newly added transaction record. No action has been performed on this transaction yet.
|
|
||||||
* SENDFAIL: The transaction was not received by the node.
|
|
||||||
* RETRY: The transaction is queued for a new send attempt after previously failing.
|
|
||||||
* READYSEND: The transaction is queued for its first send attempt
|
|
||||||
* OBSOLETED: A new transaction with the same nonce and higher gas has been sent to network.
|
|
||||||
* WAITFORGAS: The transaction is on hold pending gas funding.
|
|
||||||
- Active state: (==0)
|
|
||||||
* SENT: The transaction has been sent to the mempool.
|
|
||||||
- Inactive, finalized. (>0)
|
|
||||||
* FUBAR: Unknown error occurred and transaction is abandoned. Manual intervention needed.
|
|
||||||
* CANCELLED: The transaction was sent, but was not mined and has disappered from the mempool. This usually follows a transaction being obsoleted.
|
|
||||||
* OVERRIDDEN: Transaction has been manually overriden.
|
|
||||||
* REJECTED: The transaction was rejected by the node.
|
|
||||||
* REVERTED: The transaction was mined, but exception occurred during EVM execution. (Block number will be set)
|
|
||||||
* SUCCESS: THe transaction was successfully mined. (Block number will be set)
|
|
||||||
|
|
||||||
"""
|
|
||||||
PENDING = 0
|
|
||||||
|
|
||||||
SENDFAIL = StatusBits.DEFERRED | StatusBits.LOCAL_ERROR
|
|
||||||
RETRY = StatusBits.QUEUED | StatusBits.DEFERRED
|
|
||||||
READYSEND = StatusBits.QUEUED
|
|
||||||
|
|
||||||
OBSOLETED = StatusBits.OBSOLETE | StatusBits.IN_NETWORK
|
|
||||||
|
|
||||||
WAITFORGAS = StatusBits.GAS_ISSUES
|
|
||||||
|
|
||||||
SENT = StatusBits.IN_NETWORK
|
|
||||||
FUBAR = StatusBits.FINAL | StatusBits.UNKNOWN_ERROR
|
|
||||||
CANCELLED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.OBSOLETE
|
|
||||||
OVERRIDDEN = StatusBits.FINAL | StatusBits.OBSOLETE | StatusBits.MANUAL
|
|
||||||
|
|
||||||
REJECTED = StatusBits.NODE_ERROR | StatusBits.FINAL
|
|
||||||
REVERTED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.NETWORK_ERROR
|
|
||||||
SUCCESS = StatusBits.IN_NETWORK | StatusBits.FINAL
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
|
||||||
class LockEnum(enum.IntEnum):
|
|
||||||
"""
|
|
||||||
STICKY: When set, reset is not possible
|
|
||||||
CREATE: Disable creation of accounts
|
|
||||||
SEND: Disable sending to network
|
|
||||||
QUEUE: Disable queueing new or modified transactions
|
|
||||||
"""
|
|
||||||
STICKY=1
|
|
||||||
INIT=2
|
|
||||||
CREATE=4
|
|
||||||
SEND=8
|
|
||||||
QUEUE=16
|
|
||||||
QUERY=32
|
|
||||||
ALL=int(0xfffffffffffffffe)
|
|
||||||
|
|
||||||
|
|
||||||
def status_str(v, bits_only=False):
|
|
||||||
"""Render a human-readable string describing the status
|
|
||||||
|
|
||||||
If the bit field exactly matches a StatusEnum value, the StatusEnum label will be returned.
|
|
||||||
|
|
||||||
If a StatusEnum cannot be matched, the string will be postfixed with "*", unless explicitly instructed to return bit field labels only.
|
|
||||||
|
|
||||||
:param v: Status bit field
|
|
||||||
:type v: number
|
|
||||||
:param bits_only: Only render individual bit labels.
|
|
||||||
:type bits_only: bool
|
|
||||||
:returns: Status string
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
s = ''
|
|
||||||
if not bits_only:
|
|
||||||
try:
|
|
||||||
s = StatusEnum(v).name
|
|
||||||
return s
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if v == 0:
|
|
||||||
return 'NONE'
|
|
||||||
|
|
||||||
for i in range(16):
|
|
||||||
b = (1 << i)
|
|
||||||
if (b & 0xffff) & v:
|
|
||||||
n = StatusBits(b).name
|
|
||||||
if len(s) > 0:
|
|
||||||
s += ','
|
|
||||||
s += n
|
|
||||||
if not bits_only:
|
|
||||||
s += '*'
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def all_errors():
|
|
||||||
"""Bit mask of all error states
|
|
||||||
|
|
||||||
:returns: Error flags
|
|
||||||
:rtype: number
|
|
||||||
"""
|
|
||||||
return StatusBits.LOCAL_ERROR | StatusBits.NODE_ERROR | StatusBits.NETWORK_ERROR | StatusBits.UNKNOWN_ERROR
|
|
||||||
|
|
||||||
|
|
||||||
def is_error_status(v):
|
|
||||||
"""Check if value is an error state
|
|
||||||
|
|
||||||
:param v: Status bit field
|
|
||||||
:type v: number
|
|
||||||
:returns: True if error
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return bool(v & all_errors())
|
|
||||||
|
|
||||||
|
|
||||||
def dead():
|
|
||||||
"""Bit mask defining whether a transaction is still likely to be processed on the network.
|
|
||||||
|
|
||||||
:returns: Bit mask
|
|
||||||
:rtype: number
|
|
||||||
"""
|
|
||||||
return StatusBits.FINAL | StatusBits.OBSOLETE
|
|
||||||
|
|
||||||
|
|
||||||
def is_alive(v):
|
|
||||||
"""Check if transaction is still likely to be processed on the network.
|
|
||||||
|
|
||||||
The contingency of "likely" refers to the case a transaction has been obsoleted after sent to the network, but the network still confirms the obsoleted transaction. The return value of this method will not change as a result of this, BUT the state itself will (as the FINAL bit will be set).
|
|
||||||
|
|
||||||
:returns:
|
|
||||||
"""
|
|
||||||
return bool(v & dead() == 0)
|
|
||||||
@@ -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,19 +0,0 @@
|
|||||||
# external imports
|
|
||||||
import pytest
|
|
||||||
from eth_erc20 import ERC20
|
|
||||||
|
|
||||||
# TODO: missing dep fixture includes
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def foo_token_symbol(
|
|
||||||
default_chain_spec,
|
|
||||||
foo_token,
|
|
||||||
eth_rpc,
|
|
||||||
contract_roles,
|
|
||||||
):
|
|
||||||
|
|
||||||
c = ERC20(default_chain_spec)
|
|
||||||
o = c.symbol(foo_token, sender_address=contract_roles['CONTRACT_DEPLOYER'])
|
|
||||||
r = eth_rpc.do(o)
|
|
||||||
return c.parse_symbol(r)
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from chainlib.eth.constant import ZERO_ADDRESS
|
|||||||
from chainlib.eth.address import is_checksum_address
|
from chainlib.eth.address import is_checksum_address
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from cic_eth.api.admin import AdminApi
|
from cic_eth.api import AdminApi
|
||||||
from cic_eth.db.enum import LockEnum
|
from cic_eth.db.enum import LockEnum
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
@@ -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(
|
||||||
|
|||||||
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]
|
||||||
@@ -7,26 +7,14 @@ import tempfile
|
|||||||
import re
|
import re
|
||||||
import urllib
|
import urllib
|
||||||
import websocket
|
import websocket
|
||||||
import stat
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
# 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 (
|
||||||
@@ -39,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,
|
||||||
@@ -52,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,
|
||||||
)
|
)
|
||||||
@@ -64,14 +50,10 @@ 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()
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
config_dir = os.path.join('/usr/local/etc/cic-eth')
|
config_dir = os.path.join('/usr/local/etc/cic-eth')
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
@@ -79,12 +61,10 @@ 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')
|
||||||
argparser.add_argument('--aux-all', action='store_true', help='include tasks from all submodules from the aux module path')
|
|
||||||
argparser.add_argument('--aux', action='append', type=str, default=[], help='add single submodule from the aux module path')
|
|
||||||
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()
|
||||||
@@ -100,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'),
|
||||||
}
|
}
|
||||||
@@ -110,17 +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=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
|
||||||
|
|
||||||
|
# verify database connection with minimal sanity query
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
session.execute('select version_num from alembic_version')
|
||||||
|
session.close()
|
||||||
|
|
||||||
# set up celery
|
# set up celery
|
||||||
current_app = celery.Celery(__name__)
|
current_app = celery.Celery(__name__)
|
||||||
@@ -129,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':
|
||||||
@@ -162,96 +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')
|
|
||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
|
||||||
try:
|
|
||||||
registry = connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
|
||||||
except UnknownContractError as e:
|
|
||||||
logg.exception('Registry contract connection failed for {}: {}'.format(config.get('CIC_REGISTRY_ADDRESS'), e))
|
|
||||||
sys.exit(1)
|
|
||||||
logg.info('connected contract registry {}'.format(config.get('CIC_REGISTRY_ADDRESS')))
|
|
||||||
|
|
||||||
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
|
||||||
if trusted_addresses_src == None:
|
|
||||||
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
|
||||||
sys.exit(1)
|
|
||||||
trusted_addresses = trusted_addresses_src.split(',')
|
|
||||||
for address in trusted_addresses:
|
|
||||||
logg.info('using trusted address {}'.format(address))
|
|
||||||
|
|
||||||
connect_declarator(rpc, chain_spec, trusted_addresses)
|
|
||||||
connect_token_registry(rpc, chain_spec)
|
|
||||||
|
|
||||||
# detect aux
|
|
||||||
# TODO: move to separate file
|
|
||||||
#aux_dir = os.path.join(script_dir, '..', '..', 'aux')
|
|
||||||
aux = []
|
|
||||||
if args.aux_all:
|
|
||||||
if len(args.aux) > 0:
|
|
||||||
logg.warning('--aux-all is set so --aux will have no effect')
|
|
||||||
for p in sys.path:
|
|
||||||
logg.debug('checking for aux modules in {}'.format(p))
|
|
||||||
aux_dir = os.path.join(p, 'cic_eth_aux')
|
|
||||||
try:
|
|
||||||
d = os.listdir(aux_dir)
|
|
||||||
except FileNotFoundError:
|
|
||||||
logg.debug('no aux module found in {}'.format(aux_dir))
|
|
||||||
continue
|
|
||||||
for v in d:
|
|
||||||
if v[:1] == '.':
|
|
||||||
logg.debug('dotfile, skip {}'.format(v))
|
|
||||||
continue
|
|
||||||
aux_mod_path = os.path.join(aux_dir, v)
|
|
||||||
st = os.stat(aux_mod_path)
|
|
||||||
if not stat.S_ISDIR(st.st_mode):
|
|
||||||
logg.debug('not a dir, skip {}'.format(v))
|
|
||||||
continue
|
|
||||||
aux_mod_file = os.path.join(aux_dir, v,'__init__.py')
|
|
||||||
try:
|
|
||||||
st = os.stat(aux_mod_file)
|
|
||||||
except FileNotFoundError:
|
|
||||||
logg.debug('__init__.py not found, skip {}'.format(v))
|
|
||||||
continue
|
|
||||||
aux.append(v)
|
|
||||||
logg.debug('found module {} in {}'.format(v, aux_dir))
|
|
||||||
|
|
||||||
elif len(args.aux) > 0:
|
|
||||||
for p in sys.path:
|
|
||||||
v_found = None
|
|
||||||
for v in args.aux:
|
|
||||||
aux_dir = os.path.join(p, 'cic_eth_aux')
|
|
||||||
aux_mod_file = os.path.join(aux_dir, v, '__init__.py')
|
|
||||||
try:
|
|
||||||
st = os.stat(aux_mod_file)
|
|
||||||
v_found = v
|
|
||||||
except FileNotFoundError:
|
|
||||||
logg.debug('cannot find explicity requested aux module {} in path {}'.format(v, aux_dir))
|
|
||||||
continue
|
|
||||||
if v_found == None:
|
|
||||||
logg.critical('excplicity requested aux module {} not found in any path'.format(v))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
logg.info('aux module {} found in path {}'.format(v, aux_dir))
|
|
||||||
aux.append(v)
|
|
||||||
|
|
||||||
for v in aux:
|
|
||||||
mname = 'cic_eth_aux.' + v
|
|
||||||
mod = importlib.import_module(mname)
|
|
||||||
mod.aux_setup(rpc, config)
|
|
||||||
logg.info('loaded aux module {}'.format(mname))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argv = ['worker']
|
argv = ['worker']
|
||||||
@@ -275,19 +161,19 @@ def main():
|
|||||||
|
|
||||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
|
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
|
||||||
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')
|
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
|
||||||
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))
|
if trusted_addresses_src == None:
|
||||||
|
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
|
||||||
liveness.linux.set(rundir=config.get('CIC_RUN_DIR'))
|
sys.exit(1)
|
||||||
|
trusted_addresses = trusted_addresses_src.split(',')
|
||||||
|
for address in trusted_addresses:
|
||||||
|
logg.info('using trusted address {}'.format(address))
|
||||||
|
connect_declarator(rpc, chain_spec, trusted_addresses)
|
||||||
|
connect_token_registry(rpc, chain_spec)
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user