Compare commits

..

52 Commits

Author SHA1 Message Date
68f4ed383a adding changes 2021-08-10 12:43:11 -07:00
6d957eea6e data seeding waits on contract migration 2021-08-06 17:30:41 -07:00
f19b5a61ca builds wo dind should work since we are bind-ed to the docker.sock 2021-08-06 07:21:52 -07:00
d07ffa9fa7 maybe use dind 2021-08-05 16:59:16 -07:00
72dcb645aa buildkit 2021-08-05 16:50:54 -07:00
bb5c4a2ba9 can run with buildkit 2021-08-05 16:43:50 -07:00
25dad1b970 fx typo 2021-08-05 16:42:23 -07:00
59411c0508 integration testing 2021-08-05 16:39:48 -07:00
7c5f6826ae builds for swarm 2021-08-05 16:28:10 -07:00
f764b73f66 Testing a whitespace change for deployment 2021-07-30 17:05:25 +00:00
806b82504f Merge branch 'bvander/fix-notify-migrations' into 'master'
Bvander/fix notify migrations

See merge request grassrootseconomics/cic-internal-integration!238
2021-07-29 18:29:17 +00:00
ac76e14129 Bvander/fix notify migrations 2021-07-29 18:29:17 +00:00
1c78f4d6d6 Merge branch 'bvander/fix-notify-migrations' into 'master'
migrations fix

See merge request grassrootseconomics/cic-internal-integration!237
2021-07-28 23:33:34 +00:00
0d6e228f8a migrations fix 2021-07-28 23:33:34 +00:00
7a3cb7ab75 Merge branch 'philip/notify-fixes' into 'master'
Philip/notify fixes

See merge request grassrootseconomics/cic-internal-integration!234
2021-07-23 15:15:30 +00:00
992c7b4022 Philip/notify fixes 2021-07-23 15:15:30 +00:00
f19173001e Merge branch 'lash/chainlib-nextgen' into 'master'
Implement nextgen chainlib and chainqueue upgrade

See merge request grassrootseconomics/cic-internal-integration!231
2021-07-21 17:34:51 +00:00
Louis Holbrook
f82bb4515d Implement nextgen chainlib and chainqueue upgrade 2021-07-21 17:34:51 +00:00
24e6db7d87 Merge branch 'philip/sms-cluster-issues' into 'master'
Censors sensitive config values.

See merge request grassrootseconomics/cic-internal-integration!209
2021-07-20 16:18:27 +00:00
ecdfb9bc5a Censors sensitive config values. 2021-07-20 16:18:27 +00:00
30415ac997 Merge branch 'bvander/data-seeding-profiles' into 'master'
e2e ussd import user scripts

See merge request grassrootseconomics/cic-internal-integration!230
2021-07-19 21:30:05 +00:00
d5a8b77349 e2e ussd import user scripts 2021-07-19 21:30:04 +00:00
Louis Holbrook
ed2521b582 Merge branch 'lash/migration-fix' into 'master'
Apply config changes to contract migration

See merge request grassrootseconomics/cic-internal-integration!229
2021-07-16 13:21:08 +00:00
Louis Holbrook
395930106a Apply config changes to contract migration 2021-07-16 13:21:07 +00:00
Louis Holbrook
ee1452e530 Merge branch 'lash/fix-ussd-impotrs' into 'master'
Make verify pass in ussd imports

See merge request grassrootseconomics/cic-internal-integration!228
2021-07-16 09:30:23 +00:00
nolash
8cdaf9f28a Fix confini instantiation in ussd 2021-07-15 00:54:48 +02:00
Louis Holbrook
402b968b6d Merge branch 'lash/dispatcher-leak' into 'master'
Fix dispatcher memory leak

See merge request grassrootseconomics/cic-internal-integration!220
2021-07-14 22:02:59 +00:00
Louis Holbrook
aa13517534 Fix dispatcher memory leak 2021-07-14 22:02:59 +00:00
Louis Holbrook
884b18f2f1 Merge branch 'lash/expand-tokens-in-list' into 'master'
Expand token symbol and decimals in tx listings

See merge request grassrootseconomics/cic-internal-integration!226
2021-07-14 15:06:20 +00:00
Louis Holbrook
494a8f3e88 Expand token symbol and decimals in tx listings 2021-07-14 15:06:20 +00:00
Geoff Turk
1214f605a7 Merge branch 'geoffturk/fix-geo-range' into 'master'
Fix out of range error for geolocation in import users script

See merge request grassrootseconomics/cic-internal-integration!225
2021-07-14 14:52:40 +00:00
Louis Holbrook
0783a6001c Merge branch 'lash/eth-abi-exorcism' into 'master'
Remove eth-abi dep in data seeding

See merge request grassrootseconomics/cic-internal-integration!224
2021-07-14 14:43:33 +00:00
Louis Holbrook
f9594b766a Remove eth-abi dep in data seeding 2021-07-14 14:43:32 +00:00
geoffturk
561ae62d5e Fix out of range error for geolocation in import users script 2021-07-13 09:42:53 +00:00
Louis Holbrook
d6782abbcc Merge branch 'lash/default-token-context' into 'master'
Add contextual token symbol and name defaults

Closes #85

See merge request grassrootseconomics/cic-internal-integration!219
2021-07-12 19:50:48 +00:00
Louis Holbrook
8f173fa30b Add contextual token symbol and name defaults 2021-07-12 19:50:48 +00:00
Louis Holbrook
3741cb3283 Merge branch 'lash/account-registry-filter-match' into 'master'
Only match account registry on correct contract address

Closes cic-eth#127

See merge request grassrootseconomics/cic-internal-integration!218
2021-07-12 19:50:32 +00:00
Louis Holbrook
54dd5acb62 Only match account registry on correct contract address 2021-07-12 19:50:32 +00:00
c52885a016 update data-seeding dockerignore 2021-07-12 09:44:05 -07:00
f0dd257e05 Merge branch 'bvander/fix-onchange-test-triggers' into 'master'
Fix onchange test triggers

See merge request grassrootseconomics/cic-internal-integration!223
2021-07-12 16:22:17 +00:00
e8c870d230 Merge branch 'bvander/fix-cic-meta-builds' into 'master'
updated cic-meta with new context ect.

See merge request grassrootseconomics/cic-internal-integration!222
2021-07-12 16:21:59 +00:00
4bb36a448d updated cic-meta with new context ect. 2021-07-12 16:21:58 +00:00
231163e2fc Update apps/cic-ussd/.gitlab-ci.yml, apps/cic-notify/.gitlab-ci.yml, apps/cic-cache/.gitlab-ci.yml files 2021-07-12 16:14:33 +00:00
e599933ef8 fix requirements 2021-07-11 10:08:04 -07:00
266bc3362d just remove the cic-eth slim image for rn 2021-07-11 09:51:08 -07:00
8350381754 fix the scripts path in cic-eth 2021-07-11 09:43:05 -07:00
6ddeacf036 fix the scripts path in cic-eth 2021-07-11 09:32:00 -07:00
fe017d2b0f Merge branch 'bvander/cic-eth-base-image-and-testing' into 'master'
Faster local builds with base image and buildkit and run all unit tests

See merge request grassrootseconomics/cic-internal-integration!217
2021-07-10 15:46:15 +00:00
d7973436e6 Faster local builds with base image and buildkit and run all unit tests 2021-07-10 15:46:14 +00:00
5025c31af6 update the create eth flags with redis host 2021-07-09 10:29:34 -07:00
Louis Holbrook
021c736707 Merge branch 'lash/demurrage-task' into 'master'
Add demurrage token amount adjust task

See merge request grassrootseconomics/cic-internal-integration!214
2021-07-08 15:28:05 +00:00
Louis Holbrook
e3028a5060 Add demurrage token amount adjust task 2021-07-08 15:28:04 +00:00
137 changed files with 2383 additions and 9132 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ build/
**/coverage **/coverage
**/.venv **/.venv
.idea .idea
**/.vim

View File

@@ -1,14 +1,44 @@
include: #include:
- local: 'ci_templates/.cic-template.yml' # - local: 'ci_templates/.cic-template.yml'
- local: 'apps/contract-migration/.gitlab-ci.yml' # - local: 'apps/contract-migration/.gitlab-ci.yml'
- local: 'apps/cic-eth/.gitlab-ci.yml' # - local: 'apps/cic-eth/.gitlab-ci.yml'
- local: 'apps/cic-ussd/.gitlab-ci.yml' # - local: 'apps/cic-ussd/.gitlab-ci.yml'
- 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' # - local: 'apps/data-seeding/.gitlab-ci.yml'
image: tiangolo/docker-with-compose
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
stages: stages:
- build - build
- test - deploy
- release
variables:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
#services:
# - docker:stable-dind
build-prod:
tags:
- integration
stage: build
script:
- docker-compose build
only:
- bvander/integration-tests-on-docker
deploy-prod:
tags:
- integration
stage: deploy
script:
- docker stack deploy -c docker-compose.yml --with-registry-auth my-stack
only:
- bvander/integration-tests-on-docker

View File

@@ -2,4 +2,5 @@
omit = omit =
.venv/* .venv/*
scripts/* scripts/*
cic_cache/db/postgres/* cic_cache/db/migrations/*
cic_cache/version.py

View File

@@ -0,0 +1,4 @@
.git
.cache
.dot
**/doc

View File

@@ -1,22 +1,52 @@
.cic_cache_variables: .cic_cache_variables:
variables: variables:
APP_NAME: cic-cache APP_NAME: cic-cache
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile DOCKERFILE_PATH: docker/Dockerfile_ci
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

View File

