Compare commits

..

75 Commits

Author SHA1 Message Date
nolash
2e27fb708b Merge remote-tracking branch 'origin/master' into lash/chainlib-erc20-split 2021-05-02 19:08:39 +02:00
Louis Holbrook
e71b2411d0 Merge branch 'lash/contract-interfaces' into 'master'
cic-eth: Update deps for contract standardization refactor

See merge request grassrootseconomics/cic-internal-integration!129
2021-05-02 17:02:45 +00:00
Louis Holbrook
b4bfb76634 cic-eth: Update deps for contract standardization refactor 2021-05-02 17:02:45 +00:00
aab5c8bf85 Merge branch 'contract-migration-include-data-scripts' into 'master'
puts the scripts in the container and take out the user

See merge request grassrootseconomics/cic-internal-integration!133
2021-05-02 16:56:08 +00:00
e1564574f7 puts the scripts in the container and take out the user 2021-05-02 16:56:08 +00:00
nolash
ef1e1658f7 Update contract migrations and cic-cache to chainlib erc20 split 2021-05-02 16:22:17 +02:00
nolash
2e66e48dd6 Update to chainlib erc20 split 2021-05-02 15:51:53 +02:00
13253a2dcc Update .cic-template.yml 2021-05-02 02:54:03 +00:00
9020fe1000 Update .cic-template.yml 2021-05-02 02:47:01 +00:00
a2e7d2973c Merge branch 'fix-cic-meta-migration-onerror' into 'master'
meta exit error fixed

See merge request grassrootseconomics/cic-internal-integration!132
2021-05-01 19:52:30 +00:00
82f650e81d meta exit error fixed 2021-05-01 12:52:03 -07:00
e77940d0de Merge branch 'philip/metadata-salt' into 'master'
Update metadata pointer salt values

See merge request grassrootseconomics/cic-internal-integration!131
2021-05-01 14:52:54 +00:00
1df62717ef Update metadata pointer salt values 2021-05-01 14:52:54 +00:00
c4919d56b1 Merge branch 'philip/default-token-integration' into 'master'
Philip/default token integration

See merge request grassrootseconomics/cic-internal-integration!128
2021-05-01 14:14:20 +00:00
6d44863a49 Philip/default token integration 2021-05-01 14:14:20 +00:00
Louis Holbrook
b02cdee1bd Merge branch 'lash/cic-cache-tags' into 'master'
cic-cache: Add tx tags in persistent storage

Closes cic-cache#14

See merge request grassrootseconomics/cic-internal-integration!130
2021-05-01 13:20:14 +00:00
Louis Holbrook
75bf8f15be cic-cache: Add tx tags in persistent storage 2021-05-01 13:20:14 +00:00
8db76dc0a8 Merge branch 'fix-cic-meta-migrations' into 'master'
fixed cic meta migration and removed pg schema stuff during init

See merge request grassrootseconomics/cic-internal-integration!127
2021-04-30 08:33:34 +00:00
a3261f2f0e fixed cic meta migration and removed pg schema stuff during init 2021-04-30 08:33:33 +00:00
850dd15451 Merge branch 'philip/separate-telco-cicada-http-apis' into 'master'
Philip/separate telco cicada http apis

See merge request grassrootseconomics/cic-internal-integration!124
2021-04-30 08:15:03 +00:00
0c56e84704 Philip/separate telco cicada http apis 2021-04-30 08:15:03 +00:00
63cd8a4aab Merge branch 'lash/contracts-kill' into 'master'
Remove cic-contracts references

See merge request grassrootseconomics/cic-internal-integration!126
2021-04-30 06:25:49 +00:00
Louis Holbrook
2c326f62ae Remove cic-contracts references 2021-04-30 06:25:49 +00:00
9ed62c58ae Merge branch 'lash/right-token' into 'master'
Correct default token env vars in deployment step 2

See merge request grassrootseconomics/cic-internal-integration!125
2021-04-28 17:31:49 +00:00
nolash
04e9f45feb Correct default token env vars in deployment step 2 2021-04-28 19:24:33 +02:00
Spencer Ofwiti
9126a75c4a Merge branch 'spencer/refactor-meta-library' into 'master'
Remove library files into crdt-meta.

See merge request grassrootseconomics/cic-internal-integration!98
2021-04-28 09:11:39 +00:00
Spencer Ofwiti
1bc29588a1 Remove library files into crdt-meta. 2021-04-28 09:11:39 +00:00
e6d57d3bbb Merge branch 'fix-contract-migration-build' into 'master'
switch to deb solc package

See merge request grassrootseconomics/cic-internal-integration!123
2021-04-28 04:07:29 +00:00
f64ff1290c switch to deb solc package 2021-04-27 21:01:58 -07:00
Louis Holbrook
d5cbe9d113 Merge branch 'lash/rehabilitate-tests' into 'master'
cic-eth: Make failing tests pass again

See merge request grassrootseconomics/cic-internal-integration!117
2021-04-25 14:54:54 +00:00
Louis Holbrook
5663741ed4 cic-eth: Make failing tests pass again 2021-04-25 14:54:54 +00:00
Louis Holbrook
0f6615a925 Merge branch 'lash/get-registry-api' into 'master'
Use task pool rpc for registry and eth queries with cic-eth view cli util

See merge request grassrootseconomics/cic-internal-integration!116
2021-04-25 12:24:17 +00:00
Louis Holbrook
aa15353d68 Use task pool rpc for registry and eth queries with cic-eth view cli util 2021-04-25 12:24:17 +00:00
Louis Holbrook
f7a69830ba Merge branch 'lash/version-conflict' into 'master'
Fix cic-ussd cic-eth conflict

See merge request grassrootseconomics/cic-internal-integration!118
2021-04-25 12:14:55 +00:00
Louis Holbrook
7428420cda Fix cic-ussd cic-eth conflict 2021-04-25 12:14:55 +00:00
Louis Holbrook
7504a899a1 Merge branch 'lash/fix-chainlib-upgrade' into 'master'
Upgrade chainlib

See merge request grassrootseconomics/cic-internal-integration!119
2021-04-25 12:08:40 +00:00
Louis Holbrook
c20c5af27c Upgrade chainlib 2021-04-25 12:08:40 +00:00
Louis Holbrook
32b72274f5 Merge branch 'lash/emergency-shutdown-II' into 'master'
Add remaining health checks and shutdown on critical errors

See merge request grassrootseconomics/cic-internal-integration!115
2021-04-24 17:53:45 +00:00
Louis Holbrook
f50da54274 Add remaining health checks and shutdown on critical errors 2021-04-24 17:53:45 +00:00
Louis Holbrook
dd94b8a190 Merge branch 'lash/default-token' into 'master'
cic-eth: Add default token setting to cic-eth with api

See merge request grassrootseconomics/cic-internal-integration!113
2021-04-24 17:49:21 +00:00
Louis Holbrook
16dd210965 cic-eth: Add default token setting to cic-eth with api 2021-04-24 17:49:21 +00:00
Louis Holbrook
cd0e702e3a Merge branch 'lash/custom-meta' into 'master'
Add custom meta tags

See merge request grassrootseconomics/cic-internal-integration!114
2021-04-24 06:14:25 +00:00
Louis Holbrook
cfab16f4a9 Add custom meta tags 2021-04-24 06:14:24 +00:00
Louis Holbrook
60fdb06034 Merge branch 'lash/emergency-shutdown' into 'master'
cic-eth: Add sanity checks for emergency shutdown / liveness tests

See merge request grassrootseconomics/cic-internal-integration!110
2021-04-23 21:02:52 +00:00
Louis Holbrook
3129a78e06 cic-eth: Add sanity checks for emergency shutdown / liveness tests 2021-04-23 21:02:51 +00:00
Louis Holbrook
6b6ec8659b Merge branch 'lash/simpler-token-selector' into 'master'
Simplify token selector

See merge request grassrootseconomics/cic-internal-integration!112
2021-04-23 08:17:59 +00:00
nolash
96e755b54d Simplify token selector 2021-04-22 11:58:39 +02:00
nolash
f38458ff4c Merge branch 'master' of gitlab.com:grassrootseconomics/cic-internal-integration 2021-04-22 11:57:15 +02:00
Louis Holbrook
660d524401 Merge branch 'lash/health-util' into 'master'
K8s health utilities for cic containers

See merge request grassrootseconomics/cic-internal-integration!108
2021-04-21 17:34:13 +00:00
Louis Holbrook
1bc7cde1f0 K8s health utilities for cic containers 2021-04-21 17:34:13 +00:00
Louis Holbrook
9c22ffca38 Merge branch 'lash/ussd-final-steps' into 'master'
USSD final steps

See merge request grassrootseconomics/cic-internal-integration!111
2021-04-21 17:25:57 +00:00
Louis Holbrook
39fe4a14ec USSD final steps 2021-04-21 17:25:57 +00:00
nolash
65250196cc cic-eth versionbump 2021-04-21 19:03:14 +02:00
Louis Holbrook
0123ce13ea Merge branch 'lash/settable-gas-price' into 'master'
Adapt deployment to Bloxberg

See merge request grassrootseconomics/cic-internal-integration!99
2021-04-21 05:46:42 +00:00
Louis Holbrook
03b3e8cd3f Adapt deployment to Bloxberg 2021-04-21 05:46:42 +00:00
Louis Holbrook
3ee84f780e Merge branch 'lash/cic-cache-syncer-backend-mixup' into 'master'
CIC-cache backend syncer mixup

See merge request grassrootseconomics/cic-internal-integration!106
2021-04-20 13:25:03 +00:00
Louis Holbrook
95269f69ed CIC-cache backend syncer mixup 2021-04-20 13:25:02 +00:00
621780e9b6 Update .cic-template.yml 2021-04-19 17:56:19 +00:00
eecdca1a55 Merge branch 'philip/ussd-db-fixes' into 'master'
Philip/ussd db fixes

See merge request grassrootseconomics/cic-internal-integration!107
2021-04-19 08:44:41 +00:00
6fef0ecec9 Philip/ussd db fixes 2021-04-19 08:44:40 +00:00
Louis Holbrook
6b89a2da89 Merge branch 'lash/chainlib-regression' into 'master'
Correct chainlib import paths

See merge request grassrootseconomics/cic-internal-integration!101
2021-04-16 21:44:14 +00:00
Louis Holbrook
254f2a266b Correct chainlib import paths 2021-04-16 21:44:14 +00:00
ba18914498 Merge branch 'philip/fix-filter-callbacks' into 'master'
Philip/fix filter callbacks

See merge request grassrootseconomics/cic-internal-integration!95
2021-04-16 20:24:07 +00:00
f410e8b7e3 Philip/fix filter callbacks 2021-04-16 20:24:07 +00:00
01454c9ac0 Merge branch 'add-chainsync-db' into 'master'
Add chainsync db

See merge request grassrootseconomics/cic-internal-integration!105
2021-04-15 21:22:07 +00:00
462d7046ed Add chainsync db 2021-04-15 21:22:07 +00:00
f91b491251 Merge branch 'ida/changes-to-args-commandline' into 'master'
Ida/changes to args commandline

See merge request grassrootseconomics/cic-internal-integration!104
2021-04-15 17:04:17 +00:00
0de79521dc Ida/changes to args commandline 2021-04-15 17:04:16 +00:00
nolash
22ec8e2e0e Update sql backend symbol name, deps 2021-04-15 18:04:47 +02:00
Louis Holbrook
a8529ae2ef Merge branch 'lash/improve-cic-cache-service' into 'master'
Iimprove cic cache service

See merge request grassrootseconomics/cic-internal-integration!100
2021-04-15 14:02:11 +00:00
Louis Holbrook
98ddf56a1d Iimprove cic cache service 2021-04-15 14:02:09 +00:00
bee602b16a Merge branch 'philip/leaner-metadata-handling' into 'master'
Philip/leaner metadata handling

See merge request grassrootseconomics/cic-internal-integration!94
2021-04-14 09:00:11 +00:00
c67274846f Philip/leaner metadata handling 2021-04-14 09:00:10 +00:00
Louis Holbrook
48570b2338 Merge branch 'lash/update-syncer-imports' into 'master'
Update syncer imports

See merge request grassrootseconomics/cic-internal-integration!97
2021-04-14 08:17:48 +00:00
Louis Holbrook
c80b8771b9 Update syncer imports 2021-04-14 08:17:47 +00:00
188 changed files with 3209 additions and 2596 deletions

8
.gitignore vendored
View File

@@ -1,2 +1,10 @@
service-configs/* service-configs/*
!service-configs/.gitkeep !service-configs/.gitkeep
**/node_modules/
__pycache__
*.pyc
*.o
gmon.out
*.egg-info
dist/
build/

View File

@@ -6,3 +6,4 @@ HOST=localhost
PORT=5432 PORT=5432
ENGINE=postgresql ENGINE=postgresql
DRIVER=psycopg2 DRIVER=psycopg2
DEBUG=

View File

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

View File

@@ -2,9 +2,14 @@
import logging import logging
# local imports # local imports
from .list import list_transactions_mined from .list import (
from .list import list_transactions_account_mined list_transactions_mined,
from .list import add_transaction list_transactions_account_mined,
add_transaction,
tag_transaction,
add_tag,
)
logg = logging.getLogger() logg = logging.getLogger()

View File

