Compare commits

...

110 Commits

Author SHA1 Message Date
nolash
14ddab1b34 Add chain reader arg flags to trackerd 2021-11-03 13:03:23 +01:00
nolash
7217cb7f46 Bump version for cic-cache python package build 2021-11-03 11:19:44 +01:00
nolash
2ebcd3e3de Merge remote-tracking branch 'origin/master' into lash/bloxberg-seeding 2021-11-02 18:49:49 +01:00
Louis Holbrook
8855ccd3d2 Merge branch 'lash/better-meta-logging' into 'master'
bug: False 404 in cic-meta logging

See merge request grassrootseconomics/cic-internal-integration!302
2021-11-02 17:45:09 +00:00
Louis Holbrook
09dfdbb38a bug: False 404 in cic-meta logging 2021-11-02 17:45:09 +00:00
nolash
c440b049cc Add config dirs 2021-11-02 16:35:44 +01:00
nolash
09034af5bc Bump cic-eth version 2021-11-02 16:03:29 +01:00
nolash
dc80bae673 Upgrade cic-eth in migrations 2021-11-02 15:31:00 +01:00
nolash
d88ae00b72 Add celery cli args with defaults from redis 2021-10-31 07:58:35 +01:00
nolash
7a366edb9d WIP rehabilitate cic-eth-inspect 2021-10-30 19:09:17 +02:00
nolash
0b912b99b6 Add role listing to cic-eth tag cli tool 2021-10-30 13:19:31 +02:00
nolash
cbd4aef004 Add action confirm on sweep script 2021-10-30 10:25:39 +02:00
nolash
6f7f91780b Add script to sweep gas from signer accounts 2021-10-30 09:02:04 +02:00
1abb642361 Merge branch 'feat/automation/add-semver' into 'master'
feat: (automation) add semver

See merge request grassrootseconomics/cic-internal-integration!266
2021-10-29 23:33:46 +00:00
93bcbd7d51 feat: (automation) add semver 2021-10-29 23:33:46 +00:00
nolash
83ecdaf023 Connect token filter to tracker 2021-10-29 16:35:11 +02:00
nolash
e2ef9b43c8 Reactivate cic-eth-tasker dependency for bootstrap 2021-10-29 15:58:34 +02:00
nolash
6e58e4e4de Remove nasty residue from bootstrap 2021-10-29 14:40:06 +02:00
nolash
f46c9b0e7d Merge remote-tracking branch 'origin/master' into lash/bloxberg-seeding 2021-10-29 11:39:40 +02:00
Louis Holbrook
818899670a Merge branch 'lash/local-dev-improve' into 'master'
feat: Enable parallell contract transactions in bootstrap

See merge request grassrootseconomics/cic-internal-integration!295
2021-10-29 09:36:14 +00:00
Louis Holbrook
1882910a8e feat: Enable parallell contract transactions in bootstrap 2021-10-29 09:36:14 +00:00
nolash
6ca3fd55d7 Add gas cache oracle connection for erc20 2021-10-29 08:45:42 +02:00
nolash
258ed420b8 Merge branch 'lash/tmp-bloxberg-seeding' into lash/bloxberg-seeding 2021-10-29 07:35:08 +02:00
nolash
1c022e9853 Added changes to wrong branch 2021-10-29 07:33:38 +02:00
nolash
d35e144723 Register gas cache only for registered tokens 2021-10-29 07:00:25 +02:00
nolash
fb953d0318 Add gas cache backend, test, filter 2021-10-28 21:45:47 +02:00
nolash
858bbdb69a Merge remote-tracking branch 'origin/master' into lash/local-dev-improve 2021-10-28 14:36:45 +02:00
nolash
66e23e4e20 Test config cleanup 2021-10-28 14:11:11 +02:00
nolash
546256c86a Better gas gifting amounts and thresholds estimation, fix broken cic-eth imports 2021-10-28 13:34:39 +02:00
nolash
d9720bd0aa Merge remote-tracking branch 'origin/lash/local-dev-improve' into lash/bloxberg-seeding 2021-10-28 05:41:27 +02:00
nolash
e9e9f66d97 Correct wrong change for docker registries 2021-10-28 05:39:44 +02:00
nolash
0d640fab57 Merge remote-tracking branch 'origin/lash/local-dev-improve' into lash/bloxberg-seeding 2021-10-28 05:29:07 +02:00
nolash
4ce85bc824 Remove faulty default registry in dockerfiles 2021-10-28 05:27:13 +02:00
nolash
ce67f83457 Remove faulty default registry in docker compose 2021-10-28 05:24:11 +02:00
nolash
13f2e17931 Remove accidental 0 value override for syncer offset to trackers 2021-10-28 05:18:54 +02:00
nolash
f236234682 Merge remote-tracking branch 'origin/master' into lash/local-dev-improve 2021-10-27 16:58:38 +02:00
nolash
1f37632f0f WIP Replace env vars in data-seeding with well-known 2021-10-27 16:56:03 +02:00
3cc909c936 Merge branch 'philip/bump-ussd-version' into 'master'
Philip/bump ussd version

See merge request grassrootseconomics/cic-internal-integration!300
2021-10-27 14:42:49 +00:00
60b6e1abdb Philip/bump ussd version 2021-10-27 14:42:49 +00:00
nolash
03d7518f8c Merge branch 'lash/local-dev-improve' of gitlab.com:grassrootseconomics/cic-internal-integration into lash/local-dev-improve 2021-10-27 11:52:31 +02:00
nolash
67152d0df1 Replace KEYSTORE_PATH with WALLET_KEY_FILE in data seeding 2021-10-27 11:51:20 +02:00
9168322941 Revert base image changes. 2021-10-27 12:41:35 +03:00
2fbd338e24 Adds correct base image. 2021-10-27 11:44:23 +03:00
c7d7f2a64d Remove force reset. 2021-10-27 11:44:08 +03:00
16153df2f0 Resolve creation of phone dir when it already exists. 2021-10-27 11:43:35 +03:00
Louis Holbrook
9c7e72f71c Merge branch 'lash/fix-docs' into 'master'
refactor: Make texinfo docs buildable with makeinfo <6.0

See merge request grassrootseconomics/cic-internal-integration!299
2021-10-26 20:01:19 +00:00
Louis Holbrook
e3acc1757a refactor: Make texinfo docs buildable with makeinfo <6.0 2021-10-26 20:01:19 +00:00
Louis Holbrook
8250b15d32 Merge branch 'lash/fix-docs' into 'master'
Lash/fix docs

See merge request grassrootseconomics/cic-internal-integration!298
2021-10-26 14:59:50 +00:00
Louis Holbrook
31d7cf5789 Lash/fix docs 2021-10-26 14:59:50 +00:00
Louis Holbrook
2544c159c2 Merge branch 'lash/fix-docs' into 'master'
refactor: Confirm to external cross-repo texinfo doc maker structure

See merge request grassrootseconomics/cic-internal-integration!297
2021-10-26 14:17:27 +00:00
Louis Holbrook
7691d9a127 refactor: Confirm to external cross-repo texinfo doc maker structure 2021-10-26 14:17:27 +00:00
nolash
4391fa3aff Merge remote-tracking branch 'origin/master' into lash/local-dev-improve 2021-10-25 21:01:27 +02:00
Louis Holbrook
a2a3634683 Merge branch 'lash/meta-immutable' into 'master'
feat: Add immutable pointers to meta

Closes cic-meta#11

See merge request grassrootseconomics/cic-internal-integration!296
2021-10-25 18:51:08 +00:00
Louis Holbrook
fe0835a4e7 feat: Add immutable pointers to meta 2021-10-25 18:51:08 +00:00
nolash
cd602dee49 Remove WIP docker compose file 2021-10-25 20:12:32 +02:00
nolash
a548ba6fce Chainlib upgrade to handle none receipts, rpc node debug output in bootstrap 2021-10-25 20:09:35 +02:00
nolash
a6de7e9fe0 Merge remote-tracking branch 'origin/master' into lash/local-dev-improve 2021-10-20 20:02:19 +02:00
d8f51c5bdd Merge branch 'lash/split-migration' into 'master'
feat: Split and improve contract migration steps

See merge request grassrootseconomics/cic-internal-integration!292
2021-10-20 15:02:37 +00:00
Louis Holbrook
13fb67d2d8 feat: Split and improve contract migration steps 2021-10-20 15:02:36 +00:00
nolash
e705a94873 Resolve notify/ussd dependency conflict 2021-10-20 10:07:19 +02:00
nolash
3923de0a81 Update pip args handling in notify 2021-10-19 23:01:55 +02:00
nolash
5c0250b5b9 Rehabilitate cic-cache db migration 2021-10-19 22:58:10 +02:00
nolash
3285d8dfe5 Implement asynchronous deploys in bootstrap 2021-10-19 22:08:17 +02:00
nolash
9d349f1579 Add debug level env var to bootstrap dev container 2021-10-19 19:54:59 +02:00
nolash
837a1770d1 Upgrade deps more chainlib in bootstrap 2021-10-19 10:10:39 +02:00
003febec9d Bumps contract migration deps. 2021-10-19 10:38:21 +03:00
f066a32ce8 Adds libffi-dev for local git-tea. 2021-10-19 10:38:08 +03:00
nolash
ad493705ad Upgrade deps 2021-10-18 17:16:28 +02:00
nolash
b765c4ab88 More wrestling with chainlib-eth deps 2021-10-18 17:06:31 +02:00
nolash
e4935d3b58 Merge branch 'lash/split-migration' of gitlab.com:grassrootseconomics/cic-internal-integration into lash/split-migration 2021-10-18 16:49:58 +02:00
nolash
f88f0e321b Upgrade chainlib-eth dep 2021-10-18 16:48:14 +02:00
31fa721397 Add cic-notify container 2021-10-18 17:17:53 +03:00
16481da193 Merge remote-tracking branch 'origin/lash/split-migration' into lash/split-migration 2021-10-18 16:54:23 +03:00
97a48cd8c6 Improves ussd deps. 2021-10-18 16:53:38 +03:00
nolash
7732412341 Merge branch 'lash/split-migration' of gitlab.com:grassrootseconomics/cic-internal-integration into lash/split-migration 2021-10-18 15:51:38 +02:00
nolash
649b124a61 Ugprade chainqueue dep 2021-10-18 15:50:45 +02:00
7601e3eeff Corrects breakages in cic-ussd 2021-10-18 15:19:32 +03:00
60a9efc88b Merge remote-tracking branch 'origin/lash/split-migration' into lash/split-migration 2021-10-18 15:18:33 +03:00
45011b58c4 Cleans up configs. 2021-10-18 15:11:31 +03:00
nolash
f1a0b4ee7c Merge branch 'lash/split-migration' of gitlab.com:grassrootseconomics/cic-internal-integration into lash/split-migration 2021-10-18 14:10:52 +02:00
nolash
c57abb7ad5 Upgrade deps in cic-eth, allow for new chain spec format 2021-10-18 14:08:39 +02:00
930a99c974 Bumps cic-types version. 2021-10-18 06:52:49 +03:00
b0935caab8 Fixes imports. 2021-10-18 06:52:28 +03:00
nolash
bdd5f6fcec Update readme in data seeding 2021-10-17 19:37:29 +02:00
nolash
a293c2460e Consolidate dir handling in data seeding scripts 2021-10-17 19:27:15 +02:00
nolash
0ee6400d7d WIP rehabilitate ussd builds 2021-10-17 18:32:08 +02:00
nolash
677fb346fd Add data seeding preparation step, rehabilitation of non-custodial seeding 2021-10-17 18:05:00 +02:00
nolash
ea3c75e755 Rehabilitate traffic script 2021-10-17 14:30:42 +02:00
nolash
0b2f22c416 Rehabilitate cic-user-server 2021-10-16 20:54:41 +02:00
nolash
24385ea27d Rehabilitate cic-cache 2021-10-16 14:03:05 +02:00
nolash
9a154a8046 WIP rehabilitate cic-cache 2021-10-16 08:23:32 +02:00
nolash
d3576c8ec7 Add eth retrier to new docker compose file 2021-10-16 07:08:44 +02:00
nolash
79ee2bf4ff Add eth tracker, dispatcher to new docker compose file 2021-10-16 07:04:19 +02:00
nolash
89ac70371a Remove single function worker in test 2021-10-16 00:18:08 +02:00
nolash
5ea0318b0b Fix default token symbol config setting for aux 2021-10-15 23:21:57 +02:00
nolash
5dfb96ec0c Add new cic-signer app 2021-10-15 23:11:00 +02:00
nolash
4634ac41df Merge remote-tracking branch 'origin/master' into lash/split-migration 2021-10-15 22:19:01 +02:00
nolash
97f4fe8ca7 refactor docker-compose cic-eth-tasker, bootstrap (aka contract migration) 2021-10-15 22:16:45 +02:00
nolash
b36529f7fa WIP local docker registry adaptations 2021-10-15 20:27:03 +02:00
nolash
a6675f2348 Add environment sourcing for cic-eth-tasker docker compose 2021-10-15 18:52:37 +02:00
nolash
e3116d74d6 No export 2021-10-15 12:54:16 +02:00
nolash
c0bbdc9bec Add missing file 2021-10-15 08:43:04 +02:00
nolash
396bd4f300 update preliminary readme 2021-10-15 08:38:01 +02:00
nolash
58547b4067 Bump cic-eth-registry 2021-10-15 07:44:50 +02:00
nolash
9009815d78 Add trust address to contract migration config, get cic-eth default token from registry 2021-10-14 21:31:04 +02:00
nolash
2da19f5819 Add basic connectivity config directives 2021-10-14 17:40:53 +02:00
nolash
3948d5aa40 Add custodial initialization 2021-10-14 17:18:49 +02:00
nolash
ed432abb23 WIP refactor custodial initialization 2021-10-14 14:37:48 +02:00
nolash
f251b8b729 Remove dead code 2021-10-14 11:35:08 +02:00
nolash
36e791e08a Split contract migration into three separate steps 2021-10-14 11:33:50 +02:00
175 changed files with 9202 additions and 1691 deletions

View File

@@ -10,6 +10,7 @@ include:
#- local: 'apps/data-seeding/.gitlab-ci.yml'
stages:
- version
- build
- test
- deploy
@@ -20,9 +21,39 @@ variables:
DOCKER_BUILDKIT: "1"
COMPOSE_DOCKER_CLI_BUILD: "1"
CI_DEBUG_TRACE: "true"
SEMVERBOT_VERSION: "0.2.0"
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
#before_script:
# - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
version:
#image: python:3.7-stretch
image: registry.gitlab.com/grassrootseconomics/cic-base-images/ci-version:b01318ae
stage: version
script:
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan gitlab.com >> ~/.ssh/known_hosts && chmod 644 ~/.ssh/known_hosts
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- git remote set-url origin git@gitlab.com:grassrootseconomics/cic-internal-integration.git
- export TAG=$(sbot predict version -m auto)
- |
if [[ -z $TAG ]]
then
echo "tag could not be set $@"
exit 1
fi
- echo $TAG > version
- git tag -a v$TAG -m "ci tagged"
- git push origin v$TAG
artifacts:
paths:
- version
rules:
- if: $CI_COMMIT_REF_PROTECTED == "true"
when: always
- if: $CI_COMMIT_REF_NAME == "master"
when: always
# runs on protected branches and pushes to repo
build-push:
@@ -30,12 +61,17 @@ build-push:
tags:
- integration
#script:
# - TAG=$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA sh ./scripts/build-push.sh
# - TAG=$CI_Cbefore_script:
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- TAG=latest sh ./scripts/build-push.sh
- TAG=latest ./scripts/build-push.sh
- TAG=$(cat ./version) ./scripts/build-push.sh
rules:
- if: $CI_COMMIT_REF_PROTECTED == "true"
when: always
- if: $CI_COMMIT_REF_NAME == "master"
when: always
deploy-dev:
stage: deploy

16
.semverbot.toml Normal file
View File

@@ -0,0 +1,16 @@
[git]
[git.config]
email = "semverbot@grassroots.org"
name = "semvervot"
[git.tags]
prefix = "v"
[semver]
mode = "git-commit"
[semver.detection]
patch = ["fix", "[fix]", "patch", "[patch]"]
minor = ["minor", "[minor]", "feat", "[feat]", "release", "[release]", "bump", "[bump]"]
major = ["BREAKING CHANGE"]

View File

@@ -4,6 +4,7 @@
This repo uses docker-compose and docker buildkit. Set the following environment variables to get started:
```
export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1

View File

@@ -0,0 +1,3 @@
**/*.pyc
.pydevproject
/vendor/

View File

@@ -0,0 +1,7 @@
language: python
python:
- "2.7"
script:
- python test/wait-for-it.py

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 Giles Hall
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,75 @@
# wait-for-it
`wait-for-it.sh` is a pure bash script that will wait on the availability of a
host and TCP port. It is useful for synchronizing the spin-up of
interdependent services, such as linked docker containers. Since it is a pure
bash script, it does not have any external dependencies.
## Usage
```text
wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
```
## Examples
For example, let's test to see if we can access port 80 on `www.google.com`,
and if it is available, echo the message `google is up`.
```text
$ ./wait-for-it.sh www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google is up
```
You can set your own timeout with the `-t` or `--timeout=` option. Setting
the timeout value to 0 will disable the timeout:
```text
$ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting for www.google.com:80 without a timeout
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google is up
```
The subcommand will be executed regardless if the service is up or not. If you
wish to execute the subcommand only if the service is up, add the `--strict`
argument. In this example, we will test port 81 on `www.google.com` which will
fail:
```text
$ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up"
wait-for-it.sh: waiting 1 seconds for www.google.com:81
wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81
wait-for-it.sh: strict mode, refusing to execute subprocess
```
If you don't want to execute a subcommand, leave off the `--` argument. This
way, you can test the exit condition of `wait-for-it.sh` in your own scripts,
and determine how to proceed:
```text
$ ./wait-for-it.sh www.google.com:80
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
$ echo $?
0
$ ./wait-for-it.sh www.google.com:81
wait-for-it.sh: waiting 15 seconds for www.google.com:81
wait-for-it.sh: timeout occurred after waiting 15 seconds for www.google.com:81
$ echo $?
124
```
## Community
*Debian*: There is a [Debian package](https://tracker.debian.org/pkg/wait-for-it).

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi

View File

@@ -0,0 +1,3 @@
**/*.pyc
.pydevproject
/vendor/

View File

@@ -0,0 +1,7 @@
language: python
python:
- "2.7"
script:
- python test/wait-for-it.py

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 Giles Hall
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,75 @@
# wait-for-it
`wait-for-it.sh` is a pure bash script that will wait on the availability of a
host and TCP port. It is useful for synchronizing the spin-up of
interdependent services, such as linked docker containers. Since it is a pure
bash script, it does not have any external dependencies.
## Usage
```text
wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
```
## Examples
For example, let's test to see if we can access port 80 on `www.google.com`,
and if it is available, echo the message `google is up`.
```text
$ ./wait-for-it.sh www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google is up
```
You can set your own timeout with the `-t` or `--timeout=` option. Setting
the timeout value to 0 will disable the timeout:
```text
$ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting for www.google.com:80 without a timeout
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google is up
```
The subcommand will be executed regardless if the service is up or not. If you
wish to execute the subcommand only if the service is up, add the `--strict`
argument. In this example, we will test port 81 on `www.google.com` which will
fail:
```text
$ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up"
wait-for-it.sh: waiting 1 seconds for www.google.com:81
wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81
wait-for-it.sh: strict mode, refusing to execute subprocess
```
If you don't want to execute a subcommand, leave off the `--` argument. This
way, you can test the exit condition of `wait-for-it.sh` in your own scripts,
and determine how to proceed:
```text
$ ./wait-for-it.sh www.google.com:80
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
$ echo $?
0
$ ./wait-for-it.sh www.google.com:81
wait-for-it.sh: waiting 15 seconds for www.google.com:81
wait-for-it.sh: timeout occurred after waiting 15 seconds for www.google.com:81
$ echo $?
124
```
## Community
*Debian*: There is a [Debian package](https://tracker.debian.org/pkg/wait-for-it).

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi

View File

@@ -1 +0,0 @@
from .cache import BloomCache

View File

@@ -14,7 +14,8 @@ class ArgumentParser(BaseArgumentParser):
if local_arg_flags & CICFlag.CELERY:
self.add_argument('-q', '--celery-queue', dest='celery_queue', type=str, default='cic-cache', help='Task queue')
if local_arg_flags & CICFlag.SYNCER:
self.add_argument('--offset', type=int, default=0, help='Start block height for initial history sync')
self.add_argument('--offset', type=int, help='Start block height for initial history sync')
self.add_argument('--no-history', action='store_true', dest='no_history', help='Skip initial history sync')
if local_arg_flags & CICFlag.CHAIN:
self.add_argument('-r', '--registry-address', type=str, dest='registry_address', help='CIC registry contract address')
self.add_argument('--registry-address', type=str, dest='registry_address', help='CIC registry contract address')
self.add_argument('--trust-address', type=str, dest='trust_address', action='append', help='Add trust address')

View File

@@ -39,6 +39,9 @@ class Config(BaseConfig):
if local_arg_flags & CICFlag.CHAIN:
local_args_override['CIC_REGISTRY_ADDRESS'] = getattr(args, 'registry_address')
trust_addresses = getattr(args, 'trust_address', None)
if trust_addresses != None:
local_args_override['CIC_TRUST_ADDRESS'] = ','.join(trust_addresses)
if local_arg_flags & CICFlag.CELERY:
local_args_override['CELERY_QUEUE'] = getattr(args, 'celery_queue')

View File

@@ -8,6 +8,7 @@ import base64
import confini
# local imports
import cic_cache.cli
from cic_cache.db import dsn_from_config
from cic_cache.db.models.base import SessionBase
from cic_cache.runnable.daemons.query import (
@@ -23,26 +24,17 @@ rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
dbdir = os.path.join(rootdir, 'cic_cache', 'db')
migrationsdir = os.path.join(dbdir, 'migrations')
config_dir = os.path.join('/usr/local/etc/cic-cache')
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('-v', action='store_true', help='be verbose')
argparser.add_argument('-vv', action='store_true', help='be more verbose')
# process args
arg_flags = cic_cache.cli.argflag_std_base
local_arg_flags = cic_cache.cli.argflag_local_task
argparser = cic_cache.cli.ArgumentParser(arg_flags)
argparser.process_local_flags(local_arg_flags)
args = argparser.parse_args()
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
config = confini.Config(args.c, args.env_prefix)
config.process()
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
logg.debug('config:\n{}'.format(config))
# process config
config = cic_cache.cli.Config.from_args(args, arg_flags, local_arg_flags)
# connect to database
dsn = dsn_from_config(config)
SessionBase.connect(dsn, config.true('DATABASE_DEBUG'))

View File

@@ -9,6 +9,7 @@ import celery
import confini
# local imports
import cic_cache.cli
from cic_cache.db import dsn_from_config
from cic_cache.db.models.base import SessionBase
from cic_cache.tasks.tx import *
@@ -16,35 +17,20 @@ from cic_cache.tasks.tx import *
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
config_dir = os.path.join('/usr/local/etc/cic-cache')
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
argparser.add_argument('-q', type=str, default='cic-cache', help='queue name for worker tasks')
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', action='store_true', help='be more verbose')
# process args
arg_flags = cic_cache.cli.argflag_std_base
local_arg_flags = cic_cache.cli.argflag_local_task
argparser = cic_cache.cli.ArgumentParser(arg_flags)
argparser.process_local_flags(local_arg_flags)
args = argparser.parse_args()
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
config = confini.Config(args.c, args.env_prefix)
config.process()
# process config
config = cic_cache.cli.Config.from_args(args, arg_flags, local_arg_flags)
# connect to database
dsn = dsn_from_config(config)
SessionBase.connect(dsn)
# verify database connection with minimal sanity query
#session = SessionBase.create_session()
#session.execute('select version_num from alembic_version')
#session.close()
# set up celery
current_app = celery.Celery(__name__)
@@ -87,9 +73,9 @@ def main():
elif args.v:
argv.append('--loglevel=INFO')
argv.append('-Q')
argv.append(args.q)
argv.append(config.get('CELERY_QUEUE'))
argv.append('-n')
argv.append(args.q)
argv.append(config.get('CELERY_QUEUE'))
current_app.worker_main(argv)

View File

@@ -95,10 +95,10 @@ def main():
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
if len(syncer_backends) == 0:
initial_block_start = config.get('SYNCER_OFFSET')
initial_block_offset = block_offset
initial_block_start = int(config.get('SYNCER_OFFSET'))
initial_block_offset = int(block_offset)
if config.get('SYNCER_NO_HISTORY'):
initial_block_start = block_offset
initial_block_start = initial_block_offset
initial_block_offset += 1
syncer_backends.append(SQLBackend.initial(chain_spec, initial_block_offset, start_block_height=initial_block_start))
logg.info('found no backends to resume, adding initial sync from history start {} end {}'.format(initial_block_start, initial_block_offset))

View File

@@ -5,7 +5,7 @@ version = (
0,
2,
1,
'alpha.2',
'rc.1',
)
version_object = semver.VersionInfo(

View File

@@ -1,19 +1,17 @@
# syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
# RUN pip install $pip_extra_index_url_flag cic-base[full_graph]==0.1.2b9
ARG DOCKER_REGISTRY="registry.gitlab.com/grassrootseconomics"
FROM $DOCKER_REGISTRY/cic-base-images:python-3.8.6-dev-e8eb2ee2
COPY requirements.txt .
#RUN pip install $pip_extra_index_url_flag -r test_requirements.txt
#RUN pip install $pip_extra_index_url_flag .
#RUN pip install .[server]
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
ARG EXTRA_PIP_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG EXTRA_PIP_ARGS=""
ARG PIP_INDEX_URL="https://pypi.org/simple"
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL $EXTRA_PIP_ARGS \
pip install --index-url $PIP_INDEX_URL \
--pre \
--extra-index-url $EXTRA_PIP_INDEX_URL $EXTRA_PIP_ARGS \
-r requirements.txt
COPY . .
@@ -23,10 +21,10 @@ RUN python setup.py install
# ini files in config directory defines the configurable parameters for the application
# they can all be overridden by environment variables
# to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
COPY config/ /usr/local/etc/cic-cache/
#COPY config/ /usr/local/etc/cic-cache/
# for db migrations
RUN git clone https://github.com/vishnubob/wait-for-it.git /usr/local/bin/wait-for-it/
COPY ./aux/wait-for-it/wait-for-it.sh ./
COPY cic_cache/db/migrations/ /usr/local/share/cic-cache/alembic/
COPY /docker/start_tracker.sh ./start_tracker.sh

View File

@@ -1,5 +1,5 @@
alembic==1.4.2
confini>=0.3.6rc4,<0.5.0
confini~=0.4.2
uwsgi==2.0.19.1
moolb~=0.1.1b2
cic-eth-registry~=0.6.1a1
@@ -8,7 +8,7 @@ semver==2.13.0
psycopg2==2.8.6
celery==4.4.7
redis==3.5.3
chainsyncer[sql]>=0.0.6a3,<0.1.0
chainsyncer[sql]>=0.0.7a3,<0.1.0
erc20-faucet>=0.3.2a2, <0.4.0
chainlib-eth>=0.0.9a14,<0.1.0
chainlib-eth>=0.0.10a20,<0.1.0
eth-address-index>=0.2.3a4,<0.3.0

View File

@@ -17,11 +17,12 @@ logg = logging.getLogger()
rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
dbdir = os.path.join(rootdir, 'cic_cache', 'db')
migrationsdir = os.path.join(dbdir, 'migrations')
configdir = os.path.join(rootdir, 'cic_cache', 'data', 'config')
config_dir = os.path.join('/usr/local/etc/cic-cache')
#config_dir = os.path.join('/usr/local/etc/cic-cache')
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', type=str, default=config_dir, help='config file')
argparser.add_argument('-c', type=str, help='config file')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('--migrations-dir', dest='migrations_dir', default=migrationsdir, type=str, help='path to alembic migrations directory')
argparser.add_argument('--reset', action='store_true', help='downgrade before upgrading')
@@ -35,7 +36,7 @@ if args.vv:
elif args.v:
logging.getLogger().setLevel(logging.INFO)
config = confini.Config(args.c, args.env_prefix)
config = confini.Config(configdir, args.env_prefix, override_dirs=args.c)
config.process()
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')

View File

@@ -1,5 +1,5 @@
celery==4.4.7
erc20-demurrage-token~=0.0.5a3
cic-eth-registry~=0.6.1a5
chainlib~=0.0.9rc3
cic_eth~=0.12.4a9
cic-eth-registry~=0.6.1a6
chainlib~=0.0.9rc1
cic_eth~=0.12.4a11

View File

@@ -1,6 +1,6 @@
[metadata]
name = cic-eth-aux-erc20-demurrage-token
version = 0.0.2a6
version = 0.0.2a7
description = cic-eth tasks supporting erc20 demurrage token
author = Louis Holbrook
author_email = dev@holbrook.no

View File

@@ -1,5 +1,5 @@
SQLAlchemy==1.3.20
cic-eth-registry>=0.6.1a5,<0.7.0
cic-eth-registry>=0.6.1a6,<0.7.0
hexathon~=0.0.1a8
chainqueue>=0.0.4a6,<0.1.0
eth-erc20>=0.1.2a2,<0.2.0

View File

@@ -123,7 +123,7 @@ class AdminApi:
return s_lock.apply_async()
def tag_account(self, tag, address_hex, chain_spec):
def tag_account(self, chain_spec, tag, address):
"""Persistently associate an address with a plaintext tag.
Some tags are known by the system and is used to resolve addresses to use for certain transactions.
@@ -138,7 +138,7 @@ class AdminApi:
'cic_eth.eth.account.set_role',
[
tag,
address_hex,
address,
chain_spec.asdict(),
],
queue=self.queue,
@@ -146,6 +146,30 @@ class AdminApi:
return s_tag.apply_async()
def get_tag_account(self, chain_spec, tag=None, address=None):
if address != None:
s_tag = celery.signature(
'cic_eth.eth.account.role',
[
address,
chain_spec.asdict(),
],
queue=self.queue,
)
else:
s_tag = celery.signature(
'cic_eth.eth.account.role_account',
[
tag,
chain_spec.asdict(),
],
queue=self.queue,
)
return s_tag.apply_async()
def have_account(self, address_hex, chain_spec):
s_have = celery.signature(
'cic_eth.eth.account.have',
@@ -503,7 +527,7 @@ class AdminApi:
queue=self.queue,
)
t = s.apply_async()
role = t.get()
role = t.get()[0][1]
if role != None:
tx['sender_description'] = role
@@ -556,7 +580,7 @@ class AdminApi:
queue=self.queue,
)
t = s.apply_async()
role = t.get()
role = t.get()[0][1]
if role != None:
tx['recipient_description'] = role

View File

@@ -683,3 +683,4 @@ class Api(ApiBase):
t = self.callback_success.apply_async([r])
return t

View File

@@ -12,8 +12,9 @@ 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
from cic_eth.eth.gas import have_gas_minimum
logg = logging.getLogger().getChild(__name__)
logg = logging.getLogger(__name__)
def health(*args, **kwargs):
@@ -31,18 +32,15 @@ def health(*args, **kwargs):
return True
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
min_gas = int(config.get('ETH_GAS_HOLDER_MINIMUM_UNITS')) * int(config.get('ETH_GAS_GIFTER_REFILL_BUFFER'))
if config.get('ETH_MIN_FEE_PRICE'):
min_gas *= int(config.get('ETH_MIN_FEE_PRICE'))
r = have_gas_minimum(chain_spec, gas_provider, min_gas, session=session)
session.close()
if not r:
logg.error('EEK! gas gifter has balance {}, below minimum {}'.format(r, min_gas))
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
return r

View File

@@ -0,0 +1,18 @@
# external imports
from chainlib.chain import ChainSpec
# local imports
from cic_eth.admin.ctrl import check_lock
from cic_eth.enum import LockEnum
from cic_eth.error import LockedError
def health(*args, **kwargs):
config = kwargs['config']
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
try:
check_lock(None, chain_spec.asdict(), LockEnum.START)
except LockedError as e:
return False
return True

View File

@@ -16,16 +16,22 @@ class ArgumentParser(BaseArgumentParser):
self.add_argument('--redis-port', dest='redis_port', type=int, help='redis host to use for task submission')
self.add_argument('--redis-db', dest='redis_db', type=int, help='redis db to use')
if local_arg_flags & CICFlag.REDIS_CALLBACK:
self.add_argument('--redis-host-callback', dest='redis_host_callback', default='localhost', type=str, help='redis host to use for callback')
self.add_argument('--redis-port-callback', dest='redis_port_callback', default=6379, type=int, help='redis port to use for callback')
self.add_argument('--redis-host-callback', dest='redis_host_callback', type=str, help='redis host to use for callback (defaults to redis host)')
self.add_argument('--redis-port-callback', dest='redis_port_callback', type=int, help='redis port to use for callback (defaults to redis port)')
self.add_argument('--redis-timeout', default=20.0, type=float, help='Redis callback timeout')
if local_arg_flags & CICFlag.CELERY:
self.add_argument('--celery-scheme', type=str, help='Celery broker scheme (defaults to "redis")')
self.add_argument('--celery-host', type=str, help='Celery broker host (defaults to redis host)')
self.add_argument('--celery-port', type=str, help='Celery broker port (defaults to redis port)')
self.add_argument('--celery-db', type=int, help='Celery broker db (defaults to redis db)')
self.add_argument('--celery-result-scheme', type=str, help='Celery result backend scheme (defaults to celery broker scheme)')
self.add_argument('--celery-result-host', type=str, help='Celery result backend host (defaults to celery broker host)')
self.add_argument('--celery-result-port', type=str, help='Celery result backend port (defaults to celery broker port)')
self.add_argument('--celery-result-db', type=int, help='Celery result backend db (defaults to celery broker db)')
self.add_argument('--celery-no-result', action='store_true', help='Disable the Celery results backend')
self.add_argument('-q', '--celery-queue', dest='celery_queue', type=str, default='cic-eth', help='Task queue')
if local_arg_flags & CICFlag.SYNCER:
self.add_argument('--offset', type=int, default=0, help='Start block height for initial history sync')
self.add_argument('--offset', type=int, help='Start block height for initial history sync')
self.add_argument('--no-history', action='store_true', dest='no_history', help='Skip initial history sync')
if local_arg_flags & CICFlag.CHAIN:
self.add_argument('-r', '--registry-address', type=str, dest='registry_address', help='CIC registry contract address')

View File

@@ -24,8 +24,8 @@ class CICFlag(enum.IntEnum):
# sync - nibble 4
SYNCER = 4096
argflag_local_task = CICFlag.CELERY
argflag_local_base = argflag_std_base | Flag.CHAIN_SPEC
argflag_local_task = CICFlag.CELERY
argflag_local_taskcallback = argflag_local_task | CICFlag.REDIS | CICFlag.REDIS_CALLBACK
argflag_local_chain = CICFlag.CHAIN
argflag_local_sync = CICFlag.SYNCER | CICFlag.CHAIN

View File

@@ -1,12 +1,18 @@
# standard imports
import os
import logging
import urllib.parse
import copy
# external imports
from chainlib.eth.cli import (
Config as BaseConfig,
Flag,
)
from urlybird.merge import (
urlhostmerge,
urlmerge,
)
# local imports
from .base import CICFlag
@@ -40,6 +46,7 @@ class Config(BaseConfig):
if local_arg_flags & CICFlag.CHAIN:
local_args_override['CIC_REGISTRY_ADDRESS'] = getattr(args, 'registry_address')
if local_arg_flags & CICFlag.CELERY:
local_args_override['CELERY_QUEUE'] = getattr(args, 'celery_queue')
@@ -49,15 +56,61 @@ class Config(BaseConfig):
config.dict_override(local_args_override, 'local cli args')
if local_arg_flags & CICFlag.REDIS_CALLBACK:
config.add(getattr(args, 'redis_host_callback'), '_REDIS_HOST_CALLBACK')
config.add(getattr(args, 'redis_port_callback'), '_REDIS_PORT_CALLBACK')
local_celery_args_override = {}
if local_arg_flags & CICFlag.CELERY:
hostport = urlhostmerge(
None,
config.get('REDIS_HOST'),
config.get('REDIS_PORT'),
)
redis_url = (
'redis',
hostport,
getattr(args, 'redis_db', None),
)
celery_config_url = urllib.parse.urlsplit(config.get('CELERY_BROKER_URL'))
hostport = urlhostmerge(
celery_config_url[1],
getattr(args, 'celery_host', None),
getattr(args, 'celery_port', None),
)
celery_arg_url = (
getattr(args, 'celery_scheme', None),
hostport,
getattr(args, 'celery_db', None),
)
celery_url = urlmerge(redis_url, celery_config_url, celery_arg_url)
celery_url_string = urllib.parse.urlunsplit(celery_url)
local_celery_args_override['CELERY_BROKER_URL'] = celery_url_string
if not getattr(args, 'celery_no_result'):
local_celery_args_override['CELERY_RESULT_URL'] = config.get('CELERY_RESULT_URL')
if local_celery_args_override['CELERY_RESULT_URL'] == None:
local_celery_args_override['CELERY_RESULT_URL'] = local_celery_args_override['CELERY_BROKER_URL']
celery_config_url = urllib.parse.urlsplit(local_celery_args_override['CELERY_RESULT_URL'])
hostport = urlhostmerge(
celery_config_url[1],
getattr(args, 'celery_result_host', None),
getattr(args, 'celery_result_port', None),
)
celery_arg_url = (
getattr(args, 'celery_result_scheme', None),
hostport,
getattr(args, 'celery_result_db', None),
)
celery_url = urlmerge(celery_config_url, celery_arg_url)
logg.debug('celery url {} {}'.format(celery_config_url, celery_url))
celery_url_string = urllib.parse.urlunsplit(celery_url)
local_celery_args_override['CELERY_RESULT_URL'] = celery_url_string
config.add(config.true('CELERY_DEBUG'), 'CELERY_DEBUG', exists_ok=True)
config.dict_override(local_celery_args_override, 'local celery cli args')
if local_arg_flags & CICFlag.REDIS_CALLBACK:
redis_host_callback = getattr(args, 'redis_host_callback', config.get('REDIS_HOST'))
redis_port_callback = getattr(args, 'redis_port_callback', config.get('REDIS_PORT'))
config.add(redis_host_callback, '_REDIS_HOST_CALLBACK')
config.add(redis_port_callback, '_REDIS_PORT_CALLBACK')
logg.debug('config loaded:\n{}'.format(config))
return config

View File

@@ -1,5 +1,5 @@
[celery]
broker_url = redis://localhost:6379
broker_url =
result_url =
queue = cic-eth
debug = 0

View File

@@ -2,5 +2,5 @@
registry_address =
trust_address =
default_token_symbol =
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas
health_modules = cic_eth.check.db,cic_eth.check.redis,cic_eth.check.signer,cic_eth.check.gas,cic_eth.check.start
run_dir = /run

View File

@@ -1,2 +1,6 @@
[eth]
gas_gifter_minimum_balance = 10000000000000000000000
gas_holder_minimum_units = 180000
gas_holder_refill_units = 15
gas_holder_refill_threshold = 3
gas_gifter_refill_buffer = 3
min_fee_price = 1

View File

@@ -23,7 +23,7 @@ def upgrade():
op.create_table(
'lock',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column("address", sa.String(42), nullable=True),
sa.Column("address", sa.String, nullable=True),
sa.Column('blockchain', sa.String),
sa.Column("flags", sa.BIGINT(), nullable=False, default=0),
sa.Column("date_created", sa.DateTime, nullable=False, default=datetime.datetime.utcnow),

View File

@@ -0,0 +1,31 @@
"""Add gas cache
Revision ID: c91cafc3e0c1
Revises: aee12aeb47ec
Create Date: 2021-10-28 20:45:34.239865
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c91cafc3e0c1'
down_revision = 'aee12aeb47ec'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'gas_cache',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column("address", sa.String, nullable=False),
sa.Column("tx_hash", sa.String, nullable=True),
sa.Column("method", sa.String, nullable=True),
sa.Column("value", sa.BIGINT(), nullable=False),
)
def downgrade():
op.drop_table('gas_cache')

View File

@@ -0,0 +1,27 @@
# standard imports
import logging
# external imports
from sqlalchemy import Column, String, NUMERIC
# local imports
from .base import SessionBase
logg = logging.getLogger(__name__)
class GasCache(SessionBase):
"""Provides gas budget cache for token operations
"""
__tablename__ = 'gas_cache'
address = Column(String())
tx_hash = Column(String())
method = Column(String())
value = Column(NUMERIC())
def __init__(self, address, method, value, tx_hash):
self.address = address
self.tx_hash = tx_hash
self.method = method
self.value = value

View File

@@ -12,7 +12,7 @@ from cic_eth.error import (
IntegrityError,
)
logg = logging.getLogger()
logg = logging.getLogger(__name__)
class Nonce(SessionBase):
@@ -21,7 +21,7 @@ class Nonce(SessionBase):
__tablename__ = 'nonce'
nonce = Column(Integer)
address_hex = Column(String(42))
address_hex = Column(String(40))
@staticmethod

View File

@@ -24,8 +24,22 @@ class AccountRole(SessionBase):
tag = Column(Text)
address_hex = Column(String(42))
# TODO:
@staticmethod
def all(session=None):
session = SessionBase.bind_session(session)
pairs = []
q = session.query(AccountRole.tag, AccountRole.address_hex)
for r in q.all():
pairs.append((r[1], r[0]),)
SessionBase.release_session(session)
return pairs
@staticmethod
def get_address(tag, session):
"""Get Ethereum address matching the given tag

View File

@@ -69,9 +69,12 @@ class StatusEnum(enum.IntEnum):
class LockEnum(enum.IntEnum):
"""
STICKY: When set, reset is not possible
INIT: When set, startup is possible without second level sanity checks (e.g. gas gifter balance)
START: When set, startup is not possible, regardless of state
CREATE: Disable creation of accounts
SEND: Disable sending to network
QUEUE: Disable queueing new or modified transactions
QUERY: Disable all queue state and transaction queries
"""
STICKY=1
INIT=2
@@ -79,7 +82,8 @@ class LockEnum(enum.IntEnum):
SEND=8
QUEUE=16
QUERY=32
ALL=int(0xfffffffffffffffe)
START=int(0x80000000)
ALL=int(0x7ffffffe)
def status_str(v, bits_only=False):

View File

@@ -64,8 +64,10 @@ class LockedError(Exception):
class SeppukuError(Exception):
"""Exception base class for all errors that should cause system shutdown
"""
def __init__(self, message, lockdown=False):
self.message = message
self.lockdown = lockdown
class SignerError(SeppukuError):

View File

@@ -136,7 +136,7 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
# Generate and sign transaction
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
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, code_callback=AccountRegistry.gas)
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)
rpc_signer.disconnect()
@@ -192,7 +192,7 @@ def gift(self, account_address, chain_spec_dict):
# Generate and sign transaction
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
nonce_oracle = CustodialTaskNonceOracle(account_address, self.request.root_id, session=session) #, default_nonce)
gas_oracle = self.create_gas_oracle(rpc, MinterFaucet.gas)
gas_oracle = self.create_gas_oracle(rpc, code_callback=MinterFaucet.gas)
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)
rpc_signer.disconnect()
@@ -266,19 +266,46 @@ def set_role(self, tag, address, chain_spec_dict):
@celery_app.task(bind=True, base=BaseTask)
def role(self, address, chain_spec_dict):
"""Return account role for address
"""Return account role for address and/or role
:param account: Account to check
:type account: str, 0x-hex
:param chain_str: Chain spec string representation
:type chain_str: str
:param chain_spec_dict: Chain spec dict representation
:type chain_spec_dict: dict
:returns: Account, or None if not exists
:rtype: Varies
"""
session = self.create_session()
role_tag = AccountRole.role_for(address, session=session)
session.close()
return role_tag
return [(address, role_tag,)]
@celery_app.task(bind=True, base=BaseTask)
def role_account(self, role_tag, chain_spec_dict):
"""Return address for role.
If the role parameter is None, will return addresses for all roles.
:param role_tag: Role to match
:type role_tag: str
:param chain_spec_dict: Chain spec dict representation
:type chain_spec_dict: dict
:returns: List with a single account/tag pair for a single tag, or a list of account and tag pairs for all tags
:rtype: list
"""
session = self.create_session()
pairs = None
if role_tag != None:
addr = AccountRole.get_address(role_tag, session=session)
pairs = [(addr, role_tag,)]
else:
pairs = AccountRole.all(session=session)
session.close()
return pairs
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)

View File

@@ -10,6 +10,9 @@ from chainlib.eth.tx import (
TxFormat,
unpack,
)
from chainlib.eth.contract import (
ABIContractEncoder,
)
from cic_eth_registry import CICRegistry
from cic_eth_registry.erc20 import ERC20Token
from hexathon import (
@@ -31,10 +34,8 @@ from cic_eth.error import (
YouAreBrokeError,
)
from cic_eth.queue.tx import register_tx
from cic_eth.eth.gas import (
create_check_gas_task,
MaxGasOracle,
)
from cic_eth.eth.gas import create_check_gas_task
from cic_eth.eth.util import CacheGasOracle
from cic_eth.ext.address import translate_address
from cic_eth.task import (
CriticalSQLAlchemyTask,
@@ -154,8 +155,12 @@ def transfer_from(self, tokens, holder_address, receiver_address, value, chain_s
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
session = self.create_session()
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
enc = ABIContractEncoder()
enc.method('transferFrom')
method = enc.get()
gas_oracle = self.create_gas_oracle(rpc, t['address'], method=method, session=session, min_price=self.min_fee_price)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
try:
(tx_hash_hex, tx_signed_raw_hex) = c.transfer_from(t['address'], spender_address, holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
@@ -225,8 +230,12 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_d
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
session = self.create_session()
enc = ABIContractEncoder()
enc.method('transfer')
method = enc.get()
gas_oracle = self.create_gas_oracle(rpc, t['address'], method=method, session=session, min_price=self.min_fee_price)
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
try:
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
@@ -294,8 +303,12 @@ def approve(self, tokens, holder_address, spender_address, value, chain_spec_dic
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
session = self.create_session()
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
enc = ABIContractEncoder()
enc.method('approve')
method = enc.get()
gas_oracle = self.create_gas_oracle(rpc, t['address'], method=method, session=session)
c = ERC20(chain_spec, signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
try:
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)

View File

@@ -41,6 +41,7 @@ from chainqueue.db.models.tx import TxCache
from chainqueue.db.models.otx import Otx
# local imports
from cic_eth.db.models.gas_cache import GasCache
from cic_eth.db.models.role import AccountRole
from cic_eth.db.models.base import SessionBase
from cic_eth.error import (
@@ -65,17 +66,56 @@ from cic_eth.encode import (
ZERO_ADDRESS_NORMAL,
unpack_normal,
)
from cic_eth.error import SeppukuError
from cic_eth.eth.util import MAXIMUM_FEE_UNITS
celery_app = celery.current_app
logg = logging.getLogger()
MAXIMUM_FEE_UNITS = 8000000
class MaxGasOracle:
@celery_app.task(base=CriticalSQLAlchemyTask)
def apply_gas_value_cache(address, method, value, tx_hash):
return apply_gas_value_cache_local(address, method, value, tx_hash)
def gas(code=None):
return MAXIMUM_FEE_UNITS
def apply_gas_value_cache_local(address, method, value, tx_hash, session=None):
address = tx_normalize.executable_address(address)
tx_hash = tx_normalize.tx_hash(tx_hash)
value = int(value)
session = SessionBase.bind_session(session)
q = session.query(GasCache)
q = q.filter(GasCache.address==address)
q = q.filter(GasCache.method==method)
o = q.first()
if o == None:
o = GasCache(address, method, value, tx_hash)
elif tx.gas_used > o.value:
o.value = value
o.tx_hash = strip_0x(tx_hash)
session.add(o)
session.commit()
SessionBase.release_session(session)
def have_gas_minimum(chain_spec, address, min_gas, session=None, rpc=None):
if rpc == None:
rpc = RPCConnection.connect(chain_spec, 'default')
o = balance(add_0x(address))
r = rpc.do(o)
try:
r = int(r)
except ValueError:
r = strip_0x(r)
r = int(r, 16)
logg.debug('have gas minimum {} have gas {} minimum is {}'.format(address, r, min_gas))
if r < min_gas:
return False
return True
def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None):
@@ -346,6 +386,7 @@ def refill_gas(self, recipient_address, chain_spec_dict):
session.flush()
# finally determine the value to send
# TODO: must be dynamic; more gas if price went up.
refill_amount = 0
if not zero_amount:
refill_amount = self.safe_gas_refill_amount
@@ -357,6 +398,13 @@ def refill_gas(self, recipient_address, chain_spec_dict):
# set up evm RPC connection
rpc = RPCConnection.connect(chain_spec, 'default')
# check the gas balance of the gifter
if not have_gas_minimum(chain_spec, gas_provider, self.safe_gas_refill_amount):
raise SeppukuError('Noooooooooooo; gas gifter {} is broke!'.format(gas_provider))
if not have_gas_minimum(chain_spec, gas_provider, self.safe_gas_gifter_balance):
logg.error('Gas gifter {} gas balance is below the safe level to operate!'.format(gas_provider))
# set up transaction builder
nonce_oracle = CustodialTaskNonceOracle(gas_provider, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc)

View File

@@ -0,0 +1,54 @@
# standard imports
import logging
# external imports
from chainlib.eth.gas import RPCGasOracle
from hexathon import strip_0x
# local imports
from cic_eth.db.models.gas_cache import GasCache
from cic_eth.encode import tx_normalize
from cic_eth.db.models.base import SessionBase
MAXIMUM_FEE_UNITS = 8000000
logg = logging.getLogger(__name__)
class MaxGasOracle(RPCGasOracle):
def get_fee_units(self, code=None):
return MAXIMUM_FEE_UNITS
class CacheGasOracle(MaxGasOracle):
"""Returns a previously recorded value for fee unit expenditure for a contract call, if it exists. Otherwise returns max units.
:todo: instead of max units, connect a pluggable gas heuristics engine.
"""
def __init__(self, conn, address, method=None, session=None, min_price=None, id_generator=None):
super(CacheGasOracle, self).__init__(conn, code_callback=self.get_fee_units, min_price=min_price, id_generator=id_generator)
self.value = None
self.address = address
self.method = method
address = tx_normalize.executable_address(address)
session = SessionBase.bind_session(session)
q = session.query(GasCache)
q = q.filter(GasCache.address==address)
if method != None:
method = strip_0x(method)
q = q.filter(GasCache.method==method)
o = q.first()
if o != None:
self.value = int(o.value)
SessionBase.release_session(session)
def get_fee_units(self, code=None):
if self.value != None:
logg.debug('found stored gas unit value {} for address {} method {}'.format(self.value, self.address, self.method))
return self.value
return super(CacheGasOracle, self).get_fee_units(code=code)

View File

@@ -8,15 +8,14 @@ import confini
script_dir = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.dirname(os.path.dirname(script_dir))
config_dir = os.path.join(root_dir, 'cic_eth', 'data', 'config')
logg = logging.getLogger(__name__)
@pytest.fixture(scope='session')
def load_config():
config_dir = os.environ.get('CONFINI_DIR')
if config_dir == None:
config_dir = os.path.join(root_dir, 'config/test')
conf = confini.Config(config_dir, 'CICTEST')
override_config_dir = os.path.join(root_dir, 'config', 'test')
conf = confini.Config(config_dir, 'CICTEST', override_dirs=[override_config_dir])
conf.process()
logg.debug('config {}'.format(conf))
return conf

View File

@@ -10,7 +10,6 @@ import datetime
# external imports
import celery
from cic_eth_registry import CICRegistry
from chainlib.chain import ChainSpec
from chainlib.eth.tx import unpack
from chainlib.connection import RPCConnection

View File

@@ -3,3 +3,4 @@ from .tx import TxFilter
from .gas import GasFilter
from .register import RegistrationFilter
from .transferauth import TransferAuthFilter
from .token import TokenFilter

View File

@@ -0,0 +1,63 @@
# standard imports
import logging
# external imports
from eth_erc20 import ERC20
from chainlib.eth.contract import (
ABIContractEncoder,
ABIContractType,
)
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.address import is_same_address
from chainlib.eth.error import RequestMismatchException
from cic_eth_registry import CICRegistry
from cic_eth_registry.erc20 import ERC20Token
from eth_token_index import TokenUniqueSymbolIndex
import celery
# local imports
from .base import SyncFilter
logg = logging.getLogger(__name__)
class TokenFilter(SyncFilter):
def __init__(self, chain_spec, queue, call_address=ZERO_ADDRESS):
self.queue = queue
self.chain_spec = chain_spec
self.caller_address = call_address
def filter(self, conn, block, tx, db_session=None):
if not tx.payload:
return (None, None)
try:
r = ERC20.parse_transfer_request(tx.payload)
except RequestMismatchException:
return (None, None)
token_address = tx.inputs[0]
token = ERC20Token(self.chain_spec, conn, token_address)
registry = CICRegistry(self.chain_spec, conn)
r = registry.by_name(token.symbol, sender_address=self.caller_address)
if is_same_address(r, ZERO_ADDRESS):
return None
enc = ABIContractEncoder()
enc.method('transfer')
method = enc.get()
s = celery.signature(
'cic_eth.eth.gas.apply_gas_value_cache',
[
token_address,
method,
tx.gas_used,
tx.hash,
],
queue=self.queue,
)
return s.apply_async()

View File

@@ -67,7 +67,10 @@ from cic_eth.registry import (
connect_declarator,
connect_token_registry,
)
from cic_eth.task import BaseTask
from cic_eth.task import (
BaseTask,
CriticalWeb3Task,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -76,18 +79,18 @@ arg_flags = cic_eth.cli.argflag_std_read
local_arg_flags = cic_eth.cli.argflag_local_task
argparser = cic_eth.cli.ArgumentParser(arg_flags)
argparser.process_local_flags(local_arg_flags)
argparser.add_argument('--default-token-symbol', dest='default_token_symbol', type=str, help='Symbol of default token to use')
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('--aux-all', action='store_true', help='include tasks from all submodules from the aux module path')
argparser.add_argument('--min-fee-price', dest='min_fee_price', type=int, help='set minimum fee price for transactions, in wei')
argparser.add_argument('--aux', action='append', type=str, default=[], help='add single submodule from the aux module path')
args = argparser.parse_args()
# process config
extra_args = {
'default_token_symbol': 'CIC_DEFAULT_TOKEN_SYMBOL',
'aux_all': None,
'aux': None,
'trace_queue_status': 'TASKS_TRACE_QUEUE_STATUS',
'min_fee_price': 'ETH_MIN_FEE_PRICE',
}
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags)
@@ -187,6 +190,17 @@ elif len(args.aux) > 0:
logg.info('aux module {} found in path {}'.format(v, aux_dir))
aux.append(v)
default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
defaullt_token_address = None
if default_token_symbol:
default_token_address = registry.by_name(default_token_symbol)
else:
default_token_address = registry.by_name('DefaultToken')
c = ERC20Token(chain_spec, conn, default_token_address)
default_token_symbol = c.symbol
logg.info('found default token {} address {}'.format(default_token_symbol, default_token_address))
config.add(default_token_symbol, 'CIC_DEFAULT_TOKEN_SYMBOL', exists_ok=True)
for v in aux:
mname = 'cic_eth_aux.' + v
mod = importlib.import_module(mname)
@@ -204,13 +218,22 @@ def main():
argv.append('-n')
argv.append(config.get('CELERY_QUEUE'))
BaseTask.default_token_symbol = config.get('CIC_DEFAULT_TOKEN_SYMBOL')
BaseTask.default_token_address = registry.by_name(BaseTask.default_token_symbol)
# TODO: More elegant way of setting queue-wide settings
BaseTask.default_token_symbol = default_token_symbol
BaseTask.default_token_address = default_token_address
default_token = ERC20Token(chain_spec, conn, add_0x(BaseTask.default_token_address))
default_token.load(conn)
BaseTask.default_token_decimals = default_token.decimals
BaseTask.default_token_name = default_token.name
BaseTask.trusted_addresses = trusted_addresses
CriticalWeb3Task.safe_gas_refill_amount = int(config.get('ETH_GAS_HOLDER_MINIMUM_UNITS')) * int(config.get('ETH_GAS_HOLDER_REFILL_UNITS'))
CriticalWeb3Task.safe_gas_threshold_amount = int(config.get('ETH_GAS_HOLDER_MINIMUM_UNITS')) * int(config.get('ETH_GAS_HOLDER_REFILL_THRESHOLD'))
CriticalWeb3Task.safe_gas_gifter_balance = int(config.get('ETH_GAS_HOLDER_MINIMUM_UNITS')) * int(config.get('ETH_GAS_GIFTER_REFILL_BUFFER'))
if config.get('ETH_MIN_FEE_PRICE'):
BaseTask.min_fee_price = int(config.get('ETH_MIN_FEE_PRICE'))
CriticalWeb3Task.safe_gas_threshold_amount *= BaseTask.min_fee_price
CriticalWeb3Task.safe_gas_refill_amount *= BaseTask.min_fee_price
CriticalWeb3Task.safe_gas_gifter_balance *= BaseTask.min_fee_price
BaseTask.run_dir = config.get('CIC_RUN_DIR')
logg.info('default token set to {} {}'.format(BaseTask.default_token_symbol, BaseTask.default_token_address))

View File

@@ -36,6 +36,7 @@ from cic_eth.runnable.daemons.filters import (
TxFilter,
RegistrationFilter,
TransferAuthFilter,
TokenFilter,
)
from cic_eth.stat import init_chain_stat
from cic_eth.registry import (
@@ -99,10 +100,10 @@ def main():
syncer_backends = SQLBackend.resume(chain_spec, block_offset)
if len(syncer_backends) == 0:
initial_block_start = config.get('SYNCER_OFFSET')
initial_block_offset = block_offset
initial_block_start = int(config.get('SYNCER_OFFSET'))
initial_block_offset = int(block_offset)
if config.true('SYNCER_NO_HISTORY'):
initial_block_start = block_offset
initial_block_start = initial_block_offset
initial_block_offset += 1
syncer_backends.append(SQLBackend.initial(chain_spec, initial_block_offset, start_block_height=initial_block_start))
logg.info('found no backends to resume, adding initial sync from history start {} end {}'.format(initial_block_start, initial_block_offset))
@@ -154,6 +155,8 @@ def main():
gas_filter = GasFilter(chain_spec, config.get('CELERY_QUEUE'))
token_gas_cache_filter = TokenFilter(chain_spec, config.get('CELERY_QUEUE'))
#transfer_auth_filter = TransferAuthFilter(registry, chain_spec, config.get('_CELERY_QUEUE'))
i = 0
@@ -163,6 +166,7 @@ def main():
syncer.add_filter(registration_filter)
# TODO: the two following filter functions break the filter loop if return uuid. Pro: less code executed. Con: Possibly unintuitive flow break
syncer.add_filter(tx_filter)
syncer.add_filter(token_gas_cache_filter)
#syncer.add_filter(transfer_auth_filter)
for cf in callback_filters:
syncer.add_filter(cf)

View File

@@ -8,6 +8,7 @@ import re
# external imports
import cic_eth.cli
from chainlib.chain import ChainSpec
from chainlib.eth.address import is_address
from xdg.BaseDirectory import xdg_config_home
# local imports
@@ -21,12 +22,18 @@ logg = logging.getLogger()
arg_flags = cic_eth.cli.argflag_std_base | cic_eth.cli.Flag.UNSAFE | cic_eth.cli.Flag.CHAIN_SPEC
local_arg_flags = cic_eth.cli.argflag_local_taskcallback
argparser = cic_eth.cli.ArgumentParser(arg_flags)
argparser.add_positional('tag', type=str, help='address tag')
argparser.add_positional('address', type=str, help='address')
argparser.add_argument('--set', action='store_true', help='sets the given tag')
argparser.add_argument('--tag', type=str, help='operate on the given tag')
argparser.add_positional('address', required=False, type=str, help='address associated with tag')
argparser.process_local_flags(local_arg_flags)
args = argparser.parse_args()
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags)
extra_args = {
'set': None,
'tag': None,
'address': None,
}
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags, extra_args=extra_args)
celery_app = cic_eth.cli.CeleryApp.from_config(config)
@@ -39,7 +46,17 @@ api = AdminApi(None)
def main():
admin_api.tag_account(args.tag, args.address, chain_spec)
if config.get('_ADDRESS') != None and not is_address(config.get('_ADDRESS')):
sys.stderr.write('Invalid address {}'.format(config.get('_ADDRESS')))
sys.exit(1)
if config.get('_SET'):
admin_api.tag_account(chain_spec, config.get('_TAG'), config.get('_ADDRESS'))
else:
t = admin_api.get_tag_account(chain_spec, tag=config.get('_TAG'), address=config.get('_ADDRESS'))
r = t.get()
for v in r:
sys.stdout.write('{}\t{}\n'.format(v[1], v[0]))
if __name__ == '__main__':

View File

@@ -18,7 +18,7 @@ from cic_eth.api import Api
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger('create_account_script')
arg_flags = cic_eth.cli.argflag_std_base
arg_flags = cic_eth.cli.argflag_local_base
local_arg_flags = cic_eth.cli.argflag_local_taskcallback
argparser = cic_eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--token-symbol', dest='token_symbol', type=str, help='Token symbol')

View File

@@ -16,9 +16,14 @@ import confini
import celery
from chainlib.chain import ChainSpec
from chainlib.eth.connection import EthHTTPConnection
from hexathon import add_0x
from hexathon import (
add_0x,
strip_0x,
uniform as hex_uniform,
)
# local imports
import cic_eth.cli
from cic_eth.api.admin import AdminApi
from cic_eth.db.enum import (
StatusEnum,
@@ -31,59 +36,35 @@ 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('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
argparser.add_argument('-r', '--registry-address', dest='r', type=str, help='CIC registry address')
arg_flags = cic_eth.cli.argflag_std_base
local_arg_flags = cic_eth.cli.argflag_local_taskcallback
argparser = cic_eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('-f', '--format', dest='f', default=default_format, type=str, help='Output format')
argparser.add_argument('--status-raw', dest='status_raw', action='store_true', help='Output status bit enum names only')
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('-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')
argparser.add_argument('query', type=str, help='Transaction, transaction hash, account or "lock"')
argparser.process_local_flags(local_arg_flags)
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 = {
'ETH_PROVIDER': getattr(args, 'p'),
'CIC_CHAIN_SPEC': getattr(args, 'i'),
'CIC_REGISTRY_ADDRESS': getattr(args, 'r'),
extra_args = {
'f': '_FORMAT',
'query': '_QUERY',
}
# override args
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))
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags, extra_args=extra_args)
try:
config.add(add_0x(args.query), '_QUERY', True)
except:
config.add(args.query, '_QUERY', True)
celery_app = cic_eth.cli.CeleryApp.from_config(config)
queue = config.get('CELERY_QUEUE')
celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=config.get('CELERY_RESULT_URL'))
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
queue = args.q
# connect to celery
celery_app = cic_eth.cli.CeleryApp.from_config(config)
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
# set up rpc
rpc = cic_eth.cli.RPC.from_config(config) #, use_signer=True)
conn = rpc.get_default()
rpc = EthHTTPConnection(args.p)
#registry_address = config.get('CIC_REGISTRY_ADDRESS')
admin_api = AdminApi(rpc)
admin_api = AdminApi(conn)
t = admin_api.registry()
registry_address = t.get()
@@ -113,7 +94,7 @@ def render_tx(o, **kwargs):
for v in o.get('status_log', []):
d = datetime.datetime.fromisoformat(v[0])
e = status_str(v[1], args.status_raw)
e = status_str(v[1], config.get('_RAW'))
content += '{}: {}\n'.format(d, e)
return content
@@ -154,20 +135,24 @@ def render_lock(o, **kwargs):
def main():
txs = []
renderer = render_tx
if len(config.get('_QUERY')) > 66:
#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'), renderer=renderer)
elif len(config.get('_QUERY')) > 42:
#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'), renderer=renderer)
elif len(config.get('_QUERY')) == 42:
#registry = connect_registry(rpc, chain_spec, registry_address)
txs = admin_api.account(chain_spec, config.get('_QUERY'), include_recipient=False, renderer=render_account)
query = config.get('_QUERY')
try:
query = hex_uniform(strip_0x(query))
except TypeError:
pass
except ValueError:
pass
if len(query) > 64:
admin_api.tx(chain_spec, tx_raw=query, renderer=renderer)
elif len(query) > 40:
admin_api.tx(chain_spec, tx_hash=query, renderer=renderer)
elif len(query) == 40:
txs = admin_api.account(chain_spec, query, include_recipient=False, renderer=render_account)
renderer = render_account
elif len(config.get('_QUERY')) >= 4 and config.get('_QUERY')[:4] == 'lock':
elif len(query) >= 4 and query[:4] == 'lock':
t = admin_api.get_lock()
txs = t.get()
renderer = render_lock
@@ -175,7 +160,7 @@ def main():
r = renderer(txs)
sys.stdout.write(r + '\n')
else:
raise ValueError('cannot parse argument {}'.format(config.get('_QUERY')))
raise ValueError('cannot parse argument {}'.format(query))
if __name__ == '__main__':

View File

@@ -17,6 +17,7 @@ from cic_eth_registry.error import UnknownContractError
# local imports
from cic_eth.error import SeppukuError
from cic_eth.db.models.base import SessionBase
from cic_eth.eth.util import CacheGasOracle
#logg = logging.getLogger().getChild(__name__)
logg = logging.getLogger()
@@ -29,14 +30,32 @@ class BaseTask(celery.Task):
session_func = SessionBase.create_session
call_address = ZERO_ADDRESS
trusted_addresses = []
create_nonce_oracle = RPCNonceOracle
create_gas_oracle = RPCGasOracle
min_fee_price = 1
default_token_address = None
default_token_symbol = None
default_token_name = None
default_token_decimals = None
run_dir = '/run'
def create_gas_oracle(self, conn, address=None, *args, **kwargs):
if address == None:
return RPCGasOracle(
conn,
code_callback=kwargs.get('code_callback'),
min_price=self.min_fee_price,
id_generator=kwargs.get('id_generator'),
)
return CacheGasOracle(
conn,
address,
method=kwargs.get('method'),
min_price=self.min_fee_price,
id_generator=kwargs.get('id_generator'),
)
def create_session(self):
return BaseTask.session_func()
@@ -78,19 +97,18 @@ class CriticalWeb3Task(CriticalTask):
autoretry_for = (
ConnectionError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_threshold_amount = 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
safe_gas_gifter_balance = safe_gas_threshold_amount * 5 * 100
class CriticalSQLAlchemyAndWeb3Task(CriticalTask):
class CriticalSQLAlchemyAndWeb3Task(CriticalWeb3Task):
autoretry_for = (
sqlalchemy.exc.DatabaseError,
sqlalchemy.exc.TimeoutError,
ConnectionError,
sqlalchemy.exc.ResourceClosedError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
class CriticalSQLAlchemyAndSignerTask(CriticalTask):
@@ -100,14 +118,11 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
sqlalchemy.exc.ResourceClosedError,
)
class CriticalWeb3AndSignerTask(CriticalTask):
class CriticalWeb3AndSignerTask(CriticalWeb3Task):
autoretry_for = (
ConnectionError,
)
safe_gas_threshold_amount = 2000000000 * 60000 * 3
safe_gas_refill_amount = safe_gas_threshold_amount * 5
@celery_app.task()
def check_health(self):
pass

View File

@@ -10,7 +10,7 @@ version = (
0,
12,
4,
'alpha.11',
'alpha.14',
)
version_object = semver.VersionInfo(

View File

@@ -1,2 +0,0 @@
[accounts]
writer_address =

View File

@@ -1,2 +0,0 @@
[bancor]
dir = tests/testdata/bancor

View File

@@ -1,5 +1,3 @@
[celery]
broker_url = filesystem://
result_url = filesystem://
#broker_url = redis://
#result_url = redis://

View File

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

View File

@@ -1,2 +0,0 @@
[dispatcher]
loop_interval = 0.1

View File

@@ -1,8 +0,0 @@
[eth]
#ws_provider = ws://localhost:8546
#ttp_provider = http://localhost:8545
provider = http://localhost:8545
gas_provider_address =
#chain_id =
abi_dir =
faucet_giver_address =

View File

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

View File

@@ -1,6 +0,0 @@
[SSL]
enable_client = false
cert_file =
key_file =
password =
ca_file =

View File

@@ -1,2 +0,0 @@
[SYNCER]
loop_interval = 1

View File

@@ -1,4 +1,3 @@
@node cic-eth-accounts
@section Accounts
Accounts are private keys in the signer component keyed by "addresses," a one-way transformation of a public key. Data can be signed by using the account as identifier for corresponding RPC requests.

View File

@@ -1,4 +1,4 @@
@node cic-eth system maintenance
@anchor{cic-eth-appendix-system-maintenance}
@appendix Admin API
The admin API is still in an early stage of refinement. User friendliness can be considerably improved.
@@ -33,7 +33,7 @@ Get the current state of a lock
@appendixsection tag_account
Associate an identifier with an account address (@xref{cic-eth system accounts})
Associate an identifier with an account address (@xref{cic-eth-system-accounts})
@appendixsection have_account

View File

@@ -14,5 +14,6 @@ Released 2021 under GPL3
@c
@contents
@include index.texi
@include content.texi
@include appendix.texi

View File

@@ -0,0 +1,3 @@
@include admin.texi
@include chains.texi
@include transfertypes.texi

View File

@@ -1,4 +1,4 @@
@node cic-eth Appendix Task chains
@anchor{cic-eth-appendix-task-chains}
@appendix Task chains
TBC - explain here how to generate these chain diagrams

View File

@@ -1,4 +1,3 @@
@node cic-eth configuration
@section Configuration
Configuration parameters are grouped by configuration filename.

View File

@@ -1,6 +1,6 @@
@node cic-eth
@top cic-eth
@include intro.texi
@include dependencies.texi
@include configuration.texi
@include system.texi
@@ -9,6 +9,3 @@
@include incoming.texi
@include services.texi
@include tools.texi
@include admin.texi
@include chains.texi
@include transfertypes.texi

View File

@@ -1,4 +1,3 @@
@node cic-eth-dependencies
@section Dependencies
This application is written in Python 3.8. It is tightly coupled with @code{python-celery}, which provides the task worker ecosystem. It also uses @code{SQLAlchemy} which provides useful abstractions for persistent storage though SQL, and @code{alembic} for database schema migrations.

View File

@@ -1,4 +1,4 @@
@node cic-eth-incoming
@anchor{cic-eth-incoming}
@section Incoming transactions
All transactions in mined blocks will be passed to a selection of plugin filters to the @code{chainsyncer} component. Each of these filters are individual python module files in @code{cic_eth.runnable.daemons.filters}. This section describes their function.

View File

@@ -1,9 +1,8 @@
@node cic-eth-interacting
@section Interacting with the system
The API to the @var{cic-eth} component is a proxy for executing @emph{chains of Celery tasks}. The tasks that compose individual chains are documented in @ref{cic-eth Appendix Task chains,the Task Chain appendix}, which also describes a CLI tool that can generate graph representationso of them.
The API to the @var{cic-eth} component is a proxy for executing @emph{chains of Celery tasks}. The tasks that compose individual chains are documented in @ref{cic-eth-appendix-task-chains,the Task Chain appendix}, which also describes a CLI tool that can generate graph representationso of them.
There are two API classes, @var{Api} and @var{AdminApi}. The former is described later in this section, the latter described in @ref{cic-eth system maintenance,the Admin API appendix}.
There are two API classes, @var{Api} and @var{AdminApi}. The former is described later in this section, the latter described in @ref{cic-eth-appendix-system-maintenance,the Admin API appendix}.
@subsection Interface

View File

@@ -1,4 +1,3 @@
@node cic-eth-outgoing
@section Outgoing transactions
@strong{Important! A pre-requisite for proper functioning of the component is that no other agent is sending transactions to the network for any of the keys in the keystore.}

View File

@@ -1,4 +1,3 @@
@node cic-eth-services
@section Services
There are four daemons that together orchestrate all of the aforementioned recipes. This section will provide a high level description of them.

View File

@@ -1,10 +1,10 @@
@node cic-eth system accounts
@section System initialization
When the system starts for the first time, it is locked for any state change request other than account creation@footnote{Specifically, the @code{INIT}, @code{SEND} and @code{QUEUE} lock bits are set.}. These locks should be @emph{reset} once system initialization has been completed. Currently, system initialization only involves creating and tagging required system accounts, as specified below.
See @ref{cic-eth-locking,Locking} and @ref{cic-eth-tools-ctrl,ctrl in Tools} for details on locking.
@anchor{cic-eth-system-accounts}
@subsection System accounts
Certain accounts in the system have special roles. These are defined by @emph{tagging} certain accounts addresses with well-known identifiers.

View File

@@ -1,4 +1,3 @@
@node cic-eth-tools
@section Tools
A collection of CLI tools have been provided to help with diagnostics and other administrative tasks. These use the same configuration infrastructure as the daemons.
@@ -37,7 +36,7 @@ Execute a token transfer on behalf of a custodial account.
@subsection tag (cic-eth-tag)
Associate an account address with a string identifier. @xref{cic-eth system accounts}
Associate an account address with a string identifier. @xref{cic-eth-system-accounts}
@anchor{cic-eth-tools-ctrl}

View File

@@ -1,4 +1,3 @@
@node cic-eth Appendix Transaction types
@appendix Transfer types
@table @var

View File

@@ -1,46 +1,32 @@
# syntax = docker/dockerfile:1.2
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev-55da5f4e as dev
ARG DOCKER_REGISTRY="registry.gitlab.com/grassrootseconomics"
FROM $DOCKER_REGISTRY/cic-base-images:python-3.8.6-dev-e8eb2ee2
# Copy just the requirements and install....this _might_ give docker a hint on caching but we
# do load these all into setup.py later
# TODO can we take all the requirements out of setup.py and just do a pip install -r requirements.txt && python setup.py
#COPY cic-eth/requirements.txt .
ARG EXTRA_INDEX_URL="https://pip.grassrootseconomics.net:8433"
ARG GITLAB_PYTHON_REGISTRY="https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple"
ARG EXTRA_PIP_INDEX_URL=https://pip.grassrootseconomics.net:8433
ARG EXTRA_PIP_ARGS=""
#RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
# pip install --index-url https://pypi.org/simple \
# --force-reinstall \
# --extra-index-url $GITLAB_PYTHON_REGISTRY --extra-index-url $EXTRA_INDEX_URL \
# -r requirements.txt
ARG PIP_INDEX_URL=https://pypi.org/simple
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL \
$EXTRA_PIP_ARGS \
cic-eth-aux-erc20-demurrage-token~=0.0.2a6
pip install --index-url $PIP_INDEX_URL \
--pre \
--extra-index-url $EXTRA_PIP_INDEX_URL $EXTRA_PIP_ARGS \
cic-eth-aux-erc20-demurrage-token~=0.0.2a7
COPY *requirements.txt ./
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL \
$EXTRA_PIP_ARGS \
pip install --index-url $PIP_INDEX_URL \
--pre \
--extra-index-url $EXTRA_PIP_INDEX_URL $EXTRA_PIP_ARGS \
-r requirements.txt \
-r services_requirements.txt \
-r admin_requirements.txt
# always install the latest signer
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
pip install --index-url https://pypi.org/simple \
--extra-index-url $GITLAB_PYTHON_REGISTRY \
--extra-index-url $EXTRA_INDEX_URL \
$EXTRA_PIP_ARGS \
crypto-dev-signer
COPY . .
RUN python setup.py install
@@ -53,7 +39,7 @@ RUN chmod 755 *.sh
# # ini files in config directory defines the configurable parameters for the application
# # they can all be overridden by environment variables
# # to generate a list of environment variables from configuration, use: confini-dump -z <dir> (executable provided by confini package)
COPY config/ /usr/local/etc/cic-eth/
#COPY config/ /usr/local/etc/cic-eth/
COPY cic_eth/db/migrations/ /usr/local/share/cic-eth/alembic/
COPY crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/

View File

@@ -2,5 +2,6 @@
set -e
>&2 echo executing database migration
python scripts/migrate.py -c /usr/local/etc/cic-eth --migrations-dir /usr/local/share/cic-eth/alembic -vv
#python scripts/migrate.py -c /usr/local/etc/cic-eth --migrations-dir /usr/local/share/cic-eth/alembic -vv
python scripts/migrate.py --migrations-dir /usr/local/share/cic-eth/alembic -vv
set +e

View File

@@ -1,4 +1,4 @@
celery==4.4.7
chainlib-eth>=0.0.9rc4,<0.1.0
chainlib-eth>=0.0.10a20,<0.1.0
semver==2.13.0
crypto-dev-signer>=0.4.15rc2,<0.5.0
urlybird~=0.0.1a2

View File

@@ -1,5 +1,5 @@
chainqueue>=0.0.5a1,<0.1.0
chainsyncer[sql]>=0.0.6a3,<0.1.0
chainqueue>=0.0.6a1,<0.1.0
chainsyncer[sql]>=0.0.7a3,<0.1.0
alembic==1.4.2
confini>=0.3.6rc4,<0.5.0
redis==3.5.3
@@ -8,7 +8,7 @@ pycryptodome==3.10.1
liveness~=0.0.1a7
eth-address-index>=0.2.4a1,<0.3.0
eth-accounts-index>=0.1.2a3,<0.2.0
cic-eth-registry>=0.6.1a5,<0.7.0
cic-eth-registry>=0.6.1a6,<0.7.0
erc20-faucet>=0.3.2a2,<0.4.0
erc20-transfer-authorization>=0.3.5a2,<0.4.0
sarafu-faucet>=0.0.7a2,<0.1.0

View File

@@ -1,6 +1,7 @@
[metadata]
name = cic-eth
version = attr: cic_eth.version.__version_string__
#version = attr: cic_eth.version.__version_string__
version = 0.12.5a2
description = CIC Network Ethereum interaction
author = Louis Holbrook
author_email = dev@holbrook.no

View File

@@ -0,0 +1,97 @@
# external imports
from eth_erc20 import ERC20
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import (
Gas,
OverrideGasOracle,
)
from chainlib.eth.tx import (
TxFormat,
receipt,
raw,
unpack,
Tx,
)
from chainlib.eth.block import (
Block,
block_latest,
block_by_number,
)
from chainlib.eth.address import is_same_address
from chainlib.eth.contract import ABIContractEncoder
from hexathon import strip_0x
from eth_token_index import TokenUniqueSymbolIndex
# local imports
from cic_eth.runnable.daemons.filters.token import TokenFilter
from cic_eth.db.models.gas_cache import GasCache
from cic_eth.db.models.base import SessionBase
def test_filter_gas(
default_chain_spec,
init_database,
eth_rpc,
eth_signer,
contract_roles,
agent_roles,
token_roles,
foo_token,
token_registry,
register_lookups,
celery_session_worker,
cic_registry,
):
rpc = RPCConnection.connect(default_chain_spec, 'default')
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], eth_rpc)
gas_oracle = OverrideGasOracle(price=1000000000, limit=1000000)
c = ERC20(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], 100, tx_format=TxFormat.RLP_SIGNED)
o = raw(tx_signed_raw_hex)
eth_rpc.do(o)
o = receipt(tx_hash_hex)
rcpt = eth_rpc.do(o)
assert rcpt['status'] == 1
fltr = TokenFilter(default_chain_spec, queue=None, call_address=agent_roles['ALICE'])
o = block_latest()
r = eth_rpc.do(o)
o = block_by_number(r, include_tx=False)
r = eth_rpc.do(o)
block = Block(r)
block.txs = [tx_hash_hex]
tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
tx_src = unpack(tx_signed_raw_bytes, default_chain_spec)
tx = Tx(tx_src, block=block)
tx.apply_receipt(rcpt)
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
assert t == None
nonce_oracle = RPCNonceOracle(contract_roles['CONTRACT_DEPLOYER'], eth_rpc)
c = TokenUniqueSymbolIndex(default_chain_spec, signer=eth_signer, nonce_oracle=nonce_oracle)
(tx_hash_hex_register, o) = c.register(token_registry, contract_roles['CONTRACT_DEPLOYER'], foo_token)
eth_rpc.do(o)
o = receipt(tx_hash_hex)
r = eth_rpc.do(o)
assert r['status'] == 1
t = fltr.filter(eth_rpc, block, tx, db_session=init_database)
r = t.get_leaf()
assert t.successful()
q = init_database.query(GasCache)
q = q.filter(GasCache.tx_hash==strip_0x(tx_hash_hex))
o = q.first()
assert is_same_address(o.address, strip_0x(foo_token))
assert o.value > 0
enc = ABIContractEncoder()
enc.method('transfer')
method = enc.get()
assert o.method == method

View File

@@ -103,11 +103,11 @@ def test_tag_account(
api = AdminApi(eth_rpc, queue=None)
t = api.tag_account('foo', agent_roles['ALICE'], default_chain_spec)
t = api.tag_account(default_chain_spec, 'foo', agent_roles['ALICE'])
t.get()
t = api.tag_account('bar', agent_roles['BOB'], default_chain_spec)
t = api.tag_account(default_chain_spec, 'bar', agent_roles['BOB'])
t.get()
t = api.tag_account('bar', agent_roles['CAROL'], default_chain_spec)
t = api.tag_account(default_chain_spec, 'bar', agent_roles['CAROL'])
t.get()
assert AccountRole.get_address('foo', init_database) == tx_normalize.wallet_address(agent_roles['ALICE'])

View File

@@ -110,7 +110,7 @@ def test_tokens_noproof(
custodial_roles,
foo_token_declaration,
bar_token_declaration,
celery_worker,
celery_session_worker,
):
api = Api(str(default_chain_spec), queue=None, callback_param='foo')

View File

@@ -141,9 +141,57 @@ def test_role_task(
)
t = s.apply_async()
r = t.get()
assert r == 'foo'
assert r[0][0] == address
assert r[0][1] == 'foo'
def test_get_role_task(
init_database,
celery_session_worker,
default_chain_spec,
):
address_foo = '0x' + os.urandom(20).hex()
role_foo = AccountRole.set('foo', address_foo)
init_database.add(role_foo)
address_bar = '0x' + os.urandom(20).hex()
role_bar = AccountRole.set('bar', address_bar)
init_database.add(role_bar)
init_database.commit()
s = celery.signature(
'cic_eth.eth.account.role_account',
[
'bar',
default_chain_spec.asdict(),
],
queue=None,
)
t = s.apply_async()
r = t.get()
assert r[0][0] == address_bar
assert r[0][1] == 'bar'
s = celery.signature(
'cic_eth.eth.account.role_account',
[
None,
default_chain_spec.asdict(),
],
queue=None,
)
t = s.apply_async()
r = t.get()
x_tags = ['foo', 'bar']
x_addrs = [address_foo, address_bar]
for v in r:
x_addrs.remove(v[0])
x_tags.remove(v[1])
assert len(x_tags) == 0
assert len(x_addrs) == 0
def test_gift(
init_database,

View File

@@ -1,6 +1,6 @@
crypto-dev-signer>=0.4.15rc2,<=0.4.15
chainqueue>=0.0.5a1,<0.1.0
cic-eth-registry>=0.6.1a5,<0.7.0
chainqueue>=0.0.5a3,<0.1.0
cic-eth-registry>=0.6.1a6,<0.7.0
redis==3.5.3
hexathon~=0.0.1a8
pycryptodome==3.10.1

View File

@@ -1,19 +1,23 @@
# syntax = docker/dockerfile:1.2
#FROM node:15.3.0-alpine3.10
FROM node:lts-alpine3.14
FROM node:15.3.0-alpine3.10
WORKDIR /root
RUN apk add --no-cache postgresql bash
ARG NPM_REPOSITORY=${NPM_REPOSITORY:-https://registry.npmjs.org}
RUN npm config set snyk=false
#RUN npm config set registry={NPM_REPOSITORY}
RUN npm config set registry=${NPM_REPOSITORY}
# copy the dependencies
COPY package.json package-lock.json .
COPY package.json package-lock.json ./
RUN --mount=type=cache,mode=0755,target=/root/.npm \
npm set cache /root/.npm && \
npm ci
npm cache verify && \
npm ci --verbose
COPY webpack.config.js .
COPY tsconfig.json .
COPY webpack.config.js ./
COPY tsconfig.json ./
## required to build the cic-client-meta module
COPY . .
COPY tests/*.asc /root/pgp/

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,9 @@
create table if not exists store (
id serial primary key not null,
owner_fingerprint text not null,
owner_fingerprint text default null,
hash char(64) not null unique,
content text not null
content text not null,
mime_type text
);
create index if not exists idx_fp on store ((lower(owner_fingerprint)));

View File

@@ -1,9 +1,10 @@
create table if not exists store (
/*id serial primary key not null,*/
id integer primary key autoincrement,
owner_fingerprint text not null,
owner_fingerprint text default null,
hash char(64) not null unique,
content text not null
content text not null,
mime_type text
);
create index if not exists idx_fp on store ((lower(owner_fingerprint)));

View File

@@ -1,12 +1,13 @@
import * as Automerge from 'automerge';
import * as pgp from 'openpgp';
import * as crypto from 'crypto';
import { Envelope, Syncable } from '@cicnet/crdt-meta';
import { Envelope, Syncable, bytesToHex } from '@cicnet/crdt-meta';
function handleNoMergeGet(db, digest, keystore) {
const sql = "SELECT content FROM store WHERE hash = '" + digest + "'";
return new Promise<string|boolean>((whohoo, doh) => {
const sql = "SELECT owner_fingerprint, content, mime_type FROM store WHERE hash = '" + digest + "'";
return new Promise<any>((whohoo, doh) => {
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
@@ -16,16 +17,36 @@ function handleNoMergeGet(db, digest, keystore) {
return;
}
const immutable = rs.rows[0]['owner_fingerprint'] == undefined;
let mimeType;
if (immutable) {
if (rs.rows[0]['mime_type'] === undefined) {
mimeType = 'application/octet-stream';
} else {
mimeType = rs.rows[0]['mime_type'];
}
} else {
mimeType = 'application/json';
}
const cipherText = rs.rows[0]['content'];
pgp.message.readArmored(cipherText).then((m) => {
const opts = {
message: m,
privateKeys: [keystore.getPrivateKey()],
format: 'binary',
};
pgp.decrypt(opts).then((plainText) => {
const o = Syncable.fromJSON(plainText.data);
const r = JSON.stringify(o.m['data']);
whohoo(r);
let r;
if (immutable) {
r = plainText.data;
} else {
mimeType = 'application/json';
const d = new TextDecoder().decode(plainText.data);
const o = Syncable.fromJSON(d);
r = JSON.stringify(o.m['data']);
}
whohoo([r, mimeType]);
}).catch((e) => {
console.error('decrypt', e);
doh(e);
@@ -57,6 +78,7 @@ function handleServerMergePost(data, db, digest, keystore, signer) {
} else {
e = Envelope.fromJSON(v);
s = e.unwrap();
console.debug('s', s, o)
s.replace(o, 'server merge');
e.set(s);
s.onwrap = (e) => {
@@ -139,7 +161,13 @@ function handleClientMergeGet(db, digest, keystore) {
privateKeys: [keystore.getPrivateKey()],
};
pgp.decrypt(opts).then((plainText) => {
const o = Syncable.fromJSON(plainText.data);
let d;
if (typeof(plainText.data) == 'string') {
d = plainText.data;
} else {
d = new TextDecoder().decode(plainText.data);
}
const o = Syncable.fromJSON(d);
const e = new Envelope(o);
whohoo(e.toJSON());
}).catch((e) => {
@@ -201,10 +229,65 @@ function handleClientMergePut(data, db, digest, keystore, signer) {
});
}
function handleImmutablePost(data, db, digest, keystore, contentType) {
return new Promise<Array<string|boolean>>((whohoo, doh) => {
let data_binary = data;
const h = crypto.createHash('sha256');
h.update(data_binary);
const z = h.digest();
const r = bytesToHex(z);
if (digest) {
if (r != digest) {
doh('hash mismatch: ' + r + ' != ' + digest);
return;
}
} else {
digest = r;
console.debug('calculated digest ' + digest);
}
handleNoMergeGet(db, digest, keystore).then((haveDigest) => {
if (haveDigest !== false) {
whohoo([false, digest]);
return;
}
let message;
if (typeof(data) == 'string') {
data_binary = new TextEncoder().encode(data);
message = pgp.message.fromText(data);
} else {
message = pgp.message.fromBinary(data);
}
const opts = {
message: message,
publicKeys: keystore.getEncryptKeys(),
};
pgp.encrypt(opts).then((cipherText) => {
const sql = "INSERT INTO store (hash, content, mime_type) VALUES ('" + digest + "', '" + cipherText.data + "', '" + contentType + "') ON CONFLICT (hash) DO UPDATE SET content = EXCLUDED.content;";
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
return;
}
whohoo([true, digest]);
});
}).catch((e) => {
doh(e);
});
}).catch((e) => {
doh(e);
});
});
}
export {
handleClientMergePut,
handleClientMergeGet,
handleServerMergePost,
handleServerMergePut,
handleNoMergeGet,
handleImmutablePost,
};

View File

@@ -118,37 +118,71 @@ async function processRequest(req, res) {
return;
}
let mod = req.method.toLowerCase() + ":automerge:";
let modDetail = undefined;
let immutablePost = false;
try {
digest = parseDigest(req.url);
} catch(e) {
console.error('digest error: ' + e)
res.writeHead(400, {"Content-Type": "text/plain"});
res.end();
return;
if (req.url == '/') {
immutablePost = true;
modDetail = 'immutable';
} else {
console.error('url is not empty (' + req.url + ') and not valid digest error: ' + e)
res.writeHead(400, {"Content-Type": "text/plain"});
res.end();
return;
}
}
const mergeHeader = req.headers['x-cic-automerge'];
let mod = req.method.toLowerCase() + ":automerge:";
switch (mergeHeader) {
case "client":
mod += "client"; // client handles merges
break;
case "server":
mod += "server"; // server handles merges
break;
default:
mod += "none"; // merged object only (get only)
if (modDetail === undefined) {
const mergeHeader = req.headers['x-cic-automerge'];
switch (mergeHeader) {
case "client":
if (immutablePost) {
res.writeHead(400, 'Valid digest missing', {"Content-Type": "text/plain"});
res.end();
return;
}
modDetail = "client"; // client handles merges
break;
case "server":
if (immutablePost) {
res.writeHead(400, 'Valid digest missing', {"Content-Type": "text/plain"});
res.end();
return;
}
modDetail = "server"; // server handles merges
break;
case "immutable":
modDetail = "immutable"; // no merging, literal immutable content with content-addressing
break;
default:
modDetail = "none"; // merged object only (get only)
}
}
mod += modDetail;
let data = '';
// handle bigger chunks of data
let data;
req.on('data', (d) => {
data += d;
if (data === undefined) {
data = d;
} else {
data += d;
}
});
req.on('end', async () => {
console.debug('mode', mod);
let content = '';
req.on('end', async (d) => {
let inputContentType = req.headers['content-type'];
let debugString = 'executing mode ' + mod ;
if (data !== undefined) {
debugString += ' for content type ' + inputContentType + ' length ' + data.length;
}
console.debug(debugString);
let content;
let contentType = 'application/json';
console.debug('handling data', data);
let statusCode = 200;
let r:any = undefined;
try {
switch (mod) {
@@ -159,6 +193,7 @@ async function processRequest(req, res) {
res.end();
return;
}
content = '';
break;
case 'get:automerge:client':
@@ -176,6 +211,7 @@ async function processRequest(req, res) {
res.end();
return;
}
content = '';
break;
//case 'get:automerge:server':
// content = await handlers.handleServerMergeGet(db, digest, keystore);
@@ -183,12 +219,24 @@ async function processRequest(req, res) {
case 'get:automerge:none':
r = await handlers.handleNoMergeGet(db, digest, keystore);
if (r == false) {
if (r === false) {
res.writeHead(404, {"Content-Type": "text/plain"});
res.end();
return;
}
content = r;
content = r[0];
contentType = r[1];
break;
case 'post:automerge:immutable':
if (inputContentType === undefined) {
inputContentType = 'application/octet-stream';
}
r = await handlers.handleImmutablePost(data, db, digest, keystore, inputContentType);
if (r[0]) {
statusCode = 201;
}
content = r[1];
break;
default:
@@ -204,14 +252,21 @@ async function processRequest(req, res) {
}
if (content === undefined) {
console.error('empty content', data);
console.error('empty content', mod, digest, data);
res.writeHead(404, {"Content-Type": "text/plain"});
res.end();
return;
}
const responseContentLength = (new TextEncoder().encode(content)).length;
res.writeHead(200, {
//let responseContentLength;
//if (typeof(content) == 'string') {
// (new TextEncoder().encode(content)).length;
//}
const responseContentLength = content.length;
//if (responseContentLength === undefined) {
// responseContentLength = 0;
//}
res.writeHead(statusCode, {
"Access-Control-Allow-Origin": "*",
"Content-Type": contentType,
"Content-Length": responseContentLength,

View File

@@ -7,6 +7,8 @@ import * as handlers from '../scripts/server/handlers';
import { Envelope, Syncable, ArgPair, PGPKeyStore, PGPSigner, KeyStore, Signer } from '@cicnet/crdt-meta';
import { SqliteAdapter } from '../src/db';
const hashOfFoo = '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae';
function createKeystore() {
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc', 'utf-8');
@@ -44,11 +46,13 @@ function createDatabase(sqlite_file:string):Promise<any> {
// doh(e);
// return;
// }
// get this from real sql files sources
const sql = `CREATE TABLE store (
id integer primary key autoincrement,
owner_fingerprint text not null,
owner_fingerprint text default null,
hash char(64) not null unique,
content text not null
content text not null,
mime_type text default null
);
`
@@ -111,15 +115,18 @@ describe('server', async () => {
let j = env.toJSON();
const content = await handlers.handleClientMergePut(j, db, digest, keystore, signer);
assert(content); // true-ish
console.debug('content', content);
let v = await handlers.handleNoMergeGet(db, digest, keystore);
if (v === undefined) {
if (v === false) {
db.close();
assert.fail('');
}
db.close();
return;
v = await handlers.handleClientMergeGet(db, digest, keystore);
if (v === undefined) {
if (v === false) {
db.close();
assert.fail('');
}
@@ -187,7 +194,7 @@ describe('server', async () => {
j = await handlers.handleNoMergeGet(db, digest, keystore);
assert(v); // true-ish
let o = JSON.parse(j);
let o = JSON.parse(j[0]);
o.bar = 'xyzzy';
j = JSON.stringify(o);
@@ -212,82 +219,39 @@ describe('server', async () => {
j = await handlers.handleNoMergeGet(db, digest, keystore);
assert(j); // true-ish
o = JSON.parse(j);
o = JSON.parse(j[0]);
console.log(o);
db.close();
});
await it('server_merge', async () => {
const keystore = await createKeystore();
const signer = new PGPSigner(keystore);
const db = await createDatabase(__dirname + '/db.three.sqlite');
const digest = 'deadbeef';
let s = new Syncable(digest, {
bar: 'baz',
});
let env = await wrap(s, signer)
let j:any = env.toJSON();
let v = await handlers.handleClientMergePut(j, db, digest, keystore, signer);
assert(v); // true-ish
j = await handlers.handleNoMergeGet(db, digest, keystore);
assert(v); // true-ish
let o = JSON.parse(j);
o.bar = 'xyzzy';
j = JSON.stringify(o);
let signMaterial = await handlers.handleServerMergePost(j, db, digest, keystore, signer);
assert(signMaterial)
env = Envelope.fromJSON(signMaterial);
console.log('envvvv', env);
const signedData = await signData(env.o['digest'], keystore);
console.log('signed', signedData);
o = {
'm': env,
's': signedData,
}
j = JSON.stringify(o);
console.log(j);
v = await handlers.handleServerMergePut(j, db, digest, keystore, signer);
assert(v);
j = await handlers.handleNoMergeGet(db, digest, keystore);
assert(j); // true-ish
o = JSON.parse(j);
console.log(o);
db.close();
});
// await it('server_merge_empty', async () => {
// await it('server_merge', async () => {
// const keystore = await createKeystore();
// const signer = new PGPSigner(keystore);
//
// const db = await createDatabase(__dirname + '/db.three.sqlite');
//
// const digest = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
// let o:any = {
// foo: 'bar',
// xyzzy: 42,
// }
// let j:any = JSON.stringify(o);
// const digest = 'deadbeef';
// let s = new Syncable(digest, {
// bar: 'baz',
// });
// let env = await wrap(s, signer)
// let j:any = env.toJSON();
//
// let v = await handlers.handleClientMergePut(j, db, digest, keystore, signer);
// assert(v); // true-ish
//
// j = await handlers.handleNoMergeGet(db, digest, keystore);
// assert(v); // true-ish
//
// let o = JSON.parse(j);
// o.bar = 'xyzzy';
// j = JSON.stringify(o);
//
// let signMaterial = await handlers.handleServerMergePost(j, db, digest, keystore, signer);
// assert(signMaterial)
//
// const env = Envelope.fromJSON(signMaterial);
// env = Envelope.fromJSON(signMaterial);
//
// console.log('envvvv', env);
//
@@ -301,7 +265,7 @@ describe('server', async () => {
// j = JSON.stringify(o);
// console.log(j);
//
// let v = await handlers.handleServerMergePut(j, db, digest, keystore, signer);
// v = await handlers.handleServerMergePut(j, db, digest, keystore, signer);
// assert(v);
//
// j = await handlers.handleNoMergeGet(db, digest, keystore);
@@ -311,5 +275,88 @@ describe('server', async () => {
//
// db.close();
// });
//
await it('server_merge_empty', async () => {
const keystore = await createKeystore();
const signer = new PGPSigner(keystore);
const db = await createDatabase(__dirname + '/db.three.sqlite');
const digest = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
let o:any = {
foo: 'bar',
xyzzy: 42,
}
let j:any = JSON.stringify(o);
let signMaterial = await handlers.handleServerMergePost(j, db, digest, keystore, signer);
assert(signMaterial)
const env = Envelope.fromJSON(signMaterial);
console.log('envvvv', env);
const signedData = await signData(env.o['digest'], keystore);
console.log('signed', signedData);
o = {
'm': env,
's': signedData,
}
j = JSON.stringify(o);
console.log(j);
let v = await handlers.handleServerMergePut(j, db, digest, keystore, signer);
assert(v);
j = await handlers.handleNoMergeGet(db, digest, keystore);
assert(j); // true-ish
o = JSON.parse(j[0]);
console.log(o);
db.close();
});
await it('immutable_nodigest', async() => {
const keystore = await createKeystore();
const db = await createDatabase(__dirname + '/db.three.sqlite');
const s:string = 'foo';
let r;
r = await handlers.handleImmutablePost(s, db, undefined, keystore, 'text/plain');
assert(r[0]);
assert(hashOfFoo == r[1]);
r = await handlers.handleImmutablePost(s, db, undefined, keystore, 'text/plain');
assert(!r[0]);
assert(hashOfFoo == r[1]);
const b:Uint8Array = new TextEncoder().encode(s);
r = await handlers.handleImmutablePost(b, db, undefined, keystore, 'text/plain');
assert(!r[0]);
assert(hashOfFoo == r[1]);
});
await it('immutable_digest', async() => {
const keystore = await createKeystore();
const db = await createDatabase(__dirname + '/db.three.sqlite');
const s:string = 'foo';
const b:Uint8Array = new TextEncoder().encode(s);
let r;
r = await handlers.handleImmutablePost(b, db, hashOfFoo, keystore, 'application/octet-stream');
assert(r[0]);
assert(hashOfFoo == r[1]);
r = await handlers.handleImmutablePost(b, db, hashOfFoo, keystore, 'application/octet-stream');
assert(!r[0]);
assert(hashOfFoo == r[1]);
r = await handlers.handleImmutablePost(s, db, hashOfFoo, keystore, 'text/plain');
assert(!r[0]);
assert(hashOfFoo == r[1]);
});
});

View File

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

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