@@ -15,6 +15,8 @@ from cic_cache.db.list import (
logg = logging.getLogger() logg = logging.getLogger()
DEFAULT_FILTER_SIZE = 8192 * 8
class Cache: class Cache:
def __init__(self, session): def __init__(self, session):
@@ -25,7 +27,7 @@ class BloomCache(Cache):
@staticmethod @staticmethod
def __get_filter_size(n): def __get_filter_size(n):
n = 8192 * 8 n = DEFAULT_FILTER_SIZE
logg.warning('filter size hardcoded to {}'.format(n)) logg.warning('filter size hardcoded to {}'.format(n))
return n return n

View File

@@ -100,3 +100,4 @@ class SessionBase(Model):
logg.debug('destroying session {}'.format(session_key)) logg.debug('destroying session {}'.format(session_key))
session.commit() session.commit()
session.close() session.close()
del SessionBase.localsessions[session_key]

View File

@@ -4,6 +4,9 @@ import json
import re import re
import base64 import base64
# external imports
from hexathon import add_0x
# local imports # local imports
from cic_cache.cache import ( from cic_cache.cache import (
BloomCache, BloomCache,
@@ -11,10 +14,11 @@ from cic_cache.cache import (
) )
logg = logging.getLogger(__name__) logg = logging.getLogger(__name__)
#logg = logging.getLogger()
re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?' re_transactions_all_bloom = r'/tx/(\d+)?/?(\d+)/?'
re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)/?(\d+)?/?(\d+)/?' re_transactions_account_bloom = r'/tx/user/((0x)?[a-fA-F0-9]+)(/(\d+)(/(\d+))?)?/?'
re_transactions_all_data = r'/txa/(\d+)/(\d+)/?' re_transactions_all_data = r'/txa/(\d+)?/?(\d+)/?'
DEFAULT_LIMIT = 100 DEFAULT_LIMIT = 100
@@ -26,13 +30,13 @@ def process_transactions_account_bloom(session, env):
address = r[1] address = r[1]
if r[2] == None: if r[2] == None:
address = '0x' + address address = add_0x(address)
offset = DEFAULT_LIMIT offset = 0
if r.lastindex > 2: if r.lastindex > 2:
offset = r[3] offset = r[4]
limit = 0 limit = DEFAULT_LIMIT
if r.lastindex > 3: if r.lastindex > 4:
limit = r[4] limit = r[6]
c = BloomCache(session) c = BloomCache(session)
(lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit) (lowest_block, highest_block, bloom_filter_block, bloom_filter_tx) = c.load_transactions_account(address, offset, limit)

View File

@@ -1,52 +1,38 @@
FROM python:3.8.6-slim-buster # 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 --from=0 /usr/local/share/cic/solidity/ /usr/local/share/cic/solidity/ 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]
WORKDIR /usr/src/cic-cache ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
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
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433' COPY . .
ARG root_requirement_file='requirements.txt'
#RUN apk update && \ RUN python setup.py install
# 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.2b9
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 cic-cache/config/ /usr/local/etc/cic-cache/ COPY 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/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/ COPY cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
COPY cic-cache/docker/start_tracker.sh ./start_tracker.sh COPY /docker/start_tracker.sh ./start_tracker.sh
COPY cic-cache/docker/db.sh ./db.sh COPY /docker/db.sh ./db.sh
RUN chmod 755 ./*.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 []

View File

@@ -0,0 +1,37 @@
# 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 []

View File

@@ -1,13 +1,13 @@
cic-base==0.1.3a3+build.984b5cff cic-base~=0.2.0a4
alembic==1.4.2 alembic==1.4.2
confini~=0.3.6rc3 confini>=0.3.6rc3,<0.5.0
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.6a2
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 chainsyncer[sql]~=0.0.3a5
erc20-faucet~=0.2.2a1 erc20-faucet~=0.2.2a2

View File

@@ -5,9 +5,12 @@ import datetime
# external imports # external 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)
@@ -101,3 +104,7 @@ def tag_txs(
db.tag_transaction(init_database, txs[1], 'taag', domain='test') db.tag_transaction(init_database, txs[1], 'taag', domain='test')
@pytest.fixture(scope='session')
def zero_filter():
return moolb.Bloom(DEFAULT_FILTER_SIZE, 3)

View File

@@ -10,6 +10,7 @@ from sqlalchemy import text
from chainlib.eth.tx import Tx from chainlib.eth.tx import Tx
from chainlib.eth.block import Block from chainlib.eth.block import Block
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainlib.eth.error import RequestMismatchException
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
add_0x, add_0x,
@@ -18,10 +19,21 @@ from hexathon import (
# local imports # local imports
from cic_cache.db import add_tag from cic_cache.db import add_tag
from cic_cache.runnable.daemons.filters.erc20 import ERC20TransferFilter from cic_cache.runnable.daemons.filters.erc20 import ERC20TransferFilter
from cic_cache.runnable.daemons.filters.base import TagSyncFilter
logg = logging.getLogger() 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( def test_erc20_filter(
eth_rpc, eth_rpc,
foo_token, foo_token,
@@ -67,3 +79,95 @@ def test_erc20_filter(
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") 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() r = init_database.execute(s, {'a': fltr.tag_domain, 'b': fltr.tag_name}).fetchone()
assert r[0] == tx.hash 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)

View File

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

View File

@@ -0,0 +1,6 @@
.git
.cache
.dot
**/doc
**/.venv
**/venv

View File

@@ -1,31 +1,52 @@
.cic_eth_variables: .cic_eth_variables:
variables: variables:
APP_NAME: cic-eth APP_NAME: cic-eth
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile DOCKERFILE_PATH: docker/Dockerfile_ci
CONTEXT: apps/$APP_NAME
.cic_eth_mr_changes_target:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- $CONTEXT/$APP_NAME/**/*
when: always
build-mr-cic-eth: build-mr-cic-eth:
extends: extends:
- .cic_eth_variables - .cic_eth_variables
- .cic_eth_mr_changes_target - .py_build_target_dev
- .py_build_target_test rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/cic-eth/**/*
when: always
test-mr-cic-eth: test-mr-cic-eth:
stage: test
extends: extends:
- .cic_eth_variables - .cic_eth_variables
- .cic_eth_mr_changes_target cache:
stage: test key:
image: $IMAGE_TAG_BASE files:
- test_requirements.txt
paths:
- /root/.cache/pip
image: $MR_IMAGE_TAG
script: script:
- cd apps/$APP_NAME/ - cd apps/$APP_NAME/
- pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests - >
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
rules:
- if: $CI_COMMIT_BRANCH == "master"
changes:
- apps/cic-eth/**/*
when: always

View File

@@ -1,5 +1,5 @@
SQLAlchemy==1.3.20 SQLAlchemy==1.3.20
cic-eth-registry~=0.5.6a1 cic-eth-registry>=0.5.6a2,<0.6.0
hexathon~=0.0.1a7 hexathon~=0.0.1a7
chainqueue~=0.0.2b5 chainqueue>=0.0.3a1,<0.1.0
eth-erc20==0.0.10a2 eth-erc20>=0.0.10a3,<0.1.0

View File

@@ -6,6 +6,11 @@ import logging
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
from hexathon import (
add_0x,
strip_0x,
uniform as hex_uniform,
)
# local imports # local imports
from cic_eth.db.enum import LockEnum from cic_eth.db.enum import LockEnum
@@ -19,6 +24,12 @@ from cic_eth.error import LockedError
celery_app = celery.current_app celery_app = celery.current_app
logg = logging.getLogger() logg = logging.getLogger()
def normalize_address(a):
if a == None:
return None
return add_0x(hex_uniform(strip_0x(a)))
@celery_app.task(base=CriticalSQLAlchemyTask) @celery_app.task(base=CriticalSQLAlchemyTask)
def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.ALL, tx_hash=None): def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.ALL, tx_hash=None):
"""Task wrapper to set arbitrary locks """Task wrapper to set arbitrary locks
@@ -32,6 +43,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
""" """
address = normalize_address(address)
chain_str = '::' chain_str = '::'
if chain_spec_dict != None: if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
@@ -53,6 +65,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
""" """
address = normalize_address(address)
chain_str = '::' chain_str = '::'
if chain_spec_dict != None: if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
@@ -72,6 +85,7 @@ def lock_send(chained_input, chain_spec_dict, address=ZERO_ADDRESS, tx_hash=None
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
address = normalize_address(address)
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.set(chain_str, LockEnum.SEND, address=address, tx_hash=tx_hash) r = Lock.set(chain_str, LockEnum.SEND, address=address, tx_hash=tx_hash)
logg.debug('Send locked for {}, flag now {}'.format(address, r)) logg.debug('Send locked for {}, flag now {}'.format(address, r))
@@ -89,6 +103,7 @@ def unlock_send(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
address = normalize_address(address)
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.reset(chain_str, LockEnum.SEND, address=address) r = Lock.reset(chain_str, LockEnum.SEND, address=address)
logg.debug('Send unlocked for {}, flag now {}'.format(address, r)) logg.debug('Send unlocked for {}, flag now {}'.format(address, r))
@@ -106,6 +121,7 @@ def lock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS, tx_hash=Non
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
address = normalize_address(address)
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.set(chain_str, LockEnum.QUEUE, address=address, tx_hash=tx_hash) r = Lock.set(chain_str, LockEnum.QUEUE, address=address, tx_hash=tx_hash)
logg.debug('Queue direct locked for {}, flag now {}'.format(address, r)) logg.debug('Queue direct locked for {}, flag now {}'.format(address, r))
@@ -123,6 +139,7 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
address = normalize_address(address)
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.reset(chain_str, LockEnum.QUEUE, address=address) r = Lock.reset(chain_str, LockEnum.QUEUE, address=address)
logg.debug('Queue direct unlocked for {}, flag now {}'.format(address, r)) logg.debug('Queue direct unlocked for {}, flag now {}'.format(address, r))
@@ -131,6 +148,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):
address = normalize_address(address)
chain_str = '::' chain_str = '::'
if chain_spec_dict != None: if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = str(ChainSpec.from_dict(chain_spec_dict))

View File

@@ -14,7 +14,11 @@ from chainqueue.sql.query import get_tx
from chainqueue.sql.state import set_cancel 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 hexathon import (
strip_0x,
add_0x,
uniform as hex_uniform,
)
from potaahto.symbols import snake_and_camel from potaahto.symbols import snake_and_camel
# local imports # local imports
@@ -69,15 +73,17 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
set_cancel(chain_spec, strip_0x(tx['hash']), manual=True, session=session) set_cancel(chain_spec, strip_0x(tx['hash']), manual=True, session=session)
query_address = add_0x(hex_uniform(strip_0x(address))) # aaaaargh
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==query_address)
q = q.filter(Otx.nonce>=nonce+delta) q = q.filter(Otx.nonce>=nonce+delta)
q = q.order_by(Otx.nonce.asc()) q = q.order_by(Otx.nonce.asc())
otxs = q.all() otxs = q.all()
tx_hashes = [] tx_hashes = []
txs = [] txs = []
gas_total = 0
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)
@@ -89,8 +95,10 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
tx_new['gas_price'] += 1 tx_new['gas_price'] += 1
tx_new['gasPrice'] = tx_new['gas_price'] tx_new['gasPrice'] = tx_new['gas_price']
tx_new['nonce'] -= delta tx_new['nonce'] -= delta
gas_total += tx_new['gas_price'] * tx_new['gas']
logg.debug('tx_new {}'.format(tx_new)) logg.debug('tx_new {}'.format(tx_new))
logg.debug('gas running total {}'.format(gas_total))
del(tx_new['hash']) del(tx_new['hash'])
del(tx_new['hash_unsigned']) del(tx_new['hash_unsigned'])
@@ -122,8 +130,10 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
s = create_check_gas_task( s = create_check_gas_task(
txs, txs,
chain_spec, chain_spec,
tx_new['from'], #tx_new['from'],
gas=tx_new['gas'], address,
#gas=tx_new['gas'],
gas=gas_total,
tx_hashes_hex=tx_hashes, tx_hashes_hex=tx_hashes,
queue=queue, queue=queue,
) )
@@ -132,7 +142,8 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
'cic_eth.admin.ctrl.unlock_send', 'cic_eth.admin.ctrl.unlock_send',
[ [
chain_spec.asdict(), chain_spec.asdict(),
tx_new['from'], address,
#tx_new['from'],
], ],
queue=queue, queue=queue,
) )
@@ -140,7 +151,8 @@ def shift_nonce(self, chainspec_dict, tx_hash_orig_hex, delta=1):
'cic_eth.admin.ctrl.unlock_queue', 'cic_eth.admin.ctrl.unlock_queue',
[ [
chain_spec.asdict(), chain_spec.asdict(),
tx_new['from'], address,
#tx_new['from'],
], ],
queue=queue, queue=queue,
) )

View File

@@ -21,6 +21,7 @@ from chainlib.hash import keccak256_hex_to_hex
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
add_0x, add_0x,
uniform as hex_uniform,
) )
from chainlib.eth.gas import balance from chainlib.eth.gas import balance
from chainqueue.db.enum import ( from chainqueue.db.enum import (
@@ -307,6 +308,8 @@ class AdminApi:
:param address: Ethereum address to return transactions for :param address: Ethereum address to return transactions for
:type address: str, 0x-hex :type address: str, 0x-hex
""" """
address = add_0x(hex_uniform(strip_0x(address)))
last_nonce = -1 last_nonce = -1
s = celery.signature( s = celery.signature(
'cic_eth.queue.query.get_account_tx', 'cic_eth.queue.query.get_account_tx',

View File

@@ -8,7 +8,8 @@ Create Date: 2021-04-02 18:30:55.398388
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from chainqueue.db.migrations.sqlalchemy import ( #from chainqueue.db.migrations.sqlalchemy import (
from chainqueue.db.migrations.default.export import (
chainqueue_upgrade, chainqueue_upgrade,
chainqueue_downgrade, chainqueue_downgrade,
) )

View File

@@ -8,7 +8,8 @@ Create Date: 2021-04-02 18:36:44.459603
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from chainsyncer.db.migrations.sqlalchemy import ( #from chainsyncer.db.migrations.sqlalchemy import (
from chainsyncer.db.migrations.default.export import (
chainsyncer_upgrade, chainsyncer_upgrade,
chainsyncer_downgrade, chainsyncer_downgrade,
) )

View File

@@ -126,3 +126,4 @@ class SessionBase(Model):
logg.debug('commit and destroy session {}'.format(session_key)) logg.debug('commit and destroy session {}'.format(session_key))
session.commit() session.commit()
session.close() session.close()
del SessionBase.localsessions[session_key]

View File

@@ -23,7 +23,7 @@ from chainlib.error import JSONRPCException
from eth_accounts_index.registry import AccountRegistry from eth_accounts_index.registry import AccountRegistry
from eth_accounts_index import AccountsIndex from eth_accounts_index import AccountsIndex
from sarafu_faucet import MinterFaucet from sarafu_faucet import MinterFaucet
from chainqueue.db.models.tx import TxCache from chainqueue.sql.tx import cache_tx_dict
# local import # local import
from cic_eth_registry import CICRegistry from cic_eth_registry import CICRegistry
@@ -300,20 +300,17 @@ def cache_gift_data(
session = self.create_session() session = self.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
tx['to'], 'to': tx['to'],
ZERO_ADDRESS, 'source_token': ZERO_ADDRESS,
ZERO_ADDRESS, 'destination_token': ZERO_ADDRESS,
0, 'from_value': 0,
0, 'to_value': 0,
session=session, }
)
session.add(tx_cache) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)
@@ -342,18 +339,15 @@ def cache_account_data(
tx_data = AccountsIndex.parse_add_request(tx['data']) tx_data = AccountsIndex.parse_add_request(tx['data'])
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
tx['to'], 'to': tx['to'],
ZERO_ADDRESS, 'source_token': ZERO_ADDRESS,
ZERO_ADDRESS, 'destination_token': ZERO_ADDRESS,
0, 'from_value': 0,
0, 'to_value': 0,
session=session, }
) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.add(tx_cache)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)

View File