@@ -2,8 +2,9 @@
import logging import logging
import datetime import datetime
# third-party imports # external imports
from cic_cache.db.models.base import SessionBase from cic_cache.db.models.base import SessionBase
from sqlalchemy import text
logg = logging.getLogger() logg = logging.getLogger()
@@ -50,7 +51,8 @@ def list_transactions_account_mined(
def add_transaction( def add_transaction(
session, tx_hash, session,
tx_hash,
block_number, block_number,
tx_index, tx_index,
sender, sender,
@@ -62,6 +64,33 @@ def add_transaction(
success, success,
timestamp, timestamp,
): ):
"""Adds a single transaction to the cache persistent storage. Sensible interpretation of all fields is the responsibility of the caller.
:param session: Persistent storage session object
:type session: SQLAlchemy session
:param tx_hash: Transaction hash
:type tx_hash: str, 0x-hex
:param block_number: Block number
:type block_number: int
:param tx_index: Transaction index in block
:type tx_index: int
:param sender: Ethereum address of effective sender
:type sender: str, 0x-hex
:param receiver: Ethereum address of effective recipient
:type receiver: str, 0x-hex
:param source_token: Ethereum address of token used by sender
:type source_token: str, 0x-hex
:param destination_token: Ethereum address of token received by recipient
:type destination_token: str, 0x-hex
:param from_value: Source token value spent in transaction
:type from_value: int
:param to_value: Destination token value received in transaction
:type to_value: int
:param success: True if code execution on network was successful
:type success: bool
:param date_block: Block timestamp
:type date_block: datetime
"""
date_block = datetime.datetime.fromtimestamp(timestamp) date_block = datetime.datetime.fromtimestamp(timestamp)
s = "INSERT INTO tx (tx_hash, block_number, tx_index, sender, recipient, source_token, destination_token, from_value, to_value, success, date_block) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', {}, {}, {}, '{}')".format( s = "INSERT INTO tx (tx_hash, block_number, tx_index, sender, recipient, source_token, destination_token, from_value, to_value, success, date_block) VALUES ('{}', {}, {}, '{}', '{}', '{}', '{}', {}, {}, {}, '{}')".format(
tx_hash, tx_hash,
@@ -77,3 +106,74 @@ def add_transaction(
date_block, date_block,
) )
session.execute(s) session.execute(s)
def tag_transaction(
session,
tx_hash,
name,
domain=None,
):
"""Tag a single transaction with a single tag.
Tag must already exist in storage.
:param session: Persistent storage session object
:type session: SQLAlchemy session
:param tx_hash: Transaction hash
:type tx_hash: str, 0x-hex
:param name: Tag value
:type name: str
:param domain: Tag domain
:type domain: str
:raises ValueError: Unknown tag or transaction hash
"""
s = text("SELECT id from tx where tx_hash = :a")
r = session.execute(s, {'a': tx_hash}).fetchall()
tx_id = r[0].values()[0]
if tx_id == None:
raise ValueError('unknown tx hash {}'.format(tx_hash))
#s = text("SELECT id from tag where value = :a and domain = :b")
if domain == None:
s = text("SELECT id from tag where value = :a")
else:
s = text("SELECT id from tag where value = :a and domain = :b")
r = session.execute(s, {'a': name, 'b': domain}).fetchall()
tag_id = r[0].values()[0]
logg.debug('type {} {}'.format(type(tag_id), type(tx_id)))
if tag_id == None:
raise ValueError('unknown tag name {} domain {}'.format(name, domain))
s = text("INSERT INTO tag_tx_link (tag_id, tx_id) VALUES (:a, :b)")
r = session.execute(s, {'a': int(tag_id), 'b': int(tx_id)})
def add_tag(
session,
name,
domain=None,
):
"""Add a single tag to storage.
:param session: Persistent storage session object
:type session: SQLAlchemy session
:param name: Tag value
:type name: str
:param domain: Tag domain
:type domain: str
:raises sqlalchemy.exc.IntegrityError: Tag already exists
"""
s = None
if domain == None:
s = text("INSERT INTO tag (value) VALUES (:b)")
else:
s = text("INSERT INTO tag (domain, value) VALUES (:a, :b)")
session.execute(s, {'a': domain, 'b': name})

View File

@@ -0,0 +1,38 @@
"""Transaction tags
Revision ID: aaf2bdce7d6e
Revises: 6604de4203e2
Create Date: 2021-05-01 09:20:20.775082
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'aaf2bdce7d6e'
down_revision = '6604de4203e2'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'tag',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('domain', sa.String(), nullable=True),
sa.Column('value', sa.String(), nullable=False),
)
op.create_index('idx_tag_domain_value', 'tag', ['domain', 'value'], unique=True)
op.create_table(
'tag_tx_link',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('tag_id', sa.Integer, sa.ForeignKey('tag.id'), nullable=False),
sa.Column('tx_id', sa.Integer, sa.ForeignKey('tx.id'), nullable=False),
)
def downgrade():
op.drop_table('tag_tx_link')
op.drop_index('idx_tag_domain_value')
op.drop_table('tag')

View File

@@ -1,2 +1,27 @@
class SyncFilter: class TagSyncFilter:
pass """Holds tag name and domain for an implementing filter.
:param name: Tag value
:type name: str
:param domain: Tag domain
:type domain: str
"""
def __init__(self, name, domain=None):
self.tag_name = name
self.tag_domain = domain
def tag(self):
"""Return tag value/domain.
:rtype: Tuple
:returns: tag value/domain.
"""
return (self.tag_name, self.tag_domain)
def __str__(self):
if self.tag_domain == None:
return self.tag_name
return '{}.{}'.format(self.tag_domain, self.tag_name)

View File

@@ -2,7 +2,6 @@
import logging import logging
# external imports # external imports
from chainlib.eth.erc20 import ERC20
from chainlib.eth.address import ( from chainlib.eth.address import (
to_checksum_address, to_checksum_address,
) )
@@ -13,17 +12,19 @@ from cic_eth_registry.error import (
NotAContractError, NotAContractError,
ContractMismatchError, ContractMismatchError,
) )
from eth_erc20 import ERC20
# local imports # local imports
from .base import SyncFilter from .base import TagSyncFilter
from cic_cache import db as cic_cache_db from cic_cache import db as cic_cache_db
logg = logging.getLogger().getChild(__name__) logg = logging.getLogger().getChild(__name__)
class ERC20TransferFilter(SyncFilter): class ERC20TransferFilter(TagSyncFilter):
def __init__(self, chain_spec): def __init__(self, chain_spec):
super(ERC20TransferFilter, self).__init__('transfer', domain='erc20')
self.chain_spec = chain_spec self.chain_spec = chain_spec
@@ -46,6 +47,9 @@ class ERC20TransferFilter(SyncFilter):
except RequestMismatchException: except RequestMismatchException:
logg.debug('erc20 match but not a transfer, skipping') logg.debug('erc20 match but not a transfer, skipping')
return False return False
except ValueError:
logg.debug('erc20 match but bogus data, skipping')
return False
token_sender = tx.outputs[0] token_sender = tx.outputs[0]
token_recipient = transfer_data[0] token_recipient = transfer_data[0]
@@ -68,5 +72,12 @@ class ERC20TransferFilter(SyncFilter):
block.timestamp, block.timestamp,
) )
db_session.flush() db_session.flush()
cic_cache_db.tag_transaction(
db_session,
tx.hash,
self.tag_name,
domain=self.tag_domain,
)
db_session.commit()
return True return True

View File

@@ -7,9 +7,10 @@ import argparse
import sys import sys
import re import re
# third-party imports # external imports
import confini import confini
import celery import celery
import sqlalchemy
import rlp import rlp
import cic_base.config import cic_base.config
import cic_base.log import cic_base.log
@@ -26,7 +27,7 @@ from chainlib.eth.block import (
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
) )
from chainsyncer.backend import SyncerBackend from chainsyncer.backend.sql import SQLBackend
from chainsyncer.driver import ( from chainsyncer.driver import (
HeadSyncer, HeadSyncer,
HistorySyncer, HistorySyncer,
@@ -34,7 +35,10 @@ from chainsyncer.driver import (
from chainsyncer.db.models.base import SessionBase from chainsyncer.db.models.base import SessionBase
# local imports # local imports
from cic_cache.db import dsn_from_config from cic_cache.db import (
dsn_from_config,
add_tag,
)
from cic_cache.runnable.daemons.filters import ( from cic_cache.runnable.daemons.filters import (
ERC20TransferFilter, ERC20TransferFilter,
) )
@@ -59,6 +63,17 @@ 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'))
def register_filter_tags(filters, session):
for f in filters:
tag = f.tag()
try:
add_tag(session, tag[0], domain=tag[1])
session.commit()
logg.info('added tag name "{}" domain "{}"'.format(tag[0], tag[1]))
except sqlalchemy.exc.IntegrityError:
logg.debug('already have tag name "{}" domain "{}"'.format(tag[0], tag[1]))
def main(): def main():
# Connect to blockchain with chainlib # Connect to blockchain with chainlib
rpc = RPCConnection.connect(chain_spec, 'default') rpc = RPCConnection.connect(chain_spec, 'default')
@@ -71,13 +86,13 @@ def main():
syncers = [] syncers = []
#if SyncerBackend.first(chain_spec): #if SQLBackend.first(chain_spec):
# backend = SyncerBackend.initial(chain_spec, block_offset) # backend = SQLBackend.initial(chain_spec, block_offset)
syncer_backends = SyncerBackend.resume(chain_spec, block_offset) syncer_backends = SQLBackend.resume(chain_spec, block_offset)
if len(syncer_backends) == 0: if len(syncer_backends) == 0:
logg.info('found no backends to resume') logg.info('found no backends to resume')
syncers.append(SyncerBackend.initial(chain_spec, block_offset)) syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
else: else:
for syncer_backend in syncer_backends: for syncer_backend in syncer_backends:
logg.info('resuming sync session {}'.format(syncer_backend)) logg.info('resuming sync session {}'.format(syncer_backend))
@@ -85,7 +100,7 @@ def main():
for syncer_backend in syncer_backends: for syncer_backend in syncer_backends:
syncers.append(HistorySyncer(syncer_backend)) syncers.append(HistorySyncer(syncer_backend))
syncer_backend = SyncerBackend.live(chain_spec, block_offset+1) syncer_backend = SQLBackend.live(chain_spec, block_offset+1)
syncers.append(HeadSyncer(syncer_backend)) syncers.append(HeadSyncer(syncer_backend))
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS') trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
@@ -98,10 +113,19 @@ def main():
erc20_transfer_filter = ERC20TransferFilter(chain_spec) erc20_transfer_filter = ERC20TransferFilter(chain_spec)
filters = [
erc20_transfer_filter,
]
session = SessionBase.create_session()
register_filter_tags(filters, session)
session.close()
i = 0 i = 0
for syncer in syncers: for syncer in syncers:
logg.debug('running syncer index {}'.format(i)) logg.debug('running syncer index {}'.format(i))
syncer.add_filter(erc20_transfer_filter) for f in filters:
syncer.add_filter(f)
r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc) r = syncer.loop(int(config.get('SYNCER_LOOP_INTERVAL')), rpc)
sys.stderr.write("sync {} done at block {}\n".format(syncer, r)) sys.stderr.write("sync {} done at block {}\n".format(syncer, r))

View File

@@ -6,4 +6,4 @@ HOST=localhost
PORT=5432 PORT=5432
ENGINE=postgresql ENGINE=postgresql
DRIVER=psycopg2 DRIVER=psycopg2
DEBUG= DEBUG=0

View File

@@ -1,2 +1,4 @@
[cic] [cic]
registry_address = registry_address =
chain_spec =
trust_address =

View File

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

View File

@@ -17,7 +17,7 @@ RUN apt-get update && \
# Copy shared requirements from top of mono-repo # Copy shared requirements from top of mono-repo
RUN echo "copying root req file ${root_requirement_file}" RUN echo "copying root req file ${root_requirement_file}"
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a58 RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a76
COPY cic-cache/requirements.txt ./ COPY cic-cache/requirements.txt ./
COPY cic-cache/setup.cfg \ COPY cic-cache/setup.cfg \
@@ -43,10 +43,9 @@ COPY cic-cache/config/ /usr/local/etc/cic-cache/
RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/ RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/
COPY cic-cache/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/ COPY cic-cache/cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \ COPY cic-cache/docker/start_tracker.sh ./start_tracker.sh
mkdir -p /usr/local/share/cic/solidity && \ COPY cic-cache/docker/db.sh ./db.sh
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi RUN chmod 755 ./*.sh
# Tracker # Tracker
# ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"] # ENTRYPOINT ["/usr/local/bin/cic-cache-tracker", "-vv"]
# Server # Server

View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -e
>&2 echo executing database migration
python scripts/migrate.py -c /usr/local/etc/cic-cache --migrations-dir /usr/local/share/cic-cache/alembic -vv
set +e

View File

@@ -0,0 +1,10 @@
#!/bin/bash
. ./db.sh
if [ $? -ne "0" ]; then
>&2 echo db migrate fail
exit 1
fi
/usr/local/bin/cic-cache-trackerd $@

View File

@@ -1,13 +1,12 @@
cic-base~=0.1.2a66 cic-base~=0.1.2b8
alembic==1.4.2 alembic==1.4.2
confini~=0.3.6rc3 confini~=0.3.6rc3
uwsgi==2.0.19.1 uwsgi==2.0.19.1
moolb~=0.1.0 moolb~=0.1.0
cic-eth-registry~=0.5.4a13 cic-eth-registry~=0.5.5a4
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
chainlib~=0.0.2a10 chainsyncer[sql]~=0.0.2a4
chainsyncer[sql]~=0.0.2a1

View File

@@ -4,3 +4,8 @@ pytest-mock==3.3.1
pysqlite3==0.4.3 pysqlite3==0.4.3
sqlparse==0.4.1 sqlparse==0.4.1
pytest-celery==0.0.0a1 pytest-celery==0.0.0a1
eth_tester==0.5.0b3
py-evm==0.3.0a20
web3==5.12.2
cic-eth-registry~=0.5.5a3
cic-base[full]==0.1.2b8

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
import datetime import datetime
# third-party imports # external imports
import pytest import pytest
# local imports # local imports
@@ -84,3 +84,7 @@ def txs(
session.commit() session.commit()
return [
tx_hash_first,
tx_hash_second,
]

View File

@@ -0,0 +1,3 @@
from chainlib.eth.pytest import *
from cic_eth_registry.pytest.fixtures_tokens import *

View File

@@ -0,0 +1,69 @@
# standard imports
import os
import datetime
import logging
import json
# external imports
import pytest
from sqlalchemy import text
from chainlib.eth.tx import Tx
from chainlib.eth.block import Block
from chainlib.chain import ChainSpec
from hexathon import (
strip_0x,
add_0x,
)
# local imports
from cic_cache.db import add_tag
from cic_cache.runnable.daemons.filters.erc20 import ERC20TransferFilter
logg = logging.getLogger()
def test_cache(
eth_rpc,
foo_token,
init_database,
list_defaults,
list_actors,
tags,
):
chain_spec = ChainSpec('foo', 'bar', 42, 'baz')
fltr = ERC20TransferFilter(chain_spec)
add_tag(init_database, fltr.tag_name, domain=fltr.tag_domain)
data = 'a9059cbb'
data += strip_0x(list_actors['alice'])
data += '1000'.ljust(64, '0')
block = Block({
'hash': os.urandom(32).hex(),
'number': 42,
'timestamp': datetime.datetime.utcnow().timestamp(),
'transactions': [],
})
tx = Tx({
'to': foo_token,
'from': list_actors['bob'],
'data': data,
'value': 0,
'hash': os.urandom(32).hex(),
'nonce': 13,
'gasPrice': 10000000,
'gas': 123456,
})
block.txs.append(tx)
tx.block = block
r = fltr.filter(eth_rpc, block, tx, db_session=init_database)
assert r
s = text("SELECT x.tx_hash FROM tag a INNER JOIN tag_tx_link l ON l.tag_id = a.id INNER JOIN tx x ON x.id = l.tx_id WHERE a.domain = :a AND a.value = :b")
r = init_database.execute(s, {'a': fltr.tag_domain, 'b': fltr.tag_name}).fetchone()
assert r[0] == tx.hash

View File

@@ -2,7 +2,7 @@
import os import os
import logging import logging
# third-party imports # external imports
import pytest import pytest
import confini import confini
@@ -13,7 +13,7 @@ logg = logging.getLogger(__file__)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def load_config(): def load_config():
config_dir = os.path.join(root_dir, '.config/test') config_dir = os.path.join(root_dir, 'config/test')
conf = confini.Config(config_dir, 'CICTEST') conf = confini.Config(config_dir, 'CICTEST')
conf.process() conf.process()
logg.debug('config {}'.format(conf)) logg.debug('config {}'.format(conf))

View File

@@ -3,13 +3,16 @@ import os
import logging import logging
import re import re
# third-party imports # external imports
import pytest import pytest
import sqlparse import sqlparse
import alembic
from alembic.config import Config as AlembicConfig
# local imports # local imports
from cic_cache.db.models.base import SessionBase from cic_cache.db.models.base import SessionBase
from cic_cache.db import dsn_from_config from cic_cache.db import dsn_from_config
from cic_cache.db import add_tag
logg = logging.getLogger(__file__) logg = logging.getLogger(__file__)
@@ -26,11 +29,10 @@ def database_engine(
except FileNotFoundError: except FileNotFoundError:
pass pass
dsn = dsn_from_config(load_config) dsn = dsn_from_config(load_config)
SessionBase.connect(dsn) SessionBase.connect(dsn, debug=load_config.true('DATABASE_DEBUG'))
return dsn return dsn
# TODO: use alembic instead to migrate db, here we have to keep separate schema than migration script in script/migrate.py
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def init_database( def init_database(
load_config, load_config,
@@ -38,52 +40,23 @@ def init_database(
): ):
rootdir = os.path.dirname(os.path.dirname(__file__)) rootdir = os.path.dirname(os.path.dirname(__file__))
schemadir = os.path.join(rootdir, 'db', load_config.get('DATABASE_DRIVER')) dbdir = os.path.join(rootdir, 'cic_cache', 'db')
migrationsdir = os.path.join(dbdir, 'migrations', load_config.get('DATABASE_ENGINE'))
if load_config.get('DATABASE_ENGINE') == 'sqlite': if not os.path.isdir(migrationsdir):
rconn = SessionBase.engine.raw_connection() migrationsdir = os.path.join(dbdir, 'migrations', 'default')
f = open(os.path.join(schemadir, 'db.sql')) logg.info('using migrations directory {}'.format(migrationsdir))
s = f.read()
f.close()
rconn.executescript(s)
else:
rconn = SessionBase.engine.raw_connection()
rcursor = rconn.cursor()
#rcursor.execute('DROP FUNCTION IF EXISTS public.transaction_list')
#rcursor.execute('DROP FUNCTION IF EXISTS public.balances')
f = open(os.path.join(schemadir, 'db.sql'))
s = f.read()
f.close()
r = re.compile(r'^[A-Z]', re.MULTILINE)
for l in sqlparse.parse(s):
strl = str(l)
# we need to check for empty query lines, as sqlparse doesn't do that on its own (and psycopg complains when it gets them)
if not re.search(r, strl):
logg.warning('skipping parsed query line {}'.format(strl))
continue
rcursor.execute(strl)
rconn.commit()
rcursor.execute('SET search_path TO public')
# this doesn't work when run separately, no idea why
# functions have been manually added to original schema from cic-eth
# f = open(os.path.join(schemadir, 'proc_transaction_list.sql'))
# s = f.read()
# f.close()
# rcursor.execute(s)
#
# f = open(os.path.join(schemadir, 'proc_balances.sql'))
# s = f.read()
# f.close()
# rcursor.execute(s)
rcursor.close()
session = SessionBase.create_session() session = SessionBase.create_session()
ac = AlembicConfig(os.path.join(migrationsdir, 'alembic.ini'))
ac.set_main_option('sqlalchemy.url', database_engine)
ac.set_main_option('script_location', migrationsdir)
alembic.command.downgrade(ac, 'base')
alembic.command.upgrade(ac, 'head')
session.commit()
yield session yield session
session.commit() session.commit()
session.close() session.close()
@@ -116,3 +89,14 @@ def list_defaults(
return { return {
'block': 420000, 'block': 420000,
} }
@pytest.fixture(scope='function')
def tags(
init_database,
):
add_tag(init_database, 'foo')
add_tag(init_database, 'baz', domain='bar')
add_tag(init_database, 'xyzzy', domain='bar')
init_database.commit()

View File

@@ -4,7 +4,7 @@ import datetime
import logging import logging
import json import json
# third-party imports # external imports
import pytest import pytest
# local imports # local imports

View File

@@ -0,0 +1,37 @@
import os
import datetime
import logging
import json
# external imports
import pytest
# local imports
from cic_cache.db import tag_transaction
logg = logging.getLogger()
def test_cache(
init_database,
list_defaults,
list_actors,
list_tokens,
txs,
tags,
):
tag_transaction(init_database, txs[0], 'foo')
tag_transaction(init_database, txs[0], 'baz', domain='bar')
tag_transaction(init_database, txs[1], 'xyzzy', domain='bar')
r = init_database.execute("SELECT x.tx_hash FROM tag a INNER JOIN tag_tx_link l ON l.tag_id = a.id INNER JOIN tx x ON x.id = l.tx_id WHERE a.value = 'foo'").fetchall()
assert r[0][0] == txs[0]
r = init_database.execute("SELECT x.tx_hash FROM tag a INNER JOIN tag_tx_link l ON l.tag_id = a.id INNER JOIN tx x ON x.id = l.tx_id WHERE a.domain = 'bar' AND a.value = 'baz'").fetchall()
assert r[0][0] == txs[0]
r = init_database.execute("SELECT x.tx_hash FROM tag a INNER JOIN tag_tx_link l ON l.tag_id = a.id INNER JOIN tx x ON x.id = l.tx_id WHERE a.domain = 'bar' AND a.value = 'xyzzy'").fetchall()
assert r[0][0] == txs[1]

View File

@@ -2,7 +2,7 @@
import datetime import datetime
import logging import logging
# third-party imports # external imports
import celery import celery
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
@@ -32,7 +32,9 @@ def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.AL
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash) r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r)) logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
return chained_input return chained_input
@@ -51,7 +53,9 @@ def unlock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.
:returns: New lock state for address :returns: New lock state for address
:rtype: number :rtype: number
""" """
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.reset(chain_str, flags, address=address) r = Lock.reset(chain_str, flags, address=address)
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r)) logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
return chained_input return chained_input
@@ -127,7 +131,9 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
@celery_app.task(base=CriticalSQLAlchemyTask) @celery_app.task(base=CriticalSQLAlchemyTask)
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None): def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
chain_str = str(ChainSpec.from_dict(chain_spec_dict)) chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
session = SessionBase.create_session() session = SessionBase.create_session()
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session) r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
if address != None: if address != None:
@@ -139,3 +145,9 @@ def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
session.flush() session.flush()
session.close() session.close()
return chained_input return chained_input
@celery_app.task()
def shutdown(message):
logg.critical('shutdown called: {}'.format(message))
celery_app.control.shutdown() #broadcast('shutdown')

View File

@@ -0,0 +1,19 @@
# standard imports
import logging
# external imports
import celery
# local imports
from cic_eth.task import BaseTask
celery_app = celery.current_app
logg = logging.getLogger()
@celery_app.task(bind=True, base=BaseTask)
def default_token(self):
return {
'symbol': self.default_token_symbol,
'address': self.default_token_address,
}

View File

@@ -60,6 +60,29 @@ class AdminApi:
self.call_address = call_address self.call_address = call_address
def proxy_do(self, chain_spec, o):
s_proxy = celery.signature(
'cic_eth.task.rpc_proxy',
[
chain_spec.asdict(),
o,
'default',
],
queue=self.queue
)
return s_proxy.apply_async()
def registry(self):
s_registry = celery.signature(
'cic_eth.task.registry',
[],
queue=self.queue
)
return s_registry.apply_async()
def unlock(self, chain_spec, address, flags=None): def unlock(self, chain_spec, address, flags=None):
s_unlock = celery.signature( s_unlock = celery.signature(
'cic_eth.admin.ctrl.unlock', 'cic_eth.admin.ctrl.unlock',
@@ -146,7 +169,6 @@ class AdminApi:
# TODO: This check should most likely be in resend task itself # TODO: This check should most likely be in resend task itself
tx_dict = s_get_tx_cache.apply_async().get() tx_dict = s_get_tx_cache.apply_async().get()
#if tx_dict['status'] in [StatusEnum.REVERTED, StatusEnum.SUCCESS, StatusEnum.CANCELLED, StatusEnum.OBSOLETED]:
if not is_alive(getattr(StatusEnum, tx_dict['status']).value): if not is_alive(getattr(StatusEnum, tx_dict['status']).value):
raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex)) raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex))
@@ -226,9 +248,6 @@ class AdminApi:
break break
last_nonce = nonce_otx last_nonce = nonce_otx
#nonce_cache = Nonce.get(address)
#nonce_w3 = self.w3.eth.getTransactionCount(address, 'pending')
return { return {
'nonce': { 'nonce': {
#'network': nonce_cache, #'network': nonce_cache,
@@ -272,20 +291,6 @@ class AdminApi:
return s_nonce.apply_async() return s_nonce.apply_async()
# # TODO: this is a stub, complete all checks
# def ready(self):
# """Checks whether all required initializations have been performed.
#
# :raises cic_eth.error.InitializationError: At least one setting pre-requisite has not been met.
# :raises KeyError: An address provided for initialization is not known by the keystore.
# """
# addr = AccountRole.get_address('ETH_GAS_PROVIDER_ADDRESS')
# if addr == ZERO_ADDRESS:
# raise InitializationError('missing account ETH_GAS_PROVIDER_ADDRESS')
#
# self.w3.eth.sign(addr, text='666f6f')
def account(self, chain_spec, address, include_sender=True, include_recipient=True, renderer=None, w=sys.stdout): def account(self, chain_spec, address, include_sender=True, include_recipient=True, renderer=None, w=sys.stdout):
"""Lists locally originated transactions for the given Ethereum address. """Lists locally originated transactions for the given Ethereum address.
@@ -348,6 +353,7 @@ class AdminApi:
# TODO: Add exception upon non-existent tx aswell as invalid tx data to docstring # TODO: Add exception upon non-existent tx aswell as invalid tx data to docstring
# TODO: This method is WAY too long
def tx(self, chain_spec, tx_hash=None, tx_raw=None, registry=None, renderer=None, w=sys.stdout): def tx(self, chain_spec, tx_hash=None, tx_raw=None, registry=None, renderer=None, w=sys.stdout):
"""Output local and network details about a given transaction with local origin. """Output local and network details about a given transaction with local origin.
@@ -370,7 +376,6 @@ class AdminApi:
if tx_raw != None: if tx_raw != None:
tx_hash = add_0x(keccak256_hex_to_hex(tx_raw)) tx_hash = add_0x(keccak256_hex_to_hex(tx_raw))
#tx_hash = self.w3.keccak(hexstr=tx_raw).hex()
s = celery.signature( s = celery.signature(
'cic_eth.queue.query.get_tx_cache', 'cic_eth.queue.query.get_tx_cache',
@@ -386,38 +391,78 @@ class AdminApi:
source_token = None source_token = None
if tx['source_token'] != ZERO_ADDRESS: if tx['source_token'] != ZERO_ADDRESS:
try: if registry != None:
source_token = registry.by_address(tx['source_token']) try:
#source_token = CICRegistry.get_address(chain_spec, tx['source_token']).contract source_token = registry.by_address(tx['source_token'])
except UnknownContractError: except UnknownContractError:
#source_token_contract = self.w3.eth.contract(abi=CICRegistry.abi('ERC20'), address=tx['source_token']) logg.warning('unknown source token contract {} (direct)'.format(tx['source_token']))
#source_token = CICRegistry.add_token(chain_spec, source_token_contract) else:
logg.warning('unknown source token contract {}'.format(tx['source_token'])) s = celery.signature(
'cic_eth.task.registry_address_lookup',
[
chain_spec.asdict(),
tx['source_token'],
],
queue=self.queue
)
t = s.apply_async()
source_token = t.get()
if source_token == None:
logg.warning('unknown source token contract {} (task pool)'.format(tx['source_token']))
destination_token = None destination_token = None
if tx['source_token'] != ZERO_ADDRESS: if tx['destination_token'] != ZERO_ADDRESS:
try: if registry != None:
#destination_token = CICRegistry.get_address(chain_spec, tx['destination_token']) try:
destination_token = registry.by_address(tx['destination_token']) destination_token = registry.by_address(tx['destination_token'])
except UnknownContractError: except UnknownContractError:
#destination_token_contract = self.w3.eth.contract(abi=CICRegistry.abi('ERC20'), address=tx['source_token']) logg.warning('unknown destination token contract {}'.format(tx['destination_token']))
#destination_token = CICRegistry.add_token(chain_spec, destination_token_contract) else:
logg.warning('unknown destination token contract {}'.format(tx['destination_token'])) s = celery.signature(
'cic_eth.task.registry_address_lookup',
[
chain_spec.asdict(),
tx['destination_token'],
],
queue=self.queue
)
t = s.apply_async()
destination_token = t.get()
if destination_token == None:
logg.warning('unknown destination token contract {} (task pool)'.format(tx['destination_token']))
tx['sender_description'] = 'Custodial account' tx['sender_description'] = 'Custodial account'
tx['recipient_description'] = 'Custodial account' tx['recipient_description'] = 'Custodial account'
o = code(tx['sender']) o = code(tx['sender'])
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
if len(strip_0x(r, allow_empty=True)) > 0: if len(strip_0x(r, allow_empty=True)) > 0:
try: if registry != None:
#sender_contract = CICRegistry.get_address(chain_spec, tx['sender']) try:
sender_contract = registry.by_address(tx['sender'], sender_address=self.call_address) sender_contract = registry.by_address(tx['sender'], sender_address=self.call_address)
tx['sender_description'] = 'Contract at {}'.format(tx['sender']) #sender_contract) tx['sender_description'] = 'Contract at {}'.format(tx['sender'])
except UnknownContractError: except UnknownContractError:
tx['sender_description'] = 'Unknown contract' tx['sender_description'] = 'Unknown contract'
except KeyError as e: except KeyError as e:
tx['sender_description'] = 'Unknown contract' tx['sender_description'] = 'Unknown contract'
else:
s = celery.signature(
'cic_eth.task.registry_address_lookup',
[
chain_spec.asdict(),
tx['sender'],
],
queue=self.queue
)
t = s.apply_async()
tx['sender_description'] = t.get()
if tx['sender_description'] == None:
tx['sender_description'] = 'Unknown contract'
else: else:
s = celery.signature( s = celery.signature(
'cic_eth.eth.account.have', 'cic_eth.eth.account.have',
@@ -446,16 +491,31 @@ class AdminApi:
tx['sender_description'] = role tx['sender_description'] = role
o = code(tx['recipient']) o = code(tx['recipient'])
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
if len(strip_0x(r, allow_empty=True)) > 0: if len(strip_0x(r, allow_empty=True)) > 0:
try: if registry != None:
#recipient_contract = CICRegistry.by_address(tx['recipient']) try:
recipient_contract = registry.by_address(tx['recipient']) recipient_contract = registry.by_address(tx['recipient'])
tx['recipient_description'] = 'Contract at {}'.format(tx['recipient']) #recipient_contract) tx['recipient_description'] = 'Contract at {}'.format(tx['recipient'])
except UnknownContractError as e: except UnknownContractError as e:
tx['recipient_description'] = 'Unknown contract' tx['recipient_description'] = 'Unknown contract'
except KeyError as e: except KeyError as e:
tx['recipient_description'] = 'Unknown contract' tx['recipient_description'] = 'Unknown contract'
else:
s = celery.signature(
'cic_eth.task.registry_address_lookup',
[
chain_spec.asdict(),
tx['recipient'],
],
queue=self.queue
)
t = s.apply_async()
tx['recipient_description'] = t.get()
if tx['recipient_description'] == None:
tx['recipient_description'] = 'Unknown contract'
else: else:
s = celery.signature( s = celery.signature(
'cic_eth.eth.account.have', 'cic_eth.eth.account.have',
@@ -497,7 +557,8 @@ class AdminApi:
r = None r = None
try: try:
o = transaction(tx_hash) o = transaction(tx_hash)
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
if r != None: if r != None:
tx['network_status'] = 'Mempool' tx['network_status'] = 'Mempool'
except Exception as e: except Exception as e:
@@ -506,7 +567,8 @@ class AdminApi:
if r != None: if r != None:
try: try:
o = receipt(tx_hash) o = receipt(tx_hash)
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
logg.debug('h {} o {}'.format(tx_hash, o)) logg.debug('h {} o {}'.format(tx_hash, o))
if int(strip_0x(r['status'])) == 1: if int(strip_0x(r['status'])) == 1:
tx['network_status'] = 'Confirmed' tx['network_status'] = 'Confirmed'
@@ -521,11 +583,13 @@ class AdminApi:
pass pass
o = balance(tx['sender']) o = balance(tx['sender'])
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
tx['sender_gas_balance'] = r tx['sender_gas_balance'] = r
o = balance(tx['recipient']) o = balance(tx['recipient'])
r = self.rpc.do(o) t = self.proxy_do(chain_spec, o)
r = t.get()
tx['recipient_gas_balance'] = r tx['recipient_gas_balance'] = r
tx_unpacked = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec) tx_unpacked = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec)

View File

@@ -62,6 +62,18 @@ class Api:
) )
def default_token(self):
s_token = celery.signature(
'cic_eth.admin.token.default_token',
[],
queue=self.queue,
)
if self.callback_param != None:
s_token.link(self.callback_success)
return s_token.apply_async()
def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol): def convert_transfer(self, from_address, to_address, target_return, minimum_return, from_token_symbol, to_token_symbol):
"""Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed. """Executes a chain of celery tasks that performs conversion between two ERC20 tokens, and transfers to a specified receipient after convert has completed.

View File

View File

@@ -0,0 +1,8 @@
from cic_eth.db.models.base import SessionBase
def health(*args, **kwargs):
session = SessionBase.create_session()
session.execute('SELECT count(*) from alembic_version')
session.close()
return True

View File

@@ -0,0 +1,48 @@
# standard imports
import logging
# external imports
from chainlib.connection import RPCConnection
from chainlib.chain import ChainSpec
from chainlib.eth.gas import balance
# local imports
from cic_eth.db.models.role import AccountRole
from cic_eth.db.models.base import SessionBase
from cic_eth.db.enum import LockEnum
from cic_eth.error import LockedError
from cic_eth.admin.ctrl import check_lock
logg = logging.getLogger().getChild(__name__)
def health(*args, **kwargs):
session = SessionBase.create_session()
config = kwargs['config']
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
logg.debug('check gas balance of gas gifter for chain {}'.format(chain_spec))
try:
check_lock(None, None, LockEnum.INIT)
except LockedError:
logg.warning('INIT lock is set, skipping GAS GIFTER balance check.')
return True
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
session.close()
rpc = RPCConnection.connect(chain_spec, 'default')
o = balance(gas_provider)
r = rpc.do(o)
try:
r = int(r, 16)
except TypeError:
r = int(r)
gas_min = int(config.get('ETH_GAS_GIFTER_MINIMUM_BALANCE'))
if r < gas_min:
logg.error('EEK! gas gifter has balance {}, below minimum {}'.format(r, gas_min))
return False
return True

View File

@@ -0,0 +1,18 @@
# external imports
import redis
import os
def health(*args, **kwargs):
r = redis.Redis(
host=kwargs['config'].get('REDIS_HOST'),
port=kwargs['config'].get('REDIS_PORT'),
db=kwargs['config'].get('REDIS_DB'),
)
try:
r.set(kwargs['unit'], os.getpid())
except redis.connection.ConnectionError:
return False
except redis.connection.ResponseError:
return False
return True

View File

@@ -0,0 +1,37 @@
# standard imports
import time
import logging
from urllib.error import URLError
# external imports
from chainlib.connection import RPCConnection
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.sign import sign_message
from chainlib.error import JSONRPCException
logg = logging.getLogger().getChild(__name__)
def health(*args, **kwargs):
blocked = True
max_attempts = 5
conn = RPCConnection.connect(kwargs['config'].get('CIC_CHAIN_SPEC'), tag='signer')
for i in range(max_attempts):
idx = i + 1
logg.debug('attempt signer connection check {}/{}'.format(idx, max_attempts))
try:
conn.do(sign_message(ZERO_ADDRESS, '0x2a'))
except FileNotFoundError:
pass
except ConnectionError:
pass
except URLError:
pass
except JSONRPCException:
logg.debug('signer connection succeeded')
return True
if idx < max_attempts:
time.sleep(0.5)
return False

View File

@@ -74,10 +74,11 @@ class LockEnum(enum.IntEnum):
QUEUE: Disable queueing new or modified transactions QUEUE: Disable queueing new or modified transactions
""" """
STICKY=1 STICKY=1
CREATE=2 INIT=2
SEND=4 CREATE=4
QUEUE=8 SEND=8
QUERY=16 QUEUE=16
QUERY=32
ALL=int(0xfffffffffffffffe) ALL=int(0xfffffffffffffffe)

View File

@@ -5,8 +5,11 @@ Revises: 1f1b3b641d08
Create Date: 2021-04-02 18:41:20.864265 Create Date: 2021-04-02 18:41:20.864265
""" """
import datetime
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from chainlib.eth.constant import ZERO_ADDRESS
from cic_eth.db.enum import LockEnum
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
@@ -23,10 +26,11 @@ def upgrade():
sa.Column("address", sa.String(42), nullable=True), sa.Column("address", sa.String(42), nullable=True),
sa.Column('blockchain', sa.String), sa.Column('blockchain', sa.String),
sa.Column("flags", sa.BIGINT(), nullable=False, default=0), sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
sa.Column("date_created", sa.DateTime, nullable=False), sa.Column("date_created", sa.DateTime, nullable=False, default=datetime.datetime.utcnow),
sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True), sa.Column("otx_id", sa.Integer, sa.ForeignKey('otx.id'), nullable=True),
) )
op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True) op.create_index('idx_chain_address', 'lock', ['blockchain', 'address'], unique=True)
op.execute("INSERT INTO lock (address, date_created, blockchain, flags) VALUES('{}', '{}', '::', {})".format(ZERO_ADDRESS, datetime.datetime.utcnow(), LockEnum.INIT | LockEnum.SEND | LockEnum.QUEUE))
def downgrade(): def downgrade():

View File

@@ -48,6 +48,8 @@ class RoleMissingError(Exception):
pass pass
class IntegrityError(Exception): class IntegrityError(Exception):
"""Exception raised to signal irregularities with deduplication and ordering of tasks """Exception raised to signal irregularities with deduplication and ordering of tasks
@@ -62,15 +64,19 @@ class LockedError(Exception):
pass pass
class SignerError(Exception): class SeppukuError(Exception):
"""Exception base class for all errors that should cause system shutdown
"""
class SignerError(SeppukuError):
"""Exception raised when signer is unavailable or generates an error """Exception raised when signer is unavailable or generates an error
""" """
pass pass
class EthError(Exception): class RoleAgencyError(SeppukuError):
"""Exception raised when unspecified error from evm node is encountered """Exception raise when a role cannot perform its function. This is a critical exception
""" """
pass

View File

@@ -3,11 +3,11 @@ import logging
# external imports # external imports
import celery import celery
from erc20_single_shot_faucet import SingleShotFaucet as Faucet from erc20_faucet import Faucet
from chainlib.eth.constant import ZERO_ADDRESS
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
) )
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainlib.eth.sign import ( from chainlib.eth.sign import (
new_account, new_account,
@@ -19,8 +19,9 @@ from chainlib.eth.tx import (
unpack, unpack,
) )
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from eth_accounts_index import AccountRegistry from chainlib.error import JSONRPCException
from sarafu_faucet import MinterFaucet as Faucet from eth_accounts_index.registry import AccountRegistry # TODO, use interface module instead (needs gas limit method)
from sarafu_faucet import MinterFaucet
from chainqueue.db.models.tx import TxCache from chainqueue.db.models.tx import TxCache
# local import # local import
@@ -70,11 +71,18 @@ def create(self, password, chain_spec_dict):
a = None a = None
conn = RPCConnection.connect(chain_spec, 'signer') conn = RPCConnection.connect(chain_spec, 'signer')
o = new_account() o = new_account()
a = conn.do(o) try:
a = conn.do(o)
except ConnectionError as e:
raise SignerError(e)
except FileNotFoundError as e:
raise SignerError(e)
conn.disconnect() conn.disconnect()
# TODO: It seems infeasible that a can be None in any case, verify
if a == None: if a == None:
raise SignerError('create account') raise SignerError('create account')
logg.debug('created account {}'.format(a)) logg.debug('created account {}'.format(a))
# Initialize nonce provider record for account # Initialize nonce provider record for account
@@ -119,13 +127,13 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
if writer_address == ZERO_ADDRESS: if writer_address == ZERO_ADDRESS:
session.close() session.close()
raise RoleMissingError('call address for resgistering {}'.format(account_address)) raise RoleMissingError('call address for resgistering {}'.format(account_address))
account_registry_address = registry.by_name('AccountRegistry', sender_address=call_address) account_registry_address = registry.by_name('AccountsIndex', sender_address=call_address)
# Generate and sign transaction # Generate and sign transaction
rpc_signer = RPCConnection.connect(chain_spec, 'signer') rpc_signer = RPCConnection.connect(chain_spec, 'signer')
nonce_oracle = CustodialTaskNonceOracle(writer_address, self.request.root_id, session=session) #, default_nonce) nonce_oracle = CustodialTaskNonceOracle(writer_address, self.request.root_id, session=session) #, default_nonce)
gas_oracle = self.create_gas_oracle(rpc, AccountRegistry.gas) gas_oracle = self.create_gas_oracle(rpc, AccountsIndex.gas)
account_registry = AccountRegistry(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle) account_registry = AccountsIndex(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, tx_signed_raw_hex) = account_registry.add(account_registry_address, writer_address, account_address, tx_format=TxFormat.RLP_SIGNED) (tx_hash_hex, tx_signed_raw_hex) = account_registry.add(account_registry_address, writer_address, account_address, tx_format=TxFormat.RLP_SIGNED)
rpc_signer.disconnect() rpc_signer.disconnect()
@@ -177,7 +185,7 @@ def gift(self, account_address, chain_spec_dict):
# Generate and sign transaction # Generate and sign transaction
rpc_signer = RPCConnection.connect(chain_spec, 'signer') rpc_signer = RPCConnection.connect(chain_spec, 'signer')
nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce) nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce)
gas_oracle = self.create_gas_oracle(rpc, Faucet.gas) gas_oracle = self.create_gas_oracle(rpc, MinterFaucet.gas)
faucet = Faucet(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle) faucet = Faucet(chain_spec, signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, tx_signed_raw_hex) = faucet.give_to(faucet_address, account_address, account_address, tx_format=TxFormat.RLP_SIGNED) (tx_hash_hex, tx_signed_raw_hex) = faucet.give_to(faucet_address, account_address, account_address, tx_format=TxFormat.RLP_SIGNED)
rpc_signer.disconnect() rpc_signer.disconnect()
@@ -219,21 +227,22 @@ def have(self, account, chain_spec_dict):
""" """
chain_spec = ChainSpec.from_dict(chain_spec_dict) chain_spec = ChainSpec.from_dict(chain_spec_dict)
o = sign_message(account, '0x2a') o = sign_message(account, '0x2a')
try: conn = RPCConnection.connect(chain_spec, 'signer')
conn = RPCConnection.connect(chain_spec, 'signer')
except Exception as e:
logg.debug('cannot sign with {}: {}'.format(account, e))
return None
try: try:
conn.do(o) conn.do(o)
conn.disconnect() except ConnectionError as e:
return account raise SignerError(e)
except Exception as e: except FileNotFoundError as e:
raise SignerError(e)
except JSONRPCException as e:
logg.debug('cannot sign with {}: {}'.format(account, e)) logg.debug('cannot sign with {}: {}'.format(account, e))
conn.disconnect() conn.disconnect()
return None return None
conn.disconnect()
return account
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask) @celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
def set_role(self, tag, address, chain_spec_dict): def set_role(self, tag, address, chain_spec_dict):
@@ -329,7 +338,7 @@ def cache_account_data(
chain_spec = ChainSpec.from_dict(chain_spec_dict) chain_spec = ChainSpec.from_dict(chain_spec_dict)
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:]) tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
tx = unpack(tx_signed_raw_bytes, chain_spec) tx = unpack(tx_signed_raw_bytes, chain_spec)
tx_data = AccountRegistry.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_cache = TxCache(

View File

@@ -6,7 +6,6 @@ import celery
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainlib.eth.erc20 import ERC20
from chainlib.eth.tx import ( from chainlib.eth.tx import (
TxFormat, TxFormat,
unpack, unpack,
@@ -16,6 +15,7 @@ from cic_eth_registry.erc20 import ERC20Token
from hexathon import strip_0x from hexathon import strip_0x
from chainqueue.db.models.tx import TxCache from chainqueue.db.models.tx import TxCache
from chainqueue.error import NotLocalTxError from chainqueue.error import NotLocalTxError
from eth_erc20 import ERC20
# local imports # local imports
from cic_eth.db.models.base import SessionBase from cic_eth.db.models.base import SessionBase
@@ -108,7 +108,13 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_d
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session) nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas) gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle) c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED) try:
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
except FileNotFoundError as e:
raise SignerError(e)
except ConnectionError as e:
raise SignerError(e)
rpc_signer.disconnect() rpc_signer.disconnect()
rpc.disconnect() rpc.disconnect()
@@ -171,7 +177,12 @@ def approve(self, tokens, holder_address, spender_address, value, chain_spec_dic
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session) nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas) gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle) c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED) try:
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
except FileNotFoundError as e:
raise SignerError(e)
except ConnectionError as e:
raise SignerError(e)
rpc_signer.disconnect() rpc_signer.disconnect()
rpc.disconnect() rpc.disconnect()

