Compare commits

..

12 Commits

48 changed files with 644 additions and 646 deletions

4
.gitignore vendored
View File

@@ -4,7 +4,3 @@ service-configs/*
__pycache__
*.pyc
*.o
gmon.out
*.egg-info
dist/
build/

View File

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

View File

@@ -32,9 +32,7 @@ def lock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.AL
:returns: New lock state for address
:rtype: number
"""
chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.set(chain_str, flags, address=address, tx_hash=tx_hash)
logg.debug('Locked {} for {}, flag now {}'.format(flags, address, r))
return chained_input
@@ -53,9 +51,7 @@ def unlock(chained_input, chain_spec_dict, address=ZERO_ADDRESS, flags=LockEnum.
:returns: New lock state for address
:rtype: number
"""
chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
r = Lock.reset(chain_str, flags, address=address)
logg.debug('Unlocked {} for {}, flag now {}'.format(flags, address, r))
return chained_input
@@ -131,9 +127,7 @@ def unlock_queue(chained_input, chain_spec_dict, address=ZERO_ADDRESS):
@celery_app.task(base=CriticalSQLAlchemyTask)
def check_lock(chained_input, chain_spec_dict, lock_flags, address=None):
chain_str = '::'
if chain_spec_dict != None:
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
chain_str = str(ChainSpec.from_dict(chain_spec_dict))
session = SessionBase.create_session()
r = Lock.check(chain_str, lock_flags, address=ZERO_ADDRESS, session=session)
if address != None:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,7 +59,6 @@ args_override = {
'CIC_CHAIN_SPEC': getattr(args, 'i'),
}
# override args
config.dict_override(args_override, 'cli')
config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(config_dir, config))
@@ -68,9 +67,7 @@ celery_app = celery.Celery(broker=config.get('CELERY_BROKER_URL'), backend=confi
queue = args.q
chain_spec = None
if config.get('CIC_CHAIN_SPEC') != None and config.get('CIC_CHAIN_SPEC') != '::':
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
admin_api = AdminApi(None)
@@ -85,9 +82,6 @@ def lock_names_to_flag(s):
# TODO: move each command to submodule
def main():
chain_spec_dict = None
if chain_spec != None:
chain_spec_dict = chain_spec.asdict()
if args.command == 'unlock':
flags = lock_names_to_flag(args.flags)
if not is_checksum_address(args.address):
@@ -97,7 +91,7 @@ def main():
'cic_eth.admin.ctrl.unlock',
[
None,
chain_spec_dict,
chain_spec.asdict(),
args.address,
flags,
],
@@ -116,7 +110,7 @@ def main():
'cic_eth.admin.ctrl.lock',
[
None,
chain_spec_dict,
chain_spec.asdict(),
args.address,
flags,
],

View File

@@ -15,9 +15,6 @@ from chainlib.connection import RPCConnection
from chainlib.eth.connection import EthUnixSignerConnection
from chainlib.chain import ChainSpec
from chainqueue.db.models.otx import Otx
from cic_eth_registry.error import UnknownContractError
import liveness.linux
# local imports
from cic_eth.eth import (
@@ -55,7 +52,6 @@ from cic_eth.registry import (
connect_token_registry,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -94,15 +90,14 @@ config.censor('PASSWORD', 'DATABASE')
config.censor('PASSWORD', 'SSL')
logg.debug('config loaded from {}:\n{}'.format(args.c, config))
health_modules = config.get('CIC_HEALTH_MODULES', [])
if len(health_modules) != 0:
health_modules = health_modules.split(',')
logg.debug('health mods {}'.format(health_modules))
# connect to database
dsn = dsn_from_config(config)
SessionBase.connect(dsn, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG'))
# verify database connection with minimal sanity query
session = SessionBase.create_session()
session.execute('select version_num from alembic_version')
session.close()
# set up celery
current_app = celery.Celery(__name__)
@@ -140,15 +135,10 @@ else:
chain_spec = ChainSpec.from_chain_str(config.get('CIC_CHAIN_SPEC'))
RPCConnection.register_location(config.get('ETH_PROVIDER'), chain_spec, 'default')
#RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection)
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer')
RPCConnection.register_location(config.get('SIGNER_SOCKET_PATH'), chain_spec, 'signer', constructor=EthUnixSignerConnection)
Otx.tracing = config.true('TASKS_TRACE_QUEUE_STATUS')
#import cic_eth.checks.gas
#if not cic_eth.checks.gas.health(config=config):
# raise RuntimeError()
liveness.linux.load(health_modules, rundir=config.get('CIC_RUN_DIR'), config=config)
def main():
argv = ['worker']
@@ -172,11 +162,7 @@ def main():
rpc = RPCConnection.connect(chain_spec, 'default')
try:
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
except UnknownContractError as e:
logg.exception('Registry contract connection failed for {}: {}'.format(config.get('CIC_REGISTRY_ADDRESS'), e))
sys.exit(1)
connect_registry(rpc, chain_spec, config.get('CIC_REGISTRY_ADDRESS'))
trusted_addresses_src = config.get('CIC_TRUST_ADDRESS')
if trusted_addresses_src == None:
@@ -185,13 +171,10 @@ def main():
trusted_addresses = trusted_addresses_src.split(',')
for address in trusted_addresses:
logg.info('using trusted address {}'.format(address))
connect_declarator(rpc, chain_spec, trusted_addresses)
connect_token_registry(rpc, chain_spec)
liveness.linux.set()
current_app.worker_main(argv)
liveness.linux.reset()
@celery.signals.eventlet_pool_postshutdown.connect

View File

@@ -94,8 +94,3 @@ class CriticalWeb3AndSignerTask(CriticalTask):
def hello(self):
time.sleep(0.1)
return id(SessionBase.create_session)
@celery_app.task()
def check_health(self):
celery.app.control.shutdown()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -53,5 +53,3 @@ COPY cic-eth/crypto_dev_signer_config/ /usr/local/etc/crypto-dev-signer/
RUN git clone https://gitlab.com/grassrootseconomics/cic-contracts.git && \
mkdir -p /usr/local/share/cic/solidity && \
cp -R cic-contracts/abis /usr/local/share/cic/solidity/abi
COPY util/liveness/health.sh /usr/local/bin/health.sh

View File

@@ -1,6 +1,6 @@
cic-base==0.1.2b1
cic-base~=0.1.2a76
celery==4.4.7
crypto-dev-signer~=0.4.14b3
crypto-dev-signer~=0.4.14b2
confini~=0.3.6rc3
cic-eth-registry~=0.5.4a16
#cic-bancor~=0.0.6

View File

@@ -38,7 +38,6 @@ packages =
cic_eth.runnable.daemons.filters
cic_eth.callbacks
cic_eth.sync
cic_eth.check
scripts =
./scripts/migrate.py

View File

@@ -71,7 +71,7 @@ def process_exit_insufficient_balance(display_key: str, user: Account, ussd_sess
operational_balance = get_cached_operational_balance(blockchain_address=user.blockchain_address)
# compile response data
user_input = ussd_session.get('user_input').split('*')[-1]
user_input = ussd_session.get('session_data').get('transaction_amount')
transaction_amount = to_wei(value=int(user_input))
token_symbol = 'SRF'
@@ -86,7 +86,7 @@ def process_exit_insufficient_balance(display_key: str, user: Account, ussd_sess
amount=from_wei(transaction_amount),
token_symbol=token_symbol,
recipient_information=tx_recipient_information,
token_balance=operational_balance
token_balance=operational_balance,
)
@@ -401,7 +401,11 @@ def process_request(user_input: str, user: Account, ussd_session: Optional[dict]
'exit_invalid_pin',
'exit_invalid_new_pin',
'exit_pin_mismatch',
'exit_invalid_request'
'exit_invalid_request',
"exit_insufficient_balance",
"exit_successful_transaction",
"help",
"complete"
] and person_metadata is not None:
return UssdMenu.find_by_name(name='start')
else:

View File

@@ -45,7 +45,15 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
:rtype: bool
"""
user_input, ussd_session, user = state_machine_data
return user.verify_password(password=user_input)
pin_validity = user.verify_password(password=user_input)
if pin_validity is True:
return user.verify_password(password=user_input)
else:
# bump number for failed attempts
user.failed_pin_attempts += 1
Account.session.add(user)
Account.session.commit()
return pin_validity
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:

View File

@@ -8,7 +8,7 @@ WORKDIR /usr/src
ARG pip_extra_index_url_flag='--extra-index-url https://pip.grassrootseconomics.net:8433'
RUN apt-get update && \
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git python-pycurl libcurl4-openssl-dev libssl-dev
# create secrets directory
RUN mkdir -vp pgp/keys

View File

@@ -0,0 +1,281 @@
- config:
- testset: "account tests"
- test:
- group: "accounts"
- name: "create account [EN]"
- url: "/"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek020", "text": ""}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"175"}
- compare: {"raw_body":"", "comparator":"contains", expected: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n" }
- test:
- group: "accounts"
- name: "create account [SW]"
- url: "/"
- method: "POST"
- delay: 2
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek021", "text": ""}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"175"}
- compare: {"raw_body":"", "comparator":"contains", expected: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n" }
- test:
- group: "accounts"
- name: "select preferred language prompt [EN]"
- url: "/"
- method: "POST"
- delay: 5 # delay to allow celery tasks to run to create account
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": ""}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Welcome to Sarafu\n1. English\n2. Kiswahili\n3. Help"}
- test:
- group: "accounts"
- name: "select preferred language prompt [SW]"
- url: "/"
- method: "POST"
- delay: 5 # delay to allow celery tasks to run to create account
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": ""}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Welcome to Sarafu\n1. English\n2. Kiswahili\n3. Help"}
- test:
- group: "accounts"
- name: "pin entry [EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"54"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Please enter a PIN to manage your account.\n0. Back"}
- test:
- group: "accounts"
- name: "pin entry [SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"59"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Tafadhali weka PIN ili kudhibiti akaunti yako.\n0. Nyuma"}
- test:
- group: "accounts"
- name: "pin entry confirmation [EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"32"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter your PIN again\n0. Back"}
- test:
- group: "accounts"
- name: "pin entry confirmation [SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"31"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka PIN yako tena\n0. Nyuma"}
- test:
- group: "accounts"
- name: "given names entry[EN]"
- url: "/"
- delay: 3 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"28"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter first name\n0. Back"}
- test:
- group: "accounts"
- name: "given names entry[SW]"
- url: "/"
- delay: 3 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"37"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jina lako la kwanza\n0. Nyuma"}
- test:
- group: "accounts"
- name: "family name entry[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"27"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter last name\n0. Back"}
- test:
- group: "accounts"
- name: "family name entry[SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"37"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jina lako la mwisho\n0. Nyuma"}
- test:
- group: "accounts"
- name: "gender selection[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"42"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter gender\n1. Male\n2. Female\n0. Back"}
- test:
- group: "accounts"
- name: "gender selection[SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jinsia yako\n1. Mwanaume\n2. Mwanamke\n0. Nyuma"}
- test:
- group: "accounts"
- name: "location entry[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"26"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter location\n0. Back"}
- test:
- group: "accounts"
- name: "location entry[SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"27"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka eneo lako\n0. Nyuma"}
- test:
- group: "accounts"
- name: "product entry[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1*Kangemi"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"55"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Please enter a product or service you offer\n0. Back"}
- test:
- group: "accounts"
- name: "product entry[SW]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2*Chebarbar"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"52"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Tafadhali weka bidhaa ama huduma unauza\n0. Nyuma"}
- test:
- group: "accounts"
- name: "start menu[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1*Kangemi*Potatoes"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"51"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Balance 50.00 SRF\n1. Send\n2. My Account\n3. Help"}
- test:
- group: "accounts"
- name: "start menu[EN]"
- url: "/"
- delay: 2 # delay
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2*Musau*Mandazi"}'
- headers: {'Content-Type': 'application/json'}
- expected_status: [200]
- validators:
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 50.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}

View File

@@ -0,0 +1,9 @@
#!/bin/bash
if [ -z "$TEST_SERVER_URL" ];
then
echo "The test server url is not set !"
exit 1
fi
pyresttest "$TEST_SERVER_URL" ./test_suite.yml --log debug

View File

@@ -0,0 +1,2 @@
- import: account_tests.yml
- import: transaction_tests.yml

View File

@@ -0,0 +1,285 @@
- config:
- testset: "transaction tests"
- test:
- group: "transactions"
- name: "send tokens[EN]"
- url: "/"
- delay: "3"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": ""}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "51" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Balance 50.00 SRF\n1. Send\n2. My Account\n3. Help" }
- test:
- group: "transactions"
- name: "send tokens[SW]"
- url: "/"
- delay: "3"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": ""}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 50.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}
- test:
- group: "transactions"
- name: "enter recipients phone number[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "30" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter phone number\n0. Back" }
- test:
- group: "transactions"
- name: "enter recipients phone number[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"33"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya simu\n0. Nyuma"}
- test:
- group: "transactions"
- name: "enter token amount[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "24" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter amount\n0. Back" }
- test:
- group: "transactions"
- name: "enter token amount[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"25"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka kiwango\n0. Nyuma"}
- test:
- group: "transactions"
- name: "transaction pin authorization[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "129" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Chebet Musau +254712345679 will receive 15.00 SRF from Kimani Omollo +254712345678.\nPlease enter your PIN to confirm.\n0. Back" }
- test:
- group: "transactions"
- name: "transaction pin authorization[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"148"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Kimani Omollo +254712345678 atapokea 18.00 SRF kutoka kwa Chebet Musau +254712345679.\nTafadhali weka nambari yako ya siri kudhibitisha.\n0. Nyuma"}
- test:
- group: "transactions"
- name: "transaction pin authorization-invalid-pin[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15*6987"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "65" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Please enter your PIN. You have 2 attempts remaining.\n0. Back" }
- test:
- group: "transactions"
- name: "transaction pin authorization-invalid-pin[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18*7845"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"62"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya siri. Una majaribio 2 yaliyobaki.\n0. Nyuma"}
- test:
- group: "transactions"
- name: "transaction pin authorization-valid-pin[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15*6987*0000"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "133" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Your request has been sent. Chebet Musau +254712345679 will receive 15.00 SRF from Kimani Omollo +254712345678.\n00. Back\n99. Exit" }
- test:
- group: "transactions"
- name: "transaction pin authorization-valid-pin[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18*7845*1111"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"131"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Ombi lako limetumwa. Kimani Omollo +254712345678 atapokea 18.00 SRF kutoka kwa Chebet Musau +254712345679.\n00. Nyuma\n99. Ondoka"}
- test:
- group: "transactions"
- name: "send tokens-2[EN]"
- url: "/"
- delay: "3"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": ""}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "51" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Balance 53.00 SRF\n1. Send\n2. My Account\n3. Help" }
- test:
- group: "transactions"
- name: "send tokens-2[SW]"
- url: "/"
- delay: "3"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": ""}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 47.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}
- test:
- group: "transactions"
- name: "enter recipients phone number-2[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "30" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter phone number\n0. Back" }
- test:
- group: "transactions"
- name: "enter recipients phone number-2[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"33"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya simu\n0. Nyuma"}
- test:
- group: "transactions"
- name: "enter token amount-2[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1*0712345679"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "24" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter amount\n0. Back" }
- test:
- group: "transactions"
- name: "enter token amount-2[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1*0712345678"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"25"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka kiwango\n0. Nyuma"}
- test:
- group: "transactions"
- name: "transaction pin authorization-2[EN]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1*0712345679*850"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "156" }
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Payment of 850.00 SRF to Chebet Musau +254712345679 has failed due to insufficient balance.\nYour Sarafu-Network balances is: 53.00 SRF\n00. Back\n99. Exit"}
- test:
- group: "transactions"
- name: "transaction pin authorization-2[SW]"
- url: "/"
- delay: "2"
- method: "POST"
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1*0712345678*1800"}'
- headers: { 'Content-Type': 'application/json' }
- expected_status: [ 200 ]
- validators:
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"186"}
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Malipo ya 1800.00 SRF kwa Kimani Omollo +254712345678 halijakamilika kwa sababu salio lako haitoshi.\nAkaunti yako ya Sarafu-Network ina salio ifuatayo: 47.00 SRF\n00. Nyuma\n99. Ondoka"}

View File

@@ -1,4 +1,5 @@
cic_base[full_graph]~=0.1.2a79
cic-eth~=0.11.0b7
cic_base[full_graph]~=0.1.2a68
cic-eth~=0.11.0b3
cic-notify~=0.4.0a4
cic-types~=0.1.0a10
pyresttest==1.7.1

View File

@@ -154,8 +154,8 @@ en:
00. Back
99. Exit
exit_insufficient_balance: |-
CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficent balance.
Your Sarafu-Network balances is: %{token_balance}
CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficient balance.
Your Sarafu-Network balances is: %{token_balance} %{token_symbol}
00. Back
99. Exit
help: |-

View File

@@ -155,7 +155,7 @@ sw:
99. Ondoka
exit_insufficient_balance: |-
CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi.
Akaunti yako ya Sarafu-Network ina salio ifuatayo: %{token_balance}
Akaunti yako ya Sarafu-Network ina salio ifuatayo: %{token_balance} %{token_symbol}
00. Nyuma
99. Ondoka
help: |-

View File

@@ -57,8 +57,8 @@ WORKDIR /home/grassroots
USER grassroots
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
ARG cic_base_version=0.1.2a79
ARG cic_eth_version=0.11.0b8+build.c2286e5c
ARG cic_base_version=0.1.2a77
ARG cic_eth_version=0.11.0b6
ARG sarafu_faucet_version=0.0.2a28
ARG sarafu_token_version==0.0.1a6
ARG cic_contracts_version=0.0.2a2

View File

@@ -3,7 +3,7 @@
set -a
CIC_CHAIN_SPEC=${CIC_CHAIN_SPEC:-evm:bloxberg:8995}
DEV_TOKEN_TYPE=${DEV_TOKEN_TYPE:-GFT}
DEV_TOKEN_TYPE=${DEV_TOKEN_TYPE:-giftable}
DEV_ETH_ACCOUNT_RESERVE_MINTER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_ETH_ACCOUNT_ACCOUNTS_INDEX_WRITER=${DEV_ETH_ACCOUNT_RESERVE_MINTER:-$DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER}
DEV_RESERVE_AMOUNT=${DEV_ETH_RESERVE_AMOUNT:-""10000000000000000000000000000000000}
@@ -19,8 +19,9 @@ if [ ! -z $DEV_ETH_GAS_PRICE ]; then
>&2 echo using static gas price $DEV_ETH_GAS_PRICE
fi
if [[ $DEV_TOKEN_TYPE != 'GFT' && $DEV_TOKEN_TYPE != 'SRF' ]]; then
>&2 echo DEV_TOKEN_TYPE must be one of [GFT,SRF], but was $DEV_TOKEN_TYPE
if [[ $DEV_TOKEN_TYPE != 'giftable' && $DEV_TOKEN_TYPE != 'sarafu' ]]; then
echo $DEV_TOKEN_TYPE
>&2 echo DEV_TOKEN_TYPE must be one of [giftable,sarafu]
exit 1
fi
@@ -60,7 +61,7 @@ if [[ -n "${ETH_PROVIDER}" ]]; then
./wait-for-it.sh "${ETH_PROVIDER_HOST}:${ETH_PROVIDER_PORT}"
fi
if [ $DEV_TOKEN_TYPE == 'GFT' ]; then
if [ $DEV_TOKEN_TYPE == 'giftable' ]; then
>&2 echo "deploying 'giftable token'"
DEV_RESERVE_ADDRESS=`giftable-token-deploy $gas_price_arg -p $ETH_PROVIDER -y $DEV_ETH_KEYSTORE_FILE -i $CIC_CHAIN_SPEC -vv -w --name "Giftable Token" --symbol "GFT" --decimals 6 -vv`
else

View File

@@ -13,12 +13,6 @@ DEV_PIP_EXTRA_INDEX_URL=${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomi
DEV_DATABASE_NAME_CIC_ETH=${DEV_DATABASE_NAME_CIC_ETH:-"cic-eth"}
CIC_DATA_DIR=${CIC_DATA_DIR:-/tmp/cic}
ETH_PASSPHRASE=''
DEV_TOKEN_TYPE=${DEV_TOKEN_TYPE:-giftable}
if [ $DEV_TOKEN_TYPE = 'giftable' ]; then
token_symbol='GFT'
else
token_symbol='SRF'
fi
# Debug flag
DEV_ETH_ACCOUNT_CONTRACT_DEPLOYER=0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C
@@ -39,11 +33,11 @@ set -a
# get required addresses from registries
DEV_TOKEN_INDEX_ADDRESS=`eth-contract-registry-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -r $CIC_REGISTRY_ADDRESS -f brief TokenRegistry`
DEV_ACCOUNT_INDEX_ADDRESS=`eth-contract-registry-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -r $CIC_REGISTRY_ADDRESS -f brief AccountRegistry`
DEV_RESERVE_ADDRESS=`eth-token-index-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_TOKEN_INDEX_ADDRESS -f brief $DEV_TOKEN_TYPE`
DEV_RESERVE_ADDRESS=`eth-token-index-list -i $CIC_CHAIN_SPEC -p $ETH_PROVIDER -a $DEV_TOKEN_INDEX_ADDRESS -f brief SRF`
cat <<EOF
Token registry: $DEV_TOKEN_INDEX_ADDRESS
Account reigstry: $DEV_ACCOUNT_INDEX_ADDRESS
Reserve address: $DEV_RESERVE_ADDRESS ($DEV_TOKEN_TYPE)
Reserve address: $DEV_RESERVE_ADDRESS
EOF
>&2 echo "create account for gas gifter"
@@ -52,7 +46,6 @@ DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_H
echo DEV_ETH_ACCOUNT_GAS_GIFTER=$DEV_ETH_ACCOUNT_GAS_GIFTER >> $env_out_file
cic-eth-tag -i $CIC_CHAIN_SPEC GAS_GIFTER $DEV_ETH_ACCOUNT_GAS_GIFTER
>&2 echo "create account for sarafu gifter"
DEV_ETH_ACCOUNT_SARAFU_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
echo DEV_ETH_ACCOUNT_SARAFU_GIFTER=$DEV_ETH_ACCOUNT_SARAFU_GIFTER >> $env_out_file
@@ -104,10 +97,5 @@ export DEV_ETH_SARAFU_TOKEN_ADDRESS=$DEV_ETH_RESERVE_ADDRESS
#echo -n 0 > $init_level_file
# Remove the SEND (8), QUEUE (16) and INIT (2) locks (or'ed), set by default at migration
cic-eth-ctl -i :: unlock INIT
cic-eth-ctl -i :: unlock SEND
cic-eth-ctl -i :: unlock QUEUE
set +a
set +e

View File

@@ -1 +0,0 @@
include *health*.sh

View File

@@ -1,10 +0,0 @@
docs:
mkdir -p doc/texinfo/html
makeinfo doc/texinfo/index.texi --html -o doc/texinfo/html/
markdown: doc
pandoc -f html -t markdown --standalone doc/texinfo/html/liveness.html -o README.md
.PHONY dist:
python setup.py sdist

View File

@@ -1,105 +0,0 @@
---
description: liveness (Untitled Document)
distribution: global
Generator: makeinfo
keywords: liveness (Untitled Document)
lang: en
resource-type: document
title: liveness (Untitled Document)
---
[]{#liveness}[]{#liveness-1}
## 1 liveness {#liveness .chapter}
[]{#ilveness_005foverview}[]{#Overview}
### 1.1 Overview {#overview .section}
This is a cluster-specific convenience setup for enabling a
Kubernetes-style liveness/readiness test as outlined in
<https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>.
Conceptually, it provides an application with means to:
- Run a collection of functions to validate sanity of the environment
- Set a no-error state before execution of the main routine
- Modify the error state during execution
- Invalidating all state when execution ends
[]{#Python-module}
### 1.2 Python module {#python-module .section}
Three python methods are provided.
[]{#load}
#### 1.2.1 load {#load .subsection}
This is meant to be called after configurations and environment has been
set up, but before the execution logic has commenced.
It receives a list of externally defined fully-qualified python modules.
Each of these modules must implement the method `health(*args,**kwargs)`
in its global namespace.
Any module returning `False` will cause a `RuntimeException`.
The component will not trap any other exception from the modules.
If successful, it will write the `pid` of the application to the
specified run data folder. By default this is `/run/<HOSTNAME>`, but the
path can be modified if desired.
[]{#set}
#### 1.2.2 set {#set .subsection}
This is meant to be called during the execution of the main program
routine begins.
[]{#at-startup}
#### 1.2.2.1 at startup {#at-startup .subsubsection}
It should be called once at the *start* of execution of the main program
routine.
For one-shot routines, this would mean the start of any code only run
when the module name is `__main__`.
For daemons, it would be just before handing over execution to the main
loop.
[]{#during-execution}
#### 1.2.2.2 during execution {#during-execution .subsubsection}
Call `set(error_code=<error>, ...` any time the health state temporarily
changes. Any `error` value other than `0` is considered an unhealthy
state.
[]{#at-shutdown}
#### 1.2.2.3 at shutdown {#at-shutdown .subsubsection}
Call `reset(...)`, which will indicate that the state is to be
considered the same as at startup.
[]{#shell}
### 1.3 shell {#shell .section}
A bash script is provided for *Kubernetes* to perform the health check.
It performs the following checks:
1. A numeric value exists in `<rundir>/<unitname>/pid`{.sample}.
2. The numeric value is a directory in `/proc`{.sample} (a valid pid)
3. The file `<rundir>/<unitname>/error`{.sample} contains \"0\"
If any of these checks fail should inditcate that the container is
unhealthy.
------------------------------------------------------------------------

View File

@@ -1,71 +0,0 @@
@node liveness
@chapter liveness
@anchor{ilveness_overview}
@section Overview
This is a cluster-specific convenience setup for enabling a Kubernetes-style liveness/readiness test as outlined in @url{https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/}.
Conceptually, it provides an application with means to:
@itemize
@item Run a collection of functions to validate sanity of the environment
@item Set a no-error state before execution of the main routine
@item Modify the error state during execution
@item Invalidating all state when execution ends
@end itemize
@section Python module
Three python methods are provided.
@subsection load
This is meant to be called after configurations and environment has been set up, but before the execution logic has commenced.
It receives a list of externally defined fully-qualified python modules. Each of these modules must implement the method @code{health(*args,**kwargs)} in its global namespace.
Any module returning @code{False} will cause a @code{RuntimeException}.
The component will not trap any other exception from the modules.
If successful, it will write the @code{pid} of the application to the specified run data folder. By default this is @code{/run/<HOSTNAME>}, but the path can be modified if desired.
@subsection set
This is meant to be called during the execution of the main program routine begins.
@subsubsection at startup
It should be called once at the @emph{start} of execution of the main program routine.
For one-shot routines, this would mean the start of any code only run when the module name is @code{__main__}.
For daemons, it would be just before handing over execution to the main loop.
@subsubsection during execution
Call @code{set(error_code=<error>, ...} any time the health state temporarily changes. Any @code{error} value other than @code{0} is considered an unhealthy state.
@subsubsection at shutdown
Call @code{reset(...)}, which will indicate that the state is to be considered the same as at startup.
@section shell
A bash script is provided for @emph{Kubernetes} to perform the health check.
It performs the following checks:
@enumerate
@item A numeric value exists in @file{<rundir>/<unitname>/pid}.
@item The numeric value is a directory in @file{/proc} (a valid pid)
@item The file @file{<rundir>/<unitname>/error} contains "0"
@end enumerate
If any of these checks fail should inditcate that the container is unhealthy.

View File

@@ -1,35 +0,0 @@
#!/bin/bash
rundir=${CIC_RUNDIR:-/run}
unit=${CIC_UNIT:-$HOSTNAME}
read p < $rundir/$unit/pid
if [ -z $p ]; then
>&2 echo unit $unit has no pid
exit 1
fi
if [ ! -d /proc/$p ]; then
>&2 echo unit $unit reports non-existent pid $p
exit 1
fi
>&2 echo unit $unit has pid $p
if [ ! -f $rundir/$unit/error ]; then
>&2 echo unit $unit has unspecified state
exit 1
fi
read e 2> /dev/null < $rundir/$unit/error
if [ -z $e ]; then
>&2 echo unit $unit has unspecified state
exit 1
fi
>&2 echo unit $unit has error $e
if [ $e -gt 0 ]; then
exit 1;
fi

View File

@@ -1,54 +0,0 @@
# standard imports
import importlib
import sys
import os
import logging
logg = logging.getLogger().getChild(__name__)
pid = os.getpid()
default_namespace = os.environ.get('LIVENESS_UNIT_NAME')
if default_namespace == None:
import socket
default_namespace = socket.gethostname()
def load(check_strs, namespace=default_namespace, rundir='/run', *args, **kwargs):
if namespace == None:
import socket
namespace = socket.gethostname()
logg.info('pid ' + str(pid))
checks = []
for m in check_strs:
logg.debug('added liveness check: {}'.format(str(m)))
module = importlib.import_module(m)
checks.append(module)
for check in checks:
r = check.health(*args, **kwargs)
if r == False:
raise RuntimeError('liveness check {} failed'.format(str(check)))
logg.info('liveness check passed: {}'.format(str(check)))
app_rundir = os.path.join(rundir, namespace)
os.makedirs(app_rundir, exist_ok=True) # should not already exist
f = open(os.path.join(app_rundir, 'pid'), 'w')
f.write(str(pid))
f.close()
def set(error=0, namespace=default_namespace, rundir='/run'):
app_rundir = os.path.join(rundir, namespace)
f = open(os.path.join(app_rundir, 'error'), 'w')
f.write(str(error))
f.close()
def reset(namespace=default_namespace, rundir='/run'):
app_rundir = os.path.join(rundir, namespace)
os.unlink(os.path.join(app_rundir, 'pid'))
os.unlink(os.path.join(app_rundir, 'error'))

View File

@@ -1,7 +0,0 @@
from setuptools import setup
setup(
name='liveness',
version='0.0.1a7',
packages=['liveness'],
include_package_data=True,
)

View File

@@ -1,17 +0,0 @@
#!/bin/bash
export CIC_RUNDIR=`realpath ./tests/testdata/run`
t=`mktemp -d -p $CIC_RUNDIR`
export CIC_UNIT=`basename $t`
>&2 echo test pid $$
echo $$ > $t/pid
echo 0 > $t/error
. health.sh
echo 1 > $t/error
#unlink $t/error
. health.sh
echo if error this is not printed

View File

@@ -1,8 +0,0 @@
a = ['foo']
kw = {
'bar': 42,
}
def health(*args, **kwargs):
args[0] == a[0]
kwargs['bar'] = kw['bar']

View File

@@ -1,2 +0,0 @@
def health(*args, **kwargs):
return False

View File

@@ -1,2 +0,0 @@
def health(*args, **kwargs):
return True

View File

@@ -1,129 +0,0 @@
# standard imports
import os
import unittest
import logging
import tempfile
import socket
# local imports
import liveness.linux
## test imports
import tests.imports
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
script_dir = os.path.realpath(os.path.dirname(__file__))
data_dir = os.path.join(script_dir, 'testdata')
run_base_dir = os.path.join(data_dir, 'run')
class TestImports(unittest.TestCase):
def setUp(self):
os.makedirs(run_base_dir, exist_ok=True)
self.run_dir = tempfile.mkdtemp(dir=run_base_dir)
self.unit = 'unittest'
self.unit_dir = os.path.join(self.run_dir, self.unit)
self.pid_path = os.path.join(self.unit_dir, 'pid')
self.error_path = os.path.join(self.unit_dir, 'error')
self.host_path = os.path.join(self.run_dir, socket.gethostname())
def test_no_import(self):
liveness.linux.load([], namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_hostname(self):
liveness.linux.load([], rundir=self.run_dir)
f = open(os.path.join(self.host_path, 'pid'), 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_import_single_true(self):
checks = ['tests.imports.import_true']
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_import_single_false(self):
checks = ['tests.imports.import_false']
with self.assertRaises(RuntimeError):
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.pid_path)
def test_import_false_then_true(self):
checks = ['tests.imports.import_false', 'tests.imports.import_true']
with self.assertRaises(RuntimeError):
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.pid_path)
def test_import_multiple_true(self):
checks = ['tests.imports.import_true', 'tests.imports.import_true']
liveness.linux.load(checks, namespace=self.unit, rundir=self.run_dir)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
def test_set(self):
liveness.linux.load([], namespace='unittest', rundir=self.run_dir)
liveness.linux.set(namespace='unittest', rundir=self.run_dir)
f = open(self.error_path, 'r')
r = f.read()
f.close()
self.assertEqual('0', r)
liveness.linux.set(error=42, namespace='unittest', rundir=self.run_dir)
f = open(self.error_path, 'r')
r = f.read()
f.close()
self.assertEqual('42', r)
liveness.linux.reset(namespace='unittest', rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(self.error_path)
def test_set_hostname(self):
liveness.linux.load([], rundir=self.run_dir)
liveness.linux.set(rundir=self.run_dir)
error_path = os.path.join(self.host_path, 'error')
f = open(error_path, 'r')
r = f.read()
f.close()
self.assertEqual('0', r)
liveness.linux.reset(rundir=self.run_dir)
with self.assertRaises(FileNotFoundError):
os.stat(error_path)
def test_args(self):
checks = ['tests.imports.import_args']
aargs=['foo']
kwaargs={'bar': 42}
liveness.linux.load(checks, self.unit, self.run_dir, *aargs, **kwaargs)
f = open(self.pid_path, 'r')
r = f.read()
f.close()
self.assertEqual(str(os.getpid()), r)
if __name__ == '__main__':
unittest.main()

View File

@@ -104,7 +104,7 @@ services:
DEV_PIP_EXTRA_INDEX_URL: ${DEV_PIP_EXTRA_INDEX_URL:-https://pip.grassrootseconomics.net:8433}
RUN_MASK: ${RUN_MASK:-0} # bit flags; 1: contract migrations 2: seed data
DEV_FAUCET_AMOUNT: ${DEV_FAUCET_AMOUNT:-0}
DEV_TOKEN_TYPE: ${DEV_TOKEN_TYPE:-GFT}
DEV_TOKEN_TYPE: ${DEV_TOKEN_TYPE:-giftable}
DEV_SARAFU_DEMURRAGE_LEVEL: ${DEV_SARAFU_DEMURRAGE_LEVEL:-196454828847045000000000000000000}
DEV_ETH_GAS_PRICE: ${DEV_ETH_GAS_PRICE:-1}
command: ["./run_job.sh"]