@@ -1,385 +0,0 @@
# standard imports
import os
import logging
# third-party imports
import celery
import web3
from cic_registry import CICRegistry
from cic_registry.chain import ChainSpec
# local imports
from cic_eth.db import SessionBase
from cic_eth.db.models.convert import TxConvertTransfer
from cic_eth.db.models.otx import Otx
from cic_eth.db.models.tx import TxCache
from cic_eth.eth.task import sign_and_register_tx
from cic_eth.eth.task import create_check_gas_and_send_task
from cic_eth.eth.token import TokenTxFactory
from cic_eth.eth.factory import TxFactory
from cic_eth.eth.util import unpack_signed_raw_tx
from cic_eth.eth.rpc import RpcClient
celery_app = celery.current_app
#logg = celery_app.log.get_default_logger()
logg = logging.getLogger()
contract_function_signatures = {
'convert': 'f3898a97',
'convert2': '569706eb',
}
class BancorTxFactory(TxFactory):
"""Factory for creating Bancor network transactions.
"""
def convert(
self,
source_token_address,
destination_token_address,
reserve_address,
source_amount,
minimum_return,
chain_spec,
fee_beneficiary='0x0000000000000000000000000000000000000000',
fee_ppm=0,
):
"""Create a BancorNetwork "convert" transaction.
:param source_token_address: ERC20 contract address for token to convert from
:type source_token_address: str, 0x-hex
:param destination_token_address: ERC20 contract address for token to convert to
:type destination_token_address: str, 0x-hex
:param reserve_address: ERC20 contract address of Common reserve token
:type reserve_address: str, 0x-hex
:param source_amount: Amount of source tokens to convert
:type source_amount: int
:param minimum_return: Minimum amount of destination tokens to accept as result for conversion
:type source_amount: int
:return: Unsigned "convert" transaction in standard Ethereum format
:rtype: dict
"""
network_contract = CICRegistry.get_contract(chain_spec, 'BancorNetwork')
network_gas = network_contract.gas('convert')
tx_convert_buildable = network_contract.contract.functions.convert2(
[
source_token_address,
source_token_address,
reserve_address,
destination_token_address,
destination_token_address,
],
source_amount,
minimum_return,
fee_beneficiary,
fee_ppm,
)
tx_convert = tx_convert_buildable.buildTransaction({
'from': self.address,
'gas': network_gas,
'gasPrice': self.gas_price,
'chainId': chain_spec.chain_id(),
'nonce': self.next_nonce(),
})
return tx_convert
def unpack_convert(data):
f = data[2:10]
if f != contract_function_signatures['convert2']:
raise ValueError('Invalid convert data ({})'.format(f))
d = data[10:]
path = d[384:]
source = path[64-40:64]
destination = path[-40:]
amount = int(d[64:128], 16)
min_return = int(d[128:192], 16)
fee_recipient = d[192:256]
fee = int(d[256:320], 16)
return {
'amount': amount,
'min_return': min_return,
'source_token': web3.Web3.toChecksumAddress('0x' + source),
'destination_token': web3.Web3.toChecksumAddress('0x' + destination),
'fee_recipient': fee_recipient,
'fee': fee,
}
# Kept for historical reference, it unpacks a convert call without fee parameters
#def _unpack_convert_mint(data):
# f = data[2:10]
# if f != contract_function_signatures['convert2']:
# raise ValueError('Invalid convert data ({})'.format(f))
#
# d = data[10:]
# path = d[256:]
# source = path[64-40:64]
# destination = path[-40:]
#
# amount = int(d[64:128], 16)
# min_return = int(d[128:192], 16)
# return {
# 'amount': amount,
# 'min_return': min_return,
# 'source_token': web3.Web3.toChecksumAddress('0x' + source),
# 'destination_token': web3.Web3.toChecksumAddress('0x' + destination),
# }
@celery_app.task(bind=True)
def convert_with_default_reserve(self, tokens, from_address, source_amount, minimum_return, to_address, chain_str):
"""Performs a conversion between two liquid tokens using Bancor network.
:param tokens: Token pair, source and destination respectively
:type tokens: list of str, 0x-hex
:param from_address: Ethereum address of sender
:type from_address: str, 0x-hex
:param source_amount: Amount of source tokens to convert
:type source_amount: int
:param minimum_return: Minimum about of destination tokens to receive
:type minimum_return: int
"""
chain_spec = ChainSpec.from_chain_str(chain_str)
queue = self.request.delivery_info['routing_key']
c = RpcClient(chain_spec, holder_address=from_address)
cr = CICRegistry.get_contract(chain_spec, 'BancorNetwork')
source_token = CICRegistry.get_address(chain_spec, tokens[0]['address'])
reserve_address = CICRegistry.get_contract(chain_spec, 'BNTToken', 'ERC20').address()
tx_factory = TokenTxFactory(from_address, c)
tx_approve_zero = tx_factory.approve(source_token.address(), cr.address(), 0, chain_spec)
(tx_approve_zero_hash_hex, tx_approve_zero_signed_hex) = sign_and_register_tx(tx_approve_zero, chain_str, queue, 'cic_eth.eth.token.otx_cache_approve')
tx_approve = tx_factory.approve(source_token.address(), cr.address(), source_amount, chain_spec)
(tx_approve_hash_hex, tx_approve_signed_hex) = sign_and_register_tx(tx_approve, chain_str, queue, 'cic_eth.eth.token.otx_cache_approve')
tx_factory = BancorTxFactory(from_address, c)
tx_convert = tx_factory.convert(
tokens[0]['address'],
tokens[1]['address'],
reserve_address,
source_amount,
minimum_return,
chain_spec,
)
(tx_convert_hash_hex, tx_convert_signed_hex) = sign_and_register_tx(tx_convert, chain_str, queue, 'cic_eth.eth.bancor.otx_cache_convert')
# TODO: consider moving save recipient to async task / chain it before the tx send
if to_address != None:
save_convert_recipient(tx_convert_hash_hex, to_address, chain_str)
s = create_check_gas_and_send_task(
[tx_approve_zero_signed_hex, tx_approve_signed_hex, tx_convert_signed_hex],
chain_str,
from_address,
tx_approve_zero['gasPrice'] * tx_approve_zero['gas'],
tx_hashes_hex=[tx_approve_hash_hex],
queue=queue,
)
s.apply_async()
return tx_convert_hash_hex
#@celery_app.task()
#def process_approval(tx_hash_hex):
# t = session.query(TxConvertTransfer).query(TxConvertTransfer.approve_tx_hash==tx_hash_hex).first()
# c = session.query(Otx).query(Otx.tx_hash==t.convert_tx_hash)
# gas_limit = 8000000
# gas_price = GasOracle.gas_price()
#
# # TODO: use celery group instead
# s_queue = celery.signature(
# 'cic_eth.queue.tx.create',
# [
# nonce,
# c['address'], # TODO: check that this is in fact sender address
# c['tx_hash'],
# c['signed_tx'],
# ]
# )
# s_queue.apply_async()
#
# s_check_gas = celery.signature(
# 'cic_eth.eth.gas.check_gas',
# [
# c['address'],
# [c['signed_tx']],
# gas_limit * gas_price,
# ]
# )
# s_send = celery.signature(
# 'cic_eth.eth.tx.send',
# [],
# )
#
# s_set_sent = celery.signature(
# 'cic_eth.queue.state.set_sent',
# [False],
# )
# s_send.link(s_set_sent)
# s_check_gas.link(s_send)
# s_check_gas.apply_async()
# return tx_hash_hex
@celery_app.task()
def save_convert_recipient(convert_hash, recipient_address, chain_str):
"""Registers the recipient target for a convert-and-transfer operation.
:param convert_hash: Transaction hash of convert operation
:type convert_hash: str, 0x-hex
:param recipient_address: Address of consequtive transfer recipient
:type recipient_address: str, 0x-hex
"""
session = SessionBase.create_session()
t = TxConvertTransfer(convert_hash, recipient_address, chain_str)
session.add(t)
session.commit()
session.close()
@celery_app.task()
def save_convert_transfer(convert_hash, transfer_hash):
"""Registers that the transfer part of a convert-and-transfer operation has been executed.
:param convert_hash: Transaction hash of convert operation
:type convert_hash: str, 0x-hex
:param convert_hash: Transaction hash of transfer operation
:type convert_hash: str, 0x-hex
:returns: transfer_hash,
:rtype: list, single str, 0x-hex
"""
session = SessionBase.create_session()
t = TxConvertTransfer.get(convert_hash)
t.transfer(transfer_hash)
session.add(t)
session.commit()
session.close()
return [transfer_hash]
# TODO: seems unused, consider removing
@celery_app.task()
def resolve_converters_by_tokens(tokens, chain_str):
"""Return converters for a list of tokens.
:param tokens: Token addresses to look up
:type tokens: list of str, 0x-hex
:return: Addresses of matching converters
:rtype: list of str, 0x-hex
"""
chain_spec = ChainSpec.from_chain_str(chain_str)
for t in tokens:
c = CICRegistry.get_contract(chain_spec, 'ConverterRegistry')
fn = c.function('getConvertersByAnchors')
try:
converters = fn([t['address']]).call()
except Exception as e:
raise e
t['converters'] = converters
return tokens
@celery_app.task(bind=True)
def transfer_converted(self, tokens, holder_address, receiver_address, value, tx_convert_hash_hex, chain_str):
"""Execute the ERC20 transfer of a convert-and-transfer operation.
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 holder_address: Token receiver address
:type holder_address: str, 0x-hex
:param value: Amount of token, in 'wei'
:type value: int
:raises TokenCountError: Either none or more then one tokens have been passed as tokens argument
:return: Transaction hash
:rtype: str, 0x-hex
"""
# we only allow one token, one transfer
if len(tokens) != 1:
raise TokenCountError
chain_spec = ChainSpec.from_chain_str(chain_str)
queue = self.request.delivery_info['routing_key']
c = RpcClient(chain_spec, holder_address=holder_address)
# get transaction parameters
gas_price = c.gas_price()
tx_factory = TokenTxFactory(holder_address, c)
token_address = tokens[0]['address']
tx_transfer = tx_factory.transfer(
token_address,
receiver_address,
value,
chain_spec,
)
(tx_transfer_hash_hex, tx_transfer_signed_hex) = sign_and_register_tx(tx_transfer, chain_str, queue, 'cic_eth.eth.token.otx_cache_transfer')
# send transaction
logg.info('transfer converted token {} from {} to {} value {} {}'.format(token_address, holder_address, receiver_address, value, tx_transfer_signed_hex))
s = create_check_gas_and_send_task(
[tx_transfer_signed_hex],
chain_str,
holder_address,
tx_transfer['gasPrice'] * tx_transfer['gas'],
None,
queue,
)
s_save = celery.signature(
'cic_eth.eth.bancor.save_convert_transfer',
[
tx_convert_hash_hex,
tx_transfer_hash_hex,
],
queue=queue,
)
s_save.link(s)
s_save.apply_async()
return tx_transfer_hash_hex
@celery_app.task()
def otx_cache_convert(
tx_hash_hex,
tx_signed_raw_hex,
chain_str,
):
chain_spec = ChainSpec.from_chain_str(chain_str)
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
tx = unpack(tx_signed_raw_bytes, chain_spec)
tx_data = unpack_convert(tx['data'])
logg.debug('tx data {}'.format(tx_data))
session = TxCache.create_session()
tx_cache = TxCache(
tx_hash_hex,
tx['from'],
tx['from'],
tx_data['source_token'],
tx_data['destination_token'],
tx_data['amount'],
tx_data['amount'],
)
session.add(tx_cache)
session.commit()
session.close()
return tx_hash_hex

View File

@@ -13,9 +13,9 @@ from chainlib.eth.tx import (
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 hexathon import strip_0x from hexathon import strip_0x
from chainqueue.db.models.tx import TxCache
from chainqueue.error import NotLocalTxError from chainqueue.error import NotLocalTxError
from eth_erc20 import ERC20 from eth_erc20 import ERC20
from chainqueue.sql.tx import cache_tx_dict
# local imports # local imports
from cic_eth.db.models.base import SessionBase from cic_eth.db.models.base import SessionBase
@@ -375,19 +375,16 @@ def cache_transfer_data(
token_value = tx_data[1] token_value = tx_data[1]
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
recipient_address, 'to': recipient_address,
tx['to'], 'source_token': tx['to'],
tx['to'], 'destination_token': tx['to'],
token_value, 'from_value': token_value,
token_value, 'to_value': token_value,
session=session, }
) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.add(tx_cache)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)
@@ -417,19 +414,16 @@ def cache_transfer_from_data(
token_value = tx_data[2] token_value = tx_data[2]
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
recipient_address, 'to': recipient_address,
tx['to'], 'source_token': tx['to'],
tx['to'], 'destination_token': tx['to'],
token_value, 'from_value': token_value,
token_value, 'to_value': token_value,
session=session, }
) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.add(tx_cache)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)
@@ -458,19 +452,16 @@ def cache_approve_data(
token_value = tx_data[1] token_value = tx_data[1]
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
recipient_address, 'to': recipient_address,
tx['to'], 'source_token': tx['to'],
tx['to'], 'destination_token': tx['to'],
token_value, 'from_value': token_value,
token_value, 'to_value': token_value,
session=session, }
) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.add(tx_cache)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)

View File

@@ -9,6 +9,7 @@ from chainlib.chain import ChainSpec
from chainlib.eth.address import is_checksum_address from chainlib.eth.address import is_checksum_address
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainqueue.db.enum import StatusBits from chainqueue.db.enum import StatusBits
from chainqueue.sql.tx import cache_tx_dict
from chainlib.eth.gas import ( from chainlib.eth.gas import (
balance, balance,
price, price,
@@ -133,20 +134,17 @@ def cache_gas_data(
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_dict = {
tx_hash_hex, 'hash': tx_hash_hex,
tx['from'], 'from': tx['from'],
tx['to'], 'to': tx['to'],
ZERO_ADDRESS, 'source_token': ZERO_ADDRESS,
ZERO_ADDRESS, 'destination_token': ZERO_ADDRESS,
tx['value'], 'from_value': tx['value'],
tx['value'], 'to_value': tx['value'],
session=session, }
)
session.add(tx_cache) (tx_dict, cache_id) = cache_tx_dict(tx_dict, session=session)
session.commit()
cache_id = tx_cache.id
session.close() session.close()
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)

View File

@@ -18,7 +18,6 @@ from hexathon import (
strip_0x, strip_0x,
) )
from chainqueue.db.models.tx import Otx from chainqueue.db.models.tx import Otx
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 from potaahto.symbols import snake_and_camel

View File

@@ -14,9 +14,11 @@ 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.constant import ZERO_ADDRESS
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 cic_eth_registry.error import UnknownContractError
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.sql.query import get_tx_cache
@@ -114,9 +116,6 @@ 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_symbol = token.symbol
token_decimals = token.decimals
times = tx_times(tx['hash'], chain_spec) times = tx_times(tx['hash'], chain_spec)
tx_r = { tx_r = {
'hash': tx['hash'], 'hash': tx['hash'],
@@ -126,12 +125,6 @@ def list_tx_by_bloom(self, bloomspec, address, chain_spec_dict):
'destination_value': tx_token_value, 'destination_value': tx_token_value,
'source_token': tx['to'], 'source_token': tx['to'],
'destination_token': tx['to'], 'destination_token': tx['to'],
'source_token_symbol': token_symbol,
'destination_token_symbol': token_symbol,
'source_token_decimals': token_decimals,
'destination_token_decimals': token_decimals,
'source_token_chain': chain_str,
'destination_token_chain': chain_str,
'nonce': tx['nonce'], 'nonce': tx['nonce'],
} }
if times['queue'] != None: if times['queue'] != None:
@@ -146,8 +139,8 @@ def list_tx_by_bloom(self, bloomspec, address, chain_spec_dict):
# TODO: Surely it must be possible to optimize this # TODO: Surely it must be possible to optimize this
# TODO: DRY this with callback filter in cic_eth/runnable/manager # TODO: DRY this with callback filter in cic_eth/runnable/manager
# TODO: Remove redundant fields from end representation (timestamp, tx_hash) # TODO: Remove redundant fields from end representation (timestamp, tx_hash)
@celery_app.task() @celery_app.task(bind=True, base=BaseTask)
def tx_collate(tx_batches, chain_spec_dict, offset, limit, newest_first=True): def tx_collate(self, tx_batches, chain_spec_dict, offset, limit, newest_first=True, verify_contracts=True):
"""Merges transaction data from multiple sources and sorts them in chronological order. """Merges transaction data from multiple sources and sorts them in chronological order.
:param tx_batches: Transaction data inputs :param tx_batches: Transaction data inputs
@@ -196,6 +189,32 @@ def tx_collate(tx_batches, chain_spec_dict, offset, limit, newest_first=True):
if newest_first: if newest_first:
ks.reverse() ks.reverse()
for k in ks: for k in ks:
txs.append(txs_by_block[k]) tx = txs_by_block[k]
if verify_contracts:
try:
tx = verify_and_expand(tx, chain_spec, sender_address=BaseTask.call_address)
except UnknownContractError:
logg.error('verify failed on tx {}, skipping'.format(tx['hash']))
continue
txs.append(tx)
return txs return txs
def verify_and_expand(tx, chain_spec, sender_address=ZERO_ADDRESS):
rpc = RPCConnection.connect(chain_spec, 'default')
registry = CICRegistry(chain_spec, rpc)
if tx.get('source_token_symbol') == None and tx['source_token'] != ZERO_ADDRESS:
r = registry.by_address(tx['source_token'], sender_address=sender_address)
token = ERC20Token(chain_spec, rpc, tx['source_token'])
tx['source_token_symbol'] = token.symbol
tx['source_token_decimals'] = token.decimals
if tx.get('destination_token_symbol') == None and tx['destination_token'] != ZERO_ADDRESS:
r = registry.by_address(tx['destination_token'], sender_address=sender_address)
token = ERC20Token(chain_spec, rpc, tx['destination_token'])
tx['destination_token_symbol'] = token.symbol
tx['destination_token_decimals'] = token.decimals
return tx