View File

@@ -328,7 +328,12 @@ def refill_gas(self, recipient_address, chain_spec_dict):
# build and add transaction # build and add transaction
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address)) logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED) try:
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
except ConnectionError as e:
raise SignerError(e)
except FileNotFoundError as e:
raise SignerError(e)
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex)) logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
cache_task = 'cic_eth.eth.gas.cache_gas_data' cache_task = 'cic_eth.eth.gas.cache_gas_data'
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session) register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
@@ -404,7 +409,12 @@ def resend_with_higher_gas(self, txold_hash_hex, chain_spec_dict, gas=None, defa
c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle) c = TxFactory(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle)
logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx)) logg.debug('change gas price from old {} to new {} for tx {}'.format(tx['gasPrice'], new_gas_price, tx))
tx['gasPrice'] = new_gas_price tx['gasPrice'] = new_gas_price
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx) try:
(tx_hash_hex, tx_signed_raw_hex) = c.build_raw(tx)
except ConnectionError as e:
raise SignerError(e)
except FileNotFoundError as e:
raise SignerError(e)
queue_create( queue_create(
chain_spec, chain_spec,
tx['nonce'], tx['nonce'],

View File

@@ -1,6 +1,10 @@
# extended imports # external imports
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.status import Status as TxStatus from chainlib.status import Status as TxStatus
from cic_eth_registry.erc20 import ERC20Token
# local imports
from cic_eth.ext.address import translate_address
class ExtendedTx: class ExtendedTx:
@@ -27,12 +31,12 @@ class ExtendedTx:
self.status_code = TxStatus.PENDING.value self.status_code = TxStatus.PENDING.value
def set_actors(self, sender, recipient, trusted_declarator_addresses=None): def set_actors(self, sender, recipient, trusted_declarator_addresses=None, caller_address=ZERO_ADDRESS):
self.sender = sender self.sender = sender
self.recipient = recipient self.recipient = recipient
if trusted_declarator_addresses != None: if trusted_declarator_addresses != None:
self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec) self.sender_label = translate_address(sender, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec) self.recipient_label = translate_address(recipient, trusted_declarator_addresses, self.chain_spec, sender_address=caller_address)
def set_tokens(self, source, source_value, destination=None, destination_value=None): def set_tokens(self, source, source_value, destination=None, destination_value=None):
@@ -40,8 +44,8 @@ class ExtendedTx:
destination = source destination = source
if destination_value == None: if destination_value == None:
destination_value = source_value destination_value = source_value
st = ERC20Token(self.rpc, source) st = ERC20Token(self.chain_spec, self.rpc, source)
dt = ERC20Token(self.rpc, destination) dt = ERC20Token(self.chain_spec, self.rpc, destination)
self.source_token = source self.source_token = source
self.source_token_symbol = st.symbol self.source_token_symbol = st.symbol
self.source_token_name = st.name self.source_token_name = st.name
@@ -62,10 +66,10 @@ class ExtendedTx:
self.status_code = n self.status_code = n
def to_dict(self): def asdict(self):
o = {} o = {}
for attr in dir(self): for attr in dir(self):
if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'to_dict']: if attr[0] == '_' or attr in ['set_actors', 'set_tokens', 'set_status', 'asdict', 'rpc']:
continue continue
o[attr] = getattr(self, attr) o[attr] = getattr(self, attr)
return o return o

View File

@@ -7,7 +7,7 @@ from chainlib.chain import ChainSpec
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from cic_eth_registry import CICRegistry from cic_eth_registry import CICRegistry
from eth_address_declarator import AddressDeclarator from eth_address_declarator import Declarator
# local imports # local imports
from cic_eth.task import BaseTask from cic_eth.task import BaseTask
@@ -23,12 +23,12 @@ def translate_address(address, trusted_addresses, chain_spec, sender_address=ZER
registry = CICRegistry(chain_spec, rpc) registry = CICRegistry(chain_spec, rpc)
declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address) declarator_address = registry.by_name('AddressDeclarator', sender_address=sender_address)
c = AddressDeclarator(chain_spec) c = Declarator(chain_spec)
for trusted_address in trusted_addresses: for trusted_address in trusted_addresses:
o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address) o = c.declaration(declarator_address, trusted_address, address, sender_address=sender_address)
r = rpc.do(o) r = rpc.do(o)
declaration_hex = AddressDeclarator.parse_declaration(r) declaration_hex = Declarator.parse_declaration(r)
declaration_hex = declaration_hex[0].rstrip('0') declaration_hex = declaration_hex[0].rstrip('0')
declaration_bytes = bytes.fromhex(declaration_hex) declaration_bytes = bytes.fromhex(declaration_hex)
declaration = None declaration = None

View File

@@ -14,13 +14,13 @@ from chainlib.eth.tx import (
) )
from chainlib.eth.block import block_by_number from chainlib.eth.block import block_by_number
from chainlib.eth.contract import abi_decode_single from chainlib.eth.contract import abi_decode_single
from chainlib.eth.erc20 import ERC20
from hexathon import strip_0x from hexathon import strip_0x
from cic_eth_registry import CICRegistry from cic_eth_registry import CICRegistry
from cic_eth_registry.erc20 import ERC20Token from cic_eth_registry.erc20 import ERC20Token
from chainqueue.db.models.otx import Otx from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusEnum from chainqueue.db.enum import StatusEnum
from chainqueue.query import get_tx_cache from chainqueue.query import get_tx_cache
from eth_erc20 import ERC20
# local imports # local imports
from cic_eth.queue.time import tx_times from cic_eth.queue.time import tx_times
@@ -114,7 +114,7 @@ def list_tx_by_bloom(self, bloomspec, address, chain_spec_dict):
# TODO: pass through registry to validate declarator entry of token # TODO: pass through registry to validate declarator entry of token
#token = registry.by_address(tx['to'], sender_address=self.call_address) #token = registry.by_address(tx['to'], sender_address=self.call_address)
token = ERC20Token(rpc, tx['to']) token = ERC20Token(chain_spec, rpc, tx['to'])
token_symbol = token.symbol token_symbol = token.symbol
token_decimals = token.decimals token_decimals = token.decimals
times = tx_times(tx['hash'], chain_spec) times = tx_times(tx['hash'], chain_spec)

View File

@@ -12,6 +12,7 @@ from chainqueue.error import NotLocalTxError
# local imports # local imports
from cic_eth.task import CriticalSQLAlchemyAndWeb3Task from cic_eth.task import CriticalSQLAlchemyAndWeb3Task
from cic_eth.db.models.base import SessionBase
celery_app = celery.current_app celery_app = celery.current_app

View File

@@ -29,5 +29,5 @@ def connect(rpc, chain_spec, registry_address):
CICRegistry.address = registry_address CICRegistry.address = registry_address
registry = CICRegistry(chain_spec, rpc) registry = CICRegistry(chain_spec, rpc)
registry_address = registry.by_name('ContractRegistry') registry_address = registry.by_name('ContractRegistry')
return registry return registry

View File

@@ -23,7 +23,6 @@ default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
argparser = argparse.ArgumentParser() argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)') argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
argparser.add_argument('-r', '--registry-address', type=str, help='CIC registry address')
argparser.add_argument('-f', '--format', dest='f', default=default_format, type=str, help='Output format') argparser.add_argument('-f', '--format', dest='f', default=default_format, type=str, help='Output format')
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use') argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec') argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
@@ -59,6 +58,7 @@ args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'), 'CIC_CHAIN_SPEC': getattr(args, 'i'),
} }
# override args # override args
config.dict_override(args_override, 'cli')
config.censor('PASSWORD', 'DATABASE') config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL') config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(config_dir, config)) logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
@@ -67,7 +67,9 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
queue = args.q queue = args.q
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC')) chain_spec = None
if config.get('CIC_CHAIN_SPEC') != None and config.get('CIC_CHAIN_SPEC') != '::':
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
admin_api = AdminApi(None) admin_api = AdminApi(None)
@@ -82,6 +84,9 @@ def lock_names_to_flag(s):
# TODO: move each command to submodule # TODO: move each command to submodule
def main(): def main():
chain_spec_dict = None
if chain_spec != None:
chain_spec_dict = chain_spec.asdict()
if args.command == 'unlock': if args.command == 'unlock':
flags = lock_names_to_flag(args.flags) flags = lock_names_to_flag(args.flags)
if not is_checksum_address(args.address): if not is_checksum_address(args.address):
@@ -91,7 +96,7 @@ def main():
'cic_eth.admin.ctrl.unlock', 'cic_eth.admin.ctrl.unlock',
[ [
None, None,
chain_spec.asdict(), chain_spec_dict,
args.address, args.address,
flags, flags,
], ],
@@ -110,7 +115,7 @@ def main():
'cic_eth.admin.ctrl.lock', 'cic_eth.admin.ctrl.lock',
[ [
None, None,
chain_spec.asdict(), chain_spec_dict,
args.address, args.address,
flags, flags,
], ],

View File

