Compare commits

..

51 Commits

Author SHA1 Message Date
nolash
efa23797f3 Bump aux version 2021-07-08 11:48:50 +02:00
nolash
2c0f65ed73 Remove hardcoded value 2021-07-07 17:42:47 +02:00
nolash
3f6fbc1c61 Noop calc for non-demurrage token in aux 2021-07-07 14:17:50 +02:00
nolash
d26af206cb Add correct extras selector for contract migration cic-eth include 2021-07-07 13:52:19 +02:00
nolash
36085f218f Add necessary services requirements 2021-07-07 11:59:51 +02:00
nolash
d1d680c9aa Add aux-all to tasker in local cluster 2021-07-07 10:32:01 +02:00
nolash
abd4024322 Bump version 2021-07-07 09:59:20 +02:00
nolash
644b9ec4ec Merge remote-tracking branch 'origin/master' into lash/demurrage-task 2021-07-07 09:58:57 +02:00
Louis Holbrook
5f6c57647f Merge branch 'lash/explicit-token-type' into 'master'
Add explicit token type toggle in contract migration

See merge request grassrootseconomics/cic-internal-integration!216
2021-07-07 07:56:45 +00:00
Louis Holbrook
ed029a936c Add explicit token type toggle in contract migration 2021-07-07 07:56:45 +00:00
nolash
bc52e46620 Merge remote-tracking branch 'origin/master' into lash/demurrage-task 2021-07-07 09:47:09 +02:00
Louis Holbrook
c559bb2fee Merge branch 'lash/free-api' into 'master'
Minimal dependencies in cic-eth api setup

Closes #75

See merge request grassrootseconomics/cic-internal-integration!210
2021-07-07 07:45:21 +00:00
Louis Holbrook
9b79034ed3 Minimal dependencies in cic-eth api setup 2021-07-07 07:45:21 +00:00
nolash
ba729a0a54 Properly fix mock path in cic-eth test 2021-07-07 09:32:52 +02:00
nolash
cb4927edc9 Correct mocks path in cic tests, faulty contract-migration build 2021-07-07 09:24:40 +02:00
b7d5c6799f Merge branch 'bvander/data-seeding-updates' into 'master'
data-seeding image updates and tweak meta import

See merge request grassrootseconomics/cic-internal-integration!212
2021-07-05 17:14:47 +00:00
eef8bb2cf7 data-seeding image updates and tweak meta import 2021-07-05 17:14:46 +00:00
cf96fee430 Merge branch 'bvander/contract-migration-caching' into 'master'
refactor contract-migration and add buildkit caching

See merge request grassrootseconomics/cic-internal-integration!211
2021-07-05 17:13:27 +00:00
9740963431 refactor contract-migration and add buildkit caching 2021-07-05 17:13:26 +00:00
nolash
185458e320 Merge remote-tracking branch 'origin/master' into lash/demurrage-task 2021-07-05 13:05:02 +02:00
Louis Holbrook
a3c4932488 Merge branch 'lash/new-sarafu-token' into 'master'
Add flavors of demurrage token to dev deployment

See merge request grassrootseconomics/cic-internal-integration!181
2021-07-05 10:58:08 +00:00
Louis Holbrook
aa667951be Add flavors of demurrage token to dev deployment 2021-07-05 10:58:08 +00:00
nolash
d9214ddd62 Bump cic-eth dep to future build 2021-07-05 11:59:52 +02:00
nolash
b5d6d80d8e Add demurrage token task api 2021-07-05 11:58:07 +02:00
nolash
2f09ac9110 Change aux module to unpopulated base module name 2021-07-05 11:28:27 +02:00
nolash
bc8851ad06 Merge branch 'lash/new-sarafu-token' into lash/demurrage-task 2021-07-05 10:41:35 +02:00
nolash
46840040a0 Remove dev dockerfile 2021-07-05 10:41:19 +02:00
nolash
4e18060589 Remove dev dockerfile 2021-07-05 10:40:32 +02:00
nolash
08a77f9818 Move cic-eth token fixture to package, add demurrage token calc task 2021-07-05 10:29:33 +02:00
Louis Holbrook
c2459cfd65 Merge branch 'lash/traffic' into 'master'
data-seeding: Rehabilitate traffic scripts

See merge request grassrootseconomics/cic-internal-integration!213
2021-07-04 17:19:21 +00:00
Louis Holbrook
e7102ff02d data-seeding: Rehabilitate traffic scripts 2021-07-04 17:19:21 +00:00
nolash
5fab939270 Upgrade erc20 demurrage token deploy, explicit multi mode flag 2021-07-04 13:53:03 +02:00
nolash
51109db487 Merge branch 'lash/new-sarafu-token' into lash/demurrage-task 2021-07-04 13:26:22 +02:00
nolash
f44e2c8b45 Move registry initialization before aux 2021-07-04 13:21:07 +02:00
a942c785f6 Merge branch 'philip/improving-import-scripts-logging' into 'master'
Improves import scripts logging

Closes #74

See merge request grassrootseconomics/cic-internal-integration!207
2021-07-03 16:55:51 +00:00
70704b09ec Improves import scripts logging 2021-07-03 16:55:51 +00:00
nolash
40a7eec6ad Add aux requirement for demurrage task 2021-07-02 21:32:48 +02:00
nolash
6a75ce37c4 Add demurrage token amount adjust task 2021-07-02 21:25:47 +02:00
nolash
9b2f2ab0b1 Reduce profanity 2021-07-01 11:05:31 +02:00
nolash
98a38b7117 Upgrade cic-base for contract-migration 2021-06-30 22:51:23 +02:00
nolash
a2ca61355d Merge remote-tracking branch 'origin/master' into lash/new-sarafu-token 2021-06-30 22:01:07 +02:00
nolash
b0638f2262 Rename env var for token symbol in contract migration 2021-06-30 21:16:06 +02:00
Louis Holbrook
a075c55957 Merge branch 'lash/allowance' into 'master'
Add allowance check and transferFrom task

See merge request grassrootseconomics/cic-internal-integration!203
2021-06-30 18:15:40 +00:00
Louis Holbrook
6464f651ec Add allowance check and transferFrom task 2021-06-30 18:15:40 +00:00
Louis Holbrook
5145282946 Merge branch 'lash/faucet-verif' into 'master'
Consider faucet amount in verify balance

See merge request grassrootseconomics/cic-internal-integration!206
2021-06-30 18:14:53 +00:00
nolash
1e87f2ed31 Consider faucet amount in verify balance 2021-06-30 17:22:26 +02:00
nolash
ea4c68f311 Merge remote-tracking branch 'origin/master' into lash/new-sarafu-token 2021-06-30 16:47:36 +02:00
nolash
ba0dc9371e Add colors to run script for contract migrations 2021-06-10 07:16:59 +02:00
nolash
03ac6633a2 Merge branch 'master' into lash/new-sarafu-token 2021-06-09 15:00:02 +02:00
nolash
4dc8dff369 Remove foo 2021-06-05 17:13:14 +02:00
nolash
a74e69aeb3 Introduce dev dockerfile for contract migration enabling faster iteration in dev 2021-06-05 17:06:37 +02:00
82 changed files with 2117 additions and 600 deletions

View File

@@ -0,0 +1 @@
include *requirements.txt

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,31 +3,29 @@
APP_NAME: cic-eth
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
.cic_eth_changes_target:
.cic_eth_mr_changes_target:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
#changes:
#- $CONTEXT/$APP_NAME/**/*
changes:
- $CONTEXT/$APP_NAME/**/*
when: always
build-mr-cic-eth:
extends:
- .cic_eth_variables
- .cic_eth_changes_target
- .cic_eth_mr_changes_target
- .py_build_target_test
test-mr-cic-eth:
extends:
- .cic_eth_variables
- .cic_eth_changes_target
- .cic_eth_mr_changes_target
stage: test
image: $CI_REGISTRY_IMAGE/$APP_NAME-test:latest
image: $IMAGE_TAG_BASE
script:
- cd apps/$APP_NAME/
- pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests
needs: ["build-mr-cic-eth"]
build-push-cic-eth:
extends:
- .py_build_push
- .cic_eth_variables

View File

