cic-eth: Rehabilitate retrier with chainqueue
This commit is contained in:
@@ -528,7 +528,7 @@ class AdminApi:
|
||||
r = self.rpc.do(o)
|
||||
tx['recipient_gas_balance'] = r
|
||||
|
||||
tx_unpacked = unpack(bytes.fromhex(tx['signed_tx'][2:]), chain_spec.chain_id())
|
||||
tx_unpacked = unpack(bytes.fromhex(strip_0x(tx['signed_tx'])), chain_spec)
|
||||
tx['gas_price'] = tx_unpacked['gasPrice']
|
||||
tx['gas_limit'] = tx_unpacked['gas']
|
||||
tx['data'] = tx_unpacked['data']
|
||||
|
||||
@@ -37,7 +37,7 @@ class Api:
|
||||
self.callback_param = callback_param
|
||||
self.callback_task = callback_task
|
||||
self.queue = queue
|
||||
logg.info('api using queue {}'.format(self.queue))
|
||||
logg.debug('api using queue {}'.format(self.queue))
|
||||
self.callback_success = None
|
||||
self.callback_error = None
|
||||
if callback_queue == None:
|
||||
|
||||
@@ -387,7 +387,7 @@ def resend_with_higher_gas(self, txold_hash_hex, chain_spec_dict, gas=None, defa
|
||||
r = rpc.do(o)
|
||||
current_gas_price = int(r, 16)
|
||||
if tx['gasPrice'] > current_gas_price:
|
||||
logg.info('Network gas price {} is lower than overdue tx gas price {}'.format(curent_gas_price, tx['gasPrice']))
|
||||
logg.info('Network gas price {} is lower than overdue tx gas price {}'.format(current_gas_price, tx['gasPrice']))
|
||||
#tx['gasPrice'] = int(tx['gasPrice'] * default_factor)
|
||||
new_gas_price = tx['gasPrice'] + 1
|
||||
else:
|
||||
|
||||
@@ -98,3 +98,12 @@ def get_state_log(chain_spec_dict, tx_hash):
|
||||
r = chainqueue.state.get_state_log(chain_spec, tx_hash, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
|
||||
@celery_app.task(base=CriticalSQLAlchemyTask)
|
||||
def obsolete(chain_spec_dict, tx_hash, final):
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
session = SessionBase.create_session()
|
||||
r = chainqueue.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
|
||||
session.close()
|
||||
return r
|
||||
|
||||
34
apps/cic-eth/cic_eth/runnable/daemons/filters/straggler.py
Normal file
34
apps/cic-eth/cic_eth/runnable/daemons/filters/straggler.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
from chainqueue.state import obsolete_by_cache
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
|
||||
class StragglerFilter:
|
||||
|
||||
def __init__(self, chain_spec, queue='cic-eth'):
|
||||
self.chain_spec = chain_spec
|
||||
self.queue = queue
|
||||
|
||||
|
||||
def filter(self, conn, block, tx, db_session=None):
|
||||
logg.debug('tx {}'.format(tx))
|
||||
obsolete_by_cache(self.chain_spec, tx.hash, False, session=db_session)
|
||||
s_send = celery.signature(
|
||||
'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
[
|
||||
tx.hash,
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
return s_send.apply_async()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'stragglerfilter'
|
||||
@@ -33,7 +33,7 @@ class TxFilter(SyncFilter):
|
||||
logg.info('tx filter match on {}'.format(otx.tx_hash))
|
||||
db_session.flush()
|
||||
SessionBase.release_session(db_session)
|
||||
s = celery.signature(
|
||||
s_final_state = celery.signature(
|
||||
'cic_eth.queue.state.set_final',
|
||||
[
|
||||
self.chain_spec.asdict(),
|
||||
@@ -43,7 +43,17 @@ class TxFilter(SyncFilter):
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
t = s.apply_async()
|
||||
s_obsolete_state = celery.signature(
|
||||
'cic_eth.queue.state.obsolete',
|
||||
[
|
||||
self.chain_spec.asdict(),
|
||||
add_0x(tx_hash_hex),
|
||||
True,
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
t = celery.group(s_obsolete_state, s_final_state)()
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
||||
@@ -4,38 +4,22 @@ import sys
|
||||
import logging
|
||||
import argparse
|
||||
import re
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
import confini
|
||||
import celery
|
||||
from cic_eth_registry import CICRegistry
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.tx import unpack
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainsyncer.driver import HeadSyncer
|
||||
from chainsyncer.backend import MemBackend
|
||||
from chainsyncer.error import NoBlockForYou
|
||||
from chainsyncer.filter import SyncFilter
|
||||
|
||||
# local imports
|
||||
from cic_eth.db import dsn_from_config
|
||||
from cic_eth.db import SessionBase
|
||||
from cic_eth.queue.tx import (
|
||||
get_status_tx,
|
||||
get_tx,
|
||||
# get_upcoming_tx,
|
||||
)
|
||||
from cic_eth.admin.ctrl import lock_send
|
||||
from cic_eth.db.enum import (
|
||||
StatusEnum,
|
||||
StatusBits,
|
||||
LockEnum,
|
||||
)
|
||||
from cic_eth.db.enum import LockEnum
|
||||
from cic_eth.runnable.daemons.filters.straggler import StragglerFilter
|
||||
from cic_eth.sync.retry import RetrySyncer
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
@@ -89,189 +73,54 @@ SessionBase.connect(dsn, debug=config.true('DATABASE_DEBUG'))
|
||||
|
||||
straggler_delay = int(config.get('CIC_TX_RETRY_DELAY'))
|
||||
|
||||
# TODO: we already have the signed raw tx in get, so its a waste of cycles to get_tx here
|
||||
def sendfail_filter(w3, tx_hash, rcpt, chain_spec):
|
||||
tx_dict = get_tx(tx_hash)
|
||||
tx = unpack(tx_dict['signed_tx'], chain_spec)
|
||||
logg.debug('submitting tx {} for retry'.format(tx_hash))
|
||||
s_check = celery.signature(
|
||||
'cic_eth.admin.ctrl.check_lock',
|
||||
## TODO: we already have the signed raw tx in get, so its a waste of cycles to get_tx here
|
||||
#def sendfail_filter(w3, tx_hash, rcpt, chain_spec):
|
||||
# tx_dict = get_tx(tx_hash)
|
||||
# tx = unpack(tx_dict['signed_tx'], chain_spec)
|
||||
# logg.debug('submitting tx {} for retry'.format(tx_hash))
|
||||
# s_check = celery.signature(
|
||||
# 'cic_eth.admin.ctrl.check_lock',
|
||||
[
|
||||
tx_hash,
|
||||
chain_str,
|
||||
LockEnum.QUEUE,
|
||||
tx['from'],
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
# s_resume = celery.signature(
|
||||
# 'cic_eth.eth.tx.resume_tx',
|
||||
# tx_hash,
|
||||
# chain_str,
|
||||
# LockEnum.QUEUE,
|
||||
# tx['from'],
|
||||
# ],
|
||||
# queue=queue,
|
||||
# )
|
||||
## s_resume = celery.signature(
|
||||
## 'cic_eth.eth.tx.resume_tx',
|
||||
## [
|
||||
## chain_str,
|
||||
## ],
|
||||
## queue=queue,
|
||||
## )
|
||||
#
|
||||
## s_retry_status = celery.signature(
|
||||
## 'cic_eth.queue.state.set_ready',
|
||||
## [],
|
||||
## queue=queue,
|
||||
## )
|
||||
# s_resend = celery.signature(
|
||||
# 'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
# [
|
||||
# chain_str,
|
||||
# ],
|
||||
# queue=queue,
|
||||
# )
|
||||
|
||||
# s_retry_status = celery.signature(
|
||||
# 'cic_eth.queue.state.set_ready',
|
||||
# [],
|
||||
# queue=queue,
|
||||
# )
|
||||
s_resend = celery.signature(
|
||||
'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
[
|
||||
chain_str,
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
|
||||
#s_resume.link(s_retry_status)
|
||||
#s_check.link(s_resume)
|
||||
s_check.link(s_resend)
|
||||
s_check.apply_async()
|
||||
|
||||
|
||||
# TODO: can we merely use the dispatcher instead?
|
||||
def dispatch(conn, chain_spec):
|
||||
txs = get_status_tx(StatusEnum.RETRY, before=datetime.datetime.utcnow())
|
||||
if len(txs) == 0:
|
||||
logg.debug('no retry state txs found')
|
||||
return
|
||||
#signed_txs = list(txs.values())
|
||||
#logg.debug('signed txs {} chain {}'.format(signed_txs, chain_str))
|
||||
#for tx in signed_txs:
|
||||
for k in txs.keys():
|
||||
#tx_cache = get_tx_cache(k)
|
||||
tx_raw = txs[k]
|
||||
tx = unpack(tx_raw, chain_spec)
|
||||
|
||||
s_check = celery.signature(
|
||||
'cic_eth.admin.ctrl.check_lock',
|
||||
[
|
||||
[tx_raw],
|
||||
chain_str,
|
||||
LockEnum.QUEUE,
|
||||
tx['from'],
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
s_send = celery.signature(
|
||||
'cic_eth.eth.tx.send',
|
||||
[
|
||||
chain_str,
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
s_check.link(s_send)
|
||||
t = s_check.apply_async()
|
||||
|
||||
# try:
|
||||
# r = t.get()
|
||||
# logg.debug('submitted as {} result {} with queue task {}'.format(t, r, t.children[0].get()))
|
||||
# except PermanentTxError as e:
|
||||
# logg.error('tx {} permanently failed: {}'.format(tx, e))
|
||||
# except TemporaryTxError as e:
|
||||
# logg.error('tx {} temporarily failed: {}'.format(tx, e))
|
||||
|
||||
#
|
||||
#
|
||||
#def straggler_filter(w3, tx, rcpt, chain_str):
|
||||
# before = datetime.datetime.utcnow() - datetime.timedelta(seconds=straggler_delay)
|
||||
# txs = get_status_tx(StatusEnum.SENT, before)
|
||||
# if len(txs) == 0:
|
||||
# logg.debug('no straggler txs found')
|
||||
# return
|
||||
# txs = list(txs.keys())
|
||||
# logg.debug('straggler txs {} chain {}'.format(signed_txs, chain_str))
|
||||
# s_send = celery.signature(
|
||||
# 'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
# [
|
||||
# txs,
|
||||
# chain_str,
|
||||
# ],
|
||||
# queue=queue,
|
||||
# )
|
||||
# s_send.apply_async()
|
||||
|
||||
|
||||
class StragglerFilter:
|
||||
|
||||
def __init__(self, chain_spec, queue='cic-eth'):
|
||||
self.chain_spec = chain_spec
|
||||
self.queue = queue
|
||||
|
||||
|
||||
def filter(self, conn, block, tx, db_session=None):
|
||||
logg.debug('tx {}'.format(tx))
|
||||
s_send = celery.signature(
|
||||
'cic_eth.eth.gas.resend_with_higher_gas',
|
||||
[
|
||||
tx,
|
||||
self.chain_spec.asdict(),
|
||||
],
|
||||
queue=self.queue,
|
||||
)
|
||||
return s_send.apply_async()
|
||||
#return s_send
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'stragglerfilter'
|
||||
|
||||
|
||||
class RetrySyncer(HeadSyncer):
|
||||
|
||||
def __init__(self, conn, chain_spec, stalled_grace_seconds, batch_size=50, failed_grace_seconds=None):
|
||||
backend = MemBackend(chain_spec, None)
|
||||
super(RetrySyncer, self).__init__(backend)
|
||||
self.chain_spec = chain_spec
|
||||
if failed_grace_seconds == None:
|
||||
failed_grace_seconds = stalled_grace_seconds
|
||||
self.stalled_grace_seconds = stalled_grace_seconds
|
||||
self.failed_grace_seconds = failed_grace_seconds
|
||||
self.batch_size = batch_size
|
||||
self.conn = conn
|
||||
|
||||
|
||||
def get(self, conn):
|
||||
o = block_latest()
|
||||
r = conn.do(o)
|
||||
(pair, flags) = self.backend.get()
|
||||
n = int(r, 16)
|
||||
if n == pair[0]:
|
||||
raise NoBlockForYou('block {} already checked'.format(n))
|
||||
o = block_by_number(n)
|
||||
r = conn.do(o)
|
||||
b = Block(r)
|
||||
return b
|
||||
|
||||
|
||||
def process(self, conn, block):
|
||||
before = datetime.datetime.utcnow() - datetime.timedelta(seconds=self.stalled_grace_seconds)
|
||||
stalled_txs = get_status_tx(
|
||||
StatusBits.IN_NETWORK.value,
|
||||
not_status=StatusBits.FINAL | StatusBits.MANUAL | StatusBits.OBSOLETE,
|
||||
before=before,
|
||||
limit=self.batch_size,
|
||||
)
|
||||
# stalled_txs = get_upcoming_tx(
|
||||
# status=StatusBits.IN_NETWORK.value,
|
||||
# not_status=StatusBits.FINAL | StatusBits.MANUAL | StatusBits.OBSOLETE,
|
||||
# before=before,
|
||||
# limit=self.batch_size,
|
||||
# )
|
||||
for tx in stalled_txs:
|
||||
self.filter.apply(self.conn, block, tx)
|
||||
self.backend.set(block.number, 0)
|
||||
# #s_resume.link(s_retry_status)
|
||||
# #s_check.link(s_resume)
|
||||
# s_check.link(s_resend)
|
||||
# s_check.apply_async()
|
||||
|
||||
|
||||
def main():
|
||||
#o = block_latest()
|
||||
conn = RPCConnection.connect(chain_spec, 'default')
|
||||
#block = conn.do(o)
|
||||
syncer = RetrySyncer(conn, chain_spec, straggler_delay, batch_size=config.get('_BATCH_SIZE'))
|
||||
syncer.backend.set(0, 0)
|
||||
syncer.add_filter(StragglerFilter(chain_spec, queue=queue))
|
||||
fltr = StragglerFilter(chain_spec, queue=queue)
|
||||
syncer.add_filter(fltr)
|
||||
syncer.loop(float(straggler_delay), conn)
|
||||
|
||||
|
||||
|
||||
92
apps/cic-eth/cic_eth/sync/retry.py
Normal file
92
apps/cic-eth/cic_eth/sync/retry.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from chainsyncer.driver import HeadSyncer
|
||||
from chainsyncer.backend import MemBackend
|
||||
from chainsyncer.error import NoBlockForYou
|
||||
from chainlib.eth.block import (
|
||||
block_by_number,
|
||||
block_latest,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.tx import (
|
||||
unpack,
|
||||
Tx,
|
||||
)
|
||||
from cic_eth.queue.query import get_status_tx
|
||||
from chainqueue.db.enum import StatusBits
|
||||
from hexathon import strip_0x
|
||||
|
||||
# local imports
|
||||
from cic_eth.db import SessionBase
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class DbSessionMemBackend(MemBackend):
|
||||
|
||||
def connect(self):
|
||||
self.db_session = SessionBase.create_session()
|
||||
return self.db_session
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
self.db_session.close()
|
||||
self.db_session = None
|
||||
|
||||
|
||||
class RetrySyncer(HeadSyncer):
|
||||
|
||||
def __init__(self, conn, chain_spec, stalled_grace_seconds, batch_size=50, failed_grace_seconds=None):
|
||||
backend = DbSessionMemBackend(chain_spec, None)
|
||||
super(RetrySyncer, self).__init__(backend)
|
||||
self.chain_spec = chain_spec
|
||||
if failed_grace_seconds == None:
|
||||
failed_grace_seconds = stalled_grace_seconds
|
||||
self.stalled_grace_seconds = stalled_grace_seconds
|
||||
self.failed_grace_seconds = failed_grace_seconds
|
||||
self.batch_size = batch_size
|
||||
self.conn = conn
|
||||
|
||||
|
||||
def get(self, conn):
|
||||
o = block_latest()
|
||||
r = conn.do(o)
|
||||
(pair, flags) = self.backend.get()
|
||||
n = int(r, 16)
|
||||
if n == pair[0]:
|
||||
raise NoBlockForYou('block {} already checked'.format(n))
|
||||
o = block_by_number(n)
|
||||
r = conn.do(o)
|
||||
b = Block(r)
|
||||
return b
|
||||
|
||||
|
||||
def process(self, conn, block):
|
||||
before = datetime.datetime.utcnow() - datetime.timedelta(seconds=self.stalled_grace_seconds)
|
||||
session = SessionBase.create_session()
|
||||
stalled_txs = get_status_tx(
|
||||
self.chain_spec,
|
||||
StatusBits.IN_NETWORK.value,
|
||||
not_status=StatusBits.FINAL | StatusBits.MANUAL | StatusBits.OBSOLETE,
|
||||
before=before,
|
||||
limit=self.batch_size,
|
||||
session=session,
|
||||
)
|
||||
session.close()
|
||||
# stalled_txs = get_upcoming_tx(
|
||||
# status=StatusBits.IN_NETWORK.value,
|
||||
# not_status=StatusBits.FINAL | StatusBits.MANUAL | StatusBits.OBSOLETE,
|
||||
# before=before,
|
||||
# limit=self.batch_size,
|
||||
# )
|
||||
for tx_signed_raw_hex in stalled_txs.values():
|
||||
tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
|
||||
tx_src = unpack(tx_signed_raw_bytes, self.chain_spec)
|
||||
tx = Tx(tx_src)
|
||||
self.filter.apply(self.conn, block, tx)
|
||||
self.backend.set(block.number, 0)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ version = (
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
'alpha.3',
|
||||
'alpha.4',
|
||||
)
|
||||
|
||||
version_object = semver.VersionInfo(
|
||||
|
||||
Reference in New Issue
Block a user