View File

@@ -27,7 +27,7 @@ def database_engine(
SessionBase.poolable = False SessionBase.poolable = False
dsn = dsn_from_config(load_config) dsn = dsn_from_config(load_config)
#SessionBase.connect(dsn, True) #SessionBase.connect(dsn, True)
SessionBase.connect(dsn, debug=load_config.get('DATABASE_DEBUG') != None) SessionBase.connect(dsn, debug=load_config.true('DATABASE_DEBUG'))
return dsn return dsn

View File

@@ -100,6 +100,7 @@ def get_upcoming_tx(chain_spec, status=StatusEnum.READYSEND, not_status=None, re
q_outer = q_outer.join(Lock, isouter=True) q_outer = q_outer.join(Lock, isouter=True)
q_outer = q_outer.filter(or_(Lock.flags==None, Lock.flags.op('&')(LockEnum.SEND.value)==0)) q_outer = q_outer.filter(or_(Lock.flags==None, Lock.flags.op('&')(LockEnum.SEND.value)==0))
if not is_alive(status): if not is_alive(status):
SessionBase.release_session(session) SessionBase.release_session(session)
raise ValueError('not a valid non-final tx value: {}'.format(status)) raise ValueError('not a valid non-final tx value: {}'.format(status))

View File

@@ -80,7 +80,12 @@ def main():
t = api.create_account(register=register) t = api.create_account(register=register)
ps.get_message() ps.get_message()
o = ps.get_message(timeout=args.timeout) try:
o = ps.get_message(timeout=args.timeout)
except TimeoutError as e:
sys.stderr.write('got no new address from cic-eth before timeout: {}\n'.format(e))
sys.exit(1)
ps.unsubscribe()
m = json.loads(o['data']) m = json.loads(o['data'])
print(m['result']) print(m['result'])

View File

@@ -90,6 +90,7 @@ class DispatchSyncer:
def __init__(self, chain_spec): def __init__(self, chain_spec):
self.chain_spec = chain_spec self.chain_spec = chain_spec
self.session = None
def chain(self): def chain(self):
@@ -100,16 +101,18 @@ class DispatchSyncer:
c = len(txs.keys()) c = len(txs.keys())
logg.debug('processing {} txs {}'.format(c, list(txs.keys()))) logg.debug('processing {} txs {}'.format(c, list(txs.keys())))
chain_str = str(self.chain_spec) chain_str = str(self.chain_spec)
session = SessionBase.create_session() self.session = SessionBase.create_session()
for k in txs.keys(): for k in txs.keys():
tx_raw = txs[k] tx_raw = txs[k]
tx_raw_bytes = bytes.fromhex(strip_0x(tx_raw)) tx_raw_bytes = bytes.fromhex(strip_0x(tx_raw))
tx = unpack(tx_raw_bytes, self.chain_spec) tx = unpack(tx_raw_bytes, self.chain_spec)
try: try:
set_reserved(self.chain_spec, tx['hash'], session=session) set_reserved(self.chain_spec, tx['hash'], session=self.session)
self.session.commit()
except NotLocalTxError as e: except NotLocalTxError as e:
logg.warning('dispatcher was triggered with non-local tx {}'.format(tx['hash'])) logg.warning('dispatcher was triggered with non-local tx {}'.format(tx['hash']))
self.session.rollback()
continue continue
s_check = celery.signature( s_check = celery.signature(
@@ -132,16 +135,25 @@ class DispatchSyncer:
s_check.link(s_send) s_check.link(s_send)
t = s_check.apply_async() t = s_check.apply_async()
logg.info('processed {}'.format(k)) logg.info('processed {}'.format(k))
self.session.close()
self.session = None
def loop(self, w3, interval): def loop(self, interval):
while run: while run:
txs = {} txs = {}
typ = StatusBits.QUEUED typ = StatusBits.QUEUED
utxs = get_upcoming_tx(self.chain_spec, typ) utxs = get_upcoming_tx(self.chain_spec, typ)
for k in utxs.keys(): for k in utxs.keys():
txs[k] = utxs[k] txs[k] = utxs[k]
self.process(w3, txs) try:
conn = RPCConnection.connect(self.chain_spec, 'default')
self.process(conn, txs)
except ConnectionError as e:
if self.session != None:
self.session.close()
self.session = None
logg.error('connection to node failed: {}'.format(e))
if len(utxs) > 0: if len(utxs) > 0:
time.sleep(self.yield_delay) time.sleep(self.yield_delay)
@@ -151,8 +163,7 @@ class DispatchSyncer:
def main(): def main():
syncer = DispatchSyncer(chain_spec) syncer = DispatchSyncer(chain_spec)
conn = RPCConnection.connect(chain_spec, 'default') syncer.loop(float(config.get('DISPATCHER_LOOP_INTERVAL')))
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
sys.exit(0) sys.exit(0)

View File

@@ -11,6 +11,7 @@ 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.sql.query import get_paused_tx_cache as get_paused_tx
from chainlib.eth.address import to_checksum_address
# local imports # local imports
from cic_eth.db.models.base import SessionBase from cic_eth.db.models.base import SessionBase
@@ -47,12 +48,13 @@ class GasFilter(SyncFilter):
SessionBase.release_session(session) SessionBase.release_session(session)
address = to_checksum_address(r[0])
logg.info('resuming gas-in-waiting txs for {}'.format(r[0])) logg.info('resuming gas-in-waiting txs for {}'.format(r[0]))
if len(txs) > 0: if len(txs) > 0:
s = create_check_gas_task( s = create_check_gas_task(
list(txs.values()), list(txs.values()),
self.chain_spec, self.chain_spec,
r[0], address,
0, 0,
tx_hashes_hex=list(txs.keys()), tx_hashes_hex=list(txs.keys()),
queue=self.queue, queue=self.queue,

View File

@@ -12,20 +12,24 @@ from hexathon import (
# local imports # local imports
from .base import SyncFilter from .base import SyncFilter
logg = logging.getLogger().getChild(__name__) logg = logging.getLogger(__name__)
account_registry_add_log_hash = '0x9cc987676e7d63379f176ea50df0ae8d2d9d1141d1231d4ce15b5965f73c9430' account_registry_add_log_hash = '0x9cc987676e7d63379f176ea50df0ae8d2d9d1141d1231d4ce15b5965f73c9430'
class RegistrationFilter(SyncFilter): class RegistrationFilter(SyncFilter):
def __init__(self, chain_spec, queue): def __init__(self, chain_spec, contract_address, queue=None):
self.chain_spec = chain_spec self.chain_spec = chain_spec
self.queue = queue self.queue = queue
self.contract_address = contract_address
def filter(self, conn, block, tx, db_session=None): def filter(self, conn, block, tx, db_session=None):
registered_address = None if self.contract_address != tx.inputs[0]:
logg.debug('not an account registry tx; {} != {}'.format(self.contract_address, tx.inputs[0]))
return None
for l in tx.logs: for l in tx.logs:
event_topic_hex = l['topics'][0] event_topic_hex = l['topics'][0]
if event_topic_hex == account_registry_add_log_hash: if event_topic_hex == account_registry_add_log_hash:

View File

@@ -78,6 +78,14 @@ chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER')) cic_base.rpc.setup(chain_spec, config.get('ETH_PROVIDER'))
rpc = RPCConnection.connect(chain_spec, 'default')
registry = None
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')))
def main(): def main():
@@ -85,7 +93,6 @@ def main():
celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL')) celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
# Connect to blockchain with chainlib # Connect to blockchain with chainlib
rpc = RPCConnection.connect(chain_spec, 'default')
o = block_latest() o = block_latest()
r = rpc.do(o) r = rpc.do(o)
@@ -151,7 +158,8 @@ def main():
tx_filter = TxFilter(chain_spec, config.get('_CELERY_QUEUE')) tx_filter = TxFilter(chain_spec, config.get('_CELERY_QUEUE'))
registration_filter = RegistrationFilter(chain_spec, config.get('_CELERY_QUEUE')) account_registry_address = registry.by_name('AccountRegistry')
registration_filter = RegistrationFilter(chain_spec, account_registry_address, queue=config.get('_CELERY_QUEUE'))
gas_filter = GasFilter(chain_spec, config.get('_CELERY_QUEUE')) gas_filter = GasFilter(chain_spec, config.get('_CELERY_QUEUE'))

View File

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

View File

@@ -6,4 +6,4 @@ HOST=localhost
PORT=5432 PORT=5432
ENGINE=sqlite ENGINE=sqlite
DRIVER=pysqlite DRIVER=pysqlite
DEBUG= DEBUG=0

View File

@@ -1,81 +1,71 @@
FROM python:3.8.6-slim-buster as compile # syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
WORKDIR /usr/src # Copy just the requirements and install....this _might_ give docker a hint on caching but we
# do load these all into setup.py later
RUN apt-get update && \ # TODO can we take all the requirements out of setup.py and just do a pip install -r requirements.txt && python setup.py
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git #COPY cic-eth/requirements.txt .
#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
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 --extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL .[services]
# --- TEST IMAGE ---
FROM python:3.8.6-slim-buster as test
RUN apt-get update && \
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
WORKDIR /usr/src/cic-eth
RUN /usr/local/bin/python -m pip install --upgrade pip
COPY --from=compile /usr/local/bin/ /usr/local/bin/
COPY --from=compile /usr/local/lib/python3.8/site-packages/ \
/usr/local/lib/python3.8/site-packages/
# TODO we could use venv inside container to isolate the system and app deps further
# COPY --from=compile /usr/src/cic-eth/ .
# RUN . venv/bin/activate
ARG 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" ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
#RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
COPY cic-eth/test_requirements.txt . # pip install --index-url https://pypi.org/simple \
RUN pip install --extra-index-url $GITLAB_PYTHON_REGISTRY \ # --force-reinstall \
--extra-index-url $EXTRA_INDEX_URL -r test_requirements.txt # --extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
# -r requirements.txt
COPY cic-eth . COPY *requirements.txt ./
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 \
-r services_requirements.txt \
-r admin_requirements.txt
COPY . .
RUN python setup.py install
ENV PYTHONPATH . ENV PYTHONPATH .
ENTRYPOINT ["pytest"] COPY docker/entrypoints/* ./
# --- RUNTIME ---
FROM python:3.8.6-slim-buster as runtime
RUN apt-get update && \
apt install -y gnupg libpq-dev procps
WORKDIR /usr/src/cic-eth
COPY --from=compile /usr/local/bin/ /usr/local/bin/
COPY --from=compile /usr/local/lib/python3.8/site-packages/ \
/usr/local/lib/python3.8/site-packages/
COPY cic-eth/docker/* ./
RUN chmod 755 *.sh RUN chmod 755 *.sh
COPY cic-eth/scripts/ scripts/
# # 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 cic-eth/config/ /usr/local/etc/cic-eth/ COPY config/ /usr/local/etc/cic-eth/
COPY cic-eth/cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/ COPY cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/ COPY crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
COPY util/liveness/health.sh /usr/local/bin/health.sh # TODO this kind of code sharing across projects should be discouraged...can we make util a library?
#COPY util/liveness/health.sh /usr/local/bin/health.sh
ENTRYPOINT []
## ------------------ PRODUCTION CONTAINER ----------------------
#FROM python:3.8.6-slim-buster as prod
#
#RUN apt-get update && \
# apt install -y gnupg libpq-dev procps
#
#WORKDIR /root
#
#COPY --from=dev /usr/local/bin/ /usr/local/bin/
#COPY --from=dev /usr/local/lib/python3.8/site-packages/ \
# /usr/local/lib/python3.8/site-packages/
#
#COPY docker/entrypoints/* ./
#RUN chmod 755 *.sh
#
## # 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-eth/
#COPY cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
#COPY crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
#COPY scripts/ scripts/
#
## TODO this kind of code sharing across projects should be discouraged...can we make util a library?
##COPY util/liveness/health.sh /usr/local/bin/health.sh
#
#ENTRYPOINT []

View File

@@ -0,0 +1,69 @@
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
WORKDIR /usr/src/cic-eth
# Copy just the requirements and install....this _might_ give docker a hint on caching but we
# do load these all into setup.py later
# TODO can we take all the requirements out of setup.py and just do a pip install -r requirements.txt && python setup.py
#COPY cic-eth/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 --mount=type=cache,mode=0755,target=/root/.cache/pip \
# pip install --index-url https://pypi.org/simple \
# --force-reinstall \
# --extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
# -r requirements.txt
COPY *requirements.txt .
RUN pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL \
-r requirements.txt \
-r services_requirements.txt \
-r admin_requirements.txt
COPY . .
RUN python setup.py install
COPY docker/entrypoints/* ./
RUN chmod 755 *.sh
# # 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-eth/
COPY cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
COPY crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
# TODO this kind of code sharing across projects should be discouraged...can we make util a library?
#COPY util/liveness/health.sh /usr/local/bin/health.sh
ENTRYPOINT []
# ------------------ PRODUCTION CONTAINER ----------------------
#FROM python:3.8.6-slim-buster as prod
#
#RUN apt-get update && \
# apt install -y gnupg libpq-dev procps
#
#WORKDIR /root
#
#COPY --from=dev /usr/local/bin/ /usr/local/bin/
#COPY --from=dev /usr/local/lib/python3.8/site-packages/ \
# /usr/local/lib/python3.8/site-packages/
#
#COPY docker/entrypoints/* ./
#RUN chmod 755 *.sh
#
## # 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-eth/
#COPY cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
#COPY crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
#COPY scripts/ scripts/
#
## TODO this kind of code sharing across projects should be discouraged...can we make util a library?
##COPY util/liveness/health.sh /usr/local/bin/health.sh
#
#ENTRYPOINT []
#

View File

@@ -1,3 +1,3 @@
celery==4.4.7 celery==4.4.7
chainlib~=0.0.5a1 chainlib-eth>=0.0.6a1,<0.1.0
semver==2.13.0 semver==2.13.0

View File

@@ -1,14 +1,15 @@
chainsyncer[sql]~=0.0.3a3 chainqueue>=0.0.3a2,<0.1.0
chainqueue~=0.0.2b5 chainsyncer[sql]>=0.0.5a1,<0.1.0
alembic==1.4.2 alembic==1.4.2
confini~=0.3.6rc4 confini>=0.3.6rc4,<0.5.0
redis==3.5.3 redis==3.5.3
hexathon~=0.0.1a7 hexathon~=0.0.1a7
pycryptodome==3.10.1 pycryptodome==3.10.1
liveness~=0.0.1a7 liveness~=0.0.1a7
eth-address-index~=0.1.2a1 eth-address-index>=0.1.3a1,<0.2.0
eth-accounts-index~=0.0.12a1 eth-accounts-index>=0.0.13a1,<0.1.0
cic-eth-registry~=0.5.6a1 cic-eth-registry>=0.5.7a1,<0.6.0
erc20-faucet~=0.2.2a1 erc20-faucet>=0.2.3a1,<0.3.0
sarafu-faucet~=0.0.4a1 erc20-transfer-authorization>=0.3.3a1,<0.4.0
sarafu-faucet>=0.0.4a5,<0.1.0
moolb~=0.1.1b2 moolb~=0.1.1b2

View File

@@ -6,4 +6,4 @@ pytest-redis==2.0.0
redis==3.5.3 redis==3.5.3
eth-tester==0.5.0b3 eth-tester==0.5.0b3
py-evm==0.3.0a20 py-evm==0.3.0a20
eth-erc20~=0.0.10a2 eth-erc20~=0.0.11a1

View File

@@ -18,6 +18,7 @@ def test_filter_bogus(
cic_registry, cic_registry,
contract_roles, contract_roles,
register_lookups, register_lookups,
account_registry,
): ):
fltrs = [ fltrs = [
@@ -26,7 +27,7 @@ def test_filter_bogus(
TxFilter(default_chain_spec, None), TxFilter(default_chain_spec, None),
CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER']), CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER']),
StragglerFilter(default_chain_spec, None), StragglerFilter(default_chain_spec, None),
RegistrationFilter(default_chain_spec, queue=None), RegistrationFilter(default_chain_spec, account_registry, queue=None),
] ]
for fltr in fltrs: for fltr in fltrs:

View File

@@ -1,3 +1,7 @@
# standard imports
import logging
import os
# external imports # external imports
from eth_accounts_index.registry import AccountRegistry from eth_accounts_index.registry import AccountRegistry
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
@@ -14,12 +18,17 @@ from chainlib.eth.block import (
Block, Block,
) )
from erc20_faucet import Faucet from erc20_faucet import Faucet
from hexathon import strip_0x from hexathon import (
strip_0x,
add_0x,
)
from chainqueue.sql.query import get_account_tx from chainqueue.sql.query import get_account_tx
# local imports # local imports
from cic_eth.runnable.daemons.filters.register import RegistrationFilter from cic_eth.runnable.daemons.filters.register import RegistrationFilter
logg = logging.getLogger()
def test_register_filter( def test_register_filter(
default_chain_spec, default_chain_spec,
@@ -60,7 +69,11 @@ def test_register_filter(
tx = Tx(tx_src, block=block, rcpt=rcpt) tx = Tx(tx_src, block=block, rcpt=rcpt)
tx.apply_receipt(rcpt) tx.apply_receipt(rcpt)
fltr = RegistrationFilter(default_chain_spec, queue=None) fltr = RegistrationFilter(default_chain_spec, add_0x(os.urandom(20).hex()), queue=None)
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
assert t == None
fltr = RegistrationFilter(default_chain_spec, account_registry, queue=None)
t = fltr.filter(eth_rpc, block, tx, db_session=init_database) t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
t.get_leaf() t.get_leaf()

View File

@@ -290,6 +290,7 @@ def test_fix_nonce(
txs = get_nonce_tx_cache(default_chain_spec, 3, agent_roles['ALICE'], session=init_database) txs = get_nonce_tx_cache(default_chain_spec, 3, agent_roles['ALICE'], session=init_database)
ks = txs.keys() ks = txs.keys()
assert len(ks) == 2 assert len(ks) == 2
for k in ks: for k in ks:
hsh = add_0x(k) hsh = add_0x(k)
otx = Otx.load(hsh, session=init_database) otx = Otx.load(hsh, session=init_database)

View File

@@ -184,7 +184,7 @@ def test_admin_api_account(
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER']) api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])
r = api.account(default_chain_spec, agent_roles['ALICE']) r = api.account(default_chain_spec, agent_roles['ALICE'], include_sender=True, include_recipient=True)
assert len(r) == 5 assert len(r) == 5
api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER']) api = AdminApi(eth_rpc, queue=None, call_address=contract_roles['CONTRACT_DEPLOYER'])

View File

@@ -1,4 +1,5 @@
# external imports # external imports
import celery
from chainlib.eth.nonce import RPCNonceOracle from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import ( from chainlib.eth.tx import (
receipt, receipt,
@@ -20,6 +21,7 @@ def test_translate(
cic_registry, cic_registry,
init_celery_tasks, init_celery_tasks,
register_lookups, register_lookups,
celery_session_worker,
): ):
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc) nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc)
@@ -46,6 +48,20 @@ def test_translate(
'recipient': agent_roles['BOB'], 'recipient': agent_roles['BOB'],
'recipient_label': None, 'recipient_label': None,
} }
tx = translate_tx_addresses(tx, [contract_roles['CONTRACT_DEPLOYER']], default_chain_spec.asdict())
assert tx['sender_label'] == 'alice' #tx = translate_tx_addresses(tx, [contract_roles['CONTRACT_DEPLOYER']], default_chain_spec.asdict())
assert tx['recipient_label'] == 'bob' s = celery.signature(
'cic_eth.ext.address.translate_tx_addresses',
[
tx,
[contract_roles['CONTRACT_DEPLOYER']],
default_chain_spec.asdict(),
],
queue=None,
)
t = s.apply_async()
r = t.get_leaf()
assert t.successful()
assert r['sender_label'] == 'alice'
assert r['recipient_label'] == 'bob'

View File

@@ -0,0 +1,92 @@
# external imports
import celery
import pytest
from chainlib.connection import RPCConnection
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.gas import (
RPCGasOracle,
)
from chainlib.eth.tx import (
TxFormat,
unpack,
)
from chainlib.eth.nonce import RPCNonceOracle
from eth_erc20 import ERC20
from hexathon import (
add_0x,
strip_0x,
)
from chainqueue.db.models.tx import TxCache
from chainqueue.db.models.otx import Otx
def test_ext_tx_collate(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
custodial_roles,
agent_roles,
foo_token,
bar_token,
register_tokens,
cic_registry,
register_lookups,
init_celery_tasks,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(custodial_roles['FOO_TOKEN_GIFTER'], eth_rpc)
gas_oracle = RPCGasOracle(eth_rpc)
c = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
transfer_value_foo = 1000
transfer_value_bar = 1024
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(foo_token, custodial_roles['FOO_TOKEN_GIFTER'], agent_roles['ALICE'], transfer_value_foo, tx_format=TxFormat.RLP_SIGNED)
tx = unpack(bytes.fromhex(strip_0x(tx_signed_raw_hex)), default_chain_spec)
otx = Otx(
tx['nonce'],
tx_hash_hex,
tx_signed_raw_hex,
)
init_database.add(otx)
init_database.commit()
txc = TxCache(
tx_hash_hex,
tx['from'],
tx['to'],
foo_token,
bar_token,
transfer_value_foo,
transfer_value_bar,
666,
13,
session=init_database,
)
init_database.add(txc)
init_database.commit()
s = celery.signature(
'cic_eth.ext.tx.tx_collate',
[
{tx_hash_hex: tx_signed_raw_hex},
default_chain_spec.asdict(),
0,
100,
],
queue=None,
)
t = s.apply_async()
r = t.get_leaf()
assert t.successful()
assert len(r) == 1
tx = r[0]
assert tx['source_token_symbol'] == 'FOO'
assert tx['source_token_decimals'] == 6
assert tx['destination_token_symbol'] == 'BAR'
assert tx['destination_token_decimals'] == 9

View File

@@ -1,6 +1,7 @@
# third-party imports # extended imports
import pytest import pytest
import uuid import uuid
import unittest
# local imports # local imports
from cic_eth.db.models.nonce import ( from cic_eth.db.models.nonce import (
@@ -55,7 +56,7 @@ def test_nonce_reserve(
o = q.first() o = q.first()
assert o.nonce == 43 assert o.nonce == 43
nonce = NonceReservation.release(eth_empty_accounts[0], str(uu)) nonce = NonceReservation.release(eth_empty_accounts[0], str(uu), session=init_database)
init_database.commit() init_database.commit()
assert nonce == (str(uu), 42) assert nonce == (str(uu), 42)

View File

@@ -9,6 +9,11 @@ from chainlib.eth.gas import (
Gas, Gas,
) )
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from hexathon import (
add_0x,
strip_0x,
uniform as hex_uniform,
)
# local imports # local imports
from cic_eth.db.enum import LockEnum from cic_eth.db.enum import LockEnum
@@ -34,7 +39,10 @@ def test_upcoming_with_lock(
gas_oracle = RPCGasOracle(eth_rpc) gas_oracle = RPCGasOracle(eth_rpc)
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle) c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, tx_rpc) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6)) alice_normal = add_0x(hex_uniform(strip_0x(agent_roles['ALICE'])))
bob_normal = add_0x(hex_uniform(strip_0x(agent_roles['BOB'])))
(tx_hash_hex, tx_rpc) = c.create(alice_normal, bob_normal, 100 * (10 ** 6))
tx_signed_raw_hex = tx_rpc['params'][0] tx_signed_raw_hex = tx_rpc['params'][0]
register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database) register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database)
@@ -43,12 +51,12 @@ def test_upcoming_with_lock(
txs = get_upcoming_tx(default_chain_spec, StatusEnum.PENDING) txs = get_upcoming_tx(default_chain_spec, StatusEnum.PENDING)
assert len(txs.keys()) == 1 assert len(txs.keys()) == 1
Lock.set(str(default_chain_spec), LockEnum.SEND, address=agent_roles['ALICE']) Lock.set(str(default_chain_spec), LockEnum.SEND, address=alice_normal)
txs = get_upcoming_tx(default_chain_spec, StatusEnum.PENDING) txs = get_upcoming_tx(default_chain_spec, status=StatusEnum.PENDING)
assert len(txs.keys()) == 0 assert len(txs.keys()) == 0
(tx_hash_hex, tx_rpc) = c.create(agent_roles['BOB'], agent_roles['ALICE'], 100 * (10 ** 6)) (tx_hash_hex, tx_rpc) = c.create(bob_normal, alice_normal, 100 * (10 ** 6))
tx_signed_raw_hex = tx_rpc['params'][0] tx_signed_raw_hex = tx_rpc['params'][0]
register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database) register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database)

View File

@@ -1,7 +1,7 @@
crypto-dev-signer~=0.4.14b6 crypto-dev-signer>=0.4.14b7,<=0.4.14
chainqueue~=0.0.2b5 chainqueue~=0.0.2b6
confini~=0.3.6rc4 confini>=0.3.6rc4,<0.5.0
cic-eth-registry~=0.5.6a1 cic-eth-registry>=0.5.7a1,<0.6.0
redis==3.5.3 redis==3.5.3
hexathon~=0.0.1a7 hexathon~=0.0.1a7
pycryptodome==3.10.1 pycryptodome==3.10.1

View File

@@ -0,0 +1,5 @@
.git
.cache
.dot
**/doc
**/node_modules

View File

@@ -2,43 +2,42 @@
.cic_meta_variables: .cic_meta_variables:
variables: variables:
APP_NAME: cic-meta APP_NAME: cic-meta
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile DOCKERFILE_PATH: docker/Dockerfile_ci
IMAGE_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:unittest-$CI_COMMIT_SHORT_SHA CONTEXT: apps/$APP_NAME
.cic_meta_changes_target: build-mr-cic-meta:
extends:
- .py_build_merge_request
- .cic_meta_variables
rules: rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
# - changes: changes:
# - $CONTEXT/$APP_NAME/* - apps/cic-meta/**/*
- when: always when: always
cic-meta-build-mr:
stage: build
extends:
- .cic_meta_variables
- .cic_meta_changes_target
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 --destination $IMAGE_TAG
- /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
test-mr-cic-meta: test-mr-cic-meta:
extends: extends:
- .cic_meta_variables - .cic_meta_variables
- .cic_meta_changes_target
stage: test stage: test
image: $IMAGE_TAG image: $MR_IMAGE_TAG
script: script:
- cd /tmp/src/cic-meta - cd /root
- npm install --dev - npm install --dev
- npm run test - npm run test
- npm run test:coverage - npm run test:coverage
needs: ["cic-meta-build-mr"] needs: ["build-mr-cic-meta"]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/cic-meta/**/*
when: always
build-push-cic-meta: build-push-cic-meta:
extends: extends:
- .py_build_push - .py_build_push
- .cic_meta_variables - .cic_meta_variables
rules:
- if: $CI_COMMIT_BRANCH == "master"
changes:
- apps/cic-meta/**/*
when: always

View File

@@ -1,31 +1,33 @@
FROM node:15.3.0-alpine3.10 # syntax = docker/dockerfile:1.2
#FROM node:15.3.0-alpine3.10
FROM node:lts-alpine3.14
WORKDIR /tmp/src/cic-meta WORKDIR /root
RUN apk add --no-cache postgresql bash RUN apk add --no-cache postgresql bash
# required to build the cic-client-meta module
COPY cic-meta/src/ src/
COPY cic-meta/scripts/ scripts/
# copy the dependencies # copy the dependencies
COPY cic-meta/package.json . COPY package.json package-lock.json .
COPY cic-meta/tsconfig.json . RUN --mount=type=cache,mode=0755,target=/root/.npm \
COPY cic-meta/webpack.config.js . npm set cache /root/.npm && \
npm ci
RUN npm install
COPY cic-meta/tests/ tests/ COPY webpack.config.js .
COPY cic-meta/tests/*.asc /root/pgp/ COPY tsconfig.json .
## required to build the cic-client-meta module
COPY src/ src/
COPY scripts/ scripts/
COPY tests/ tests/
COPY tests/*.asc /root/pgp/
# copy runtime configs ## copy runtime configs
COPY cic-meta/.config/ /usr/local/etc/cic-meta/ COPY .config/ /usr/local/etc/cic-meta/
#
# db migrations ## db migrations
COPY cic-meta/docker/db.sh ./db.sh COPY docker/db.sh ./db.sh
RUN chmod 755 ./db.sh RUN chmod 755 ./db.sh
#
RUN alias tsc=node_modules/typescript/bin/tsc RUN alias tsc=node_modules/typescript/bin/tsc
COPY cic-meta/docker/start_server.sh ./start_server.sh COPY docker/start_server.sh ./start_server.sh
RUN chmod 755 ./start_server.sh RUN chmod 755 ./start_server.sh
ENTRYPOINT ["sh", "./start_server.sh"] ENTRYPOINT ["sh", "./start_server.sh"]

View File

@@ -0,0 +1,32 @@
# syntax = docker/dockerfile:1.2
#FROM node:15.3.0-alpine3.10
FROM node:lts-alpine3.14
WORKDIR /root
RUN apk add --no-cache postgresql bash
# copy the dependencies
COPY package.json package-lock.json .
RUN npm set cache /root/.npm && \
npm ci
COPY webpack.config.js .
COPY tsconfig.json .
## required to build the cic-client-meta module
COPY src/ src/
COPY scripts/ scripts/
COPY tests/ tests/
COPY tests/*.asc /root/pgp/
## copy runtime configs
COPY .config/ /usr/local/etc/cic-meta/
#
## db migrations
COPY docker/db.sh ./db.sh
RUN chmod 755 ./db.sh
#
RUN alias tsc=node_modules/typescript/bin/tsc
COPY docker/start_server.sh ./start_server.sh
RUN chmod 755 ./start_server.sh
ENTRYPOINT ["sh", "./start_server.sh"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,52 @@
.cic_notify_variables: .cic_notify_variables:
variables: variables:
APP_NAME: cic-notify APP_NAME: cic-notify
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile DOCKERFILE_PATH: docker/Dockerfile_ci
CONTEXT: apps/$APP_NAME
.cic_notify_changes_target:
rules:
- changes:
- $CONTEXT/$APP_NAME/*
build-mr-cic-notify: build-mr-cic-notify:
extends: extends:
- .cic_notify_changes_target
- .py_build_merge_request - .py_build_merge_request
- .cic_notify_variables - .cic_notify_variables
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/cic-notify/**/*
when: always
test-mr-cic-notify:
stage: test
extends:
- .cic_notify_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_notify --cov-fail-under=90 --cov-report term-missing tests
needs: ["build-mr-cic-notify"]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/$APP_NAME/**/*
when: always
build-push-cic-notify: build-push-cic-notify:
extends: extends:
- .py_build_push - .py_build_push
- .cic_notify_variables - .cic_notify_variables
rules:
- if: $CI_COMMIT_BRANCH == "master"
changes:
- apps/cic-notify/**/*
when: always

View File

@@ -34,6 +34,8 @@ elif args.v:
config = confini.Config(args.c, args.env_prefix) config = confini.Config(args.c, args.env_prefix)
config.process() config.process()
config.add(args.q, '_CELERY_QUEUE', True) config.add(args.q, '_CELERY_QUEUE', True)
config.censor('API_KEY', 'AFRICASTALKING')
config.censor('API_USERNAME', 'AFRICASTALKING')
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config)) logg.debug('config loaded from {}:\n{}'.format(args.c, config))

View File

@@ -9,7 +9,7 @@ import semver
logg = logging.getLogger() logg = logging.getLogger()
version = (0, 4, 0, 'alpha.7') version = (0, 4, 0, 'alpha.10')
version_object = semver.VersionInfo( version_object = semver.VersionInfo(
major=version[0], major=version[0],

View File

@@ -1,38 +1,28 @@
FROM python:3.8.6-slim-buster # syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
RUN apt-get update && \ #RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a62
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps
WORKDIR /usr/src/cic-notify ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
COPY requirements.txt .
ARG pip_extra_index_url_flag='--index https://pypi.org/simple --extra-index-url https://pip.grassrootseconomics.net:8433' RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a62 pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
-r requirements.txt
COPY cic-notify/setup.cfg \ COPY . .
cic-notify/setup.py \ RUN python setup.py install
./
COPY cic-notify/cic_notify/ ./cic_notify/ COPY docker/*.sh .
RUN chmod +x *.sh
COPY cic-notify/requirements.txt \
cic-notify/test_requirements.txt \
./
COPY cic-notify/scripts/ scripts/
RUN pip install $pip_extra_index_url_flag .[africastalking,notifylog]
COPY cic-notify/tests/ tests/
COPY cic-notify/docker/db.sh \
cic-notify/docker/start_tasker.sh \
/root/
#RUN apk add postgresql-client
#RUN apk add bash
# 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 cic-notify/.config/ /usr/local/etc/cic-notify/ COPY .config/ /usr/local/etc/cic-notify/
COPY cic-notify/cic_notify/db/migrations/ /usr/local/share/cic-notify/alembic/ COPY cic_notify/db/migrations/ /usr/local/share/cic-notify/alembic/
WORKDIR /root
ENTRYPOINT []

View File

@@ -0,0 +1,27 @@
# 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.2a62
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
COPY requirements.txt .
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
COPY docker/*.sh .
RUN chmod +x *.sh
# 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-notify/
COPY cic_notify/db/migrations/ /usr/local/share/cic-notify/alembic/
ENTRYPOINT []

View File

@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
set -e
migrate.py -c /usr/local/etc/cic-notify --migrations-dir /usr/local/share/cic-notify/alembic -vv python scripts/migrate.py -c /usr/local/etc/cic-notify --migrations-dir /usr/local/share/cic-notify/alembic -vv

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
. ./db.sh . /root/db.sh
/usr/local/bin/cic-notify-tasker -vv $@ /usr/local/bin/cic-notify-tasker -vv $@

View File

@@ -1 +1,7 @@
cic_base[full_graph]==0.1.3a3+build.984b5cff confini~=0.4.1a1
africastalking==1.2.3
SQLAlchemy==1.3.20
alembic==1.4.2
psycopg2==2.8.6
celery==4.4.7
redis==3.5.3

View File

@@ -35,9 +35,10 @@ elif args.v:
config = confini.Config(args.c, args.env_prefix) config = confini.Config(args.c, args.env_prefix)
config.process() config.process()
config.censor('API_KEY', 'AFRICASTALKING')
config.censor('API_USERNAME', 'AFRICASTALKING')
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
#config.censor('PASSWORD', 'SSL') logg.debug('config loaded from {}:\n{}'.format(args.c, config))
logg.debug('config:\n{}'.format(config))
migrations_dir = os.path.join(args.migrations_dir, config.get('DATABASE_ENGINE')) migrations_dir = os.path.join(args.migrations_dir, config.get('DATABASE_ENGINE'))
if not os.path.isdir(migrations_dir): if not os.path.isdir(migrations_dir):

View File

@@ -29,18 +29,11 @@ packages =
cic_notify.db cic_notify.db
cic_notify.db.models cic_notify.db.models
cic_notify.ext cic_notify.ext
cic_notify.tasks
cic_notify.tasks.sms cic_notify.tasks.sms
cic_notify.runnable cic_notify.runnable
scripts = scripts =
scripts/migrate.py ./scripts/migrate.py
[options.extras_require]
africastalking = africastalking==1.2.3
notifylog = psycopg2==2.8.6
testing =
pytest==6.0.1
pytest-celery==0.0.0a1
pytest-mock==3.3.1
pysqlite3==0.4.3
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =

View File

@@ -2,3 +2,4 @@ pytest~=6.0.1
pytest-celery~=0.0.0a1 pytest-celery~=0.0.0a1
pytest-mock~=3.3.1 pytest-mock~=3.3.1
pysqlite3~=0.4.3 pysqlite3~=0.4.3
pytest-cov==2.10.1

View File

@@ -1,7 +1,7 @@
[app] [app]
ALLOWED_IP=0.0.0.0/0 ALLOWED_IP=0.0.0.0/0
LOCALE_FALLBACK=en LOCALE_FALLBACK=sw
LOCALE_PATH=/usr/src/cic-ussd/var/lib/locale/ LOCALE_PATH=var/lib/locale/
MAX_BODY_LENGTH=1024 MAX_BODY_LENGTH=1024
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I= PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
SERVICE_CODE=*483*46#,*483*061#,*384*96# SERVICE_CODE=*483*46#,*483*061#,*384*96#
@@ -11,13 +11,13 @@ SUPPORT_PHONE_NUMBER=0757628885
REGION=KE REGION=KE
[ussd] [ussd]
MENU_FILE=/usr/src/data/ussd_menu.json MENU_FILE=data/ussd_menu.json
user = user =
pass = pass =
[statemachine] [statemachine]
STATES=/usr/src/cic-ussd/states/ STATES=states/
TRANSITIONS=/usr/src/cic-ussd/transitions/ TRANSITIONS=transitions/
[client] [client]
host = host =

View File

@@ -1,5 +1,5 @@
[pgp] [pgp]
export_dir = /usr/src/pgp/keys/ export_dir = pgp/keys/
keys_path = /usr/src/secrets/ keys_path = /usr/src/secrets/
private_keys = privatekeys_meta.asc private_keys = privatekeys_meta.asc
passphrase = passphrase =

View File

@@ -0,0 +1,4 @@
.git
.cache
.dot
**/doc

View File

@@ -1,22 +1,52 @@
.cic_ussd_variables: .cic_ussd_variables:
variables: variables:
APP_NAME: cic-ussd APP_NAME: cic-ussd
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile DOCKERFILE_PATH: docker/Dockerfile_ci
CONTEXT: apps/$APP_NAME
.cic_ussd_changes_target:
rules:
- changes:
- $CONTEXT/$APP_NAME/*
build-mr-cic-ussd: build-mr-cic-ussd:
extends: extends:
- .cic_ussd_changes_target
- .py_build_merge_request - .py_build_merge_request
- .cic_ussd_variables - .cic_ussd_variables
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/cic-ussd/**/*
when: always
test-mr-cic-ussd:
stage: test
extends:
- .cic_ussd_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_eth --cov-fail-under=90 --cov-report term-missing tests
needs: ["build-mr-cic-ussd"]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- apps/$APP_NAME/**/*
when: always
build-push-cic-ussd: build-push-cic-ussd:
extends: extends:
- .py_build_push - .py_build_push
- .cic_ussd_variables - .cic_ussd_variables
rules:
- if: $CI_COMMIT_BRANCH == "master"
changes:
- apps/cic-ussd/**/*
when: always

View File

@@ -15,45 +15,47 @@ from cic_ussd.conversions import from_wei
logg = logging.getLogger() logg = logging.getLogger()
class BalanceManager: def get_balances(
address: str,
def __init__(self, address: str, chain_str: str, token_symbol: str): chain_str: str,
""" token_symbol: str,
:param address: Ethereum address of account whose balance is being queried asynchronous: bool = False,
:type address: str, 0x-hex callback_param: any = None,
:param chain_str: The chain name and network id. callback_task='cic_ussd.tasks.callback_handler.process_balances_callback') -> Union[celery.Task, dict]:
:type chain_str: str """
:param token_symbol: ERC20 token symbol of whose balance is being queried This function queries cic-eth for an account's balances, It provides a means to receive the balance either
:type token_symbol: str asynchronously or synchronously depending on the provided value for teh asynchronous parameter. It returns a
""" dictionary containing network, outgoing and incoming balances.
self.address = address :param address: Ethereum address of the recipient
self.chain_str = chain_str :type address: str, 0x-hex
self.token_symbol = token_symbol :param chain_str: The chain name and network id.
:type chain_str: str
def get_balances(self, asynchronous: bool = False) -> Union[celery.Task, dict]: :param callback_param:
""" :type callback_param:
This function queries cic-eth for an account's balances, It provides a means to receive the balance either :param callback_task:
asynchronously or synchronously depending on the provided value for teh asynchronous parameter. It returns a :type callback_task:
dictionary containing network, outgoing and incoming balances. :param token_symbol: ERC20 token symbol of the account whose balance is being queried.
:param asynchronous: Boolean value checking whether to return balances asynchronously :type token_symbol: str
:type asynchronous: bool :param asynchronous: Boolean value checking whether to return balances asynchronously.
:return: :type asynchronous: bool
:rtype: :return:
""" :rtype:
if asynchronous: """
cic_eth_api = Api( logg.debug(f'Retrieving balance for address: {address}')
chain_str=self.chain_str, if asynchronous:
callback_queue='cic-ussd', cic_eth_api = Api(
callback_task='cic_ussd.tasks.callback_handler.process_balances_callback', chain_str=chain_str,
callback_param='' callback_queue='cic-ussd',
) callback_task=callback_task,
cic_eth_api.balance(address=self.address, token_symbol=self.token_symbol) callback_param=callback_param
else: )
cic_eth_api = Api(chain_str=self.chain_str) cic_eth_api.balance(address=address, token_symbol=token_symbol)
balance_request_task = cic_eth_api.balance( else:
address=self.address, cic_eth_api = Api(chain_str=chain_str)
token_symbol=self.token_symbol) balance_request_task = cic_eth_api.balance(
return balance_request_task.get()[0] address=address,
token_symbol=token_symbol)
return balance_request_task.get()[0]
def compute_operational_balance(balances: dict) -> float: def compute_operational_balance(balances: dict) -> float:

View File

@@ -127,3 +127,4 @@ class SessionBase(Model):
logg.debug('commit and destroy session {}'.format(session_key)) logg.debug('commit and destroy session {}'.format(session_key))
session.commit() session.commit()
session.close() session.close()
del SessionBase.localsessions[session_key]

View File

@@ -48,3 +48,6 @@ class InitializationError(Exception):
pass pass
class UnknownUssdRecipient(Exception):
"""Raised when a recipient of a transaction is not known to the ussd application."""

View File

@@ -127,14 +127,19 @@ class MetadataRequestsHandler(Metadata):
if not isinstance(result_data, dict): if not isinstance(result_data, dict):
raise ValueError(f'Invalid result data object: {result_data}.') raise ValueError(f'Invalid result data object: {result_data}.')
if result.status_code == 200 and self.cic_type == ':cic.person': if result.status_code == 200:
# validate person metadata if self.cic_type == ':cic.person':
person = Person() # validate person metadata
person_data = person.deserialize(person_data=result_data) person = Person()
person_data = person.deserialize(person_data=result_data)
# format new person data for caching # format new person data for caching
data = json.dumps(person_data.serialize()) serialized_person_data = person_data.serialize()
data = json.dumps(serialized_person_data)
else:
data = json.dumps(result_data)
# cache metadata # cache metadata
cache_data(key=self.metadata_pointer, data=data) cache_data(key=self.metadata_pointer, data=data)
logg.debug(f'caching: {data} with key: {self.metadata_pointer}') logg.debug(f'caching: {data} with key: {self.metadata_pointer}')
return result_data

View File

@@ -1,6 +1,7 @@
# standard imports # standard imports
# external imports # external imports
import celery
# local imports # local imports
from .base import MetadataRequestsHandler from .base import MetadataRequestsHandler

View File

@@ -12,7 +12,7 @@ from tinydb.table import Document
# local imports # local imports
from cic_ussd.account import define_account_tx_metadata, retrieve_account_statement from cic_ussd.account import define_account_tx_metadata, retrieve_account_statement
from cic_ussd.balance import BalanceManager, compute_operational_balance, get_cached_operational_balance from cic_ussd.balance import compute_operational_balance, get_balances, get_cached_operational_balance
from cic_ussd.chain import Chain from cic_ussd.chain import Chain
from cic_ussd.db.models.account import Account from cic_ussd.db.models.account import Account
from cic_ussd.db.models.base import SessionBase from cic_ussd.db.models.base import SessionBase
@@ -182,7 +182,6 @@ def process_transaction_pin_authorization(account: Account, display_key: str, se
token_symbol = retrieve_token_symbol() token_symbol = retrieve_token_symbol()
user_input = ussd_session.get('session_data').get('transaction_amount') user_input = ussd_session.get('session_data').get('transaction_amount')
transaction_amount = to_wei(value=int(user_input)) transaction_amount = to_wei(value=int(user_input))
logg.debug('Requires integration to determine user tokens.')
return process_pin_authorization( return process_pin_authorization(
account=account, account=account,
display_key=display_key, display_key=display_key,
@@ -380,12 +379,9 @@ def process_start_menu(display_key: str, user: Account):
token_symbol = retrieve_token_symbol() token_symbol = retrieve_token_symbol()
chain_str = Chain.spec.__str__() chain_str = Chain.spec.__str__()
blockchain_address = user.blockchain_address blockchain_address = user.blockchain_address
balance_manager = BalanceManager(address=blockchain_address,
chain_str=chain_str,
token_symbol=token_symbol)
# get balances synchronously for display on start menu # get balances synchronously for display on start menu
balances_data = balance_manager.get_balances() balances_data = get_balances(address=blockchain_address, chain_str=chain_str, token_symbol=token_symbol)
key = create_cached_data_key( key = create_cached_data_key(
identifier=bytes.fromhex(blockchain_address[2:]), identifier=bytes.fromhex(blockchain_address[2:]),

View File

@@ -28,7 +28,7 @@ elif args.v:
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
# parse config # parse config
config = Config(config_dir=args.c, env_prefix=args.env_prefix) config = Config(args.c, env_prefix=args.env_prefix)
config.process() config.process()
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config)) logg.debug('config loaded from {}:\n{}'.format(args.c, config))

View File

@@ -8,13 +8,16 @@ import tempfile
import celery import celery
import i18n import i18n
import redis import redis
from chainlib.chain import ChainSpec
from confini import Config from confini import Config
# local imports # local imports
from cic_ussd.chain import Chain
from cic_ussd.db import dsn_from_config from cic_ussd.db import dsn_from_config
from cic_ussd.db.models.base import SessionBase from cic_ussd.db.models.base import SessionBase
from cic_ussd.metadata.signer import Signer from cic_ussd.metadata.signer import Signer
from cic_ussd.metadata.base import Metadata from cic_ussd.metadata.base import Metadata
from cic_ussd.phone_number import Support
from cic_ussd.redis import InMemoryStore from cic_ussd.redis import InMemoryStore
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
from cic_ussd.validator import validate_presence from cic_ussd.validator import validate_presence
@@ -82,6 +85,15 @@ Signer.key_file_path = key_file_path
i18n.load_path.append(config.get('APP_LOCALE_PATH')) i18n.load_path.append(config.get('APP_LOCALE_PATH'))
i18n.set('fallback', config.get('APP_LOCALE_FALLBACK')) i18n.set('fallback', config.get('APP_LOCALE_FALLBACK'))
chain_spec = ChainSpec(
common_name=config.get('CIC_COMMON_NAME'),
engine=config.get('CIC_ENGINE'),
network_id=config.get('CIC_NETWORK_ID')
)
Chain.spec = chain_spec
Support.phone_number = config.get('APP_SUPPORT_PHONE_NUMBER')
# set up celery # set up celery
current_app = celery.Celery(__name__) current_app = celery.Celery(__name__)

View File

@@ -45,7 +45,7 @@ elif args.v:
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
# parse config # parse config
config = Config(config_dir=args.c, env_prefix=args.env_prefix) config = Config(args.c, env_prefix=args.env_prefix)
config.process() config.process()
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
logg.debug('config loaded from {}:\n{}'.format(args.c, config)) logg.debug('config loaded from {}:\n{}'.format(args.c, config))

View File

@@ -1,8 +1,4 @@
# standard import # standard import
import os
import logging
import urllib
import json
# third-party imports # third-party imports
# this must be included for the package to be recognized as a tasks package # this must be included for the package to be recognized as a tasks package
@@ -14,3 +10,5 @@ from .logger import *
from .ussd_session import * from .ussd_session import *
from .callback_handler import * from .callback_handler import *
from .metadata import * from .metadata import *
from .notifications import *
from .processor import *

View File

@@ -7,14 +7,14 @@ from datetime import datetime, timedelta
import celery import celery
# local imports # local imports
from cic_ussd.balance import compute_operational_balance, get_balances
from cic_ussd.chain import Chain
from cic_ussd.conversions import from_wei from cic_ussd.conversions import from_wei
from cic_ussd.db.models.base import SessionBase from cic_ussd.db.models.base import SessionBase
from cic_ussd.db.models.account import Account from cic_ussd.db.models.account import Account
from cic_ussd.account import define_account_tx_metadata
from cic_ussd.error import ActionDataNotFoundError from cic_ussd.error import ActionDataNotFoundError
from cic_ussd.redis import InMemoryStore, cache_data, create_cached_data_key from cic_ussd.redis import InMemoryStore, cache_data, create_cached_data_key, get_cached_data
from cic_ussd.tasks.base import CriticalSQLAlchemyTask from cic_ussd.tasks.base import CriticalSQLAlchemyTask
from cic_ussd.transactions import IncomingTransactionProcessor
logg = logging.getLogger(__file__) logg = logging.getLogger(__file__)
celery_app = celery.current_app celery_app = celery.current_app
@@ -58,9 +58,9 @@ def process_account_creation_callback(self, result: str, url: str, status_code:
# add phone number metadata lookup # add phone number metadata lookup
s_phone_pointer = celery.signature( s_phone_pointer = celery.signature(
'cic_ussd.tasks.metadata.add_phone_pointer', 'cic_ussd.tasks.metadata.add_phone_pointer',
[result, phone_number] [result, phone_number]
) )
s_phone_pointer.apply_async(queue=queue) s_phone_pointer.apply_async(queue=queue)
# add custom metadata tags # add custom metadata tags
@@ -87,59 +87,106 @@ def process_account_creation_callback(self, result: str, url: str, status_code:
session.close() session.close()
@celery_app.task @celery_app.task(bind=True)
def process_incoming_transfer_callback(result: dict, param: str, status_code: int): def process_transaction_callback(self, result: dict, param: str, status_code: int):
session = SessionBase.create_session()
if status_code == 0: if status_code == 0:
chain_str = Chain.spec.__str__()
# collect result data # collect transaction metadata
destination_token_symbol = result.get('destination_token_symbol')
destination_token_value = result.get('destination_token_value')
recipient_blockchain_address = result.get('recipient') recipient_blockchain_address = result.get('recipient')
sender_blockchain_address = result.get('sender') sender_blockchain_address = result.get('sender')
token_symbol = result.get('destination_token_symbol') source_token_symbol = result.get('source_token_symbol')
value = result.get('destination_token_value') source_token_value = result.get('source_token_value')
# try to find users in system # build stakeholder callback params
recipient_user = session.query(Account).filter_by(blockchain_address=recipient_blockchain_address).first() recipient_metadata = {
sender_user = session.query(Account).filter_by(blockchain_address=sender_blockchain_address).first() "token_symbol": destination_token_symbol,
"token_value": destination_token_value,
"blockchain_address": recipient_blockchain_address,
"tag": "recipient",
"tx_param": param
}
# check whether recipient is in the system # retrieve account balances
if not recipient_user: get_balances(
session.close() address=recipient_blockchain_address,
raise ValueError( callback_param=recipient_metadata,
f'Tx for recipient: {recipient_blockchain_address} was received but has no matching user in the system.' chain_str=chain_str,
) callback_task='cic_ussd.tasks.callback_handler.process_transaction_balances_callback',
token_symbol=destination_token_symbol,
asynchronous=True)
# process incoming transactions # only retrieve sender if transaction is a transfer
incoming_tx_processor = IncomingTransactionProcessor(phone_number=recipient_user.phone_number, if param == 'transfer':
preferred_language=recipient_user.preferred_language, sender_metadata = {
token_symbol=token_symbol, "blockchain_address": sender_blockchain_address,
value=value) "token_symbol": source_token_symbol,
"token_value": source_token_value,
"tag": "sender",
"tx_param": param
}
if param == 'tokengift': get_balances(
incoming_tx_processor.process_token_gift_incoming_transactions() address=sender_blockchain_address,
elif param == 'transfer': callback_param=sender_metadata,
if sender_user: chain_str=chain_str,
sender_information = define_account_tx_metadata(user=sender_user) callback_task='cic_ussd.tasks.callback_handler.process_transaction_balances_callback',
incoming_tx_processor.process_transfer_incoming_transaction( token_symbol=source_token_symbol,
sender_information=sender_information, asynchronous=True)
recipient_blockchain_address=recipient_blockchain_address else:
) raise ValueError(f'Unexpected status code: {status_code}.')
else:
logg.warning(
f'Tx with sender: {sender_blockchain_address} was received but has no matching user in the system.' @celery_app.task(bind=True)
) def process_transaction_balances_callback(self, result: list, param: dict, status_code: int):
incoming_tx_processor.process_transfer_incoming_transaction( queue = self.request.delivery_info.get('routing_key')
sender_information='GRASSROOTS ECONOMICS', if status_code == 0:
recipient_blockchain_address=recipient_blockchain_address # retrieve balance data
) balances_data = result[0]
else: operational_balance = compute_operational_balance(balances=balances_data)
session.close()
raise ValueError(f'Unexpected transaction param: {param}.') # retrieve account's address
blockchain_address = param.get('blockchain_address')
# append balance to transaction metadata
transaction_metadata = param
transaction_metadata['operational_balance'] = operational_balance
# retrieve account's preferences
s_preferences_metadata = celery.signature(
'cic_ussd.tasks.metadata.query_preferences_metadata',
[blockchain_address],
queue=queue
)
# parse metadata and run validations
s_process_account_metadata = celery.signature(
'cic_ussd.tasks.processor.process_tx_metadata_for_notification',
[transaction_metadata],
queue=queue
)
# issue notification of transaction
s_notify_account = celery.signature(
'cic_ussd.tasks.notifications.notify_account_of_transaction',
queue=queue
)
if param.get('tx_param') == 'transfer':
celery.chain(s_preferences_metadata, s_process_account_metadata, s_notify_account).apply_async()
if param.get('tx_param') == 'tokengift':
s_process_account_metadata = celery.signature(
'cic_ussd.tasks.processor.process_tx_metadata_for_notification',
[{}, transaction_metadata],
queue=queue
)
celery.chain(s_process_account_metadata, s_notify_account).apply_async()
else: else:
session.close()
raise ValueError(f'Unexpected status code: {status_code}.') raise ValueError(f'Unexpected status code: {status_code}.')
session.close()
@celery_app.task @celery_app.task
def process_balances_callback(result: list, param: str, status_code: int): def process_balances_callback(result: list, param: str, status_code: int):
@@ -151,6 +198,7 @@ def process_balances_callback(result: list, param: str, status_code: int):
salt=':cic.balances_data' salt=':cic.balances_data'
) )
cache_data(key=key, data=json.dumps(balances_data)) cache_data(key=key, data=json.dumps(balances_data))
logg.debug(f'caching: {balances_data} with key: {key}')
else: else:
raise ValueError(f'Unexpected status code: {status_code}.') raise ValueError(f'Unexpected status code: {status_code}.')

View File

@@ -26,6 +26,7 @@ def query_person_metadata(blockchain_address: str):
:rtype: :rtype:
""" """
identifier = blockchain_address_to_metadata_pointer(blockchain_address=blockchain_address) identifier = blockchain_address_to_metadata_pointer(blockchain_address=blockchain_address)
logg.debug(f'Retrieving person metadata for address: {blockchain_address}.')
person_metadata_client = PersonMetadata(identifier=identifier) person_metadata_client = PersonMetadata(identifier=identifier)
person_metadata_client.query() person_metadata_client.query()
@@ -72,3 +73,15 @@ def add_preferences_metadata(blockchain_address: str, data: dict):
identifier = blockchain_address_to_metadata_pointer(blockchain_address=blockchain_address) identifier = blockchain_address_to_metadata_pointer(blockchain_address=blockchain_address)
custom_metadata_client = PreferencesMetadata(identifier=identifier) custom_metadata_client = PreferencesMetadata(identifier=identifier)
custom_metadata_client.create(data=data) custom_metadata_client.create(data=data)
@celery_app.task()
def query_preferences_metadata(blockchain_address: str):
"""This method retrieves preferences metadata based on an account's blockchain address.
:param blockchain_address: Blockchain address of an account.
:type blockchain_address: str | Ox-hex
"""
identifier = blockchain_address_to_metadata_pointer(blockchain_address=blockchain_address)
logg.debug(f'Retrieving preferences metadata for address: {blockchain_address}.')
person_metadata_client = PreferencesMetadata(identifier=identifier)
return person_metadata_client.query()

View File

@@ -0,0 +1,70 @@
# standard imports
import datetime
import logging
# third-party imports
import celery
# local imports
from cic_ussd.notifications import Notifier
from cic_ussd.phone_number import Support
celery_app = celery.current_app
logg = logging.getLogger(__file__)
notifier = Notifier()
@celery_app.task
def notify_account_of_transaction(notification_data: dict):
"""
:param notification_data:
:type notification_data:
:return:
:rtype:
"""
account_tx_role = notification_data.get('account_tx_role')
amount = notification_data.get('amount')
balance = notification_data.get('balance')
phone_number = notification_data.get('phone_number')
preferred_language = notification_data.get('preferred_language')
token_symbol = notification_data.get('token_symbol')
transaction_account_metadata = notification_data.get('transaction_account_metadata')
transaction_type = notification_data.get('transaction_type')
timestamp = datetime.datetime.now().strftime('%d-%m-%y, %H:%M %p')
if transaction_type == 'tokengift':
support_phone = Support.phone_number
notifier.send_sms_notification(
key='sms.account_successfully_created',
phone_number=phone_number,
preferred_language=preferred_language,
balance=balance,
support_phone=support_phone,
token_symbol=token_symbol
)
if transaction_type == 'transfer':
if account_tx_role == 'recipient':
notifier.send_sms_notification(
key='sms.received_tokens',
phone_number=phone_number,
preferred_language=preferred_language,
amount=amount,
token_symbol=token_symbol,
tx_sender_information=transaction_account_metadata,
timestamp=timestamp,
balance=balance
)
else:
notifier.send_sms_notification(
key='sms.sent_tokens',
phone_number=phone_number,
preferred_language=preferred_language,
amount=amount,
token_symbol=token_symbol,
tx_recipient_information=transaction_account_metadata,
timestamp=timestamp,
balance=balance
)

View File

@@ -0,0 +1,88 @@
# standard imports
import logging
# third-party imports
import celery
from i18n import config
# local imports
from cic_ussd.account import define_account_tx_metadata
from cic_ussd.db.models.account import Account
from cic_ussd.db.models.base import SessionBase
from cic_ussd.error import UnknownUssdRecipient
from cic_ussd.transactions import from_wei
celery_app = celery.current_app
logg = logging.getLogger(__file__)
@celery_app.task
def process_tx_metadata_for_notification(result: celery.Task, transaction_metadata: dict):
"""
:param result:
:type result:
:param transaction_metadata:
:type transaction_metadata:
:return:
:rtype:
"""
notification_data = {}
# get preferred language
preferred_language = result.get('preferred_language')
if not preferred_language:
preferred_language = config.get('fallback')
notification_data['preferred_language'] = preferred_language
# validate account information against present ussd storage data.
session = SessionBase.create_session()
blockchain_address = transaction_metadata.get('blockchain_address')
tag = transaction_metadata.get('tag')
account = session.query(Account).filter_by(blockchain_address=blockchain_address).first()
if not account and tag == 'recipient':
session.close()
raise UnknownUssdRecipient(
f'Tx for recipient: {blockchain_address} was received but has no matching user in the system.'
)
# get phone number associated with account
phone_number = account.phone_number
notification_data['phone_number'] = phone_number
# get account's role in transaction i.e sender / recipient
tx_param = transaction_metadata.get('tx_param')
notification_data['transaction_type'] = tx_param
# get token amount and symbol
if tag == 'recipient':
account_tx_role = tag
amount = transaction_metadata.get('token_value')
amount = from_wei(value=amount)
token_symbol = transaction_metadata.get('token_symbol')
else:
account_tx_role = tag
amount = transaction_metadata.get('token_value')
amount = from_wei(value=amount)
token_symbol = transaction_metadata.get('token_symbol')
notification_data['account_tx_role'] = account_tx_role
notification_data['amount'] = amount
notification_data['token_symbol'] = token_symbol
# get account's standard ussd identification pattern
if tx_param == 'transfer':
tx_account_metadata = define_account_tx_metadata(user=account)
notification_data['transaction_account_metadata'] = tx_account_metadata
if tag == 'recipient':
notification_data['notification_key'] = 'sms.received_tokens'
else:
notification_data['notification_key'] = 'sms.sent_tokens'
if tx_param == 'tokengift':
notification_data['notification_key'] = 'sms.account_successfully_created'
# get account's balance
notification_data['balance'] = transaction_metadata.get('operational_balance')
return notification_data

View File

@@ -7,9 +7,9 @@ from datetime import datetime
from cic_eth.api import Api from cic_eth.api import Api
# local imports # local imports
from cic_ussd.balance import get_cached_operational_balance from cic_ussd.balance import get_balances, get_cached_operational_balance
from cic_ussd.notifications import Notifier from cic_ussd.notifications import Notifier
from cic_ussd.phone_number import Support
logg = logging.getLogger() logg = logging.getLogger()
notifier = Notifier() notifier = Notifier()
@@ -50,61 +50,6 @@ def to_wei(value: int) -> int:
return int(value * 1e+6) return int(value * 1e+6)
class IncomingTransactionProcessor:
def __init__(self, phone_number: str, preferred_language: str, token_symbol: str, value: int):
"""
:param phone_number: The recipient's phone number.
:type phone_number: str
:param preferred_language: The user's preferred language.
:type preferred_language: str
:param token_symbol: The symbol for the token the recipient receives.
:type token_symbol: str
:param value: The amount of tokens received in the transactions.
:type value: int
"""
self.phone_number = phone_number
self.preferred_language = preferred_language
self.token_symbol = token_symbol
self.value = value
def process_token_gift_incoming_transactions(self):
"""This function processes incoming transactions with a "tokengift" param, it collects all appropriate data to
send out notifications to users when their accounts are successfully created.
"""
balance = from_wei(value=self.value)
key = 'sms.account_successfully_created'
notifier.send_sms_notification(key=key,
phone_number=self.phone_number,
preferred_language=self.preferred_language,
balance=balance,
token_symbol=self.token_symbol)
def process_transfer_incoming_transaction(self, sender_information: str, recipient_blockchain_address: str):
"""This function processes incoming transactions with the "transfer" param and issues notifications to users
about reception of funds into their accounts.
:param sender_information: A string with a user's full name and phone number.
:type sender_information: str
:param recipient_blockchain_address:
type recipient_blockchain_address: str
"""
key = 'sms.received_tokens'
amount = from_wei(value=self.value)
timestamp = datetime.now().strftime('%d-%m-%y, %H:%M %p')
operational_balance = get_cached_operational_balance(blockchain_address=recipient_blockchain_address)
notifier.send_sms_notification(key=key,
phone_number=self.phone_number,
preferred_language=self.preferred_language,
amount=amount,
token_symbol=self.token_symbol,
tx_sender_information=sender_information,
timestamp=timestamp,
balance=operational_balance)
class OutgoingTransactionProcessor: class OutgoingTransactionProcessor:
def __init__(self, chain_str: str, from_address: str, to_address: str): def __init__(self, chain_str: str, from_address: str, to_address: str):
@@ -116,6 +61,7 @@ class OutgoingTransactionProcessor:
:param to_address: Ethereum address of the recipient :param to_address: Ethereum address of the recipient
:type to_address: str, 0x-hex :type to_address: str, 0x-hex
""" """
self.chain_str = chain_str
self.cic_eth_api = Api(chain_str=chain_str) self.cic_eth_api = Api(chain_str=chain_str)
self.from_address = from_address self.from_address = from_address
self.to_address = to_address self.to_address = to_address

View File

@@ -1,14 +1,5 @@
# FROM python:3.8.5-alpine # 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
# set working directory
WORKDIR /usr/src
# add args for installing from self-hosted packages
ARG pip_extra_index_url_flag='--extra-index-url https://pip.grassrootseconomics.net:8433'
RUN apt-get update && \
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
# create secrets directory # create secrets directory
RUN mkdir -vp pgp/keys RUN mkdir -vp pgp/keys
@@ -17,39 +8,25 @@ RUN mkdir -vp pgp/keys
RUN mkdir -vp cic-ussd RUN mkdir -vp cic-ussd
RUN mkdir -vp data RUN mkdir -vp data
COPY cic-ussd/setup.cfg \ ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
cic-ussd/setup.py \ ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
cic-ussd/ COPY requirements.txt .
COPY cic-ussd/requirements.txt \ RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
cic-ussd/test_requirements.txt \ pip install --index-url https://pypi.org/simple \
cic-ussd/ --extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
-r requirements.txt
# install requirements COPY . .
RUN cd cic-ussd && \ RUN python setup.py install
pip install -r requirements.txt $pip_extra_index_url_flag
# copy all necessary files COPY cic_ussd/db/ussd_menu.json data/
COPY cic-ussd/cic_ussd/ cic-ussd/cic_ussd/
COPY cic-ussd/cic_ussd/db/ussd_menu.json data/
COPY cic-ussd/scripts/ cic-ussd/scripts/
COPY cic-ussd/states/ cic-ussd/states/
COPY cic-ussd/transitions/ cic-ussd/transitions/
COPY cic-ussd/var/ cic-ussd/var/
COPY cic-ussd/docker/db.sh \
cic-ussd/docker/start_cic_user_tasker.sh \
cic-ussd/docker/start_cic_user_ussd_server.sh\
cic-ussd/docker/start_cic_user_server.sh\
/root/
COPY docker/*.sh .
RUN chmod +x /root/*.sh RUN chmod +x /root/*.sh
RUN cd cic-ussd && \
pip install $pip_extra_index_url_flag .
# copy config and migration files to definitive file so they can be referenced in path definitions for running scripts # copy config and migration files to definitive file so they can be referenced in path definitions for running scripts
COPY cic-ussd/.config/ /usr/local/etc/cic-ussd/ COPY .config/ /usr/local/etc/cic-ussd/
COPY cic-ussd/cic_ussd/db/migrations/ /usr/local/share/cic-ussd/alembic COPY cic_ussd/db/migrations/ /usr/local/share/cic-ussd/alembic
WORKDIR /root ENTRYPOINT []

View File

@@ -0,0 +1,32 @@
# syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
# create secrets directory
RUN mkdir -vp pgp/keys
# create application directory
RUN mkdir -vp cic-ussd
RUN mkdir -vp data
COPY 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 --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
COPY cic_ussd/db/ussd_menu.json data/
COPY docker/*.sh .
RUN chmod +x /root/*.sh
# copy config and migration files to definitive file so they can be referenced in path definitions for running scripts
COPY .config/ /usr/local/etc/cic-ussd/
COPY cic_ussd/db/migrations/ /usr/local/share/cic-ussd/alembic
ENTRYPOINT []

View File

@@ -4,4 +4,4 @@
user_server_port=${SERVER_PORT:-9500} user_server_port=${SERVER_PORT:-9500}
/usr/local/bin/uwsgi --wsgi-file /usr/local/lib/python3.8/site-packages/cic_ussd/runnable/daemons/cic_user_server.py --http :"$user_server_port" --pyargv "$@" /usr/local/bin/uwsgi --wsgi-file cic_ussd/runnable/daemons/cic_user_server.py --http :"$user_server_port" --pyargv "$@"

View File

@@ -4,4 +4,4 @@
user_ussd_server_port=${SERVER_PORT:-9000} user_ussd_server_port=${SERVER_PORT:-9000}
/usr/local/bin/uwsgi --wsgi-file /usr/local/lib/python3.8/site-packages/cic_ussd/runnable/daemons/cic_user_ussd_server.py --http :"$user_ussd_server_port" --pyargv "$@" /usr/local/bin/uwsgi --wsgi-file cic_ussd/runnable/daemons/cic_user_ussd_server.py --http :"$user_ussd_server_port" --pyargv "$@"

View File

@@ -1,4 +1,17 @@
cic_base[full_graph]==0.1.3a3+build.984b5cff cic-eth~=0.12.2a3
cic-eth~=0.12.0a1 cic-notify~=0.4.0a10
cic-notify~=0.4.0a7 cic-types~=0.1.0a14
cic-types~=0.1.0a11 confini~=0.4.1a1
semver==2.13.0
alembic==1.4.2
SQLAlchemy==1.3.20
psycopg2==2.8.6
tinydb==4.2.0
phonenumbers==8.12.12
redis==3.5.3
celery==4.4.7
python-i18n[YAML]==0.3.9
pyxdg==0.27
bcrypt==3.2.0
uWSGI==2.0.19.1
transitions==0.8.4

View File

@@ -30,7 +30,7 @@ arg_parser.add_argument('-vv', action='store_true', help='be more verbose')
args = arg_parser.parse_args() args = arg_parser.parse_args()
config = Config(config_dir=args.c, env_prefix=args.env_prefix) config = Config(args.c, env_prefix=args.env_prefix)
config.process() config.process()
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
logg.debug(f'config:\n{config}') logg.debug(f'config:\n{config}')

Some files were not shown because too many files have changed in this diff Show More