@@ -1,2 +1,2 @@
include *requirements.txt
include *requirements.txt config/test/*

View File

@@ -0,0 +1,5 @@
SQLAlchemy==1.3.20
cic-eth-registry~=0.5.6a1
hexathon~=0.0.1a7
chainqueue~=0.0.2b5
eth-erc20==0.0.10a2

View File

@@ -5,4 +5,3 @@
"""
from .api_task import Api
from .api_admin import AdminApi

View File

@@ -8,59 +8,19 @@ import logging
# external imports
import celery
from cic_eth_registry import CICRegistry
from chainlib.chain import ChainSpec
# local imports
from cic_eth.db.enum import LockEnum
from cic_eth.api.base import ApiBase
from cic_eth.enum import LockEnum
app = celery.current_app
logg = logging.getLogger(__name__)
class Api:
"""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,
)
class Api(ApiBase):
def default_token(self):
s_token = celery.signature(

View File

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

View File

@@ -1,158 +1 @@
# 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)
from cic_eth.enum import *

View File

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

View File

View File

@@ -37,7 +37,7 @@ def celery_includes():
'cic_eth.eth.account',
'cic_eth.callbacks.noop',
'cic_eth.callbacks.http',
'tests.mock.filter',
'cic_eth.pytest.mock.filter',
]

View File

@@ -13,7 +13,9 @@ logg = logging.getLogger(__name__)
@pytest.fixture(scope='session')
def load_config():
config_dir = os.path.join(root_dir, 'config/test')
config_dir = os.environ.get('CONFINI_DIR')
if config_dir == None:
config_dir = os.path.join(root_dir, 'config/test')
conf = confini.Config(config_dir, 'CICTEST')
conf.process()
logg.debug('config {}'.format(conf))

View File

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

View File

@@ -12,7 +12,7 @@ from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.address import is_checksum_address
# local imports
from cic_eth.api import AdminApi
from cic_eth.api.admin import AdminApi
from cic_eth.db.enum import LockEnum
logging.basicConfig(level=logging.WARNING)

View File

@@ -7,6 +7,8 @@ import tempfile
import re
import urllib
import websocket
import stat
import importlib
# external imports
import celery
@@ -68,6 +70,8 @@ from cic_eth.task import BaseTask
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join('/usr/local/etc/cic-eth')
argparser = argparse.ArgumentParser()
@@ -79,6 +83,8 @@ argparser.add_argument('--default-token-symbol', dest='default_token_symbol', ty
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
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('-vv', action='store_true', help='be more verbose')
args = argparser.parse_args()
@@ -109,6 +115,8 @@ if len(health_modules) != 0:
health_modules = health_modules.split(',')
logg.debug('health mods {}'.format(health_modules))
# connect to database
dsn = dsn_from_config(config)
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
@@ -167,6 +175,84 @@ Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
# 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():
argv = ['worker']
if args.vv:
@@ -189,24 +275,6 @@ def main():
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)
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
default_token = ERC20Token(chain_spec, rpc, BaseTask.default_token_address)

View File

@@ -12,10 +12,8 @@ import confini
import celery
# local imports
from cic_eth.api import (
Api,
AdminApi,
)
from cic_eth.api import Api
from cic_eth.api.admin import AdminApi
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()

View File

@@ -11,7 +11,7 @@ from chainlib.chain import ChainSpec
from chainlib.eth.connection import EthHTTPConnection
# local imports
from cic_eth.api.api_admin import AdminApi
from cic_eth.api.admin import AdminApi
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()

View File

@@ -12,7 +12,7 @@ from chainlib.chain import ChainSpec
from xdg.BaseDirectory import xdg_config_home
# local imports
from cic_eth.api import AdminApi
from cic_eth.api.admin import AdminApi
from cic_eth.db import dsn_from_config
from cic_eth.db.models.base import SessionBase

View File

@@ -19,7 +19,7 @@ from chainlib.eth.connection import EthHTTPConnection
from hexathon import add_0x
# local imports
from cic_eth.api import AdminApi
from cic_eth.api.admin import AdminApi
from cic_eth.db.enum import (
StatusEnum,
status_str,

View File

@@ -1,6 +1,5 @@
# import
import time
import requests
import logging
import uuid
@@ -76,7 +75,7 @@ class CriticalSQLAlchemyTask(CriticalTask):
class CriticalWeb3Task(CriticalTask):
autoretry_for = (
requests.exceptions.ConnectionError,
ConnectionError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
@@ -86,7 +85,7 @@ class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
autoretry_for = (
sqlalchemy.exc.DatabaseError,
sqlalchemy.exc.TimeoutError,
requests.exceptions.ConnectionError,
ConnectionError,
sqlalchemy.exc.ResourceClosedError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
@@ -102,7 +101,7 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
class CriticalWeb3AndSignerTask(CriticalTask):
autoretry_for = (
requests.exceptions.ConnectionError,
ConnectionError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5

View File

@@ -8,9 +8,9 @@ import semver
version = (
0,
11,
1,
'alpha.3',
12,
0,
'alpha.2',
)
version_object = semver.VersionInfo(

View File

@@ -1,6 +1,6 @@
FROM python:3.8.6-slim-buster as compile
WORKDIR /usr/src/cic-eth
WORKDIR /usr/src
RUN apt-get update && \
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
@@ -8,12 +8,21 @@ RUN apt-get update && \
#RUN python -m venv venv && . venv/bin/activate
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433'
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip install semver
# TODO use a packaging style that lets us copy requirments only ie. pip-tools
COPY cic-eth-aux/ ./cic-eth-aux/
WORKDIR /usr/src/cic-eth-aux/erc20-demurrage-token
RUN pip install --extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL .
WORKDIR /usr/src/cic-eth
COPY cic-eth/ .
RUN pip install $pip_extra_index_url_flag .
RUN pip install --extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL .[services]
# --- TEST IMAGE ---
FROM python:3.8.6-slim-buster as test
@@ -32,8 +41,12 @@ COPY --from=compile /usr/local/lib/python3.8/site-packages/ \
# COPY --from=compile /usr/src/cic-eth/ .
# RUN . venv/bin/activate
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
COPY cic-eth/test_requirements.txt .
RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
RUN pip install --extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL -r test_requirements.txt
COPY cic-eth .

View File

@@ -8,7 +8,7 @@ set -e
echo "!!! starting signer"
python /usr/local/bin/crypto-dev-daemon -c /usr/local/etc/crypto-dev-signer -vv 2> /tmp/signer.log &
echo "!!! starting tracker"
echo "!!! starting taskerd"
/usr/local/bin/cic-eth-taskerd $@
# thanks! https://docs.docker.com/config/containers/multi-service_container/

View File

@@ -1,25 +1,3 @@
cic-base==0.1.3a3+build.984b5cff
celery==4.4.7
crypto-dev-signer~=0.4.14b6
confini~=0.3.6rc3
cic-eth-registry~=0.5.6a1
redis==3.5.3
alembic==1.4.2
websockets==8.1
requests~=2.24.0
eth_accounts_index~=0.0.12a1
erc20-transfer-authorization~=0.3.2a1
uWSGI==2.0.19.1
chainlib~=0.0.5a1
semver==2.13.0
websocket-client==0.57.0
moolb~=0.1.1b2
eth-address-index~=0.1.2a1
chainlib-eth~=0.0.5a1
hexathon~=0.0.1a7
chainsyncer[sql]~=0.0.3a3
chainqueue~=0.0.2b5
sarafu-faucet~=0.0.4a1
erc20-faucet~=0.2.2a1
coincurve==15.0.0
potaahto~=0.0.1a2
pycryptodome==3.10.1

View File

@@ -0,0 +1,14 @@
chainsyncer[sql]~=0.0.3a3
chainqueue~=0.0.2b5
alembic==1.4.2
confini~=0.3.6rc4
redis==3.5.3
hexathon~=0.0.1a7
pycryptodome==3.10.1
liveness~=0.0.1a7
eth-address-index~=0.1.2a1
eth-accounts-index~=0.0.12a1
cic-eth-registry~=0.5.6a1
erc20-faucet~=0.2.2a1
sarafu-faucet~=0.0.4a1
moolb~=0.1.1b2

View File

@@ -39,22 +39,25 @@ packages =
cic_eth.callbacks
cic_eth.sync
cic_eth.check
# should be concealed behind extras "test" if possible (but its not unfortunately)
cic_eth.pytest
cic_eth.pytest.mock
scripts =
./scripts/migrate.py
[options.entry_points]
console_scripts =
# daemons
cic-eth-taskerd = cic_eth.runnable.daemons.tasker:main
cic-eth-trackerd = cic_eth.runnable.daemons.tracker:main
cic-eth-dispatcherd = cic_eth.runnable.daemons.dispatcher:main
cic-eth-retrierd = cic_eth.runnable.daemons.retry:main
cic-eth-taskerd = cic_eth.runnable.daemons.tasker:main [services]
cic-eth-trackerd = cic_eth.runnable.daemons.tracker:main [services]
cic-eth-dispatcherd = cic_eth.runnable.daemons.dispatcher:main [services]
cic-eth-retrierd = cic_eth.runnable.daemons.retry:main [services]
# tools
cic-eth-create = cic_eth.runnable.create:main
cic-eth-inspect = cic_eth.runnable.view:main
cic-eth-ctl = cic_eth.runnable.ctrl:main
cic-eth-info = cic_eth.runnable.info:main
cic-eth-create = cic_eth.runnable.create:main [tools]
cic-eth-inspect = cic_eth.runnable.view:main [tools]
cic-eth-ctl = cic_eth.runnable.ctrl:main [tools]
cic-eth-info = cic_eth.runnable.info:main [tools]
# TODO: Merge this with ctl when subcmds sorted to submodules
cic-eth-tag = cic_eth.runnable.tag:main
cic-eth-resend = cic_eth.runnable.resend:main
cic-eth-transfer = cic_eth.runnable.transfer:main
cic-eth-tag = cic_eth.runnable.tag:main [tools]
cic-eth-resend = cic_eth.runnable.resend:main [tools]
cic-eth-transfer = cic_eth.runnable.transfer:main [tools]

View File

@@ -11,6 +11,41 @@ while True:
requirements.append(l.rstrip())
f.close()
admin_requirements = []
f = open('admin_requirements.txt', 'r')
while True:
l = f.readline()
if l == '':
break
admin_requirements.append(l.rstrip())
f.close()
tools_requirements = []
f = open('tools_requirements.txt', 'r')
while True:
l = f.readline()
if l == '':
break
tools_requirements.append(l.rstrip())
f.close()
services_requirements = []
f = open('services_requirements.txt', 'r')
while True:
l = f.readline()
if l == '':
break
services_requirements.append(l.rstrip())
f.close()
setup(
install_requires=requirements
)
install_requires=requirements,
extras_require = {
'tools': tools_requirements,
'admin_api': admin_requirements,
'services': services_requirements,
}
)

View File

@@ -2,5 +2,8 @@ pytest==6.0.1
pytest-celery==0.0.0a1
pytest-mock==3.3.1
pytest-cov==2.10.1
pytest-redis==2.0.0
redis==3.5.3
eth-tester==0.5.0b3
py-evm==0.3.0a20
eth-erc20~=0.0.10a2

View File

@@ -5,6 +5,7 @@ import logging
import uuid
# external imports
import pytest
from eth_erc20 import ERC20
import redis
@@ -22,6 +23,7 @@ from cic_eth.pytest.fixtures_celery import *
from cic_eth.pytest.fixtures_database import *
from cic_eth.pytest.fixtures_role import *
from cic_eth.pytest.fixtures_contract import *
from cic_eth.pytest.fixtures_token import *
from chainlib.eth.pytest import *
from eth_contract_registry.pytest import *
from cic_eth_registry.pytest.fixtures_contracts import *
@@ -37,20 +39,6 @@ def api(
return Api(chain_str, queue=None, callback_param='foo')
@pytest.fixture(scope='function')
def foo_token_symbol(
default_chain_spec,
foo_token,
eth_rpc,
contract_roles,
):
c = ERC20(default_chain_spec)
o = c.symbol(foo_token, sender_address=contract_roles['CONTRACT_DEPLOYER'])
r = eth_rpc.do(o)
return c.parse_symbol(r)
@pytest.fixture(scope='function')
def default_token(
foo_token,

View File

@@ -40,7 +40,7 @@ from chainqueue.sql.query import (
)
# local imports
from cic_eth.api import AdminApi
from cic_eth.api.admin import AdminApi
from cic_eth.db.models.role import AccountRole
from cic_eth.db.enum import LockEnum
from cic_eth.error import InitializationError

View File

@@ -35,7 +35,7 @@ from eth_erc20 import ERC20
from cic_eth_registry import CICRegistry
# local imports
from cic_eth.api.api_admin import AdminApi
from cic_eth.api.admin import AdminApi
from cic_eth.eth.gas import cache_gas_data
from cic_eth.eth.erc20 import cache_transfer_data

View File

@@ -15,7 +15,7 @@ from cic_eth.db.models.nonce import (
)
# test imports
from tests.mock.filter import (
from cic_eth.pytest.mock.filter import (
block_filter,
tx_filter,
)
@@ -110,7 +110,7 @@ def test_list_tx(
logg.debug('r {}'.format(r))
# test the api
t = api.list(agent_roles['ALICE'], external_task='tests.mock.filter.filter')
t = api.list(agent_roles['ALICE'], external_task='cic_eth.pytest.mock.filter.filter')
r = t.get_leaf()
assert t.successful()

View File

@@ -0,0 +1,8 @@
crypto-dev-signer~=0.4.14b6
chainqueue~=0.0.2b5
confini~=0.3.6rc4
cic-eth-registry~=0.5.6a1
redis==3.5.3
hexathon~=0.0.1a7
pycryptodome==3.10.1
pyxdg==0.27

View File

@@ -127,6 +127,8 @@ def main():
argv.append('--loglevel=INFO')
argv.append('-Q')
argv.append(args.q)
argv.append('-n')
argv.append(args.q)
current_app.worker_main(argv)

View File

@@ -1,4 +1,4 @@
cic_base[full_graph]==0.1.3a3+build.984b5cff
cic-eth~=0.11.1a3
cic-eth~=0.12.0a1
cic-notify~=0.4.0a7
cic-types~=0.1.0a11

View File

@@ -1,7 +1,7 @@
.contract_migration_variables:
variables:
APP_NAME: contract-migration
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile_ci
.contract_migration_changes_target:
rules:

View File

@@ -1,28 +1,22 @@
# syntax = docker/dockerfile:1.2
FROM python:3.8.6-slim-buster as compile-image
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e
RUN apt-get update
RUN apt-get install -y --no-install-recommends git gcc g++ libpq-dev gawk jq telnet wget openssl iputils-ping gnupg socat bash procps make python2 cargo
WORKDIR /root
RUN touch /etc/apt/sources.list.d/ethereum.list
RUN echo 'deb http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' > /etc/apt/sources.list.d/ethereum.list
RUN echo 'deb-src http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' >> /etc/apt/sources.list.d/ethereum.list
RUN cat etc/apt/sources.list.d/ethereum.list
RUN cat /etc/apt/sources.list.d/ethereum.list
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 2A518C819BE37D2C2031944D1C52189C923F6CA9
RUN apt-get update
RUN apt-get install solc
RUN pip install --upgrade pip
#RUN apt-get install solc
WORKDIR /root
RUN mkdir -vp /usr/local/etc/cic
COPY contract-migration/nvm.sh .
ENV CONFINI_DIR /usr/local/etc/cic/
RUN mkdir -vp $CONFINI_DIR
ARG cic_config_commit=35c69ba75f00c8147150acf325565d5391cf25bf
ARG cic_config_commit=0abe0867f18077907c7023bf0ef5e466a3984dd8
ARG cic_config_url=https://gitlab.com/grassrootseconomics/cic-config.git/
RUN echo Install confini schema files && \
git clone --depth 1 $cic_config_url cic-config && \
@@ -31,70 +25,18 @@ RUN echo Install confini schema files && \
git checkout $cic_config_commit && \
cp -v *.ini $CONFINI_DIR
# Install nvm with node and npm
# https://stackoverflow.com/questions/25899912/how-to-install-nvm-in-docker
ENV NVM_DIR /root/.nvm
ENV NODE_VERSION 15.3.0
ENV BANCOR_NODE_VERSION 10.16.0
RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash \
&& . $NVM_DIR/nvm.sh \
&& nvm install $NODE_VERSION \
&& nvm alias default $NODE_VERSION \
&& nvm use $NODE_VERSION
# && chown -R root:root "$NVM_DIR/versions/node/v$NODE_VERSION"
ENV NODE_PATH $NVM_DIR/versions/node//v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node//v$NODE_VERSION/bin:$PATH
#RUN useradd --create-home grassroots
# WORKDIR /home/grassroots
# USER grassroots
COPY contract-migration/requirements.txt .
ARG pip_extra_args=""
ARG pip_index_url=https://pypi.org/simple
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
RUN pip install --index-url https://pypi.org/simple \
--extra-index-url $pip_extra_index_url -r requirements.txt
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
ARG pip_trusted_host=pypi.org
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
pip install --index-url https://pypi.org/simple \
--force-reinstall \
--trusted-host $pip_trusted_host \
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
-r requirements.txt
# -------------- begin runtime container ----------------
FROM python:3.8.6-slim-buster as runtime-image
RUN apt-get update
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
RUN apt-get install -y jq bash iputils-ping socat
COPY --from=compile-image /usr/local/bin/ /usr/local/bin/
COPY --from=compile-image /usr/local/etc/cic/ /usr/local/etc/cic/
COPY --from=compile-image /usr/local/lib/python3.8/site-packages/ \
/usr/local/lib/python3.8/site-packages/
ENV EXTRA_INDEX_URL https://pip.grassrootseconomics.net:8433
# RUN useradd -u 1001 --create-home grassroots
# RUN adduser grassroots sudo && \
# echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# WORKDIR /home/grassroots
COPY contract-migration/testdata/pgp testdata/pgp
COPY contract-migration/sarafu_declaration.json sarafu_declaration.json
COPY contract-migration/keystore keystore
COPY contract-migration/envlist .
# A shared output dir for environment configs
RUN mkdir -p /tmp/cic/config
# RUN chown grassroots:grassroots /tmp/cic/config
RUN chmod a+rwx /tmp/cic/config
COPY contract-migration/*.sh ./
# RUN chown grassroots:grassroots -R .
RUN chmod gu+x *.sh
# we copied these from the root build container.
# this is dumb though...I guess the compile image should have the same user
# RUN chown grassroots:grassroots -R /usr/local/lib/python3.8/site-packages/
# USER grassroots
ENTRYPOINT [ ]
COPY contract-migration/ .
RUN chmod +x *.sh

View File

@@ -0,0 +1,42 @@
# syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e
WORKDIR /root
# solc install which we needed for bancor. Leaving as an artfact of HOW to do it.
#RUN touch /etc/apt/sources.list.d/ethereum.list
#RUN echo 'deb http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' > /etc/apt/sources.list.d/ethereum.list
#RUN echo 'deb-src http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' >> /etc/apt/sources.list.d/ethereum.list
#RUN cat /etc/apt/sources.list.d/ethereum.list
#RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 2A518C819BE37D2C2031944D1C52189C923F6CA9
#RUN apt-get install solc
RUN mkdir -vp /usr/local/etc/cic
ENV CONFINI_DIR /usr/local/etc/cic/
RUN mkdir -vp $CONFINI_DIR
ARG cic_config_commit=0abe0867f18077907c7023bf0ef5e466a3984dd8
ARG cic_config_url=https://gitlab.com/grassrootseconomics/cic-config.git/
RUN echo Install confini schema files && \
git clone --depth 1 $cic_config_url cic-config && \
cd cic-config && \
git fetch --depth 1 origin $cic_config_commit && \
git checkout $cic_config_commit && \
cp -v *.ini $CONFINI_DIR
COPY contract-migration/requirements.txt .
ARG pip_index_url=https://pypi.org/simple
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
ARG pip_trusted_host=pypi.org
RUN pip install --index-url https://pypi.org/simple \
--force-reinstall \
--trusted-host $pip_trusted_host \
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
-r requirements.txt
COPY contract-migration/ .
RUN chmod +x *.sh

View File

@@ -0,0 +1,3 @@
#eth-contract-registry==0.5.5a3
#erc20-demurrage-token==0.0.2a3
#eth-address-index==0.1.1a12

View File

@@ -1,4 +1,5 @@
cic-base[full_graph]==0.1.2b15
sarafu-faucet==0.0.3a3
sarafu-token==0.0.1a8
cic-eth==0.11.0b16
cic_base[full]==0.1.3a4+build.ce68c833
sarafu-faucet~=0.0.4a1
cic-eth[tools]==0.12.0a2
eth-erc20~=0.0.10a3
erc20-demurrage-token==0.0.2a3

View File

@@ -2,8 +2,28 @@
set -a
CIC_CHAIN_SPEC=${CIC_CHAIN_SPEC:-evm:bloxberg:8995}
default_token=giftable_erc20_token
CIC_DEFAULT_TOKEN_SYMBOL=${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
TOKEN_SYMBOL=${CIC_DEFAULT_TOKEN_SYMBOL}
TOKEN_NAME=${TOKEN_NAME:-$TOKEN_SYMBOL}
TOKEN_TYPE=${TOKEN_TYPE:-$default_token}
if [ $TOKEN_TYPE == 'default' ]; then
>&2 echo resolving "default" token to $default_token
TOKEN_TYPE=$default_token
fi
cat <<EOF
external token settings:
token_type: $TOKEN_TYPE
token_symbol: $TOKEN_SYMBOL
token_name: $TOKEN_NAME
token_decimals: $TOKEN_DECIMALS
token_demurrage: $TOKEN_DEMURRAGE_LEVEL
token_redistribution_period: $TOKEN_REDISTRIBUTION_PERIOD
token_supply_limit: $TOKEN_SUPPLY_LIMIT
EOF
CIC_CHAIN_SPEC=${CIC_CHAIN_SPEC:-evm:bloxberg:8995}
TOKEN_SYMBOL=${TOKEN_SYMBOL:-GFT}
DEV_ETH_ACCOUNT_RESERVE_MINTER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_RESERVE_AMOUNT=${DEV_ETH_RESERVE_AMOUNT:-""10000000000000000000000000000000000}
@@ -19,11 +39,6 @@ if [ ! -z $DEV_ETH_GAS_PRICE ]; then
>&2 echo using static gas price $DEV_ETH_GAS_PRICE
fi
if [[ $CIC_DEFAULT_TOKEN_SYMBOL != 'GFT' && $CIC_DEFAULT_TOKEN_SYMBOL != 'SRF' ]]; then
>&2 echo CIC_DEFAULT_TOKEN_SYMBOL must be one of [GFT,SRF], but was $CIC_DEFAULT_TOKEN_SYMBOL
exit 1
fi
echo "environment:"
printenv
echo \n
@@ -60,17 +75,23 @@ if [[ -n "${ETH_PROVIDER}" ]]; then
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
fi
if [ $CIC_DEFAULT_TOKEN_SYMBOL == 'GFT' ]; then
>&2 echo "deploying 'giftable token'"
DEV_RESERVE_ADDRESS=`giftable-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Giftable Token" --symbol "GFT" --decimals 6 -vv`
if [ $TOKEN_TYPE == $default_token ]; then
>&2 echo deploying default token $TOKEN_TYPE
DEV_RESERVE_ADDRESS=`giftable-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -ww --name $TOKEN_NAME --symbol $TOKEN_SYMBOL --decimals 6 -vv`
elif [ $TOKEN_TYPE == 'erc20_demurrage_token' ]; then
>&2 echo deploying token $TOKEN_TYPE
if [ -z $TOKEN_SINK_ADDRESS ]; then
if [ ! -z $TOKEN_REDISTRIBUTION_PERIOD ]; then
>&2 echo -e "\033[;93mtoken sink address not set, so redistribution will be BURNED\033[;39m"
fi
fi
DEV_RESERVE_ADDRESS=`erc20-demurrage-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -ww`
else
>&2 echo "deploying 'sarafu' token'"
DEV_RESERVE_ADDRESS=`sarafu-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Sarafu" --decimals 6 -vv SRF $DEV_SARAFU_DEMURRAGE_LEVEL`
>&2 echo unknown token type $TOKEN_TYPE
exit 1
fi
giftable-token-gift $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w -a $DEV_RESERVE_ADDRESS $DEV_RESERVE_AMOUNT
#BANCOR_REGISTRY_ADDRESS=`cic-bancor-deploy $gas_price_arg --bancor-dir /usr/local/share/cic/bancor -z $DEV_ETH_RESERVE_ADDRESS -p $ETH_PROVIDER -o $DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER`
>&2 echo "deploy account index contract"
DEV_ACCOUNT_INDEX_ADDRESS=`eth-accounts-index-deploy $gas_price_arg -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -vv -w`
>&2 echo "add deployer address as account index writer"

View File

@@ -2,13 +2,22 @@
if [[ $((RUN_MASK & 1)) -eq 1 ]]
then
>&2 echo -e "\033[;96mRUNNING\033[;39m RUN_MASK 1 - contract deployment"
./reset.sh
if [ $? -ne "0" ]; then
exit 1;
>&2 echo -e "\033[;31mFAILED\033[;39m RUN_MASK 1 - contract deployment"
exit 1;
fi
>&2 echo -e "\033[;32mSUCCEEDED\033[;39m RUN_MASK 1 - contract deployment"
fi
if [[ $((RUN_MASK & 2)) -eq 2 ]]
then
>&2 echo -e "\033[;96mRUNNING\033[;39m RUN_MASK 2 - custodial service initialization"
./seed_cic_eth.sh
if [ $? -ne "0" ]; then
>&2 echo -e "\033[;31mFAILED\033[;39m RUN_MASK 2 - custodial service initialization"
exit 1;
fi
>&2 echo -e "\033[;32mSUCCEEDED\033[;39m RUN_MASK 2 - custodial service initialization"
fi

View File

@@ -14,6 +14,7 @@ DEV_DATABASE_NAME_CIC_ETH=${DEV_DATABASE_NAME_CIC_ETH:-"cic-eth"}
CIC_DATA_DIR=${CIC_DATA_DIR:-/tmp/cic}
ETH_PASSPHRASE=''
CIC_DEFAULT_TOKEN_SYMBOL=${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
TOKEN_SYMBOL=$CIC_DEFAULT_TOKEN_SYMBOL
if [[ $CIC_DEFAULT_TOKEN_SYMBOL != 'GFT' && $CIC_DEFAULT_TOKEN_SYMBOL != 'SRF' ]]; then
>&2 echo CIC_DEFAULT_TOKEN_SYMBOL must be one of [GFT,SRF], but was $CIC_DEFAULT_TOKEN_SYMBOL
exit 1
@@ -98,8 +99,8 @@ echo DEV_ETH_SARAFU_TOKEN_ADDRESS=$DEV_ETH_RESERVE_ADDRESS >> $env_out_file
export DEV_ETH_SARAFU_TOKEN_ADDRESS=$DEV_ETH_RESERVE_ADDRESS
# Transfer tokens to gifter address
>&2 echo "transfer sarafu tokens to token gifter address"
>&2 erc20-transfer -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER --token-address $DEV_RESERVE_ADDRESS -w $debug $DEV_ETH_ACCOUNT_SARAFU_GIFTER ${token_amount:0:-1}
>&2 echo "transfer tokens to token gifter address"
>&2 erc20-transfer -y $keystore_file -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER --gas-limit 100000 --token-address $DEV_RESERVE_ADDRESS -w $debug $DEV_ETH_ACCOUNT_SARAFU_GIFTER ${token_amount:0:-1}
#echo -n 0 > $init_level_file

View File

@@ -1,7 +1,7 @@
.data_seeding_variables:
variables:
APP_NAME: data-seeding
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile_ci
.data_seeding_changes_target:
rules:

View File

@@ -10,7 +10,7 @@ import redis
import celery
from cic_eth_registry.registry import CICRegistry
from chainsyncer.backend.memory import MemBackend
from chainsyncer.driver import HeadSyncer
from chainsyncer.driver.head import HeadSyncer
from chainlib.eth.connection import EthHTTPConnection
from chainlib.chain import ChainSpec
from chainlib.eth.gas import RPCGasOracle
@@ -24,6 +24,7 @@ from cic_base import (
rpc,
signer as signer_funcs,
)
from cic_base.eth.syncer import chain_interface
# local imports
#import common
@@ -120,7 +121,7 @@ def main():
'api_queue': config.get('_CELERY_QUEUE'),
}
syncer = HeadSyncer(syncer_backend, block_callback=handler.refresh)
syncer = HeadSyncer(syncer_backend, chain_interface, block_callback=handler.refresh)
syncer.add_filter(handler)
syncer.loop(1, conn)

View File

@@ -93,9 +93,9 @@ function importMeta(keystore) {
let files;
try {
err, files = fs.readdirSync(workDir);
} catch {
console.error('source directory not yet ready', workDir);
files = fs.readdirSync(workDir);
} catch (err) {
console.error('source directory not yet ready', workDir, 'reason: ', err);
setTimeout(importMeta, batchDelay, keystore);
return;
}

View File

@@ -1,28 +1,22 @@
# standard imports
import os
import sys
import logging
import argparse
import hashlib
import redis
import celery
import logging
import sys
import os
# external imports
import celery
import confini
from chainlib.eth.connection import EthHTTPConnection
import redis
from chainlib.chain import ChainSpec
from hexathon import (
strip_0x,
add_0x,
)
from chainlib.eth.address import to_checksum_address
from chainlib.eth.connection import EthHTTPConnection
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
from cic_types.models.person import Person
# local imports
from import_util import BalanceProcessor
from import_task import *
from import_task import ImportTask, MetadataTask
from import_util import BalanceProcessor, get_celery_worker_status
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -41,37 +35,41 @@ argparser.add_argument('--meta-port', dest='meta_port', type=int, help='metadata
argparser.add_argument('--redis-host', dest='redis_host', type=str, help='redis host to use for task submission')
argparser.add_argument('--redis-port', dest='redis_port', type=int, help='redis host to use for task submission')
argparser.add_argument('--redis-db', dest='redis_db', type=int, help='redis db to use for task submission and callback')
argparser.add_argument('--token-symbol', default='SRF', type=str, dest='token_symbol', help='Token symbol to use for trnsactions')
argparser.add_argument('--token-symbol', default='GFT', type=str, dest='token_symbol',
help='Token symbol to use for transactions')
argparser.add_argument('--head', action='store_true', help='start at current block height (overrides --offset)')
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='celery queue to submit transaction tasks to')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str,
help='environment prefix for variables to overwrite configuration')
argparser.add_argument('-q', type=str, default='cic-import-ussd', help='celery queue to submit transaction tasks to')
argparser.add_argument('--offset', type=int, default=0, help='block offset to start syncer from')
argparser.add_argument('-v', help='be verbose', action='store_true')
argparser.add_argument('-vv', help='be more verbose', action='store_true')
argparser.add_argument('user_dir', default='out', type=str, help='user export directory')
args = argparser.parse_args(sys.argv[1:])
if args.v == True:
if args.v:
logging.getLogger().setLevel(logging.INFO)
elif args.vv == True:
elif args.vv:
logging.getLogger().setLevel(logging.DEBUG)
config_dir = os.path.join(args.c)
os.makedirs(config_dir, 0o777, True)
config = confini.Config(config_dir, args.env_prefix)
config.process()
# override args
args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'ETH_PROVIDER': getattr(args, 'p'),
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
'REDIS_HOST': getattr(args, 'redis_host'),
'REDIS_PORT': getattr(args, 'redis_port'),
'REDIS_DB': getattr(args, 'redis_db'),
'META_HOST': getattr(args, 'meta_host'),
'META_PORT': getattr(args, 'meta_port'),
'KEYSTORE_FILE_PATH': getattr(args, 'y')
}
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'ETH_PROVIDER': getattr(args, 'p'),
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
'REDIS_HOST': getattr(args, 'redis_host'),
'REDIS_PORT': getattr(args, 'redis_port'),
'REDIS_DB': getattr(args, 'redis_db'),
'META_HOST': getattr(args, 'meta_host'),
'META_PORT': getattr(args, 'meta_port'),
'KEYSTORE_FILE_PATH': getattr(args, 'y')
}
config.dict_override(args_override, 'cli flag')
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
@@ -81,14 +79,19 @@ redis_host = config.get('REDIS_HOST')
redis_port = config.get('REDIS_PORT')
redis_db = config.get('REDIS_DB')
r = redis.Redis(redis_host, redis_port, redis_db)
celery_app = celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY_BROKER_URL'))
# create celery apps
celery_app = celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY_BROKER_URL'))
status = get_celery_worker_status(celery_app=celery_app)
signer_address = None
keystore = DictKeystore()
if args.y != None:
if args.y is not None:
logg.debug('loading keystore file {}'.format(args.y))
signer_address = keystore.import_keystore_file(args.y)
logg.debug('now have key for signer address {}'.format(signer_address))
# define signer
signer = EIP155Signer(keystore)
queue = args.q
@@ -103,7 +106,7 @@ chain_spec = ChainSpec.from_chain_str(chain_str)
old_chain_spec_str = args.old_chain_spec
old_chain_spec = ChainSpec.from_chain_str(old_chain_spec_str)
user_dir = args.user_dir # user_out_dir from import_users.py
user_dir = args.user_dir # user_out_dir from import_users.py
token_symbol = args.token_symbol
@@ -111,20 +114,22 @@ MetadataTask.meta_host = config.get('META_HOST')
MetadataTask.meta_port = config.get('META_PORT')
ImportTask.chain_spec = chain_spec
def main():
conn = EthHTTPConnection(config.get('ETH_PROVIDER'))
ImportTask.balance_processor = BalanceProcessor(conn, chain_spec, config.get('CIC_REGISTRY_ADDRESS'), signer_address, signer)
ImportTask.balance_processor = BalanceProcessor(conn, chain_spec, config.get('CIC_REGISTRY_ADDRESS'),
signer_address, signer)
ImportTask.balance_processor.init(token_symbol)
# TODO get decimals from token
balances = {}
f = open('{}/balances.csv'.format(user_dir, 'r'))
remove_zeros = 10**6
remove_zeros = 10 ** 6
i = 0
while True:
l = f.readline()
if l == None:
if l is None:
break
r = l.split(',')
try:
@@ -143,15 +148,23 @@ def main():
ImportTask.import_dir = user_dir
s = celery.signature(
'import_task.send_txs',
[
MetadataTask.balance_processor.nonce_offset,
],
queue='cic-import-ussd',
)
'import_task.send_txs',
[
MetadataTask.balance_processor.nonce_offset,
],
queue=queue,
)
s.apply_async()
argv = ['worker', '-Q', 'cic-import-ussd', '--loglevel=DEBUG']
argv = ['worker']
if args.vv:
argv.append('--loglevel=DEBUG')
elif args.v:
argv.append('--loglevel=INFO')
argv.append('-Q')
argv.append(args.q)
argv.append('-n')
argv.append(args.q)
celery_app.worker_main(argv)

View File

@@ -9,7 +9,7 @@ import celery
import confini
# local imports
from import_task import *
from import_util import get_celery_worker_status
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -39,9 +39,12 @@ elif args.vv:
config_dir = args.c
config = confini.Config(config_dir, os.environ.get('CONFINI_ENV_PREFIX'))
config.process()
config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
status = get_celery_worker_status(celery_app=celery_app)
db_configs = {
'database': config.get('DATABASE_NAME'),
@@ -61,7 +64,8 @@ def main():
(db_configs, phone_to_pins),
queue=args.q
)
s_import_pins.apply_async()
result = s_import_pins.apply_async()
logg.debug(f'TASK: {result.id}, STATUS: {result.status}')
if __name__ == '__main__':

View File

@@ -1,36 +1,33 @@
# standard imports
import os
import logging
import random
import urllib.parse
import urllib.error
import urllib.request
import json
import logging
import os
import random
import urllib.error
import urllib.parse
import urllib.request
# external imports
import celery
import psycopg2
from psycopg2 import extras
from hexathon import (
strip_0x,
add_0x,
)
from chainlib.eth.address import to_checksum_address
from chainlib.eth.tx import (
unpack,
raw,
)
from cic_types.processor import generate_metadata_pointer
unpack,
raw,
)
from cic_types.models.person import Person
from cic_types.processor import generate_metadata_pointer
from hexathon import (
strip_0x,
add_0x,
)
#logg = logging.getLogger().getChild(__name__)
logg = logging.getLogger()
celery_app = celery.current_app
class ImportTask(celery.Task):
balances = None
import_dir = 'out'
count = 0
@@ -38,16 +35,16 @@ class ImportTask(celery.Task):
balance_processor = None
max_retries = None
class MetadataTask(ImportTask):
class MetadataTask(ImportTask):
meta_host = None
meta_port = None
meta_path = ''
meta_ssl = False
autoretry_for = (
urllib.error.HTTPError,
OSError,
)
urllib.error.HTTPError,
OSError,
)
retry_jitter = True
retry_backoff = True
retry_backoff_max = 60
@@ -64,12 +61,12 @@ class MetadataTask(ImportTask):
def old_address_from_phone(base_path, phone):
pidx = generate_metadata_pointer(phone.encode('utf-8'), ':cic.phone')
phone_idx_path = os.path.join('{}/phone/{}/{}/{}'.format(
base_path,
pidx[:2],
pidx[2:4],
pidx,
)
)
base_path,
pidx[:2],
pidx[2:4],
pidx,
)
)
f = open(phone_idx_path, 'r')
old_address = f.read()
f.close()
@@ -97,11 +94,11 @@ def generate_metadata(self, address, phone):
logg.debug('address {}'.format(address))
old_address_upper = strip_0x(old_address).upper()
metadata_path = '{}/old/{}/{}/{}.json'.format(
self.import_dir,
old_address_upper[:2],
old_address_upper[2:4],
old_address_upper,
)
self.import_dir,
old_address_upper[:2],
old_address_upper[2:4],
old_address_upper,
)
f = open(metadata_path, 'r')
o = json.load(f)
@@ -116,12 +113,12 @@ def generate_metadata(self, address, phone):
new_address_clean = strip_0x(address)
filepath = os.path.join(
self.import_dir,
'new',
new_address_clean[:2].upper(),
new_address_clean[2:4].upper(),
new_address_clean.upper() + '.json',
)
self.import_dir,
'new',
new_address_clean[:2].upper(),
new_address_clean[2:4].upper(),
new_address_clean.upper() + '.json',
)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
o = u.serialize()
@@ -131,10 +128,10 @@ def generate_metadata(self, address, phone):
meta_key = generate_metadata_pointer(bytes.fromhex(new_address_clean), ':cic.person')
meta_filepath = os.path.join(
self.import_dir,
'meta',
'{}.json'.format(new_address_clean.upper()),
)
self.import_dir,
'meta',
'{}.json'.format(new_address_clean.upper()),
)
os.symlink(os.path.realpath(filepath), meta_filepath)
# write ussd data
@@ -180,10 +177,8 @@ def generate_metadata(self, address, phone):
@celery_app.task(bind=True, base=MetadataTask)
def opening_balance_tx(self, address, phone, serial):
old_address = old_address_from_phone(self.import_dir, phone)
k = to_checksum_address(strip_0x(old_address))
balance = self.balances[k]
logg.debug('found balance {} for address {} phone {}'.format(balance, old_address, phone))
@@ -196,39 +191,39 @@ def opening_balance_tx(self, address, phone, serial):
logg.debug('generated tx token value {} to {} tx hash {}'.format(decimal_balance, address, tx_hash_hex))
tx_path = os.path.join(
self.import_dir,
'txs',
strip_0x(tx_hash_hex),
)
self.import_dir,
'txs',
strip_0x(tx_hash_hex),
)
f = open(tx_path, 'w')
f.write(strip_0x(o))
f.close()
tx_nonce_path = os.path.join(
self.import_dir,
'txs',
'.' + str(tx['nonce']),
)
self.import_dir,
'txs',
'.' + str(tx['nonce']),
)
os.symlink(os.path.realpath(tx_path), tx_nonce_path)
return tx['hash']
@celery_app.task(bind=True, base=ImportTask, autoretry_for=(FileNotFoundError,), max_retries=None, default_retry_delay=0.1)
@celery_app.task(bind=True, base=ImportTask, autoretry_for=(FileNotFoundError,), max_retries=None,
default_retry_delay=0.1)
def send_txs(self, nonce):
if nonce == self.count + self.balance_processor.nonce_offset:
logg.info('reached nonce {} (offset {} + count {}) exiting'.format(nonce, self.balance_processor.nonce_offset, self.count))
logg.info('reached nonce {} (offset {} + count {}) exiting'.format(nonce, self.balance_processor.nonce_offset,
self.count))
return
logg.debug('attempt to open symlink for nonce {}'.format(nonce))
tx_nonce_path = os.path.join(
self.import_dir,
'txs',
'.' + str(nonce),
)
self.import_dir,
'txs',
'.' + str(nonce),
)
f = open(tx_nonce_path, 'r')
tx_signed_raw_hex = f.read()
f.close()
@@ -238,21 +233,20 @@ def send_txs(self, nonce):
o = raw(add_0x(tx_signed_raw_hex))
tx_hash_hex = self.balance_processor.conn.do(o)
logg.info('sent nonce {} tx hash {}'.format(nonce, tx_hash_hex)) #tx_signed_raw_hex))
logg.info('sent nonce {} tx hash {}'.format(nonce, tx_hash_hex)) # tx_signed_raw_hex))
nonce += 1
queue = self.request.delivery_info.get('routing_key')
s = celery.signature(
'import_task.send_txs',
[
nonce,
],
queue=queue,
)
'import_task.send_txs',
[
nonce,
],
queue=queue,
)
s.apply_async()
return nonce
@@ -310,4 +304,3 @@ def set_ussd_data(config: dict, ussd_data: dict):
# close connections
db_cursor.close()
db_conn.close()

View File

@@ -17,6 +17,9 @@ import redis
from chainlib.chain import ChainSpec
from cic_types.models.person import Person
# local imports
from import_util import get_celery_worker_status
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -28,11 +31,14 @@ argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='Chain spe
argparser.add_argument('--redis-host', dest='redis_host', type=str, help='redis host to use for task submission')
argparser.add_argument('--redis-port', dest='redis_port', type=int, help='redis host to use for task submission')
argparser.add_argument('--redis-db', dest='redis_db', type=int, help='redis db to use for task submission and callback')
argparser.add_argument('--batch-size', dest='batch_size', default=100, type=int, help='burst size of sending transactions to node') # batch size should be slightly below cumulative gas limit worth, eg 80000 gas txs with 8000000 limit is a bit less than 100 batch size
argparser.add_argument('--batch-size', dest='batch_size', default=100, type=int,
help='burst size of sending transactions to node') # batch size should be slightly below cumulative gas limit worth, eg 80000 gas txs with 8000000 limit is a bit less than 100 batch size
argparser.add_argument('--batch-delay', dest='batch_delay', default=3, type=int, help='seconds delay between batches')
argparser.add_argument('--timeout', default=60.0, type=float, help='Callback timeout')
argparser.add_argument('--ussd-host', dest='ussd_host', type=str, help="host to ussd app responsible for processing ussd requests.")
argparser.add_argument('--ussd-port', dest='ussd_port', type=str, help="port to ussd app responsible for processing ussd requests.")
argparser.add_argument('--ussd-host', dest='ussd_host', type=str,
help="host to ussd app responsible for processing ussd requests.")
argparser.add_argument('--ussd-port', dest='ussd_port', type=str,
help="port to ussd app responsible for processing ussd requests.")
argparser.add_argument('--ussd-no-ssl', dest='ussd_no_ssl', help='do not use ssl (careful)', action='store_true')
argparser.add_argument('-q', type=str, default='cic-eth', help='Task queue')
argparser.add_argument('-v', action='store_true', help='Be verbose')
@@ -49,13 +55,16 @@ config_dir = args.c
config = confini.Config(config_dir, os.environ.get('CONFINI_ENV_PREFIX'))
config.process()
args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'REDIS_HOST': getattr(args, 'redis_host'),
'REDIS_PORT': getattr(args, 'redis_port'),
'REDIS_DB': getattr(args, 'redis_db'),
}
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'REDIS_HOST': getattr(args, 'redis_host'),
'REDIS_PORT': getattr(args, 'redis_port'),
'REDIS_DB': getattr(args, 'redis_db'),
}
config.dict_override(args_override, 'cli')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
get_celery_worker_status(celery_app=celery_app)
redis_host = config.get('REDIS_HOST')
redis_port = config.get('REDIS_PORT')
@@ -65,22 +74,22 @@ r = redis.Redis(redis_host, redis_port, redis_db)
ps = r.pubsub()
user_new_dir = os.path.join(args.user_dir, 'new')
os.makedirs(user_new_dir)
os.makedirs(user_new_dir, exist_ok=True)
ussd_data_dir = os.path.join(args.user_dir, 'ussd')
os.makedirs(ussd_data_dir)
os.makedirs(ussd_data_dir, exist_ok=True)
preferences_dir = os.path.join(args.user_dir, 'preferences')
os.makedirs(os.path.join(preferences_dir, 'meta'))
os.makedirs(os.path.join(preferences_dir, 'meta'), exist_ok=True)
meta_dir = os.path.join(args.user_dir, 'meta')
os.makedirs(meta_dir)
os.makedirs(meta_dir, exist_ok=True)
user_old_dir = os.path.join(args.user_dir, 'old')
os.stat(user_old_dir)
txs_dir = os.path.join(args.user_dir, 'txs')
os.makedirs(txs_dir)
os.makedirs(txs_dir, exist_ok=True)
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
chain_str = str(chain_spec)
@@ -95,6 +104,7 @@ if ussd_no_ssl is True:
else:
ussd_ssl = True
def build_ussd_request(phone, host, port, service_code, username, password, ssl=False):
url = 'http'
if ssl:
@@ -109,13 +119,13 @@ def build_ussd_request(phone, host, port, service_code, username, password, ssl=
session = uuid.uuid4().hex
data = {
'sessionId': session,
'serviceCode': service_code,
'phoneNumber': phone,
'text': service_code,
}
'sessionId': session,
'serviceCode': service_code,
'phoneNumber': phone,
'text': service_code,
}
req = urllib.request.Request(url)
req.method=('POST')
req.method = 'POST'
data_str = urlencode(data)
data_bytes = data_str.encode('utf-8')
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
@@ -150,7 +160,7 @@ if __name__ == '__main__':
j = 0
for x in os.walk(user_old_dir):
for y in x[2]:
if y[len(y)-5:] != '.json':
if y[len(y) - 5:] != '.json':
continue
# handle json containing person object
filepath = os.path.join(x[0], y)
@@ -164,35 +174,35 @@ if __name__ == '__main__':
f.close()
u = Person.deserialize(o)
new_address = register_ussd(i, u)
register_ussd(i, u)
phone_object = phonenumbers.parse(u.tel)
phone = phonenumbers.format_number(phone_object, phonenumbers.PhoneNumberFormat.E164)
s_phone = celery.signature(
'import_task.resolve_phone',
[
phone,
],
queue='cic-import-ussd',
)
'import_task.resolve_phone',
[
phone,
],
queue='cic-import-ussd',
)
s_meta = celery.signature(
'import_task.generate_metadata',
[
phone,
],
queue='cic-import-ussd',
)
'import_task.generate_metadata',
[
phone,
],
queue='cic-import-ussd',
)
s_balance = celery.signature(
'import_task.opening_balance_tx',
[
phone,
i,
],
queue='cic-import-ussd',
)
'import_task.opening_balance_tx',
[
phone,
i,
],
queue='cic-import-ussd',
)
s_meta.link(s_balance)
s_phone.link(s_meta)
@@ -206,4 +216,3 @@ if __name__ == '__main__':
if j == batch_size:
time.sleep(batch_delay)
j = 0

View File

@@ -2,15 +2,16 @@
import logging
# external imports
from eth_contract_registry import Registry
from eth_token_index import TokenUniqueSymbolIndex
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.nonce import OverrideNonceOracle
from eth_erc20 import ERC20
from chainlib.eth.tx import (
count,
TxFormat,
)
count,
TxFormat,
)
from celery import Celery
from eth_contract_registry import Registry
from eth_erc20 import ERC20
from eth_token_index import TokenUniqueSymbolIndex
logg = logging.getLogger().getChild(__name__)
@@ -18,10 +19,9 @@ logg = logging.getLogger().getChild(__name__)
class BalanceProcessor:
def __init__(self, conn, chain_spec, registry_address, signer_address, signer):
self.chain_spec = chain_spec
self.conn = conn
#self.signer_address = signer_address
# self.signer_address = signer_address
self.registry_address = registry_address
self.token_index_address = None
@@ -35,7 +35,6 @@ class BalanceProcessor:
self.gas_oracle = OverrideGasOracle(conn=conn, limit=8000000)
self.value_multiplier = 1
def init(self, token_symbol):
# Get Token registry address
@@ -57,16 +56,25 @@ class BalanceProcessor:
n = tx_factory.parse_decimals(r)
self.value_multiplier = 10 ** n
def get_rpc_tx(self, recipient, value, i):
logg.debug('initiating nonce offset {} for recipient {}'.format(self.nonce_offset + i, recipient))
nonce_oracle = OverrideNonceOracle(self.signer_address, self.nonce_offset + i)
tx_factory = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
return tx_factory.transfer(self.token_address, self.signer_address, recipient, value, tx_format=TxFormat.RLP_SIGNED)
#(tx_hash_hex, o) = tx_factory.transfer(self.token_address, self.signer_address, recipient, value)
#self.conn.do(o)
#return tx_hash_hex
return tx_factory.transfer(self.token_address, self.signer_address, recipient, value,
tx_format=TxFormat.RLP_SIGNED)
# (tx_hash_hex, o) = tx_factory.transfer(self.token_address, self.signer_address, recipient, value)
# self.conn.do(o)
# return tx_hash_hex
def get_decimal_amount(self, value):
return value * self.value_multiplier
def get_celery_worker_status(celery_app: Celery):
inspector = celery_app.control.inspect()
availability = inspector.ping()
status = {
'availability': availability,
}
logg.debug(f'RUNNING WITH STATUS: {status}')
return status

View File

@@ -1,5 +1,5 @@
[pgp]
exports_dir = ../contract-migration/testdata/pgp
exports_dir = testdata/pgp
private_key_file = privatekeys_meta.asc
public_key_file = publickeys_meta.asc
passphrase = merman

View File

@@ -1,20 +1,23 @@
# syntax = docker/dockerfile:1.2
#FROM python:3.8.6-slim-buster as compile-image
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-5ab8bf45
WORKDIR /root
RUN mkdir -vp /usr/local/etc/cic
COPY data-seeding/package.json \
data-seeding/package-lock.json \
.
RUN npm install
RUN --mount=type=cache,mode=0755,target=/root/node_modules npm install
COPY data-seeding/requirements.txt .
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 --extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL -r requirements.txt
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip pip install \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL -r requirements.txt
COPY data-seeding/ .

View File

@@ -0,0 +1,24 @@
# syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-5ab8bf45
WORKDIR /root
RUN mkdir -vp /usr/local/etc/cic
COPY data-seeding/package.json \
data-seeding/package-lock.json \
.
RUN npm install
COPY data-seeding/requirements.txt .
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 \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL -r requirements.txt
COPY data-seeding/ .
ENTRYPOINT [ ]

View File

@@ -1,5 +1,4 @@
cic_base[full_graph]==0.1.3a3+build.984b5cff
sarafu-faucet==0.0.4a1
cic-eth==0.11.1a1
cic-eth[tools]==0.12.0a1
cic-types==0.1.0a13
crypto-dev-signer==0.4.14b6

View File

@@ -0,0 +1,83 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=zMNq
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
xgjhMwawpFV3APdc0dOIvUdZAFQ=
=BKz+
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,83 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=qmcx
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
n0OSM7kpK5v6VPdtByaN6w==
=EmWr
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,83 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=B12U
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
32d1dwRCBUARSk9jwmwsycaIC9eupit1mLY=
=vg+T
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,241 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
1VrzRbOS5ymseDJcjejtMxuCOuSTN9R5KuQ=
=VqO+
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -0,0 +1,114 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
7qTmw0n71OJB0zWJVnQd8yPzx5ARKgWKQ+IVfzIqBezm2EVHqtO9+du7slEJD0xk
Du/O9JkCZt4nwB5Nz2uKxzqG7Th84QolJ7OYDbZlfMfCPj2j4jGhBjULtcYWC3XF
lL+6gIQwtfIEkSjSG1JPt9Zjeh8urPzJvkQZ68VDjAXDgFxZBLHGat42cAHJHgRF
OGlbDl1mVgglRKrszCqQF48cFtS+7AKquIR8jROXP9ZySJZ8qCxQ5xDv1y7Ql/K2
eZJKyVCoW/7MrJsyzGLtuBrSPTek9R/ir8qyqvPNMW/Oit9i47lRXfPxNuc4yWkw
3o+O3I6Y5iEWoGVUwgrGV4hEaaIYJpnIhMuzcZ5MExRiZwp7oHHPJEBUzXWAA6/l
I1K5w2V1x7ppTQDBMgI6p4abhbFn/z2h+SMxABEBAAG0HEFsaWNlIENyeXB0IDxh
bGljZUBhY21lLm9yZz6JAdQEEwEIAD4WIQSyyqioEFbhGRV+rnhK64QdamhpJwUC
X8DRGQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBK64Qdamhp
J3DyDACqCHGZpbWaRtcC9A7REd8qNBvcVZsvd8kUcEqSv0dCXTEnfSIE99+qoenA
rMdNowrLUfQc+XKBjmWLc69u2C3Hhhw5bEu0QIojIkLKttgPWjeIpezyIILnYNud
SPhTJBklH2LwaXIBUicYI7DLu9S+uZeXU+AHxchyedDfuopRs3qNOGRfqVjZlzp+
BvDN8X3i/uO3mLgdTi+Cd7rbyrI354PGdMI7Y+uO23bDKolhoJqJ0oSvqSdiPOEW
z8LKDUeRzqmiaX1DQSd06Fcjnp52hbpvzYBOshh9WPDQXoyNRTGJGcAtJwZFGr6u
uuxZEAyGmucicDU7ojKDJEetk8fYqt47jQ31a2xvxQWTwkthPjFwhbsrgiEmd/dn
1Wn9DcdbFyFhK/aZqTclBx7NEQ4RDilWaTibMpl5HzZ3BxeTSMpvAXzfkv7JR186
gcaN4v3S1rlQPDZEZ3rsJKntaPCJlP+DXEDMg8lGesb7oD6oY6TGokUoxpY7uoem
A0Qfm8O5AY0EX8DRGQEMAN7P3NQSOQsG6xxbNpYGh2l62bMqhL61yy3HjLPJK59e
kMaO2NcV1fyPb+wFHK0HWIsbQ7Ouf6bXYMBVSSELjDCkCuBHpGiuHZg1329aev/d
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
wmwsycaIC9eupit1mLY=
=1aEW
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,151 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
KuNFGWsKI1qTd1aEMRQzT4s+JJM=
=8257
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE4DCCAsgCAQMwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNVBAYTAktZMQ8wDQYD
VQQIDAZLaWxpZmkxEDAOBgNVBAcMB01uYXJhbmkxHTAbBgNVBAoMFEdyYXNzcm9v
dHMgRWNvbm9taWNzMQswCQYDVQQLDAJIUTEgMB4GA1UEAwwXZ3Jhc3Nyb290c2Vj
b25vbWljcy5vcmcxITAfBgkqhkiG9w0BCQEWEndpbGxAZ3Jhc3NlY29uLm9yZzAe
Fw0yMDAzMjgwMDE3MzRaFw0yMjEyMjQwMDE3MzRaMIHJMQswCQYDVQQGEwJNTjEU
MBIGA1UECgwLVGhlIE1vbmdvbHMxGzAZBgNVBAMMEnd3dy51bGFhbmJhYXRhci5t
bjEVMBMGA1UEKQwMS2h1YmxhaSBLaGFuMR8wHQYJKoZIhvcNAQkBFhBmb29Ac2Vj
aG9zdC5pbmZvMRAwDgYDVQQqDAdLaHVibGFpMQ0wCwYDVQQEDARLaGFuMRQwEgYD
VQQHDAtVbGFhbmJhYXRhcjEYMBYGA1UECAwPVGhlIFdob2xlIFdvcmxkMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtT1IP3Ls0ha5cX5RiMnyjPzjGjvS
GrWhDU579+qQ4T4k9ONq8fxNv95ZdxkccUyXZVR24pq+ALxmV65soCAGNJHb00lE
XWY3mQ9Jd10pC8UQPqfQ4Ez/J0Q+oIwpTdUCQStIe8QHz6sh8AVmPrTKWphjD5Up
PuWjFz4qs6h46hzNDoc90HPr4rqnxzBIo4Phs4a8/46FWvphufybKgorz+B5uWeB
Uzi3fOOKNAk8UmZK8fWB8DMIm8NFcOZV7METJmMBH1v5sY6zQWNBOHJmin02VkCW
rie16Ll+vfWp1jQEh442qgBFk0NiZBoGWJamYa7RmwH5d4LPGLiDUVgsnwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4ICAQBy0DIHBXVDPy4dTrmjcr78ytxy26p+8kjlzTQF
w9a9F+CPsRNV4KAmXJKUlyBTPiVocVhhSmj2Mz9OtI9FFWkSleKzxUtWlDEjIwGj
PRqWKhj/Y8LJsgdcUGCBtraBMCD5D1xyNx3n+zwhNp+HNB4ThVlV0iGCoOOq7XZu
4QHwkuy0ILcXt3NU6tWNXXsP544BxsCFHgyu+Ks+JkJzSFzGe+ybEMblXyHJZtOC
uyCLQN2Uo44Mtdo728p00mRGSFlouFfQWMu/7lQPNYouQ7XiLXy+mNeNN0CYUjpg
a7wfPExMljJEkgYyWT4JMuTB5RTsjQqw41aGtfy021L7yCZnVR2GG3pmeRQtqe4r
+V+WHV3YOkW5vObd5RE3ykntFkbrZNodBzxp6qKF4qsfqQH6WJFVXh0b36VmOdSa
qWxidz+SeCI379nemy93bcusQxRDXc+j5hbpW2pTtz2uxN5Y86DWEdSjpmiu6ExT
eBUyoK/5Zes/oZ3R9vj0V9jhpGN1/FahkXfkjZcuwXXFXuwk8rODU9DKGOEqFPKI
P/MZSVkfNvkBhiNozBXx1X/vJ2QoQKqE9zPfXlmvZkLiUaMjqd+h3qRe0vqXlQU+
ZH8NrKTi5xS/2VGtz4ksGlQp7GMZ0XFWL23BGhUfQVRLKMPCqta+GTuJUo3r5HVL
YiI4dw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,A748148F1C16F6129F04A346A324170A
k5K2EMTBDsaa9LqsnWnDV2Ocd14fZ6lXsNBoPZR1axrOV+zNZtDDIQwFP0FHmTN6
KrxDuDvPjkhoebMiLkRyWc6d4fslWQnr3vASi8gONYGT3pXKbuPNEMSXJeRfxvzC
CAcSh9rItcVxKv7m8+vuUWUy8m6TGBLRpDwiYf/9KTjGaPO2fnDBLHPlBuHVEmQW
xa0MOknleWnMucGMAtNdsiOVULgyljnoexgbES0VAosegnCKaRA5mRt47V0XUFDl
cFxXsHlZ4Wo4R9BowkWhpxX+IBVBmr1Rbmhkl8Z/k3SWfmAd2PNqHckl4oZsG0NN
kfi/x5RvMFXGwfW0q5XK/4oK1KbxV7UTn/lozkyvixSWDrN+2jETNCJyL//oXUcS
ZTr1+qic92fAAonx1JkX1QwQlNTb0ogmgpJzvAKqGqlNpSL3D0xySNTsSM0fEeZV
1qEnWTp1WpoyxG1CeiV0MDN9wkh9aV+KYm0FFo8PEqG6L1jsVNJKMtySeCXmWETK
S6iUBwiNf8atQwERfWx0wqSwHtJs9+zqUasskqYWJcO9T3TGqdc8UBaeVGAGdWFF
soGsBqrVyU8TEAuK6UReJyFQtHqXbm7kFIJ8J6fgc/QG4Dhy4laBKpR5Lg1W3fCa
g+o4OvkWr0Mvu0/y528qL0cqb3JoxvAtsZ8cHiiy1g3AiSr9ehlvtVboyg1ViP5d
cZZyU0NaeEUYOIULeBap4LNUBPQUSN5lw15C7N/reI8uuTNp8u8/6ITXKH3tknJr
n6Jhd6uFuYNImRhHgQ5rKgai5+Nhc6RqdO5K05fVMt8wIng5RkShQzQEZtaynWyg
3VbmJrKXjYesMxYFuKYP4efEVH/9tUeHiqlgVBF2N2Ntd4vKYied3mllthAo8BVC
0S0A+cZ2K65MKSoh/FHxxqfXo3AF6dL58DC+9jnzhKNgErVSTti0/Ps6pxrloNuN
ILUVeUQNhVlZu7KpJHININCnicohj8wAJDVZo7bb3iZfRGDIAFfgKuEALk+CRd0H
D6PukualFGd9w+KDLmUVdpkUTq9+UaZcJMXqGAN1ihIz44Xvk4QgFRN+9Szz4HbL
j5rTNqRmXjWjPahk90D/cOQuuE81GsVdiLnFzEacAlDGoGgV+Wcr/lQ06H099Hz1
v9hSwXoI+EbeQTjoqenQV62V7wuOiAKg3SpekGsYAsuuPElCBwYGTzWvotf9BBFe
LxhfVTCXD1cOQo3pgC8nHBwQSJ6TEXMrR52lgIkOZq/EPhPAYyO5A6ab2tKD1Zah
7qlkDDov7f4eTJrXfPnOu/tl3voN+VOxVbxFqsJuMPRCDc/DI18mOi+4yeUFK22W
pOCa+Etv7tGa3KlOuU/VWQfGTsICC8ejCIBgwmcbJ5HndEtzrPQv0ESziIcy/ybr
zkmlKJ7n7MZLNx/if5Ly9rxg+Y1al5JkOWpIqGKIhP075M2H9Kcg08TqHY6YoG5w
eJuudcyosGggxo/oTCCPrHNnPSvaauaZRynF02AQ1hle83V7T1Sy0jbtoGRXM7Hq
7AyQDLAcCregAkUuCMF/pktcJLQ2rY738RzNyaR3OiaTgKjko553QkciLP28qwZh
-----END RSA PRIVATE KEY-----

35
apps/data-seeding/testdata/ssl/ge.crt vendored Normal file
View File

@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----

View File

@@ -254,6 +254,10 @@ class Verifier:
if len(k) > 7 and k[:7] == 'verify_':
logg.debug('verifier has verify method {}'.format(k))
verifymethods.append(k[7:])
o = self.faucet_tx_factory.token_amount(self.faucet_address, sender_address=ZERO_ADDRESS)
r = self.conn.do(o)
self.faucet_amount = self.faucet_tx_factory.parse_token_amount(r)
logg.info('faucet amount set to {} at verify initialization time'.format(self.faucet_amount))
self.state = VerifierState(verifymethods, active_tests=active_tests)
@@ -284,6 +288,7 @@ class Verifier:
except ValueError:
actual_balance = int(r)
balance = int(balance / 1000000) * 1000000
balance += self.faucet_amount
logg.debug('balance for {}: {}'.format(address, balance))
if balance != actual_balance:
raise VerifierError((actual_balance, balance), 'balance')

View File

@@ -9,16 +9,18 @@ fi
t=$(mktemp)
>&2 echo using tmp $t
repos=(../../cic-cache ../../cic-eth ../../cic-ussd ../../data-seeding ../../cic-notify)
repos=(../../cic-cache ../../cic-eth ../../cic-ussd ../../data-seeding ../../cic-notify ../../contract-migration)
for r in ${repos[@]}; do
f="$r/requirements.txt"
>&2 echo updating $f
pyreq-update $f base_requirement.txt -vv > $t
pyreq-update $f base_requirement.txt -v > $t
cp $t $f
f="$r/test_requirements.txt"
>&2 echo updating $f
pyreq-update $f base_requirement.txt -vv > $t
cp $t $f
if [ -f $f ]; then
>&2 echo updating $f
pyreq-update $f base_requirement.txt -v > $t
cp $t $f
fi
done

View File

@@ -21,14 +21,12 @@ variables:
.py_build_target_test:
stage: build
variables:
CI_DEBUG_TRACE: "true"
IMAGE_TAG_BASE: $CI_REGISTRY_IMAGE/$APP_NAME:mr-unittest-$CI_COMMIT_SHORT_SHA
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
- /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --cache-repo $CI_REGISTRY_IMAGE --target test --tarPath $APP_NAME-test-image.tar --destination $CI_REGISTRY_IMAGE/$APP_NAME-test:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --cache-repo $CI_REGISTRY_IMAGE --target test --destination $IMAGE_TAG_BASE
.py_build_push:
stage: build

View File

@@ -72,11 +72,12 @@ services:
contract-migration:
build:
dockerfile: contract-migration/docker/Dockerfile
# target: compile-image
args:
pip_index_url: ${PIP_DEFAULT_INDEX_URL:-https://pypi.org/simple}
pip_extra_args: $PIP_EXTRA_ARGS
context: apps/
dockerfile: contract-migration/docker/Dockerfile
# image: registry.gitlab.com/grassrootseconomics/cic-internal-integration/contract-migration:latest
environment:
# ETH_PROVIDER should be broken out into host/port but cic-eth expects this
@@ -101,9 +102,17 @@ services:
DEV_PIP_EXTRA_INDEX_URL: ${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomics.net:8433}
RUN_MASK: ${RUN_MASK:-0} # bit flags; 1: contract migrations 2: seed data
DEV_FAUCET_AMOUNT: ${DEV_FAUCET_AMOUNT:-0}
CIC_DEFAULT_TOKEN_SYMBOL: ${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
DEV_SARAFU_DEMURRAGE_LEVEL: ${DEV_SARAFU_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
#DEV_SARAFU_DEMURRAGE_LEVEL: ${DEV_SARAFU_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
DEV_ETH_GAS_PRICE: ${DEV_ETH_GAS_PRICE:-1}
CIC_DEFAULT_TOKEN_SYMBOL: ${CIC_DEFAULT_TOKEN_SYMBOL:-GFT}
TOKEN_NAME: $TOKEN_NAME
TOKEN_DECIMALS: $TOKEN_DECIMALS
TOKEN_REDISTRIBUTION_PERIOD: $TOKEN_REDISTRIBUTION_PERIOD
TOKEN_SUPPLY_LIMIT: $TOKEN_SUPPLY_LIMIT
TOKEN_DEMURRAGE_LEVEL: ${TOKEN_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
TOKEN_SINK_ADDRESS: $TOKEN_SINK_ADDRESS
TOKEN_TYPE: ${TOKEN_TYPE:-default}
#CONFINI_DIR: ${CONFINI_DIR:-/tmp/cic/config}
command: ["./run_job.sh"]
#command: ["./reset.sh"]
depends_on:
@@ -265,7 +274,7 @@ services:
- -c
- |
if [[ -f /tmp/cic/config/.env ]]; then source /tmp/cic/config/.env; fi
./start_tasker.sh -q cic-eth -vv
./start_tasker.sh --aux-all -q cic-eth -vv
# command: [/bin/sh, "./start_tasker.sh", -q, cic-eth, -vv ]
cic-eth-tracker: