cic-stack/apps/cic-eth/cic_eth/admin/nonce.py

129 lines
3.8 KiB
Python

# standard imports
import logging
# third-party imports
import celery
from cic_registry.chain import ChainSpec
# local imports
from cic_eth.db.models.base import SessionBase
from cic_eth.db.models.otx import Otx
from cic_eth.db.models.tx import TxCache
from cic_eth.db.models.nonce import Nonce
from cic_eth.admin.ctrl import lock_send
from cic_eth.admin.ctrl import unlock_send
from cic_eth.admin.ctrl import lock_queue
from cic_eth.admin.ctrl import unlock_queue
from cic_eth.queue.tx import get_tx
from cic_eth.queue.tx import set_cancel
from cic_eth.queue.tx import create as queue_create
from cic_eth.eth.util import unpack_signed_raw_tx
from cic_eth.eth.task import sign_tx
from cic_eth.eth.task import create_check_gas_and_send_task
celery_app = celery.current_app
logg = logging.getLogger()
@celery_app.task(bind=True)
def shift_nonce(self, chain_str, tx_hash_orig_hex, delta=1):
"""Shift all transactions with nonces higher than the offset by the provided position delta.
Transactions who are replaced by transactions that move nonces will be marked as OVERRIDDEN.
:param chainstr: Chain specification string representation
:type chainstr: str
:param tx_hash_orig_hex: Transaction hash to resolve to sender and nonce to use as shift offset
:type tx_hash_orig_hex: str, 0x-hex
:param delta: Amount
"""
queue = None
try:
queue = self.request.delivery_info.get('routing_key')
except AttributeError:
pass
chain_spec = ChainSpec.from_chain_str(chain_str)
tx_brief = get_tx(tx_hash_orig_hex)
tx_raw = bytes.fromhex(tx_brief['signed_tx'][2:])
tx = unpack_signed_raw_tx(tx_raw, chain_spec.chain_id())
nonce = tx_brief['nonce']
address = tx['from']
logg.debug('shifting nonce {} position(s) for address {}, offset {}'.format(delta, address, nonce))
lock_queue(None, chain_str, address)
lock_send(None, chain_str, address)
session = SessionBase.create_session()
q = session.query(Otx)
q = q.join(TxCache)
q = q.filter(TxCache.sender==address)
q = q.filter(Otx.nonce>=nonce+delta)
q = q.order_by(Otx.nonce.asc())
otxs = q.all()
tx_hashes = []
txs = []
for otx in otxs:
tx_raw = bytes.fromhex(otx.signed_tx[2:])
tx_new = unpack_signed_raw_tx(tx_raw, chain_spec.chain_id())
tx_previous_hash_hex = tx_new['hash']
tx_previous_nonce = tx_new['nonce']
del(tx_new['hash'])
del(tx_new['hash_unsigned'])
tx_new['nonce'] -= delta
(tx_hash_hex, tx_signed_raw_hex) = sign_tx(tx_new, chain_str)
logg.debug('tx {} -> {} nonce {} -> {}'.format(tx_previous_hash_hex, tx_hash_hex, tx_previous_nonce, tx_new['nonce']))
otx = Otx(
nonce=tx_new['nonce'],
address=tx_new['from'],
tx_hash=tx_hash_hex,
signed_tx=tx_signed_raw_hex,
)
session.add(otx)
session.commit()
# TODO: cancel all first, then replace. Otherwise we risk two non-locked states for two different nonces.
set_cancel(tx_previous_hash_hex, True)
TxCache.clone(tx_previous_hash_hex, tx_hash_hex)
tx_hashes.append(tx_hash_hex)
txs.append(tx_signed_raw_hex)
session.close()
s = create_check_gas_and_send_task(
txs,
chain_str,
tx_new['from'],
tx_new['gas'],
tx_hashes,
queue,
)
s_unlock_send = celery.signature(
'cic_eth.admin.ctrl.unlock_send',
[
chain_str,
tx_new['from'],
],
queue=queue,
)
s_unlock_direct = celery.signature(
'cic_eth.admin.ctrl.unlock_queue',
[
chain_str,
tx_new['from'],
],
queue=queue,
)
s_unlocks = celery.group(s_unlock_send, s_unlock_direct)
s.link(s_unlocks)
s.apply_async()