@@ -15,7 +15,6 @@ from cic_eth_registry import CICRegistry
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainlib.eth.tx import unpack from chainlib.eth.tx import unpack
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainsyncer.error import SyncDone
from hexathon import strip_0x from hexathon import strip_0x
from chainqueue.db.enum import ( from chainqueue.db.enum import (
StatusEnum, StatusEnum,
@@ -153,10 +152,7 @@ class DispatchSyncer:
def main(): def main():
syncer = DispatchSyncer(chain_spec) syncer = DispatchSyncer(chain_spec)
conn = RPCConnection.connect(chain_spec, 'default') conn = RPCConnection.connect(chain_spec, 'default')
try: syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
syncer.loop(conn, float(config.get('DISPATCHER_LOOP_INTERVAL')))
except SyncDone as e:
sys.stderr.write("dispatcher done at block {}\n".format(e))
sys.exit(0) sys.exit(0)

View File

@@ -1,7 +1,7 @@
# standard imports # standard imports
import logging import logging
# third-party imports # external imports
import celery import celery
from cic_eth_registry.error import UnknownContractError from cic_eth_registry.error import UnknownContractError
from chainlib.status import Status as TxStatus from chainlib.status import Status as TxStatus
@@ -9,7 +9,11 @@ from chainlib.eth.address import to_checksum_address
from chainlib.eth.error import RequestMismatchException from chainlib.eth.error import RequestMismatchException
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.erc20 import ERC20 from chainlib.eth.erc20 import ERC20
from hexathon import strip_0x from hexathon import (
strip_0x,
add_0x,
)
from erc20_faucet import Faucet
# local imports # local imports
from .base import SyncFilter from .base import SyncFilter
@@ -18,65 +22,72 @@ from cic_eth.eth.meta import ExtendedTx
logg = logging.getLogger().getChild(__name__) logg = logging.getLogger().getChild(__name__)
def parse_transfer(tx):
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = r[1]
transfer_data['from'] = tx['from']
transfer_data['token_address'] = tx['to']
return ('transfer', transfer_data)
def parse_transferfrom(tx):
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = unpack_transferfrom(tx.payload)
transfer_data['from'] = r[0]
transfer_data['to'] = r[1]
transfer_data['value'] = r[2]
transfer_data['token_address'] = tx['to']
return ('transferfrom', transfer_data)
def parse_giftto(tx):
# TODO: broken
logg.error('broken')
return
transfer_data = unpack_gift(tx.payload)
transfer_data['from'] = tx.inputs[0]
transfer_data['value'] = 0
transfer_data['token_address'] = ZERO_ADDRESS
# TODO: would be better to query the gift amount from the block state
for l in tx.logs:
topics = l['topics']
logg.debug('topixx {}'.format(topics))
if strip_0x(topics[0]) == '45c201a59ac545000ead84f30b2db67da23353aa1d58ac522c48505412143ffa':
#transfer_data['value'] = web3.Web3.toInt(hexstr=strip_0x(l['data']))
transfer_data['value'] = int.from_bytes(bytes.fromhex(strip_0x(l_data)))
#token_address_bytes = topics[2][32-20:]
token_address = strip_0x(topics[2])[64-40:]
transfer_data['token_address'] = to_checksum_address(token_address)
return ('tokengift', transfer_data)
class CallbackFilter(SyncFilter): class CallbackFilter(SyncFilter):
trusted_addresses = [] trusted_addresses = []
def __init__(self, chain_spec, method, queue): def __init__(self, chain_spec, method, queue, caller_address=ZERO_ADDRESS):
self.queue = queue self.queue = queue
self.method = method self.method = method
self.chain_spec = chain_spec self.chain_spec = chain_spec
self.caller_address = caller_address
def parse_transfer(self, tx, conn):
if not tx.payload:
return (None, None)
r = ERC20.parse_transfer_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = r[1]
transfer_data['from'] = tx.outputs[0]
transfer_data['token_address'] = tx.inputs[0]
return ('transfer', transfer_data)
def parse_transferfrom(self, tx, conn):
if not tx.payload:
return (None, None)
r = ERC20.parse_transfer_from_request(tx.payload)
transfer_data = {}
transfer_data['from'] = r[0]
transfer_data['to'] = r[1]
transfer_data['value'] = r[2]
transfer_data['token_address'] = tx.inputs[0]
return ('transferfrom', transfer_data)
def parse_giftto(self, tx, conn):
if not tx.payload:
return (None, None)
r = Faucet.parse_give_to_request(tx.payload)
transfer_data = {}
transfer_data['to'] = r[0]
transfer_data['value'] = tx.value
transfer_data['from'] = tx.outputs[0]
#transfer_data['token_address'] = tx.inputs[0]
faucet_contract = tx.inputs[0]
o = Faucet.token(faucet_contract, sender_address=self.caller_address)
r = conn.do(o)
transfer_data['token_address'] = add_0x(c.parse_token(r))
o = c.token_amount(faucet_contract, sender_address=self.caller_address)
r = conn.do(o)
transfer_data['value'] = c.parse_token_amount(r)
return ('tokengift', transfer_data)
def call_back(self, transfer_type, result): def call_back(self, transfer_type, result):
logg.debug('result {}'.format(result)) result['chain_spec'] = result['chain_spec'].asdict()
s = celery.signature( s = celery.signature(
self.method, self.method,
[ [
result, result,
transfer_type, transfer_type,
int(result['status_code'] == 0), int(result['status_code'] != 0),
], ],
queue=self.queue, queue=self.queue,
) )
@@ -92,26 +103,29 @@ class CallbackFilter(SyncFilter):
# s_translate.link(s) # s_translate.link(s)
# s_translate.apply_async() # s_translate.apply_async()
t = s.apply_async() t = s.apply_async()
return s return t
def parse_data(self, tx): def parse_data(self, tx, conn):
transfer_type = None transfer_type = None
transfer_data = None transfer_data = None
# TODO: what's with the mix of attributes and dict keys # TODO: what's with the mix of attributes and dict keys
logg.debug('have payload {}'.format(tx.payload)) logg.debug('have payload {}'.format(tx.payload))
method_signature = tx.payload[:8]
logg.debug('tx status {}'.format(tx.status)) logg.debug('tx status {}'.format(tx.status))
for parser in [ for parser in [
parse_transfer, self.parse_transfer,
parse_transferfrom, self.parse_transferfrom,
parse_giftto, self.parse_giftto,
]: ]:
try: try:
(transfer_type, transfer_data) = parser(tx) if tx:
break (transfer_type, transfer_data) = parser(tx, conn)
if transfer_type == None:
continue
else:
pass
except RequestMismatchException: except RequestMismatchException:
continue continue
@@ -128,7 +142,7 @@ class CallbackFilter(SyncFilter):
transfer_data = None transfer_data = None
transfer_type = None transfer_type = None
try: try:
(transfer_type, transfer_data) = self.parse_data(tx) (transfer_type, transfer_data) = self.parse_data(tx, conn)
except TypeError: except TypeError:
logg.debug('invalid method data length for tx {}'.format(tx.hash)) logg.debug('invalid method data length for tx {}'.format(tx.hash))
return return
@@ -144,16 +158,17 @@ class CallbackFilter(SyncFilter):
result = None result = None
try: try:
tokentx = ExtendedTx(conn, tx.hash, self.chain_spec) tokentx = ExtendedTx(conn, tx.hash, self.chain_spec)
tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses) tokentx.set_actors(transfer_data['from'], transfer_data['to'], self.trusted_addresses, caller_address=self.caller_address)
tokentx.set_tokens(transfer_data['token_address'], transfer_data['value']) tokentx.set_tokens(transfer_data['token_address'], transfer_data['value'])
if transfer_data['status'] == 0: if transfer_data['status'] == 0:
tokentx.set_status(1) tokentx.set_status(1)
else: else:
tokentx.set_status(0) tokentx.set_status(0)
t = self.call_back(transfer_type, tokentx.to_dict()) result = tokentx.asdict()
logg.info('callback success task id {} tx {}'.format(t, tx.hash)) t = self.call_back(transfer_type, result)
logg.info('callback success task id {} tx {} queue {}'.format(t, tx.hash, t.queue))
except UnknownContractError: except UnknownContractError:
logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tc.queue, tc.method, transfer_data['to'], tx.hash)) logg.debug('callback filter {}:{} skipping "transfer" method on unknown contract {} tx {}'.format(tx.queue, tx.method, transfer_data['to'], tx.hash))
def __str__(self): def __str__(self):

View File

@@ -11,10 +11,19 @@ import websocket
# external imports # external imports
import celery import celery
import confini import confini
from chainlib.connection import RPCConnection from chainlib.connection import (
from chainlib.eth.connection import EthUnixSignerConnection RPCConnection,
ConnType,
)
from chainlib.eth.connection import (
EthUnixSignerConnection,
EthHTTPSignerConnection,
)
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainqueue.db.models.otx import Otx from chainqueue.db.models.otx import Otx
from cic_eth_registry.error import UnknownContractError
import liveness.linux
# local imports # local imports
from cic_eth.eth import ( from cic_eth.eth import (
@@ -27,6 +36,7 @@ from cic_eth.eth import (
from cic_eth.admin import ( from cic_eth.admin import (
debug, debug,
ctrl, ctrl,
token
) )
from cic_eth.queue import ( from cic_eth.queue import (
query, query,
@@ -39,6 +49,7 @@ from cic_eth.queue import (
from cic_eth.callbacks import ( from cic_eth.callbacks import (
Callback, Callback,
http, http,
noop,
#tcp, #tcp,
redis, redis,
) )
@@ -50,6 +61,8 @@ from cic_eth.registry import (
connect_declarator, connect_declarator,
connect_token_registry, connect_token_registry,
) )
from cic_eth.task import BaseTask
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger() logg = logging.getLogger()
@@ -61,6 +74,7 @@ argparser.add_argument('-p', '--provider', dest='p', type=str, help='rpc provide
argparser.add_argument('-c', type=str, default=config_dir, help='config file') argparser.add_argument('-c', type=str, default=config_dir, help='config file')
argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks') argparser.add_argument('-q', type=str, default='cic-eth', help='queue name for worker tasks')
argparser.add_argument('-r', type=str, help='CIC registry address') argparser.add_argument('-r', type=str, help='CIC registry address')
argparser.add_argument('--default-token-symbol', dest='default_token_symbol', type=str, help='Symbol of default token to use')
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi') argparser.add_argument('--abi-dir', dest='abi_dir', type=str, help='Directory containing bytecode and abi')
argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage') argparser.add_argument('--trace-queue-status', default=None, dest='trace_queue_status', action='store_true', help='set to perist all queue entry status changes to storage')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec') argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
@@ -80,6 +94,7 @@ config.process()
args_override = { args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'), 'CIC_CHAIN_SPEC': getattr(args, 'i'),
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'), 'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
'CIC_DEFAULT_TOKEN_SYMBOL': getattr(args, 'default_token_symbol'),
'ETH_PROVIDER': getattr(args, 'p'), 'ETH_PROVIDER': getattr(args, 'p'),
'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'), 'TASKS_TRACE_QUEUE_STATUS': getattr(args, 'trace_queue_status'),
} }
@@ -89,14 +104,15 @@ config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL') config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(args.c, config)) logg.debug('config loaded from {}:\n{}'.format(args.c, config))
health_modules = config.get('CIC_HEALTH_MODULES', [])
if len(health_modules) != 0:
health_modules = health_modules.split(',')
logg.debug('health mods {}'.format(health_modules))
# connect to database # connect to database
dsn = dsn_from_config(config) dsn = dsn_from_config(config)
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG')) SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
# verify database connection with minimal sanity query
session = SessionBase.create_session()
session.execute('select version_num from alembic_version')
session.close()
# set up celery # set up celery
current_app = celery.Celery(__name__) current_app = celery.Celery(__name__)
@@ -133,11 +149,18 @@ else:
}) })
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC')) chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
RPCConnection.register_constructor(ConnType.UNIX, EthUnixSignerConnection, 'signer')
RPCConnection.register_constructor(ConnType.HTTP, EthHTTPSignerConnection, 'signer')
RPCConnection.register_constructor(ConnType.HTTP_SSL, EthHTTPSignerConnection, 'signer')
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default') RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection) RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer')
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS') Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
#import cic_eth.checks.gas
#if not cic_eth.checks.gas.health(config=config):
# raise RuntimeError()
liveness.linux.load(health_modules, rundir=config.get('CIC_RUN_DIR'), config=config, unit='cic-eth-tasker')
def main(): def main():
argv = ['worker'] argv = ['worker']
@@ -161,7 +184,11 @@ def main():
rpc = RPCConnection.connect(chain_spec, 'default') rpc = RPCConnection.connect(chain_spec, 'default')
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS')) 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)
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS') trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
if trusted_addresses_src == None: if trusted_addresses_src == None:
@@ -170,10 +197,18 @@ def main():
trusted_addresses = trusted_addresses_src.split(',') trusted_addresses = trusted_addresses_src.split(',')
for address in trusted_addresses: for address in trusted_addresses:
logg.info('using trusted address {}'.format(address)) logg.info('using trusted address {}'.format(address))
connect_declarator(rpc, chain_spec, trusted_addresses) connect_declarator(rpc, chain_spec, trusted_addresses)
connect_token_registry(rpc, chain_spec) connect_token_registry(rpc, chain_spec)
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
BaseTask.run_dir = config.get('CIC_RUN_DIR')
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))
liveness.linux.set(rundir=config.get('CIC_RUN_DIR'))
current_app.worker_main(argv) current_app.worker_main(argv)
liveness.linux.reset(rundir=config.get('CIC_RUN_DIR'))
@celery.signals.eventlet_pool_postshutdown.connect @celery.signals.eventlet_pool_postshutdown.connect

View File

@@ -15,7 +15,6 @@ import cic_base.config
import cic_base.log import cic_base.log
import cic_base.argparse import cic_base.argparse
import cic_base.rpc import cic_base.rpc
from cic_eth_registry import CICRegistry
from cic_eth_registry.error import UnknownContractError from cic_eth_registry.error import UnknownContractError
from chainlib.chain import ChainSpec from chainlib.chain import ChainSpec
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
@@ -26,7 +25,7 @@ from chainlib.eth.block import (
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
) )
from chainsyncer.backend import SyncerBackend from chainsyncer.backend.sql import SQLBackend
from chainsyncer.driver import ( from chainsyncer.driver import (
HeadSyncer, HeadSyncer,
HistorySyncer, HistorySyncer,
@@ -43,6 +42,12 @@ from cic_eth.runnable.daemons.filters import (
TransferAuthFilter, TransferAuthFilter,
) )
from cic_eth.stat import init_chain_stat from cic_eth.stat import init_chain_stat
from cic_eth.registry import (
connect as connect_registry,
connect_declarator,
connect_token_registry,
)
script_dir = os.path.realpath(os.path.dirname(__file__)) script_dir = os.path.realpath(os.path.dirname(__file__))
@@ -88,18 +93,18 @@ def main():
syncers = [] syncers = []
#if SyncerBackend.first(chain_spec): #if SQLBackend.first(chain_spec):
# backend = SyncerBackend.initial(chain_spec, block_offset) # backend = SQLBackend.initial(chain_spec, block_offset)
syncer_backends = SyncerBackend.resume(chain_spec, block_offset) syncer_backends = SQLBackend.resume(chain_spec, block_offset)
if len(syncer_backends) == 0: if len(syncer_backends) == 0:
logg.info('found no backends to resume') logg.info('found no backends to resume')
syncer_backends.append(SyncerBackend.initial(chain_spec, block_offset)) syncer_backends.append(SQLBackend.initial(chain_spec, block_offset))
else: else:
for syncer_backend in syncer_backends: for syncer_backend in syncer_backends:
logg.info('resuming sync session {}'.format(syncer_backend)) logg.info('resuming sync session {}'.format(syncer_backend))
syncer_backends.append(SyncerBackend.live(chain_spec, block_offset+1)) syncer_backends.append(SQLBackend.live(chain_spec, block_offset+1))
for syncer_backend in syncer_backends: for syncer_backend in syncer_backends:
try: try:
@@ -109,6 +114,8 @@ def main():
logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend)) logg.info('Initializing HEAD syncer on backend {}'.format(syncer_backend))
syncers.append(HeadSyncer(syncer_backend)) syncers.append(HeadSyncer(syncer_backend))
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS') trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
if trusted_addresses_src == None: if trusted_addresses_src == None:
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS') logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
@@ -116,6 +123,8 @@ def main():
trusted_addresses = trusted_addresses_src.split(',') trusted_addresses = trusted_addresses_src.split(',')
for address in trusted_addresses: for address in trusted_addresses:
logg.info('using trusted address {}'.format(address)) logg.info('using trusted address {}'.format(address))
connect_declarator(rpc, chain_spec, trusted_addresses)
connect_token_registry(rpc, chain_spec)
CallbackFilter.trusted_addresses = trusted_addresses CallbackFilter.trusted_addresses = trusted_addresses
callback_filters = [] callback_filters = []

View File

@@ -0,0 +1,65 @@
#!python3
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import logging
import argparse
import os
# external imports
import confini
import celery
# local imports
from cic_eth.api import Api
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_format = 'terminal'
default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/etc/cic')
argparser = argparse.ArgumentParser()
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, help='chain spec')
argparser.add_argument('-c', type=str, default=default_config_dir, help='config root to use')
argparser.add_argument('-q', type=str, default='cic-eth', help='celery queue to submit transaction tasks to')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', help='be more verbose', action='store_true')
args = argparser.parse_args()
if args.v == True:
logging.getLogger().setLevel(logging.INFO)
elif args.vv == True:
logging.getLogger().setLevel(logging.DEBUG)
config_dir = os.path.join(args.c)
os.makedirs(config_dir, 0o777, True)
config = confini.Config(config_dir, args.env_prefix)
config.process()
args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
}
config.dict_override(args_override, 'cli args')
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
queue = args.q
api = Api(config.get('CIC_CHAIN_SPEC'), queue=queue)
def main():
t = api.default_token()
token_info = t.get()
print('Default token symbol: {}'.format(token_info['symbol']))
print('Default token address: {}'.format(token_info['address']))
if __name__ == '__main__':
main()

View File

@@ -85,9 +85,6 @@ def main():
callback_queue=args.q, callback_queue=args.q,
) )
#register = not args.no_register
#logg.debug('register {}'.format(register))
#t = api.create_account(register=register)
t = api.transfer(config.get('_SENDER'), config.get('_RECIPIENT'), config.get('_VALUE'), config.get('_SYMBOL')) t = api.transfer(config.get('_SENDER'), config.get('_RECIPIENT'), config.get('_VALUE'), config.get('_SYMBOL'))
ps.get_message() ps.get_message()

View File

@@ -81,10 +81,14 @@ chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
rpc = EthHTTPConnection(args.p) rpc = EthHTTPConnection(args.p)
registry_address = config.get('CIC_REGISTRY_ADDRESS') #registry_address = config.get('CIC_REGISTRY_ADDRESS')
admin_api = AdminApi(rpc) admin_api = AdminApi(rpc)
t = admin_api.registry()
registry_address = t.get()
logg.info('got registry address from task pool: {}'.format(registry_address))
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS') trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
if trusted_addresses_src == None: if trusted_addresses_src == None:
logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS') logg.critical('At least one trusted address must be declared in CIC_TRUST_ADDRESS')
@@ -151,14 +155,16 @@ def main():
txs = [] txs = []
renderer = render_tx renderer = render_tx
if len(config.get('_QUERY')) > 66: if len(config.get('_QUERY')) > 66:
registry = connect_registry(rpc, chain_spec, registry_address) #registry = connect_registry(rpc, chain_spec, registry_address)
admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), registry=registry, renderer=renderer) #admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), registry=registry, renderer=renderer)
admin_api.tx(chain_spec, tx_raw=config.get('_QUERY'), renderer=renderer)
elif len(config.get('_QUERY')) > 42: elif len(config.get('_QUERY')) > 42:
registry = connect_registry(rpc, chain_spec, registry_address) #registry = connect_registry(rpc, chain_spec, registry_address)
admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), registry=registry, renderer=renderer) #admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), registry=registry, renderer=renderer)
admin_api.tx(chain_spec, tx_hash=config.get('_QUERY'), renderer=renderer)
elif len(config.get('_QUERY')) == 42: elif len(config.get('_QUERY')) == 42:
registry = connect_registry(rpc, chain_spec, registry_address) #registry = connect_registry(rpc, chain_spec, registry_address)
txs = admin_api.account(chain_spec, config.get('_QUERY'), include_recipient=False, renderer=render_account) txs = admin_api.account(chain_spec, config.get('_QUERY'), include_recipient=False, renderer=render_account)
renderer = render_account renderer = render_account
elif len(config.get('_QUERY')) >= 4 and config.get('_QUERY')[:4] == 'lock': elif len(config.get('_QUERY')) >= 4 and config.get('_QUERY')[:4] == 'lock':

View File

@@ -4,7 +4,7 @@ import datetime
# external imports # external imports
from chainsyncer.driver import HeadSyncer from chainsyncer.driver import HeadSyncer
from chainsyncer.backend import MemBackend from chainsyncer.backend.memory import MemBackend
from chainsyncer.error import NoBlockForYou from chainsyncer.error import NoBlockForYou
from chainlib.eth.block import ( from chainlib.eth.block import (
block_by_number, block_by_number,

View File

@@ -7,18 +7,20 @@ import uuid
# external imports # external imports
import celery import celery
import sqlalchemy import sqlalchemy
from chainlib.chain import ChainSpec
from chainlib.connection import RPCConnection
from chainlib.eth.constant import ZERO_ADDRESS from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.nonce import RPCNonceOracle from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import RPCGasOracle from chainlib.eth.gas import RPCGasOracle
from cic_eth_registry import CICRegistry
from cic_eth_registry.error import UnknownContractError
import liveness.linux
# local imports # local imports
from cic_eth.error import ( from cic_eth.error import SeppukuError
SignerError,
EthError,
)
from cic_eth.db.models.base import SessionBase from cic_eth.db.models.base import SessionBase
logg = logging.getLogger(__name__) logg = logging.getLogger().getChild(__name__)
celery_app = celery.current_app celery_app = celery.current_app
@@ -29,6 +31,9 @@ class BaseTask(celery.Task):
call_address = ZERO_ADDRESS call_address = ZERO_ADDRESS
create_nonce_oracle = RPCNonceOracle create_nonce_oracle = RPCNonceOracle
create_gas_oracle = RPCGasOracle create_gas_oracle = RPCGasOracle
default_token_address = None
default_token_symbol = None
run_dir = '/run'
def create_session(self): def create_session(self):
return BaseTask.session_func() return BaseTask.session_func()
@@ -38,6 +43,19 @@ class BaseTask(celery.Task):
logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id)) logg.debug('task {} root uuid {}'.format(self.__class__.__name__, self.request.root_id))
return return
def on_failure(self, exc, task_id, args, kwargs, einfo):
if isinstance(exc, SeppukuError):
liveness.linux.reset(rundir=self.run_dir)
logg.critical(einfo)
msg = 'received critical exception {}, calling shutdown'.format(str(exc))
s = celery.signature(
'cic_eth.admin.ctrl.shutdown',
[msg],
queue=self.request.delivery_info.get('routing_key'),
)
s.apply_async()
class CriticalTask(BaseTask): class CriticalTask(BaseTask):
retry_jitter = True retry_jitter = True
@@ -67,7 +85,6 @@ class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
sqlalchemy.exc.TimeoutError, sqlalchemy.exc.TimeoutError,
requests.exceptions.ConnectionError, requests.exceptions.ConnectionError,
sqlalchemy.exc.ResourceClosedError, sqlalchemy.exc.ResourceClosedError,
EthError,
) )
safe_gas_threshold_amount = 2000000000 * 60000 * 3 safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5 safe_gas_refill_amount = safe_gas_threshold_amount * 5
@@ -78,19 +95,45 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
sqlalchemy.exc.DatabaseError, sqlalchemy.exc.DatabaseError,
sqlalchemy.exc.TimeoutError, sqlalchemy.exc.TimeoutError,
sqlalchemy.exc.ResourceClosedError, sqlalchemy.exc.ResourceClosedError,
SignerError,
) )
class CriticalWeb3AndSignerTask(CriticalTask): class CriticalWeb3AndSignerTask(CriticalTask):
autoretry_for = ( autoretry_for = (
requests.exceptions.ConnectionError, requests.exceptions.ConnectionError,
SignerError,
) )
safe_gas_threshold_amount = 2000000000 * 60000 * 3 safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5 safe_gas_refill_amount = safe_gas_threshold_amount * 5
@celery_app.task(bind=True, base=BaseTask) @celery_app.task()
def hello(self): def check_health(self):
time.sleep(0.1) pass
return id(SessionBase.create_session)
# TODO: registry / rpc methods should perhaps be moved to better named module
@celery_app.task()
def registry():
return CICRegistry.address
@celery_app.task()
def registry_address_lookup(chain_spec_dict, address, connection_tag='default'):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
registry = CICRegistry(chain_spec, conn)
return registry.by_address(address)
@celery_app.task(throws=(UnknownContractError,))
def registry_name_lookup(chain_spec_dict, name, connection_tag='default'):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
registry = CICRegistry(chain_spec, conn)
return registry.by_name(name)
@celery_app.task()
def rpc_proxy(chain_spec_dict, o, connection_tag='default'):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
conn = RPCConnection.connect(chain_spec, tag=connection_tag)
return conn.do(o)

View File

@@ -10,7 +10,7 @@ version = (
0, 0,
11, 11,
0, 0,
'beta.3', 'beta.12',
) )
version_object = semver.VersionInfo( version_object = semver.VersionInfo(

View File

@@ -3,3 +3,6 @@ registry_address =
chain_spec = evm:bloxberg:8996 chain_spec = evm:bloxberg:8996
tx_retry_delay = tx_retry_delay =
trust_address = trust_address =
default_token_symbol = GFT
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
run_dir = /run

View File

@@ -3,3 +3,6 @@ registry_address =
chain_spec = evm:bloxberg:8996 chain_spec = evm:bloxberg:8996
trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C trust_address = 0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
tx_retry_delay = 20 tx_retry_delay = 20
default_token_symbol = GFT
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
run_dir = /run

View File

@@ -1,8 +1,3 @@
[eth] [eth]
#ws_provider = ws://localhost:8546
#ttp_provider = http://localhost:8545
provider = http://localhost:63545 provider = http://localhost:63545
gas_provider_address = gas_gifter_minimum_balance = 10000000000000000000000
#chain_id =
abi_dir = /home/lash/src/ext/cic/grassrootseconomics/cic-contracts/abis
account_accounts_index_writer =

View File

@@ -1,5 +1,5 @@
[signer] [signer]
socket_path = /tmp/crypto-dev-signer/jsonrpc.ipc socket_path = ipc:///tmp/crypto-dev-signer/jsonrpc.ipc
secret = deedbeef secret = deedbeef
database_name = signer_test database_name = signer_test
dev_keys_path = dev_keys_path =

View File

@@ -1,8 +1,3 @@
[eth] [eth]
#ws_provider = ws://localhost:8546
#ttp_provider = http://localhost:8545
provider = http://localhost:8545 provider = http://localhost:8545
gas_provider_address = gas_gifter_minimum_balance = 10000000000000000000000
#chain_id =
abi_dir = /usr/local/share/cic/solidity/abi
account_accounts_index_writer =

View File

@@ -29,7 +29,7 @@ RUN /usr/local/bin/python -m pip install --upgrade pip
# python merge_requirements.py | tee merged_requirements.txt # python merge_requirements.py | tee merged_requirements.txt
#RUN cd cic-base && \ #RUN cd cic-base && \
# pip install $pip_extra_index_url_flag -r ./merged_requirements.txt # pip install $pip_extra_index_url_flag -r ./merged_requirements.txt
RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2a62 RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b8
COPY cic-eth/scripts/ scripts/ COPY cic-eth/scripts/ scripts/
COPY cic-eth/setup.cfg cic-eth/setup.py ./ COPY cic-eth/setup.cfg cic-eth/setup.py ./
@@ -50,6 +50,4 @@ COPY cic-eth/config/ /usr/local/etc/cic-eth/
COPY cic-eth/cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/ COPY cic-eth/cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/ COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \ COPY util/liveness/health.sh /usr/local/bin/health.sh
mkdir -p /usr/local/share/cic/solidity && \
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi

View File

@@ -1,25 +1,24 @@
cic-base~=0.1.2a67 cic-base==0.1.2b8
celery==4.4.7 celery==4.4.7
crypto-dev-signer~=0.4.14a17 crypto-dev-signer~=0.4.14b3
confini~=0.3.6rc3 confini~=0.3.6rc3
cic-eth-registry~=0.5.4a13 cic-eth-registry~=0.5.5a4
#cic-bancor~=0.0.6
redis==3.5.3 redis==3.5.3
alembic==1.4.2 alembic==1.4.2
websockets==8.1 websockets==8.1
requests~=2.24.0 requests~=2.24.0
eth_accounts_index~=0.0.11a7 eth_accounts_index~=0.0.11a11
erc20-transfer-authorization~=0.3.1a3 erc20-transfer-authorization~=0.3.1a6
#simple-rlp==0.1.2
uWSGI==2.0.19.1 uWSGI==2.0.19.1
semver==2.13.0 semver==2.13.0
websocket-client==0.57.0 websocket-client==0.57.0
moolb~=0.1.1b2 moolb~=0.1.1b2
eth-address-index~=0.1.1a7 eth-address-index~=0.1.1a11
chainlib~=0.0.2a5 chainlib~=0.0.3a1
hexathon~=0.0.1a7 hexathon~=0.0.1a7
chainsyncer[sql]~=0.0.2a1 chainsyncer[sql]~=0.0.2a4
chainqueue~=0.0.1a7 chainqueue~=0.0.2a2
pysha3==1.0.2 sarafu-faucet==0.0.3a1
coincurve==15.0.0 coincurve==15.0.0
sarafu-faucet~=0.0.2a19 sarafu-faucet==0.0.3a2
potaahto~=0.0.1a1

View File

@@ -38,6 +38,7 @@ packages =
cic_eth.runnable.daemons.filters cic_eth.runnable.daemons.filters
cic_eth.callbacks cic_eth.callbacks
cic_eth.sync cic_eth.sync
cic_eth.check
scripts = scripts =
./scripts/migrate.py ./scripts/migrate.py
@@ -52,6 +53,7 @@ console_scripts =
cic-eth-create = cic_eth.runnable.create:main cic-eth-create = cic_eth.runnable.create:main
cic-eth-inspect = cic_eth.runnable.view:main cic-eth-inspect = cic_eth.runnable.view:main
cic-eth-ctl = cic_eth.runnable.ctrl:main cic-eth-ctl = cic_eth.runnable.ctrl:main
cic-eth-info = cic_eth.runnable.info:main
# TODO: Merge this with ctl when subcmds sorted to submodules # TODO: Merge this with ctl when subcmds sorted to submodules
cic-eth-tag = cic_eth.runnable.tag:main cic-eth-tag = cic_eth.runnable.tag:main
cic-eth-resend = cic_eth.runnable.resend:main cic-eth-resend = cic_eth.runnable.resend:main

View File

@@ -4,4 +4,4 @@ pytest-mock==3.3.1
pytest-cov==2.10.1 pytest-cov==2.10.1
eth-tester==0.5.0b3 eth-tester==0.5.0b3
py-evm==0.3.0a20 py-evm==0.3.0a20
giftable-erc20-token==0.0.8a4 giftable-erc20-token==0.0.8a9

View File

@@ -3,8 +3,12 @@ import os
import sys import sys
import logging import logging
# external imports
from chainlib.eth.erc20 import ERC20
# local imports # local imports
from cic_eth.api import Api from cic_eth.api import Api
from cic_eth.task import BaseTask
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)
@@ -28,3 +32,26 @@ def api(
): ):
chain_str = str(default_chain_spec) chain_str = str(default_chain_spec)
return Api(chain_str, queue=None, callback_param='foo') return Api(chain_str, queue=None, callback_param='foo')
@pytest.fixture(scope='function')
def foo_token_symbol(
default_chain_spec,
foo_token,
eth_rpc,
contract_roles,
):
c = ERC20(default_chain_spec)
o = c.symbol(foo_token, sender_address=contract_roles['CONTRACT_DEPLOYER'])
r = eth_rpc.do(o)
return c.parse_symbol(r)
@pytest.fixture(scope='function')
def default_token(
foo_token,
foo_token_symbol,
):
BaseTask.default_token_symbol = foo_token_symbol
BaseTask.default_token_address = foo_token

View File

@@ -0,0 +1,225 @@
# standard import
import logging
import datetime
import os
# external imports
import pytest
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.tx import (
receipt,
transaction,
Tx,
)
from chainlib.eth.block import Block
from chainlib.eth.erc20 import ERC20
from sarafu_faucet import MinterFaucet
from eth_accounts_index import AccountRegistry
from potaahto.symbols import snake_and_camel
from hexathon import add_0x
# local imports
from cic_eth.runnable.daemons.filters.callback import CallbackFilter
logg = logging.getLogger()
@pytest.mark.skip()
def test_transfer_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
token_roles,
contract_roles,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
logg.debug(r)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_transfer(tx, eth_rpc)
assert transfer_type == 'transfer'
@pytest.mark.skip()
def test_transfer_from_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
token_roles,
contract_roles,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.approve(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer_from(foo_token, agent_roles['ALICE'], token_roles['FOO_TOKEN_OWNER'], agent_roles['BOB'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_transferfrom(tx, eth_rpc)
assert transfer_type == 'transferfrom'
def test_faucet_gift_to_tx(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
agent_roles,
contract_roles,
faucet,
account_registry,
celery_session_worker,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
gas_oracle = OverrideGasOracle(conn=rpc, limit=800000)
nonce_oracle = RPCNonceOracle(contract_roles['ACCOUNT_REGISTRY_WRITER'], rpc)
txf = AccountRegistry(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.add(account_registry, contract_roles['ACCOUNT_REGISTRY_WRITER'], agent_roles['ALICE'])
r = rpc.do(o)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], rpc)
txf = MinterFaucet(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.give_to(faucet, agent_roles['ALICE'], agent_roles['ALICE'])
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
tx_src = snake_and_camel(r)
tx = Tx(tx_src)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
(transfer_type, transfer_data) = fltr.parse_giftto(tx, eth_rpc)
assert transfer_type == 'tokengift'
assert transfer_data['token_address'] == foo_token
def test_callback_filter(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
foo_token,
token_roles,
agent_roles,
contract_roles,
register_lookups,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], rpc)
gas_oracle = OverrideGasOracle(conn=rpc, limit=200000)
txf = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, o) = txf.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 1024)
r = rpc.do(o)
o = transaction(tx_hash_hex)
r = rpc.do(o)
logg.debug(r)
mockblock_src = {
'hash': add_0x(os.urandom(32).hex()),
'number': '0x2a',
'transactions': [tx_hash_hex],
'timestamp': datetime.datetime.utcnow().timestamp(),
}
mockblock = Block(mockblock_src)
tx_src = snake_and_camel(r)
tx = Tx(tx_src, block=mockblock)
o = receipt(tx_hash_hex)
r = rpc.do(o)
assert r['status'] == 1
rcpt = snake_and_camel(r)
tx.apply_receipt(rcpt)
fltr = CallbackFilter(default_chain_spec, None, None, caller_address=contract_roles['CONTRACT_DEPLOYER'])
class CallbackMock:
def __init__(self):
self.results = {}
self.queue = 'test'
def call_back(self, transfer_type, result):
self.results[transfer_type] = result
return self
mock = CallbackMock()
fltr.call_back = mock.call_back
fltr.filter(eth_rpc, mockblock, tx, init_database)
assert mock.results.get('transfer') != None
assert mock.results['transfer']['destination_token'] == foo_token

View File

@@ -65,6 +65,7 @@ def test_tx(
tx_hash_hex_orig = tx_hash_hex tx_hash_hex_orig = tx_hash_hex
gas_oracle = OverrideGasOracle(price=1100000000, limit=21000) gas_oracle = OverrideGasOracle(price=1100000000, limit=21000)
c = Gas(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED) (tx_hash_hex, tx_signed_raw_hex) = c.create(agent_roles['ALICE'], agent_roles['BOB'], 100 * (10 ** 6), tx_format=TxFormat.RLP_SIGNED)
queue_create( queue_create(
default_chain_spec, default_chain_spec,

View File

@@ -34,6 +34,7 @@ def celery_includes():
'cic_eth.admin.ctrl', 'cic_eth.admin.ctrl',
'cic_eth.admin.nonce', 'cic_eth.admin.nonce',
'cic_eth.admin.debug', 'cic_eth.admin.debug',
'cic_eth.admin.token',
'cic_eth.eth.account', 'cic_eth.eth.account',
'cic_eth.callbacks.noop', 'cic_eth.callbacks.noop',
'cic_eth.callbacks.http', 'cic_eth.callbacks.http',

View File

@@ -2,7 +2,7 @@
import os import os
import logging import logging
# third-party imports # external imports
import pytest import pytest
import alembic import alembic
from alembic.config import Config as AlembicConfig from alembic.config import Config as AlembicConfig
@@ -53,6 +53,9 @@ def init_database(
alembic.command.downgrade(ac, 'base') alembic.command.downgrade(ac, 'base')
alembic.command.upgrade(ac, 'head') alembic.command.upgrade(ac, 'head')
session.execute('DELETE FROM lock')
session.commit()
yield session yield session
session.commit() session.commit()
session.close() session.close()

View File

@@ -273,7 +273,7 @@ def test_tx(
eth_signer, eth_signer,
agent_roles, agent_roles,
contract_roles, contract_roles,
celery_worker, celery_session_worker,
): ):
nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc) nonce_oracle = RPCNonceOracle(agent_roles['ALICE'], eth_rpc)

View File

@@ -35,7 +35,7 @@ def test_list_tx(
foo_token, foo_token,
register_tokens, register_tokens,
init_eth_tester, init_eth_tester,
celery_worker, celery_session_worker,
): ):
tx_hashes = [] tx_hashes = []

View File

@@ -0,0 +1,21 @@
# external imports
import celery
def test_default_token(
default_token,
celery_session_worker,
foo_token,
foo_token_symbol,
):
s = celery.signature(
'cic_eth.admin.token.default_token',
[],
queue=None,
)
t = s.apply_async()
r = t.get()
assert r['address'] == foo_token
assert r['symbol'] == foo_token_symbol

View File

@@ -1,5 +1,5 @@
[pgp] [pgp]
exports_dir = pgp exports_dir = /root/pgp
privatekey_file = privatekeys.asc privatekey_file = privatekeys.asc
passphrase = merman passphrase = merman
publickey_trusted_file = publickeys.asc publickey_trusted_file = publickeys.asc

View File

@@ -3,4 +3,3 @@ dist
dist-web dist-web
dist-server dist-server
scratch scratch
tests

View File

@@ -2,26 +2,31 @@ FROM node:15.3.0-alpine3.10
WORKDIR /tmp/src/cic-meta WORKDIR /tmp/src/cic-meta
RUN apk add --no-cache postgresql bash
COPY cic-meta/package.json \ COPY cic-meta/package.json \
./ ./
COPY cic-meta/src/ src/ COPY cic-meta/src/ src/
COPY cic-meta/tests/ tests/ COPY cic-meta/tests/ tests/
COPY cic-meta/scripts/ scripts/ COPY cic-meta/scripts/ scripts/
#COPY docker/*.sh /root/
RUN alias tsc=node_modules/typescript/bin/tsc
RUN npm install RUN npm install
# see exports_dir gpg.ini
COPY cic-meta/tests/*.asc /root/pgp/
RUN alias tsc=node_modules/typescript/bin/tsc
COPY cic-meta/.config/ /usr/local/etc/cic-meta/ COPY cic-meta/.config/ /usr/local/etc/cic-meta/
# COPY cic-meta/scripts/server/initdb/server.postgres.sql /usr/local/share/cic-meta/sql/server.sql # COPY cic-meta/scripts/server/initdb/server.postgres.sql /usr/local/share/cic-meta/sql/server.sql
COPY cic-meta/docker/db.sh ./db.sh COPY cic-meta/docker/db.sh ./db.sh
RUN chmod 755 ./db.sh RUN chmod 755 ./db.sh
RUN alias ts-node=/tmp/src/cic-meta/node_modules/ts-node/dist/bin.js #RUN alias ts-node=/tmp/src/cic-meta/node_modules/ts-node/dist/bin.js
ENTRYPOINT [ "./node_modules/ts-node/dist/bin.js", "./scripts/server/server.ts" ] #ENTRYPOINT [ "./node_modules/ts-node/dist/bin.js", "./scripts/server/server.ts" ]
# COPY cic-meta/docker/start_server.sh ./start_server.sh COPY cic-meta/docker/start_server.sh ./start_server.sh
# RUN chmod 755 ./start_server.sh RUN chmod 755 ./start_server.sh
ENTRYPOINT ["sh", "./start_server.sh"]

View File

@@ -1,3 +1,6 @@
#!/bin/bash #!/bin/bash
set -e
PGPASSWORD=$DATABASE_PASSWORD psql -v ON_ERROR_STOP=1 -U $DATABASE_USER -h $DATABASE_HOST -p $DATABASE_PORT -d $DATABASE_NAME -f $SCHEMA_SQL_PATH
PGPASSWORD=$DATABASE_PASSWORD psql -U $DATABASE_USER -h $DATABASE_HOST -p $DATABASE_PORT -d $DATABASE_NAME /usr/local/share/cic-meta/sql/server.sql

View File

@@ -1,3 +1,9 @@
#!/bin/bash
set -euo pipefail
# db migration
sh ./db.sh sh ./db.sh
/usr/local/bin/node /usr/local/bin/cic-meta-server $@ # /usr/local/bin/node /usr/local/bin/cic-meta-server $@
# ./node_modules/ts-node/dist/bin.js", "./scripts/server/server.ts $@
npm run start "$@"

View File

@@ -1,6 +1,6 @@
{ {
"name": "cic-client-meta", "name": "cic-client-meta",
"version": "0.0.7-alpha.2", "version": "0.0.7-alpha.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -852,6 +852,75 @@
"printj": "~1.1.0" "printj": "~1.1.0"
} }
}, },
"crdt-meta": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/crdt-meta/-/crdt-meta-0.0.8.tgz",
"integrity": "sha512-CS0sS0L2QWthz7vmu6vzl3p4kcpJ+IKILBJ4tbgN4A3iNG8wnBeuDIv/z3KFFQjcfuP4QAh6E9LywKUTxtDc3g==",
"requires": {
"automerge": "^0.14.2",
"ini": "^1.3.8",
"openpgp": "^4.10.8",
"pg": "^8.5.1",
"sqlite3": "^5.0.2"
},
"dependencies": {
"automerge": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/automerge/-/automerge-0.14.2.tgz",
"integrity": "sha512-shiwuJHCbNRI23WZyIECLV4Ovf3WiAFJ7P9BH4l5gON1In/UUbjcSJKRygtIirObw2UQumeYxp3F2XBdSvQHnA==",
"requires": {
"immutable": "^3.8.2",
"transit-immutable-js": "^0.7.0",
"transit-js": "^0.8.861",
"uuid": "^3.4.0"
}
},
"node-addon-api": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
"integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw=="
},
"pg": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.3.0",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"pg-pool": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg=="
},
"pg-protocol": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"sqlite3": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.2.tgz",
"integrity": "sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==",
"requires": {
"node-addon-api": "^3.0.0",
"node-gyp": "3.x",
"node-pre-gyp": "^0.11.0"
}
}
}
},
"create-hash": { "create-hash": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
@@ -966,17 +1035,17 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.3", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.11.9",
"brorand": "^1.0.1", "brorand": "^1.1.0",
"hash.js": "^1.0.0", "hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0", "hmac-drbg": "^1.0.1",
"inherits": "^2.0.1", "inherits": "^2.0.4",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.0" "minimalistic-crypto-utils": "^1.0.1"
} }
}, },
"emoji-regex": { "emoji-regex": {
@@ -1489,9 +1558,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
}, },
"interpret": { "interpret": {
"version": "2.2.0", "version": "2.2.0",
@@ -1957,9 +2026,9 @@
} }
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true "dev": true
}, },
"yargs": { "yargs": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "cic-client-meta", "name": "cic-client-meta",
"version": "0.0.7-alpha.6", "version": "0.0.7-alpha.8",
"description": "Signed CRDT metadata graphs for the CIC network", "description": "Signed CRDT metadata graphs for the CIC network",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
@@ -10,13 +10,15 @@
"build-server": "tsc -d --outDir dist-server scripts/server/*.ts", "build-server": "tsc -d --outDir dist-server scripts/server/*.ts",
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack", "pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"prepare": "npm run build && npm run build-server" "prepare": "npm run build && npm run build-server",
"start": "./node_modules/ts-node/dist/bin.js ./scripts/server/server.ts"
}, },
"dependencies": { "dependencies": {
"@ethereumjs/tx": "^3.0.0-beta.1", "@ethereumjs/tx": "^3.0.0-beta.1",
"automerge": "^0.14.1", "automerge": "^0.14.1",
"crdt-meta": "0.0.8",
"ethereumjs-wallet": "^1.0.1", "ethereumjs-wallet": "^1.0.1",
"ini": "^1.3.5", "ini": "^1.3.8",
"openpgp": "^4.10.8", "openpgp": "^4.10.8",
"pg": "^8.4.2", "pg": "^8.4.2",
"sqlite3": "^5.0.0", "sqlite3": "^5.0.0",
@@ -40,6 +42,6 @@
], ],
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"engines": { "engines": {
"node": "~14.16.1" "node": ">=14.16.1"
} }
} }

View File

@@ -1,4 +1,4 @@
const config = require('./src/config'); import { Config } from 'crdt-meta';
const fs = require('fs'); const fs = require('fs');
if (process.argv[2] === undefined) { if (process.argv[2] === undefined) {
@@ -15,6 +15,6 @@ try {
process.exit(1); process.exit(1);
} }
const c = new config.Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']); const c = new Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']);
c.process(); c.process();
process.stdout.write(c.toString()); process.stdout.write(c.toString());

View File

@@ -1,15 +0,0 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username grassroots --dbname cic_meta <<-EOSQL
create table if not exists store (
id serial primary key not null,
owner_fingerprint text not null,
hash char(64) not null unique,
content text not null
);
create index if not exists idx_fp on store ((lower(owner_fingerprint)));
EOSQL

View File

@@ -1,4 +1,4 @@
create table if not exists cic_meta.store ( create table if not exists store (
id serial primary key not null, id serial primary key not null,
owner_fingerprint text not null, owner_fingerprint text not null,
hash char(64) not null unique, hash char(64) not null unique,

View File

@@ -1,8 +1,7 @@
import * as Automerge from 'automerge'; import * as Automerge from 'automerge';
import * as pgp from 'openpgp'; import * as pgp from 'openpgp';
import * as pg from 'pg';
import { Envelope, Syncable } from '../../src/sync'; import { Envelope, Syncable } from 'crdt-meta';
function handleNoMergeGet(db, digest, keystore) { function handleNoMergeGet(db, digest, keystore) {

View File

@@ -1,15 +1,11 @@
import * as http from 'http'; import * as http from 'http';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as pgp from 'openpgp';
import * as handlers from './handlers'; import * as handlers from './handlers';
import { Envelope, Syncable } from '../../src/sync'; import { PGPKeyStore, PGPSigner, Config, SqliteAdapter, PostgresAdapter } from 'crdt-meta';
import { PGPKeyStore, PGPSigner } from '../../src/auth';
import { standardArgs } from './args'; import { standardArgs } from './args';
import { Config } from '../../src/config';
import { SqliteAdapter, PostgresAdapter } from '../../src/db';
let configPath = '/usr/local/etc/cic-meta'; let configPath = '/usr/local/etc/cic-meta';

View File

@@ -1,191 +0,0 @@
import * as pgp from 'openpgp';
import * as crypto from 'crypto';
interface Signable {
digest():string;
}
type KeyGetter = () => any;
type Signature = {
engine:string
algo:string
data:string
digest:string
}
interface Signer {
prepare(Signable):boolean;
onsign(Signature):void;
onverify(boolean):void;
sign(digest:string):void
verify(digest:string, signature:Signature):void
fingerprint():string
}
interface Authoritative {
}
interface KeyStore {
getPrivateKey: KeyGetter
getFingerprint: () => string
getTrustedKeys: () => Array<any>
getTrustedActiveKeys: () => Array<any>
getEncryptKeys: () => Array<any>
}
class PGPKeyStore implements KeyStore {
fingerprint: string
pk: any
pubk = {
active: [],
trusted: [],
encrypt: [],
}
loads = 0x00;
loadsTarget = 0x0f;
onload: (k:KeyStore) => void;
constructor(passphrase:string, pkArmor:string, pubkActiveArmor:string, pubkTrustedArmor:string, pubkEncryptArmor:string, onload = (ks:KeyStore) => {}) {
this._readKey(pkArmor, undefined, 1, passphrase);
this._readKey(pubkActiveArmor, 'active', 2);
this._readKey(pubkTrustedArmor, 'trusted', 4);
this._readKey(pubkEncryptArmor, 'encrypt', 8);
this.onload = onload;
}
private _readKey(a:string, x:any, n:number, pass?:string) {
pgp.key.readArmored(a).then((k) => {
if (pass !== undefined) {
this.pk = k.keys[0];
this.pk.decrypt(pass).then(() => {
this.fingerprint = this.pk.getFingerprint();
console.log('private key (sign)', this.fingerprint);
this._registerLoad(n);
});
} else {
this.pubk[x] = k.keys;
k.keys.forEach((pubk) => {
console.log('public key (' + x + ')', pubk.getFingerprint());
});
this._registerLoad(n);
}
});
}
private _registerLoad(b:number) {
this.loads |= b;
if (this.loads == this.loadsTarget) {
this.onload(this);
}
}
public getTrustedKeys(): Array<any> {
return this.pubk['trusted'];
}
public getTrustedActiveKeys(): Array<any> {
return this.pubk['active'];
}
public getEncryptKeys(): Array<any> {
return this.pubk['encrypt'];
}
public getPrivateKey(): any {
return this.pk;
}
public getFingerprint(): string {
return this.fingerprint;
}
}
class PGPSigner implements Signer {
engine = 'pgp'
algo = 'sha256'
dgst: string
signature: Signature
keyStore: KeyStore
onsign: (Signature) => void
onverify: (boolean) => void
constructor(keyStore:KeyStore) {
this.keyStore = keyStore
this.onsign = (string) => {};
this.onverify = (boolean) => {};
}
public fingerprint(): string {
return this.keyStore.getFingerprint();
}
public prepare(material:Signable):boolean {
this.dgst = material.digest();
return true;
}
public verify(digest:string, signature:Signature) {
pgp.signature.readArmored(signature.data).then((s) => {
const opts = {
message: pgp.cleartext.fromText(digest),
publicKeys: this.keyStore.getTrustedKeys(),
signature: s,
};
pgp.verify(opts).then((v) => {
let i = 0;
for (i = 0; i < v.signatures.length; i++) {
const s = v.signatures[i];
if (s.valid) {
this.onverify(s);
return;
}
}
console.error('checked ' + i + ' signature(s) but none valid');
this.onverify(false);
});
}).catch((e) => {
console.error(e);
this.onverify(false);
});
}
public sign(digest:string) {
const m = pgp.cleartext.fromText(digest);
const pk = this.keyStore.getPrivateKey();
const opts = {
message: m,
privateKeys: [pk],
detached: true,
}
pgp.sign(opts).then((s) => {
this.signature = {
engine: this.engine,
algo: this.algo,
data: s.signature,
// TODO: fix for browser later
digest: digest,
};
this.onsign(this.signature);
}).catch((e) => {
console.error(e);
this.onsign(undefined);
});
}
}
export {
Signature,
Authoritative,
Signer,
KeyGetter,
Signable,
KeyStore,
PGPSigner,
PGPKeyStore,
};

View File

@@ -1,71 +0,0 @@
import * as fs from 'fs';
import * as ini from 'ini';
import * as path from 'path';
class Config {
filepath: string
store: Object
censor: Array<string>
require: Array<string>
env_prefix: string
constructor(filepath:string, env_prefix?:string) {
this.filepath = filepath;
this.store = {};
this.censor = [];
this.require = [];
this.env_prefix = '';
if (env_prefix !== undefined) {
this.env_prefix = env_prefix + "_";
}
}
public process() {
const d = fs.readdirSync(this.filepath);
const r = /.*\.ini$/;
for (let i = 0; i < d.length; i++) {
const f = d[i];
if (!f.match(r)) {
return;
}
const fp = path.join(this.filepath, f);
const v = fs.readFileSync(fp, 'utf-8');
const inid = ini.decode(v);
const inik = Object.keys(inid);
for (let j = 0; j < inik.length; j++) {
const k_section = inik[j]
const k = k_section.toUpperCase();
Object.keys(inid[k_section]).forEach((k_directive) => {
const kk = k_directive.toUpperCase();
const kkk = k + '_' + kk;
let r = inid[k_section][k_directive];
const k_env = this.env_prefix + kkk
const env = process.env[k_env];
if (env !== undefined) {
console.debug('Environment variable ' + k_env + ' overrides ' + kkk);
r = env;
}
this.store[kkk] = r;
});
}
}
}
public get(s:string) {
return this.store[s];
}
public toString() {
let s = '';
Object.keys(this.store).forEach((k) => {
s += k + '=' + this.store[k] + '\n';
});
return s;
}
}
export { Config };

View File

@@ -1,38 +0,0 @@
import { JSONSerializable } from './format';
const ENGINE_NAME = 'automerge';
const ENGINE_VERSION = '0.14.1';
const NETWORK_NAME = 'cic';
const NETWORK_VERSION = '1';
const CRYPTO_NAME = 'pgp';
const CRYPTO_VERSION = '2';
type VersionedSpec = {
name: string
version: string
ext?: Object
}
const engineSpec:VersionedSpec = {
name: ENGINE_NAME,
version: ENGINE_VERSION,
}
const cryptoSpec:VersionedSpec = {
name: CRYPTO_NAME,
version: CRYPTO_VERSION,
}
const networkSpec:VersionedSpec = {
name: NETWORK_NAME,
version: NETWORK_VERSION,
}
export {
engineSpec,
cryptoSpec,
networkSpec,
VersionedSpec,
};

View File

@@ -1,27 +0,0 @@
import * as crypto from 'crypto';
const _algs = {
'SHA-256': 'sha256',
}
function cryptoWrapper() {
}
cryptoWrapper.prototype.digest = async function(s, d) {
const h = crypto.createHash(_algs[s]);
h.update(d);
return h.digest();
}
let subtle = undefined;
if (typeof window !== 'undefined') {
subtle = window.crypto.subtle;
} else {
subtle = new cryptoWrapper();
}
export {
subtle,
}

View File

@@ -1,90 +0,0 @@
import * as pg from 'pg';
import * as sqlite from 'sqlite3';
type DbConfig = {
name: string
host: string
port: number
user: string
password: string
}
interface DbAdapter {
query: (s:string, callback:(e:any, rs:any) => void) => void
close: () => void
}
const re_creatematch = /^(CREATE)/i
const re_getmatch = /^(SELECT)/i;
const re_setmatch = /^(INSERT|UPDATE)/i;
class SqliteAdapter implements DbAdapter {
db: any
constructor(dbConfig:DbConfig, callback?:(any) => void) {
this.db = new sqlite.Database(dbConfig.name); //, callback);
}
public query(s:string, callback:(e:any, rs?:any) => void): void {
const local_callback = (e, rs) => {
let r = undefined;
if (rs !== undefined) {
r = {
rowCount: rs.length,
rows: rs,
}
}
callback(e, r);
};
if (s.match(re_getmatch)) {
this.db.all(s, local_callback);
} else if (s.match(re_setmatch)) {
this.db.run(s, local_callback);
} else if (s.match(re_creatematch)) {
this.db.run(s, callback);
} else {
throw 'unhandled query';
}
}
public close() {
this.db.close();
}
}
class PostgresAdapter implements DbAdapter {
db: any
constructor(dbConfig:DbConfig) {
let o = dbConfig;
o['database'] = o.name;
this.db = new pg.Pool(o);
return this.db;
}
public query(s:string, callback:(e:any, rs:any) => void): void {
this.db.query(s, (e, rs) => {
let r = {
length: rs.rowCount,
}
rs.length = rs.rowCount;
if (e === undefined) {
e = null;
}
console.debug(e, rs);
callback(e, rs);
});
}
public close() {
this.db.end();
}
}
export {
DbConfig,
SqliteAdapter,
PostgresAdapter,
}

View File

@@ -1,68 +0,0 @@
import * as crypto from './crypto';
interface Addressable {
key(): string
digest(): string
}
function stringToBytes(s:string) {
const a = new Uint8Array(20);
let j = 2;
for (let i = 0; i < a.byteLength; i++) {
const n = parseInt(s.substring(j, j+2), 16);
a[i] = n;
j += 2;
}
return a;
}
function bytesToHex(a:Uint8Array) {
let s = '';
for (let i = 0; i < a.byteLength; i++) {
const h = '00' + a[i].toString(16);
s += h.slice(-2);
}
return s;
}
async function mergeKey(a:Uint8Array, s:Uint8Array) {
const y = new Uint8Array(a.byteLength + s.byteLength);
for (let i = 0; i < a.byteLength; i++) {
y[i] = a[i];
}
for (let i = 0; i < s.byteLength; i++) {
y[a.byteLength + i] = s[i];
}
const z = await crypto.subtle.digest('SHA-256', y);
return bytesToHex(new Uint8Array(z));
}
async function toKey(v:string, salt:string) {
const a = stringToBytes(v);
const s = new TextEncoder().encode(salt);
return await mergeKey(a, s);
}
async function toAddressKey(zeroExHex:string, salt:string) {
const a = addressToBytes(zeroExHex);
const s = new TextEncoder().encode(salt);
return await mergeKey(a, s);
}
const re_addrHex = /^0[xX][a-fA-F0-9]{40}$/;
function addressToBytes(s:string) {
if (!s.match(re_addrHex)) {
throw 'invalid address hex';
}
return stringToBytes(s);
}
export {
toKey,
toAddressKey,
mergeKey,
bytesToHex,
addressToBytes,
Addressable,
}

View File

@@ -1,58 +0,0 @@
import { v4 as uuidv4 } from 'uuid';
import { Syncable } from './sync';
import { Store } from './store';
import { PubSub } from './transport';
function toIndexKey(id:string):string {
const d = Date.now();
return d + '_' + id + '_' + uuidv4();
}
const _re_indexKey = /^\d+_(.+)_[-\d\w]+$/;
function fromIndexKey(s:string):string {
const m = s.match(_re_indexKey);
if (m === null) {
throw 'Invalid index key';
}
return m[1];
}
class Dispatcher {
idx: Array<string>
syncer: PubSub
store: Store
constructor(store:Store, syncer:PubSub) {
this.idx = new Array<string>()
this.syncer = syncer;
this.store = store;
}
public isDirty(): boolean {
return this.idx.length > 0;
}
public add(id:string, item:Syncable): string {
const v = item.toJSON();
const k = toIndexKey(id);
this.store.put(k, v, true);
localStorage.setItem(k, v);
this.idx.push(k);
return k;
}
public sync(offset:number): number {
let i = 0;
this.idx.forEach((k) => {
const v = localStorage.getItem(k);
const k_id = fromIndexKey(k);
this.syncer.pub(v); // this must block until guaranteed delivery
localStorage.removeItem(k);
i++;
});
return i;
}
}
export { Dispatcher, toIndexKey, fromIndexKey }

View File

@@ -1,5 +0,0 @@
interface JSONSerializable {
toJSON(): string
}
export { JSONSerializable };

View File

@@ -1,5 +1,2 @@
export { PGPSigner, PGPKeyStore, Signer, KeyStore } from './auth'; export { User } from './user';
export { ArgPair,  Envelope, Syncable } from './sync'; export { Phone } from './phone';
export { User } from './assets/user';
export { Phone } from './assets/phone';
export { Config } from './config';

View File

@@ -1,5 +1,4 @@
import { ArgPair, Syncable } from '../sync'; import { Syncable, Addressable, mergeKey } from 'crdt-meta';
import { Addressable, mergeKey } from '../digest';
class Phone extends Syncable implements Addressable { class Phone extends Syncable implements Addressable {

View File

@@ -1,9 +0,0 @@
import { Syncable } from './sync';
interface Store {
put(string, Syncable, boolean?)
get(string):Syncable
delete(string)
}
export { Store };

View File

@@ -1,266 +0,0 @@
import * as Automerge from 'automerge';
import { JSONSerializable } from './format';
import { Authoritative, Signer, PGPSigner, Signable, Signature } from './auth';
import { engineSpec, cryptoSpec, networkSpec, VersionedSpec } from './constants';
const fullSpec:VersionedSpec = {
name: 'cic',
version: '1',
ext: {
network: cryptoSpec,
engine: engineSpec,
},
}
class Envelope {
o = fullSpec
constructor(payload:Object) {
this.set(payload);
}
public set(payload:Object) {
this.o['payload'] = payload
}
public get():string {
return this.o['payload'];
}
public toJSON() {
return JSON.stringify(this.o);
}
public static fromJSON(s:string): Envelope {
const e = new Envelope(undefined);
e.o = JSON.parse(s);
return e;
}
public unwrap(): Syncable {
return Syncable.fromJSON(this.o['payload']);
}
}
class ArgPair {
k:string
v:any
constructor(k:string, v:any) {
this.k = k;
this.v = v;
}
}
class SignablePart implements Signable {
s: string
constructor(s:string) {
this.s = s;
}
public digest():string {
return this.s;
}
}
function orderDict(src) {
let dst;
if (Array.isArray(src)) {
dst = [];
src.forEach((v) => {
if (typeof(v) == 'object') {
v = orderDict(v);
}
dst.push(v);
});
} else {
dst = {}
Object.keys(src).sort().forEach((k) => {
let v = src[k];
if (typeof(v) == 'object') {
v = orderDict(v);
}
dst[k] = v;
});
}
return dst;
}
class Syncable implements JSONSerializable, Authoritative, Signable {
id: string
timestamp: number
m: any // automerge object
e: Envelope
signer: Signer
onwrap: (string) => void
onauthenticate: (boolean) => void
// TODO: Move data to sub-object so timestamp, id, signature don't collide
constructor(id:string, v:Object) {
this.id = id;
const o = {
'id': id,
'timestamp': Math.floor(Date.now() / 1000),
'data': v,
}
//this.m = Automerge.from(v)
this.m = Automerge.from(o)
}
public setSigner(signer:Signer) {
this.signer = signer;
this.signer.onsign = (s) => {
this.wrap(s);
};
}
// TODO: To keep integrity, the non-link key/value pairs for each step also need to be hashed
public digest(): string {
const links = [];
Automerge.getAllChanges(this.m).forEach((ch:Object) => {
const op:Array<any> = ch['ops'];
ch['ops'].forEach((op:Array<Object>) => {
if (op['action'] == 'link') {
//console.log('op link', op);
links.push([op['obj'], op['value']]);
}
});
});
//return JSON.stringify(links);
const j = JSON.stringify(links);
return Buffer.from(j).toString('base64');
}
private wrap(s:any) {
this.m = Automerge.change(this.m, 'sign', (doc) => {
doc['signature'] = s;
});
this.e = new Envelope(this.toJSON());
console.log('wrappin s', s, typeof(s));
this.e.o['digest'] = s.digest;
if (this.onwrap !== undefined) {
this.onwrap(this.e);
}
}
// private _verifyLoop(i:number, history:Array<any>, signable:Signable, result:boolean) {
// if (!result) {
// this.onauthenticate(false);
// return;
// } else if (history.length == 0) {
// this.onauthenticate(true);
// return;
// }
// const h = history.shift()
// if (i % 2 == 0) {
// i++;
// signable = {
// digest: () => {
// return Automerge.save(h.snapshot)
// },
// };
// this._verifyLoop(i, history, signable, true);
// } else {
// i++;
// const signature = h.snapshot['signature'];
// console.debug('signature', signature, signable.digest());
// this.signer.onverify = (v) => {
// this._verifyLoop(i, history, signable, v)
// }
// this.signer.verify(signable, signature);
// }
// }
//
// // TODO: This should replay the graph and check signatures on each step
// public _authenticate(full:boolean=false) {
// let h = Automerge.getHistory(this.m);
// h.forEach((m) => {
// //console.debug(m.snapshot);
// });
// const signable = {
// digest: () => { return '' },
// }
// if (!full) {
// h = h.slice(h.length-2);
// }
// this._verifyLoop(0, h, signable, true);
// }
public authenticate(full:boolean=false) {
if (full) {
console.warn('only doing shallow authentication for now, sorry');
}
//console.log('authenticating', signable.digest());
//console.log('signature', this.m.signature);
this.signer.onverify = (v) => {
//this._verifyLoop(i, history, signable, v)
this.onauthenticate(v);
}
this.signer.verify(this.m.signature.digest, this.m.signature);
}
public sign() {
//this.signer.prepare(this);
this.signer.sign(this.digest());
}
public update(changes:Array<ArgPair>, changesDescription:string) {
this.m = Automerge.change(this.m, changesDescription, (m) => {
changes.forEach((c) => {
let path = c.k.split('.');
let target = m['data'];
while (path.length > 1) {
const part = path.shift();
target = target[part];
}
target[path[0]] = c.v;
});
m['timestamp'] = Math.floor(Date.now() / 1000);
});
}
public replace(o:Object, changesDescription:string) {
this.m = Automerge.change(this.m, changesDescription, (m) => {
Object.keys(o).forEach((k) => {
m['data'][k] = o[k];
});
Object.keys(m).forEach((k) => {
if (o[k] == undefined) {
delete m['data'][k];
}
});
m['timestamp'] = Math.floor(Date.now() / 1000);
});
}
public merge(s:Syncable) {
this.m = Automerge.merge(s.m, this.m);
}
public toJSON(): string {
const s = Automerge.save(this.m);
const o = JSON.parse(s);
const oo = orderDict(o)
return JSON.stringify(oo);
}
public static fromJSON(s:string): Syncable {
const doc = Automerge.load(s);
let y = new Syncable(doc['id'], {});
y.m = doc
return y
}
}
export { JSONSerializable, Syncable, ArgPair, Envelope };

View File

@@ -1,11 +0,0 @@
interface SubConsumer {
post(string)
}
interface PubSub {
pub(v:string):boolean
close()
}
export { PubSub, SubConsumer };

View File

@@ -1,5 +1,4 @@
import { ArgPair, Syncable } from '../sync'; import { Syncable, Addressable, toAddressKey } from 'crdt-meta';
import { Addressable, addressToBytes, bytesToHex, toAddressKey } from '../digest';
const keySalt = new TextEncoder().encode(':cic.person'); const keySalt = new TextEncoder().encode(':cic.person');
class User extends Syncable implements Addressable { class User extends Syncable implements Addressable {

View File

@@ -1,50 +0,0 @@
import * as Automerge from 'automerge';
import assert = require('assert');
import { Dispatcher, toIndexKey, fromIndexKey } from '../src/dispatch';
import { User } from '../src/assets/user';
import { Syncable, ArgPair } from '../src/sync';
import { MockSigner, MockStore } from './mock';
describe('basic', () => {
it('store', () => {
const store = new MockStore('s');
assert.equal(store.name, 's');
const mockSigner = new MockSigner();
const v = new Syncable('foo', {baz: 42});
v.setSigner(mockSigner);
store.put('foo', v);
const one = store.get('foo').toJSON();
const vv = new Syncable('bar', {baz: 666});
vv.setSigner(mockSigner);
assert.throws(() => {
store.put('foo', vv)
});
store.put('foo', vv, true);
const other = store.get('foo').toJSON();
assert.notEqual(one, other);
store.delete('foo');
assert.equal(store.get('foo'), undefined);
});
it('add_doc_to_dispatcher', () => {
const store = new MockStore('s');
//const syncer = new MockSyncer();
const dispatcher = new Dispatcher(store, undefined);
const user = new User('foo');
dispatcher.add(user.id, user);
assert(dispatcher.isDirty());
});
it('dispatch_keyindex', () => {
const s = 'foo';
const k = toIndexKey(s);
const v = fromIndexKey(k);
assert.equal(s, v);
});
});

View File

@@ -1,212 +0,0 @@
import * as Automerge from 'automerge';
import assert = require('assert');
import * as pgp from 'openpgp';
import * as fs from 'fs';
import { PGPSigner } from '../src/auth';
import { Syncable, ArgPair } from '../src/sync';
import { MockKeyStore, MockSigner } from './mock';
describe('sync', async () => {
it('sync_merge', () => {
const mockSigner = new MockSigner();
const s = new Syncable('foo', {
bar: 'baz',
});
s.setSigner(mockSigner);
const changePair = new ArgPair('xyzzy', 42);
s.update([changePair], 'ch-ch-cha-changes');
assert.equal(s.m.data['xyzzy'], 42)
assert.equal(s.m.data['bar'], 'baz')
assert.equal(s.m['id'], 'foo')
assert.equal(Automerge.getHistory(s.m).length, 2);
});
it('sync_serialize', () => {
const mockSigner = new MockSigner();
const s = new Syncable('foo', {
bar: 'baz',
});
s.setSigner(mockSigner);
const j = s.toJSON();
const ss = Syncable.fromJSON(j);
assert.equal(ss.m['id'], 'foo');
assert.equal(ss.m['data']['bar'], 'baz');
assert.equal(Automerge.getHistory(ss.m).length, 1);
});
it('sync_sign_and_wrap', () => {
const mockSigner = new MockSigner();
const s = new Syncable('foo', {
bar: 'baz',
});
s.setSigner(mockSigner);
s.onwrap = (e) => {
const j = e.toJSON();
const v = JSON.parse(j);
assert.deepEqual(v.payload, e.o.payload);
}
s.sign();
});
it('sync_verify_success', async () => {
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
const pks = await pgp.key.readArmored(pksa);
await pks.keys[0].decrypt('merman');
await pks.keys[1].decrypt('beastman');
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
const pubks = await pgp.key.readArmored(pubksa);
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
const oneSigner = new PGPSigner(oneStore);
const twoSigner = new PGPSigner(twoStore);
const threeSigner = new PGPSigner(threeStore);
const x = new Syncable('foo', {
bar: 'baz',
});
x.setSigner(oneSigner);
// TODO: make this look better
x.onwrap = (e) => {
let updateData = new ArgPair('bar', 'xyzzy');
x.update([updateData], 'change one');
x.onwrap = (e) => {
x.setSigner(twoSigner);
updateData = new ArgPair('bar', 42);
x.update([updateData], 'change two');
x.onwrap = (e) => {
const p = e.unwrap();
p.setSigner(twoSigner);
p.onauthenticate = (v) => {
assert(v);
}
p.authenticate();
}
x.sign();
};
x.sign();
}
x.sign();
});
it('sync_verify_fail', async () => {
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
const pks = await pgp.key.readArmored(pksa);
await pks.keys[0].decrypt('merman');
await pks.keys[1].decrypt('beastman');
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
const pubks = await pgp.key.readArmored(pubksa);
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
const oneSigner = new PGPSigner(oneStore);
const twoSigner = new PGPSigner(twoStore);
const threeSigner = new PGPSigner(threeStore);
const x = new Syncable('foo', {
bar: 'baz',
});
x.setSigner(oneSigner);
// TODO: make this look better
x.onwrap = (e) => {
let updateData = new ArgPair('bar', 'xyzzy');
x.update([updateData], 'change one');
x.onwrap = (e) => {
x.setSigner(twoSigner);
updateData = new ArgPair('bar', 42);
x.update([updateData], 'change two');
x.onwrap = (e) => {
const p = e.unwrap();
p.setSigner(threeSigner);
p.onauthenticate = (v) => {
assert(!v);
}
p.authenticate();
}
x.sign();
};
x.sign();
}
x.sign();
});
xit('sync_verify_shallow_tricked', async () => {
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
const pks = await pgp.key.readArmored(pksa);
await pks.keys[0].decrypt('merman');
await pks.keys[1].decrypt('beastman');
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
const pubks = await pgp.key.readArmored(pubksa);
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
const oneSigner = new PGPSigner(oneStore);
const twoSigner = new PGPSigner(twoStore);
const threeSigner = new PGPSigner(threeStore);
const x = new Syncable('foo', {
bar: 'baz',
});
x.setSigner(twoSigner);
// TODO: make this look better
x.onwrap = (e) => {
let updateData = new ArgPair('bar', 'xyzzy');
x.update([updateData], 'change one');
x.onwrap = (e) => {
updateData = new ArgPair('bar', 42);
x.update([updateData], 'change two');
x.setSigner(oneSigner);
x.onwrap = (e) => {
const p = e.unwrap();
p.setSigner(threeSigner);
p.onauthenticate = (v) => {
assert(v);
p.onauthenticate = (v) => {
assert(!v);
}
p.authenticate(true);
}
p.authenticate();
}
x.sign();
};
x.sign();
}
x.sign();
});
});

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