Merge branch 'lash/cic-eth-status-enum-bitfield' into 'master'
Translate StatusEnum to flags instead of number ranges See merge request grassrootseconomics/cic-internal-integration!14
This commit is contained in:
commit
d872e78e39
@ -16,7 +16,10 @@ from cic_eth.db.models.role import AccountRole
|
|||||||
from cic_eth.db.models.otx import Otx
|
from cic_eth.db.models.otx import Otx
|
||||||
from cic_eth.db.models.tx import TxCache
|
from cic_eth.db.models.tx import TxCache
|
||||||
from cic_eth.db.models.nonce import Nonce
|
from cic_eth.db.models.nonce import Nonce
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
is_alive,
|
||||||
|
)
|
||||||
from cic_eth.error import InitializationError
|
from cic_eth.error import InitializationError
|
||||||
from cic_eth.db.error import TxStateChangeError
|
from cic_eth.db.error import TxStateChangeError
|
||||||
from cic_eth.eth.rpc import RpcClient
|
from cic_eth.eth.rpc import RpcClient
|
||||||
@ -110,24 +113,32 @@ class AdminApi:
|
|||||||
|
|
||||||
# TODO: This check should most likely be in resend task itself
|
# TODO: This check should most likely be in resend task itself
|
||||||
tx_dict = s_get_tx_cache.apply_async().get()
|
tx_dict = s_get_tx_cache.apply_async().get()
|
||||||
if tx_dict['status'] in [StatusEnum.REVERTED, StatusEnum.SUCCESS, StatusEnum.CANCELLED, StatusEnum.OBSOLETED]:
|
#if tx_dict['status'] in [StatusEnum.REVERTED, StatusEnum.SUCCESS, StatusEnum.CANCELLED, StatusEnum.OBSOLETED]:
|
||||||
|
if not is_alive(getattr(StatusEnum, tx_dict['status']).value):
|
||||||
raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex))
|
raise TxStateChangeError('Cannot resend mined or obsoleted transaction'.format(txold_hash_hex))
|
||||||
|
|
||||||
s = None
|
if not in_place:
|
||||||
if in_place:
|
|
||||||
s = celery.signature(
|
|
||||||
'cic_eth.eth.tx.resend_with_higher_gas',
|
|
||||||
[
|
|
||||||
tx_hash_hex,
|
|
||||||
chain_str,
|
|
||||||
None,
|
|
||||||
1.01,
|
|
||||||
],
|
|
||||||
queue=self.queue,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('resend as new not yet implemented')
|
raise NotImplementedError('resend as new not yet implemented')
|
||||||
|
|
||||||
|
s = celery.signature(
|
||||||
|
'cic_eth.eth.tx.resend_with_higher_gas',
|
||||||
|
[
|
||||||
|
chain_str,
|
||||||
|
None,
|
||||||
|
1.01,
|
||||||
|
],
|
||||||
|
queue=self.queue,
|
||||||
|
)
|
||||||
|
|
||||||
|
s_manual = celery.signature(
|
||||||
|
'cic_eth.queue.tx.set_manual',
|
||||||
|
[
|
||||||
|
tx_hash_hex,
|
||||||
|
],
|
||||||
|
queue=self.queue,
|
||||||
|
)
|
||||||
|
s_manual.link(s)
|
||||||
|
|
||||||
if unlock:
|
if unlock:
|
||||||
s_gas = celery.signature(
|
s_gas = celery.signature(
|
||||||
'cic_eth.admin.ctrl.unlock_send',
|
'cic_eth.admin.ctrl.unlock_send',
|
||||||
@ -139,7 +150,7 @@ class AdminApi:
|
|||||||
)
|
)
|
||||||
s.link(s_gas)
|
s.link(s_gas)
|
||||||
|
|
||||||
return s.apply_async()
|
return s_manual.apply_async()
|
||||||
|
|
||||||
def check_nonce(self, address):
|
def check_nonce(self, address):
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
|
@ -1,6 +1,26 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
|
class StatusBits(enum.IntEnum):
|
||||||
|
QUEUED = 0x01
|
||||||
|
IN_NETWORK = 0x08
|
||||||
|
|
||||||
|
DEFERRED = 0x10
|
||||||
|
GAS_ISSUES = 0x20
|
||||||
|
|
||||||
|
LOCAL_ERROR = 0x100
|
||||||
|
NODE_ERROR = 0x200
|
||||||
|
NETWORK_ERROR = 0x400
|
||||||
|
UNKNOWN_ERROR = 0x800
|
||||||
|
|
||||||
|
FINAL = 0x1000
|
||||||
|
OBSOLETE = 0x2000
|
||||||
|
MANUAL = 0x8000
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
class StatusEnum(enum.IntEnum):
|
class StatusEnum(enum.IntEnum):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -22,21 +42,27 @@ class StatusEnum(enum.IntEnum):
|
|||||||
* SUCCESS: THe transaction was successfully mined. (Block number will be set)
|
* SUCCESS: THe transaction was successfully mined. (Block number will be set)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
PENDING=-9
|
PENDING = 0
|
||||||
SENDFAIL=-8
|
|
||||||
RETRY=-7
|
SENDFAIL = StatusBits.DEFERRED | StatusBits.LOCAL_ERROR
|
||||||
READYSEND=-6
|
RETRY = StatusBits.QUEUED | StatusBits.DEFERRED
|
||||||
OBSOLETED=-2
|
READYSEND = StatusBits.QUEUED
|
||||||
WAITFORGAS=-1
|
|
||||||
SENT=0
|
OBSOLETED = StatusBits.OBSOLETE | StatusBits.IN_NETWORK
|
||||||
FUBAR=1
|
|
||||||
CANCELLED=2
|
WAITFORGAS = StatusBits.GAS_ISSUES
|
||||||
OVERRIDDEN=3
|
|
||||||
REJECTED=7
|
SENT = StatusBits.IN_NETWORK
|
||||||
REVERTED=8
|
FUBAR = StatusBits.FINAL | StatusBits.UNKNOWN_ERROR
|
||||||
SUCCESS=9
|
CANCELLED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.OBSOLETE
|
||||||
|
OVERRIDDEN = StatusBits.FINAL | StatusBits.OBSOLETE | StatusBits.MANUAL
|
||||||
|
|
||||||
|
REJECTED = StatusBits.NODE_ERROR | StatusBits.FINAL
|
||||||
|
REVERTED = StatusBits.IN_NETWORK | StatusBits.FINAL | StatusBits.NETWORK_ERROR
|
||||||
|
SUCCESS = StatusBits.IN_NETWORK | StatusBits.FINAL
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
class LockEnum(enum.IntEnum):
|
class LockEnum(enum.IntEnum):
|
||||||
"""
|
"""
|
||||||
STICKY: When set, reset is not possible
|
STICKY: When set, reset is not possible
|
||||||
@ -48,4 +74,40 @@ class LockEnum(enum.IntEnum):
|
|||||||
CREATE=2
|
CREATE=2
|
||||||
SEND=4
|
SEND=4
|
||||||
QUEUE=8
|
QUEUE=8
|
||||||
|
QUERY=16
|
||||||
ALL=int(0xfffffffffffffffe)
|
ALL=int(0xfffffffffffffffe)
|
||||||
|
|
||||||
|
|
||||||
|
def status_str(v, bits_only=False):
|
||||||
|
s = ''
|
||||||
|
if not bits_only:
|
||||||
|
try:
|
||||||
|
s = StatusEnum(v).name
|
||||||
|
return s
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
b = (1 << i)
|
||||||
|
if (b & 0xffff) & v:
|
||||||
|
n = StatusBits(b).name
|
||||||
|
if len(s) > 0:
|
||||||
|
s += ','
|
||||||
|
s += n
|
||||||
|
if not bits_only:
|
||||||
|
s += '*'
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def all_errors():
|
||||||
|
return StatusBits.LOCAL_ERROR | StatusBits.NODE_ERROR | StatusBits.NETWORK_ERROR | StatusBits.UNKNOWN_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
def is_error_status(v):
|
||||||
|
return bool(v & all_errors())
|
||||||
|
|
||||||
|
|
||||||
|
def is_alive(v):
|
||||||
|
return bool(v & (StatusBits.FINAL | StatusBits.OBSOLETE) == 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
# stanard imports
|
||||||
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# third-party imports
|
||||||
from sqlalchemy import Column, Integer
|
from sqlalchemy import Column, Integer
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
Model = declarative_base(name='Model')
|
Model = declarative_base(name='Model')
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +26,11 @@ class SessionBase(Model):
|
|||||||
transactional = True
|
transactional = True
|
||||||
"""Whether the database backend supports query transactions. Should be explicitly set by initialization code"""
|
"""Whether the database backend supports query transactions. Should be explicitly set by initialization code"""
|
||||||
poolable = True
|
poolable = True
|
||||||
"""Whether the database backend supports query transactions. Should be explicitly set by initialization code"""
|
"""Whether the database backend supports connection pools. Should be explicitly set by initialization code"""
|
||||||
|
procedural = True
|
||||||
|
"""Whether the database backend supports stored procedures"""
|
||||||
|
localsessions = {}
|
||||||
|
"""Contains dictionary of sessions initiated by db model components"""
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -71,3 +80,23 @@ class SessionBase(Model):
|
|||||||
"""
|
"""
|
||||||
SessionBase.engine.dispose()
|
SessionBase.engine.dispose()
|
||||||
SessionBase.engine = None
|
SessionBase.engine = None
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bind_session(session=None):
|
||||||
|
localsession = session
|
||||||
|
if localsession == None:
|
||||||
|
localsession = SessionBase.create_session()
|
||||||
|
localsession_key = str(id(localsession))
|
||||||
|
logg.debug('creating new session {}'.format(localsession_key))
|
||||||
|
SessionBase.localsessions[localsession_key] = localsession
|
||||||
|
return localsession
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def release_session(session=None):
|
||||||
|
session_key = str(id(session))
|
||||||
|
if SessionBase.localsessions.get(session_key) != None:
|
||||||
|
logg.debug('destroying session {}'.format(session_key))
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
@ -8,7 +8,12 @@ from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
|
|||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from .base import SessionBase
|
from .base import SessionBase
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
status_str,
|
||||||
|
is_error_status,
|
||||||
|
)
|
||||||
from cic_eth.db.error import TxStateChangeError
|
from cic_eth.db.error import TxStateChangeError
|
||||||
#from cic_eth.eth.util import address_hex_from_signed_tx
|
#from cic_eth.eth.util import address_hex_from_signed_tx
|
||||||
|
|
||||||
@ -54,21 +59,24 @@ class Otx(SessionBase):
|
|||||||
block = Column(Integer)
|
block = Column(Integer)
|
||||||
|
|
||||||
|
|
||||||
def __set_status(self, status, session=None):
|
def __set_status(self, status, session):
|
||||||
localsession = session
|
self.status |= status
|
||||||
if localsession == None:
|
session.add(self)
|
||||||
localsession = SessionBase.create_session()
|
session.flush()
|
||||||
|
|
||||||
self.status = status
|
|
||||||
localsession.add(self)
|
|
||||||
localsession.flush()
|
|
||||||
|
|
||||||
if self.tracing:
|
def __reset_status(self, status, session):
|
||||||
self.__state_log(session=localsession)
|
status_edit = ~status & self.status
|
||||||
|
self.status &= status_edit
|
||||||
|
session.add(self)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
|
||||||
if session==None:
|
def __status_already_set(self, status):
|
||||||
localsession.commit()
|
r = bool(self.status & status)
|
||||||
localsession.close()
|
if r:
|
||||||
|
logg.warning('status bit {} already set on {}'.format(status.name, self.tx_hash))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def set_block(self, block, session=None):
|
def set_block(self, block, session=None):
|
||||||
@ -102,9 +110,23 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
if self.status >= StatusEnum.SENT.value:
|
if self.__status_already_set(StatusBits.GAS_ISSUES):
|
||||||
raise TxStateChangeError('WAITFORGAS cannot succeed final state, had {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.WAITFORGAS, session)
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('GAS_ISSUES cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('GAS_ISSUES cannot be set on an entry with IN_NETWORK state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.GAS_ISSUES, session)
|
||||||
|
self.__reset_status(StatusBits.QUEUED | StatusBits.DEFERRED, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def fubar(self, session=None):
|
def fubar(self, session=None):
|
||||||
@ -112,28 +134,89 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
Only manipulates object, does not transaction or commit to backend.
|
Only manipulates object, does not transaction or commit to backend.
|
||||||
"""
|
"""
|
||||||
self.__set_status(StatusEnum.FUBAR, session)
|
if self.__status_already_set(StatusBits.UNKNOWN_ERROR):
|
||||||
|
return
|
||||||
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('FUBAR cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if is_error_status(self.status):
|
||||||
|
raise TxStateChangeError('FUBAR cannot be set on an entry with an error state already set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.UNKNOWN_ERROR | StatusBits.FINAL, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def reject(self, session=None):
|
def reject(self, session=None):
|
||||||
"""Marks transaction as "rejected," which means the node rejected sending the transaction to the network. The nonce has not been spent, and the transaction should be replaced.
|
"""Marks transaction as "rejected," which means the node rejected sending the transaction to the network. The nonce has not been spent, and the transaction should be replaced.
|
||||||
|
|
||||||
Only manipulates object, does not transaction or commit to backend.
|
Only manipulates object, does not transaction or commit to backend.
|
||||||
"""
|
"""
|
||||||
if self.status >= StatusEnum.SENT.value:
|
if self.__status_already_set(StatusBits.NODE_ERROR):
|
||||||
raise TxStateChangeError('REJECTED cannot succeed SENT or final state, had {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.REJECTED, session)
|
|
||||||
|
|
||||||
|
|
||||||
def override(self, session=None):
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('REJECTED cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('REJECTED cannot be set on an entry already IN_NETWORK ({})'.format(status_str(self.status)))
|
||||||
|
if is_error_status(self.status):
|
||||||
|
raise TxStateChangeError('REJECTED cannot be set on an entry with an error state already set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.NODE_ERROR | StatusBits.FINAL, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
def override(self, manual=False, session=None):
|
||||||
"""Marks transaction as manually overridden.
|
"""Marks transaction as manually overridden.
|
||||||
|
|
||||||
Only manipulates object, does not transaction or commit to backend.
|
Only manipulates object, does not transaction or commit to backend.
|
||||||
"""
|
"""
|
||||||
if self.status >= StatusEnum.SENT.value:
|
|
||||||
raise TxStateChangeError('OVERRIDDEN cannot succeed SENT or final state, had {}'.format(StatusEnum(self.status).name))
|
|
||||||
self.__set_status(StatusEnum.OVERRIDDEN, session)
|
|
||||||
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('OVERRIDDEN/OBSOLETED cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('OVERRIDDEN/OBSOLETED cannot be set on an entry already IN_NETWORK ({})'.format(status_str(self.status)))
|
||||||
|
if self.status & StatusBits.OBSOLETE:
|
||||||
|
raise TxStateChangeError('OVERRIDDEN/OBSOLETED cannot be set on an entry already OBSOLETE ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.OBSOLETE, session)
|
||||||
|
#if manual:
|
||||||
|
# self.__set_status(StatusBits.MANUAL, session)
|
||||||
|
self.__reset_status(StatusBits.QUEUED | StatusBits.IN_NETWORK, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
def manual(self, session=None):
|
||||||
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('OVERRIDDEN/OBSOLETED cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.MANUAL, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
def retry(self, session=None):
|
def retry(self, session=None):
|
||||||
"""Marks transaction as ready to retry after a timeout following a sendfail or a completed gas funding.
|
"""Marks transaction as ready to retry after a timeout following a sendfail or a completed gas funding.
|
||||||
@ -142,9 +225,23 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
if self.status != StatusEnum.SENT.value and self.status != StatusEnum.SENDFAIL.value:
|
if self.__status_already_set(StatusBits.QUEUED):
|
||||||
raise TxStateChangeError('RETRY must follow SENT or SENDFAIL, but had {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.RETRY, session)
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('RETRY cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if not is_error_status(self.status) and not StatusBits.IN_NETWORK & self.status > 0:
|
||||||
|
raise TxStateChangeError('RETRY cannot be set on an entry that has no error ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.QUEUED, session)
|
||||||
|
self.__reset_status(StatusBits.GAS_ISSUES, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def readysend(self, session=None):
|
def readysend(self, session=None):
|
||||||
@ -154,9 +251,23 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
if self.status != StatusEnum.PENDING.value and self.status != StatusEnum.WAITFORGAS.value:
|
if self.__status_already_set(StatusBits.QUEUED):
|
||||||
raise TxStateChangeError('READYSEND must follow PENDING or WAITFORGAS, but had {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.READYSEND, session)
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('READYSEND cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if is_error_status(self.status):
|
||||||
|
raise TxStateChangeError('READYSEND cannot be set on an errored state ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.QUEUED, session)
|
||||||
|
self.__reset_status(StatusBits.GAS_ISSUES, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def sent(self, session=None):
|
def sent(self, session=None):
|
||||||
@ -166,9 +277,22 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
if self.status > StatusEnum.SENT:
|
if self.__status_already_set(StatusBits.IN_NETWORK):
|
||||||
raise TxStateChangeError('SENT after {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.SENT, session)
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('SENT cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.IN_NETWORK, session)
|
||||||
|
self.__reset_status(StatusBits.DEFERRED | StatusBits.QUEUED | StatusBits.LOCAL_ERROR | StatusBits.NODE_ERROR, session)
|
||||||
|
logg.debug('<<< status {}'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def sendfail(self, session=None):
|
def sendfail(self, session=None):
|
||||||
@ -178,9 +302,23 @@ class Otx(SessionBase):
|
|||||||
|
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
if self.status not in [StatusEnum.PENDING, StatusEnum.SENT, StatusEnum.WAITFORGAS]:
|
if self.__status_already_set(StatusBits.NODE_ERROR):
|
||||||
raise TxStateChangeError('SENDFAIL must follow SENT or PENDING, but had {}'.format(StatusEnum(self.status).name))
|
return
|
||||||
self.__set_status(StatusEnum.SENDFAIL, session)
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('SENDFAIL cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('SENDFAIL cannot be set on an entry with IN_NETWORK state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
|
self.__set_status(StatusBits.LOCAL_ERROR | StatusBits.DEFERRED, session)
|
||||||
|
self.__reset_status(StatusBits.QUEUED | StatusBits.GAS_ISSUES, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def minefail(self, block, session=None):
|
def minefail(self, block, session=None):
|
||||||
@ -192,14 +330,25 @@ class Otx(SessionBase):
|
|||||||
:type block: number
|
:type block: number
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
|
if self.__status_already_set(StatusBits.NETWORK_ERROR):
|
||||||
|
return
|
||||||
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('REVERTED cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if not self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('REVERTED cannot be set on an entry without IN_NETWORK state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
if block != None:
|
if block != None:
|
||||||
self.block = block
|
self.block = block
|
||||||
if self.status != StatusEnum.SENT:
|
|
||||||
logg.warning('REVERTED should follow SENT, but had {}'.format(StatusEnum(self.status).name))
|
self.__set_status(StatusBits.NETWORK_ERROR | StatusBits.FINAL, session)
|
||||||
#if self.status != StatusEnum.PENDING and self.status != StatusEnum.OBSOLETED and self.status != StatusEnum.SENT:
|
|
||||||
#if self.status > StatusEnum.SENT:
|
if self.tracing:
|
||||||
# raise TxStateChangeError('REVERTED must follow OBSOLETED, PENDING or SENT, but had {}'.format(StatusEnum(self.status).name))
|
self.__state_log(session=session)
|
||||||
self.__set_status(StatusEnum.REVERTED, session)
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def cancel(self, confirmed=False, session=None):
|
def cancel(self, confirmed=False, session=None):
|
||||||
@ -213,18 +362,36 @@ class Otx(SessionBase):
|
|||||||
:type confirmed: bool
|
:type confirmed: bool
|
||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('CANCEL cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
if confirmed:
|
if confirmed:
|
||||||
if self.status != StatusEnum.OBSOLETED:
|
if not self.status & StatusBits.OBSOLETE:
|
||||||
logg.warning('CANCELLED must follow OBSOLETED, but had {}'.format(StatusEnum(self.status).name))
|
raise TxStateChangeError('CANCEL can only be set on an entry marked OBSOLETE ({})'.format(status_str(self.status)))
|
||||||
#raise TxStateChangeError('CANCELLED must follow OBSOLETED, but had {}'.format(StatusEnum(self.status).name))
|
|
||||||
self.__set_status(StatusEnum.CANCELLED, session)
|
self.__set_status(StatusEnum.CANCELLED, session)
|
||||||
elif self.status != StatusEnum.OBSOLETED:
|
else:
|
||||||
if self.status > StatusEnum.SENT:
|
|
||||||
logg.warning('OBSOLETED must follow PENDING, SENDFAIL or SENT, but had {}'.format(StatusEnum(self.status).name))
|
|
||||||
#raise TxStateChangeError('OBSOLETED must follow PENDING, SENDFAIL or SENT, but had {}'.format(StatusEnum(self.status).name))
|
|
||||||
self.__set_status(StatusEnum.OBSOLETED, session)
|
self.__set_status(StatusEnum.OBSOLETED, session)
|
||||||
|
|
||||||
|
|
||||||
|
# if confirmed:
|
||||||
|
# if self.status != StatusEnum.OBSOLETED:
|
||||||
|
# logg.warning('CANCELLED must follow OBSOLETED, but had {}'.format(StatusEnum(self.status).name))
|
||||||
|
# #raise TxStateChangeError('CANCELLED must follow OBSOLETED, but had {}'.format(StatusEnum(self.status).name))
|
||||||
|
# self.__set_status(StatusEnum.CANCELLED, session)
|
||||||
|
# elif self.status != StatusEnum.OBSOLETED:
|
||||||
|
# if self.status > StatusEnum.SENT:
|
||||||
|
# logg.warning('OBSOLETED must follow PENDING, SENDFAIL or SENT, but had {}'.format(StatusEnum(self.status).name))
|
||||||
|
# #raise TxStateChangeError('OBSOLETED must follow PENDING, SENDFAIL or SENT, but had {}'.format(StatusEnum(self.status).name))
|
||||||
|
# self.__set_status(StatusEnum.OBSOLETED, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
def success(self, block, session=None):
|
def success(self, block, session=None):
|
||||||
"""Marks that transaction was successfully mined.
|
"""Marks that transaction was successfully mined.
|
||||||
|
|
||||||
@ -235,13 +402,24 @@ class Otx(SessionBase):
|
|||||||
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
:raises cic_eth.db.error.TxStateChangeError: State change represents a sequence of events that should not exist.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
session = SessionBase.bind_session(session)
|
||||||
|
|
||||||
|
if self.status & StatusBits.FINAL:
|
||||||
|
raise TxStateChangeError('SUCCESS cannot be set on an entry with FINAL state set ({})'.format(status_str(self.status)))
|
||||||
|
if not self.status & StatusBits.IN_NETWORK:
|
||||||
|
raise TxStateChangeError('SUCCESS cannot be set on an entry without IN_NETWORK state set ({})'.format(status_str(self.status)))
|
||||||
|
if is_error_status(self.status):
|
||||||
|
raise TxStateChangeError('SUCCESS cannot be set on an entry with error state set ({})'.format(status_str(self.status)))
|
||||||
|
|
||||||
if block != None:
|
if block != None:
|
||||||
self.block = block
|
self.block = block
|
||||||
if self.status != StatusEnum.SENT:
|
|
||||||
logg.error('SUCCESS should follow SENT, but had {}'.format(StatusEnum(self.status).name))
|
|
||||||
#raise TxStateChangeError('SUCCESS must follow SENT, but had {}'.format(StatusEnum(self.status).name))
|
|
||||||
self.__set_status(StatusEnum.SUCCESS, session)
|
self.__set_status(StatusEnum.SUCCESS, session)
|
||||||
|
|
||||||
|
if self.tracing:
|
||||||
|
self.__state_log(session=session)
|
||||||
|
|
||||||
|
SessionBase.release_session(session)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get(status=0, limit=4096, status_exact=True):
|
def get(status=0, limit=4096, status_exact=True):
|
||||||
@ -450,6 +628,3 @@ class OtxSync(SessionBase):
|
|||||||
self.tx_height_session = 0
|
self.tx_height_session = 0
|
||||||
self.block_height_backlog = 0
|
self.block_height_backlog = 0
|
||||||
self.tx_height_backlog = 0
|
self.tx_height_backlog = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,10 @@ from .rpc import RpcClient
|
|||||||
from cic_eth.db import Otx, SessionBase
|
from cic_eth.db import Otx, SessionBase
|
||||||
from cic_eth.db.models.tx import TxCache
|
from cic_eth.db.models.tx import TxCache
|
||||||
from cic_eth.db.models.lock import Lock
|
from cic_eth.db.models.lock import Lock
|
||||||
from cic_eth.db.enum import LockEnum
|
from cic_eth.db.enum import (
|
||||||
|
LockEnum,
|
||||||
|
StatusBits,
|
||||||
|
)
|
||||||
from cic_eth.error import PermanentTxError
|
from cic_eth.error import PermanentTxError
|
||||||
from cic_eth.error import TemporaryTxError
|
from cic_eth.error import TemporaryTxError
|
||||||
from cic_eth.error import NotLocalTxError
|
from cic_eth.error import NotLocalTxError
|
||||||
@ -399,9 +402,10 @@ def refill_gas(self, recipient_address, chain_str):
|
|||||||
chain_spec = ChainSpec.from_chain_str(chain_str)
|
chain_spec = ChainSpec.from_chain_str(chain_str)
|
||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
|
status_filter = StatusBits.FINAL | StatusBits.NODE_ERROR | StatusBits.NETWORK_ERROR | StatusBits.UNKNOWN_ERROR
|
||||||
q = session.query(Otx.tx_hash)
|
q = session.query(Otx.tx_hash)
|
||||||
q = q.join(TxCache)
|
q = q.join(TxCache)
|
||||||
q = q.filter(Otx.status<=0)
|
q = q.filter(Otx.status.op('&')(StatusBits.FINAL.value)==0)
|
||||||
q = q.filter(TxCache.from_value!='0x00')
|
q = q.filter(TxCache.from_value!='0x00')
|
||||||
q = q.filter(TxCache.recipient==recipient_address)
|
q = q.filter(TxCache.recipient==recipient_address)
|
||||||
c = q.count()
|
c = q.count()
|
||||||
@ -495,7 +499,7 @@ def resend_with_higher_gas(self, txold_hash_hex, chain_str, gas=None, default_fa
|
|||||||
|
|
||||||
tx_signed_raw_bytes = bytes.fromhex(otx.signed_tx[2:])
|
tx_signed_raw_bytes = bytes.fromhex(otx.signed_tx[2:])
|
||||||
tx = unpack_signed_raw_tx(tx_signed_raw_bytes, chain_spec.chain_id())
|
tx = unpack_signed_raw_tx(tx_signed_raw_bytes, chain_spec.chain_id())
|
||||||
logg.debug('otx {} {}'.format(tx, otx.signed_tx))
|
logg.debug('resend otx {} {}'.format(tx, otx.signed_tx))
|
||||||
|
|
||||||
queue = self.request.delivery_info['routing_key']
|
queue = self.request.delivery_info['routing_key']
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import datetime
|
|||||||
# third-party imports
|
# third-party imports
|
||||||
import celery
|
import celery
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy import not_
|
||||||
from sqlalchemy import tuple_
|
from sqlalchemy import tuple_
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
@ -16,8 +17,12 @@ from cic_eth.db.models.otx import OtxStateLog
|
|||||||
from cic_eth.db.models.tx import TxCache
|
from cic_eth.db.models.tx import TxCache
|
||||||
from cic_eth.db.models.lock import Lock
|
from cic_eth.db.models.lock import Lock
|
||||||
from cic_eth.db import SessionBase
|
from cic_eth.db import SessionBase
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
from cic_eth.db.enum import LockEnum
|
StatusEnum,
|
||||||
|
LockEnum,
|
||||||
|
StatusBits,
|
||||||
|
is_alive,
|
||||||
|
)
|
||||||
from cic_eth.eth.util import unpack_signed_raw_tx # TODO: should not be in same sub-path as package that imports queue.tx
|
from cic_eth.eth.util import unpack_signed_raw_tx # TODO: should not be in same sub-path as package that imports queue.tx
|
||||||
from cic_eth.error import NotLocalTxError
|
from cic_eth.error import NotLocalTxError
|
||||||
from cic_eth.error import LockedError
|
from cic_eth.error import LockedError
|
||||||
@ -70,10 +75,7 @@ def create(nonce, holder_address, tx_hash, signed_tx, chain_str, obsolete_predec
|
|||||||
|
|
||||||
for otx in q.all():
|
for otx in q.all():
|
||||||
logg.info('otx {} obsoleted by {}'.format(otx.tx_hash, tx_hash))
|
logg.info('otx {} obsoleted by {}'.format(otx.tx_hash, tx_hash))
|
||||||
if otx.status == StatusEnum.SENT:
|
otx.cancel(confirmed=False, session=session)
|
||||||
otx.cancel(False, session=session)
|
|
||||||
elif otx.status != StatusEnum.OBSOLETED:
|
|
||||||
otx.override(session=session)
|
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
@ -167,6 +169,7 @@ def set_final_status(tx_hash, block=None, fail=False):
|
|||||||
|
|
||||||
return tx_hash
|
return tx_hash
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
@celery_app.task()
|
||||||
def set_cancel(tx_hash, manual=False):
|
def set_cancel(tx_hash, manual=False):
|
||||||
"""Used to set the status when a transaction is cancelled.
|
"""Used to set the status when a transaction is cancelled.
|
||||||
@ -250,6 +253,33 @@ def set_fubar(tx_hash):
|
|||||||
|
|
||||||
return tx_hash
|
return tx_hash
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task()
|
||||||
|
def set_manual(tx_hash):
|
||||||
|
"""Used to set the status when queue is manually changed
|
||||||
|
|
||||||
|
Will set the state to MANUAL
|
||||||
|
|
||||||
|
:param tx_hash: Transaction hash of record to modify
|
||||||
|
:type tx_hash: str, 0x-hex
|
||||||
|
:raises NotLocalTxError: If transaction not found in queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
o = session.query(Otx).filter(Otx.tx_hash==tx_hash).first()
|
||||||
|
if o == None:
|
||||||
|
session.close()
|
||||||
|
raise NotLocalTxError('queue does not contain tx hash {}'.format(tx_hash))
|
||||||
|
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
o.manual(session=session)
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
return tx_hash
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
@celery_app.task()
|
||||||
def set_ready(tx_hash):
|
def set_ready(tx_hash):
|
||||||
"""Used to mark a transaction as ready to be sent to network
|
"""Used to mark a transaction as ready to be sent to network
|
||||||
@ -265,14 +295,11 @@ def set_ready(tx_hash):
|
|||||||
raise NotLocalTxError('queue does not contain tx hash {}'.format(tx_hash))
|
raise NotLocalTxError('queue does not contain tx hash {}'.format(tx_hash))
|
||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
if o.status == StatusEnum.WAITFORGAS or o.status == StatusEnum.PENDING:
|
if o.status & StatusBits.GAS_ISSUES or o.status == StatusEnum.PENDING:
|
||||||
o.readysend(session=session)
|
o.readysend(session=session)
|
||||||
else:
|
else:
|
||||||
o.retry(session=session)
|
o.retry(session=session)
|
||||||
|
|
||||||
logg.debug('ot otx otx {} {}'.format(tx_hash, o))
|
|
||||||
|
|
||||||
session.add(o)
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@ -304,6 +331,7 @@ def set_waitforgas(tx_hash):
|
|||||||
|
|
||||||
return tx_hash
|
return tx_hash
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task()
|
@celery_app.task()
|
||||||
def get_state_log(tx_hash):
|
def get_state_log(tx_hash):
|
||||||
|
|
||||||
@ -483,13 +511,14 @@ def get_paused_txs(status=None, sender=None, chain_id=0):
|
|||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
|
|
||||||
if status != None:
|
if status != None:
|
||||||
if status == StatusEnum.PENDING or status >= StatusEnum.SENT:
|
#if status == StatusEnum.PENDING or status >= StatusEnum.SENT:
|
||||||
|
if status == StatusEnum.PENDING or status & StatusBits.IN_NETWORK or not is_alive(status):
|
||||||
raise ValueError('not a valid paused tx value: {}'.format(status))
|
raise ValueError('not a valid paused tx value: {}'.format(status))
|
||||||
q = q.filter(Otx.status==status)
|
q = q.filter(Otx.status.op('&')(status.value)==status.value)
|
||||||
q = q.join(TxCache)
|
q = q.join(TxCache)
|
||||||
else:
|
else:
|
||||||
q = q.filter(Otx.status>StatusEnum.PENDING)
|
q = q.filter(Otx.status>StatusEnum.PENDING.value)
|
||||||
q = q.filter(Otx.status<StatusEnum.SENT)
|
q = q.filter(not_(Otx.status.op('&')(StatusBits.IN_NETWORK.value)>0))
|
||||||
|
|
||||||
if sender != None:
|
if sender != None:
|
||||||
q = q.filter(TxCache.sender==sender)
|
q = q.filter(TxCache.sender==sender)
|
||||||
@ -508,7 +537,7 @@ def get_paused_txs(status=None, sender=None, chain_id=0):
|
|||||||
return txs
|
return txs
|
||||||
|
|
||||||
|
|
||||||
def get_status_tx(status, before=None, limit=0):
|
def get_status_tx(status, before=None, exact=False, limit=0):
|
||||||
"""Retrieve transaction with a specific queue status.
|
"""Retrieve transaction with a specific queue status.
|
||||||
|
|
||||||
:param status: Status to match transactions with
|
:param status: Status to match transactions with
|
||||||
@ -525,7 +554,10 @@ def get_status_tx(status, before=None, limit=0):
|
|||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
q = q.join(TxCache)
|
q = q.join(TxCache)
|
||||||
q = q.filter(TxCache.date_updated<before)
|
q = q.filter(TxCache.date_updated<before)
|
||||||
q = q.filter(Otx.status==status)
|
if exact:
|
||||||
|
q = q.filter(Otx.status==status.value)
|
||||||
|
else:
|
||||||
|
q = q.filter(Otx.status.op('&')(status.value)==status.value)
|
||||||
i = 0
|
i = 0
|
||||||
for o in q.all():
|
for o in q.all():
|
||||||
if limit > 0 and i == limit:
|
if limit > 0 and i == limit:
|
||||||
@ -565,9 +597,12 @@ def get_upcoming_tx(status=StatusEnum.READYSEND, recipient=None, before=None, ch
|
|||||||
q_outer = q_outer.join(Lock, isouter=True)
|
q_outer = q_outer.join(Lock, isouter=True)
|
||||||
q_outer = q_outer.filter(or_(Lock.flags==None, Lock.flags.op('&')(LockEnum.SEND.value)==0))
|
q_outer = q_outer.filter(or_(Lock.flags==None, Lock.flags.op('&')(LockEnum.SEND.value)==0))
|
||||||
|
|
||||||
if status >= StatusEnum.SENT:
|
if not is_alive(status):
|
||||||
raise ValueError('not a valid non-final tx value: {}'.format(s))
|
raise ValueError('not a valid non-final tx value: {}'.format(status))
|
||||||
q_outer = q_outer.filter(Otx.status==status.value)
|
if status == StatusEnum.PENDING:
|
||||||
|
q_outer = q_outer.filter(Otx.status==status.value)
|
||||||
|
else:
|
||||||
|
q_outer = q_outer.filter(Otx.status.op('&')(status.value)==status.value)
|
||||||
|
|
||||||
if recipient != None:
|
if recipient != None:
|
||||||
q_outer = q_outer.filter(TxCache.recipient==recipient)
|
q_outer = q_outer.filter(TxCache.recipient==recipient)
|
||||||
|
@ -2,7 +2,7 @@ web3==5.12.2
|
|||||||
celery==4.4.7
|
celery==4.4.7
|
||||||
crypto-dev-signer~=0.4.13rc2
|
crypto-dev-signer~=0.4.13rc2
|
||||||
confini~=0.3.6b1
|
confini~=0.3.6b1
|
||||||
cic-registry~=0.5.3a10
|
cic-registry~=0.5.3a12
|
||||||
cic-bancor~=0.0.6
|
cic-bancor~=0.0.6
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
|
@ -10,7 +10,11 @@ import web3
|
|||||||
# local imports
|
# local imports
|
||||||
from cic_eth.api import AdminApi
|
from cic_eth.api import AdminApi
|
||||||
from cic_eth.db.models.role import AccountRole
|
from cic_eth.db.models.role import AccountRole
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
status_str,
|
||||||
|
)
|
||||||
from cic_eth.error import InitializationError
|
from cic_eth.error import InitializationError
|
||||||
from cic_eth.eth.task import sign_and_register_tx
|
from cic_eth.eth.task import sign_and_register_tx
|
||||||
from cic_eth.eth.tx import cache_gas_refill_data
|
from cic_eth.eth.tx import cache_gas_refill_data
|
||||||
@ -64,7 +68,11 @@ def test_resend_inplace(
|
|||||||
|
|
||||||
api = AdminApi(c, queue=None)
|
api = AdminApi(c, queue=None)
|
||||||
t = api.resend(tx_dict['hash'], chain_str, unlock=True)
|
t = api.resend(tx_dict['hash'], chain_str, unlock=True)
|
||||||
tx_hash_new_hex = t.get()
|
t.get()
|
||||||
|
i = 0
|
||||||
|
tx_hash_new_hex = None
|
||||||
|
for r in t.collect():
|
||||||
|
tx_hash_new_hex = r[1]
|
||||||
assert t.successful()
|
assert t.successful()
|
||||||
|
|
||||||
tx_raw_new = get_tx(tx_hash_new_hex)
|
tx_raw_new = get_tx(tx_hash_new_hex)
|
||||||
@ -74,142 +82,144 @@ def test_resend_inplace(
|
|||||||
assert tx_dict_new['gasPrice'] > gas_price_before
|
assert tx_dict_new['gasPrice'] > gas_price_before
|
||||||
|
|
||||||
tx_dict_after = get_tx(tx_dict['hash'])
|
tx_dict_after = get_tx(tx_dict['hash'])
|
||||||
assert tx_dict_after['status'] == StatusEnum.OVERRIDDEN
|
|
||||||
|
logg.debug('logggg {}'.format(status_str(tx_dict_after['status'])))
|
||||||
|
assert tx_dict_after['status'] & StatusBits.MANUAL
|
||||||
|
|
||||||
|
|
||||||
def test_check_fix_nonce(
|
#def test_check_fix_nonce(
|
||||||
default_chain_spec,
|
# default_chain_spec,
|
||||||
init_database,
|
# init_database,
|
||||||
init_eth_account_roles,
|
# init_eth_account_roles,
|
||||||
init_w3,
|
# init_w3,
|
||||||
eth_empty_accounts,
|
# eth_empty_accounts,
|
||||||
celery_session_worker,
|
# celery_session_worker,
|
||||||
):
|
# ):
|
||||||
|
#
|
||||||
chain_str = str(default_chain_spec)
|
# chain_str = str(default_chain_spec)
|
||||||
|
#
|
||||||
sigs = []
|
# sigs = []
|
||||||
for i in range(5):
|
# for i in range(5):
|
||||||
s = celery.signature(
|
# s = celery.signature(
|
||||||
'cic_eth.eth.tx.refill_gas',
|
# 'cic_eth.eth.tx.refill_gas',
|
||||||
[
|
# [
|
||||||
eth_empty_accounts[i],
|
# eth_empty_accounts[i],
|
||||||
chain_str,
|
# chain_str,
|
||||||
],
|
# ],
|
||||||
queue=None,
|
# queue=None,
|
||||||
)
|
# )
|
||||||
sigs.append(s)
|
# sigs.append(s)
|
||||||
|
#
|
||||||
t = celery.group(sigs)()
|
# t = celery.group(sigs)()
|
||||||
txs = t.get()
|
# txs = t.get()
|
||||||
assert t.successful()
|
# assert t.successful()
|
||||||
|
#
|
||||||
tx_hash = web3.Web3.keccak(hexstr=txs[2])
|
# tx_hash = web3.Web3.keccak(hexstr=txs[2])
|
||||||
c = RpcClient(default_chain_spec)
|
# c = RpcClient(default_chain_spec)
|
||||||
api = AdminApi(c, queue=None)
|
# api = AdminApi(c, queue=None)
|
||||||
address = init_eth_account_roles['eth_account_gas_provider']
|
# address = init_eth_account_roles['eth_account_gas_provider']
|
||||||
nonce_spec = api.check_nonce(address)
|
# nonce_spec = api.check_nonce(address)
|
||||||
assert nonce_spec['nonce']['network'] == 0
|
# assert nonce_spec['nonce']['network'] == 0
|
||||||
assert nonce_spec['nonce']['queue'] == 4
|
# assert nonce_spec['nonce']['queue'] == 4
|
||||||
assert nonce_spec['nonce']['blocking'] == None
|
# assert nonce_spec['nonce']['blocking'] == None
|
||||||
|
#
|
||||||
s_set = celery.signature(
|
# s_set = celery.signature(
|
||||||
'cic_eth.queue.tx.set_rejected',
|
# 'cic_eth.queue.tx.set_rejected',
|
||||||
[
|
# [
|
||||||
tx_hash.hex(),
|
# tx_hash.hex(),
|
||||||
],
|
# ],
|
||||||
queue=None,
|
# queue=None,
|
||||||
)
|
# )
|
||||||
t = s_set.apply_async()
|
# t = s_set.apply_async()
|
||||||
t.get()
|
# t.get()
|
||||||
t.collect()
|
# t.collect()
|
||||||
assert t.successful()
|
# assert t.successful()
|
||||||
|
#
|
||||||
|
#
|
||||||
nonce_spec = api.check_nonce(address)
|
# nonce_spec = api.check_nonce(address)
|
||||||
assert nonce_spec['nonce']['blocking'] == 2
|
# assert nonce_spec['nonce']['blocking'] == 2
|
||||||
assert nonce_spec['tx']['blocking'] == tx_hash.hex()
|
# assert nonce_spec['tx']['blocking'] == tx_hash.hex()
|
||||||
|
#
|
||||||
t = api.fix_nonce(address, nonce_spec['nonce']['blocking'])
|
# t = api.fix_nonce(address, nonce_spec['nonce']['blocking'])
|
||||||
t.get()
|
# t.get()
|
||||||
t.collect()
|
# t.collect()
|
||||||
assert t.successful()
|
# assert t.successful()
|
||||||
|
#
|
||||||
for tx in txs[3:]:
|
# for tx in txs[3:]:
|
||||||
tx_hash = web3.Web3.keccak(hexstr=tx)
|
# tx_hash = web3.Web3.keccak(hexstr=tx)
|
||||||
tx_dict = get_tx(tx_hash.hex())
|
# tx_dict = get_tx(tx_hash.hex())
|
||||||
assert tx_dict['status'] == StatusEnum.OVERRIDDEN
|
# assert tx_dict['status'] == StatusEnum.OVERRIDDEN
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_tag_account(
|
#def test_tag_account(
|
||||||
init_database,
|
# init_database,
|
||||||
eth_empty_accounts,
|
# eth_empty_accounts,
|
||||||
init_rpc,
|
# init_rpc,
|
||||||
):
|
# ):
|
||||||
|
#
|
||||||
api = AdminApi(init_rpc)
|
# api = AdminApi(init_rpc)
|
||||||
|
#
|
||||||
api.tag_account('foo', eth_empty_accounts[0])
|
# api.tag_account('foo', eth_empty_accounts[0])
|
||||||
api.tag_account('bar', eth_empty_accounts[1])
|
# api.tag_account('bar', eth_empty_accounts[1])
|
||||||
api.tag_account('bar', eth_empty_accounts[2])
|
# api.tag_account('bar', eth_empty_accounts[2])
|
||||||
|
#
|
||||||
assert AccountRole.get_address('foo') == eth_empty_accounts[0]
|
# assert AccountRole.get_address('foo') == eth_empty_accounts[0]
|
||||||
assert AccountRole.get_address('bar') == eth_empty_accounts[2]
|
# assert AccountRole.get_address('bar') == eth_empty_accounts[2]
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_ready(
|
#def test_ready(
|
||||||
init_database,
|
# init_database,
|
||||||
eth_empty_accounts,
|
# eth_empty_accounts,
|
||||||
init_rpc,
|
# init_rpc,
|
||||||
w3,
|
# w3,
|
||||||
):
|
# ):
|
||||||
|
#
|
||||||
api = AdminApi(init_rpc)
|
# api = AdminApi(init_rpc)
|
||||||
|
#
|
||||||
with pytest.raises(InitializationError):
|
# with pytest.raises(InitializationError):
|
||||||
api.ready()
|
# api.ready()
|
||||||
|
#
|
||||||
bogus_account = os.urandom(20)
|
# bogus_account = os.urandom(20)
|
||||||
bogus_account_hex = '0x' + bogus_account.hex()
|
# bogus_account_hex = '0x' + bogus_account.hex()
|
||||||
|
#
|
||||||
api.tag_account('ETH_GAS_PROVIDER_ADDRESS', web3.Web3.toChecksumAddress(bogus_account_hex))
|
# api.tag_account('ETH_GAS_PROVIDER_ADDRESS', web3.Web3.toChecksumAddress(bogus_account_hex))
|
||||||
with pytest.raises(KeyError):
|
# with pytest.raises(KeyError):
|
||||||
api.ready()
|
# api.ready()
|
||||||
|
#
|
||||||
api.tag_account('ETH_GAS_PROVIDER_ADDRESS', eth_empty_accounts[0])
|
# api.tag_account('ETH_GAS_PROVIDER_ADDRESS', eth_empty_accounts[0])
|
||||||
api.ready()
|
# api.ready()
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_tx(
|
#def test_tx(
|
||||||
default_chain_spec,
|
# default_chain_spec,
|
||||||
cic_registry,
|
# cic_registry,
|
||||||
init_database,
|
# init_database,
|
||||||
init_rpc,
|
# init_rpc,
|
||||||
init_w3,
|
# init_w3,
|
||||||
celery_session_worker,
|
# celery_session_worker,
|
||||||
):
|
# ):
|
||||||
|
#
|
||||||
tx = {
|
# tx = {
|
||||||
'from': init_w3.eth.accounts[0],
|
# 'from': init_w3.eth.accounts[0],
|
||||||
'to': init_w3.eth.accounts[1],
|
# 'to': init_w3.eth.accounts[1],
|
||||||
'nonce': 42,
|
# 'nonce': 42,
|
||||||
'gas': 21000,
|
# 'gas': 21000,
|
||||||
'gasPrice': 1000000,
|
# 'gasPrice': 1000000,
|
||||||
'value': 128,
|
# 'value': 128,
|
||||||
'chainId': default_chain_spec.chain_id(),
|
# 'chainId': default_chain_spec.chain_id(),
|
||||||
'data': '',
|
# 'data': '',
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
(tx_hash_hex, tx_signed_raw_hex) = sign_tx(tx, str(default_chain_spec))
|
# (tx_hash_hex, tx_signed_raw_hex) = sign_tx(tx, str(default_chain_spec))
|
||||||
queue_create(
|
# queue_create(
|
||||||
tx['nonce'],
|
# tx['nonce'],
|
||||||
tx['from'],
|
# tx['from'],
|
||||||
tx_hash_hex,
|
# tx_hash_hex,
|
||||||
tx_signed_raw_hex,
|
# tx_signed_raw_hex,
|
||||||
str(default_chain_spec),
|
# str(default_chain_spec),
|
||||||
)
|
# )
|
||||||
tx_recovered = unpack_signed_raw_tx(bytes.fromhex(tx_signed_raw_hex[2:]), default_chain_spec.chain_id())
|
# tx_recovered = unpack_signed_raw_tx(bytes.fromhex(tx_signed_raw_hex[2:]), default_chain_spec.chain_id())
|
||||||
cache_gas_refill_data(tx_hash_hex, tx_recovered)
|
# cache_gas_refill_data(tx_hash_hex, tx_recovered)
|
||||||
|
#
|
||||||
api = AdminApi(init_rpc, queue=None)
|
# api = AdminApi(init_rpc, queue=None)
|
||||||
tx = api.tx(default_chain_spec, tx_hash=tx_hash_hex)
|
# tx = api.tx(default_chain_spec, tx_hash=tx_hash_hex)
|
||||||
|
@ -10,7 +10,10 @@ from cic_registry import zero_address
|
|||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.otx import Otx
|
from cic_eth.db.models.otx import Otx
|
||||||
from cic_eth.db.models.tx import TxCache
|
from cic_eth.db.models.tx import TxCache
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
)
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
@ -169,6 +172,9 @@ def test_status_fubar(
|
|||||||
)
|
)
|
||||||
t = s.apply_async()
|
t = s.apply_async()
|
||||||
t.get()
|
t.get()
|
||||||
|
for n in t.collect():
|
||||||
|
pass
|
||||||
assert t.successful()
|
assert t.successful()
|
||||||
init_database.refresh(otx)
|
|
||||||
assert otx.status == StatusEnum.FUBAR
|
otx = Otx.load(tx_hash)
|
||||||
|
assert otx.status & StatusBits.UNKNOWN_ERROR
|
||||||
|
@ -8,7 +8,11 @@ import celery
|
|||||||
# local imports
|
# local imports
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
from cic_eth.db.models.otx import Otx
|
from cic_eth.db.models.otx import Otx
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
is_error_status,
|
||||||
|
)
|
||||||
from cic_eth.eth.task import sign_and_register_tx
|
from cic_eth.eth.task import sign_and_register_tx
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@ -101,7 +105,7 @@ def test_states_failed(
|
|||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hash_hex).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hash_hex).first()
|
||||||
otx.sendfail(session=init_database)
|
otx.sendfail(session=init_database)
|
||||||
init_database.add(otx)
|
|
||||||
init_database.commit()
|
init_database.commit()
|
||||||
|
|
||||||
s = celery.signature(
|
s = celery.signature(
|
||||||
@ -121,5 +125,9 @@ def test_states_failed(
|
|||||||
pass
|
pass
|
||||||
assert t.successful()
|
assert t.successful()
|
||||||
|
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hash_hex).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hash_hex).first()
|
||||||
assert otx.status == StatusEnum.RETRY.value
|
assert otx.status & StatusEnum.RETRY == StatusEnum.RETRY
|
||||||
|
#assert otx.status & StatusBits.QUEUED
|
||||||
|
assert is_error_status(otx.status)
|
||||||
|
@ -24,7 +24,6 @@ class Response:
|
|||||||
|
|
||||||
status = 200
|
status = 200
|
||||||
|
|
||||||
@pytest.mark.skip()
|
|
||||||
def test_callback_http(
|
def test_callback_http(
|
||||||
celery_session_worker,
|
celery_session_worker,
|
||||||
mocker,
|
mocker,
|
||||||
@ -43,7 +42,6 @@ def test_callback_http(
|
|||||||
t.get()
|
t.get()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip()
|
|
||||||
def test_callback_tcp(
|
def test_callback_tcp(
|
||||||
celery_session_worker,
|
celery_session_worker,
|
||||||
):
|
):
|
||||||
|
20
apps/cic-eth/tests/unit/db/test_enum.py
Normal file
20
apps/cic-eth/tests/unit/db/test_enum.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
status_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_str():
|
||||||
|
|
||||||
|
# String representation for a status in StatusEnum
|
||||||
|
s = status_str(StatusEnum.REVERTED)
|
||||||
|
assert s == 'REVERTED'
|
||||||
|
|
||||||
|
# String representation for a status not in StatusEnum
|
||||||
|
s = status_str(StatusBits.LOCAL_ERROR | StatusBits.NODE_ERROR)
|
||||||
|
assert s == 'LOCAL_ERROR,NODE_ERROR*'
|
||||||
|
|
||||||
|
# String representation for a status in StatusEnum, but bits only representation bit set
|
||||||
|
s = status_str(StatusEnum.REVERTED, bits_only=True)
|
||||||
|
assert s == 'IN_NETWORK,NETWORK_ERROR,FINAL'
|
@ -9,7 +9,11 @@ import pytest
|
|||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
from cic_eth.db.models.otx import OtxStateLog
|
from cic_eth.db.models.otx import OtxStateLog
|
||||||
from cic_eth.db.models.otx import Otx
|
from cic_eth.db.models.otx import Otx
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
is_alive,
|
||||||
|
)
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
@ -70,15 +74,24 @@ def test_state_log(
|
|||||||
otx = Otx.add(0, address, tx_hash, signed_tx, session=init_database)
|
otx = Otx.add(0, address, tx_hash, signed_tx, session=init_database)
|
||||||
|
|
||||||
otx.waitforgas(session=init_database)
|
otx.waitforgas(session=init_database)
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
|
otx.readysend(session=init_database)
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
otx.sent(session=init_database)
|
otx.sent(session=init_database)
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
otx.success(1024, session=init_database)
|
otx.success(1024, session=init_database)
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
q = init_database.query(OtxStateLog)
|
q = init_database.query(OtxStateLog)
|
||||||
q = q.filter(OtxStateLog.otx_id==otx.id)
|
q = q.filter(OtxStateLog.otx_id==otx.id)
|
||||||
q = q.order_by(OtxStateLog.date.asc())
|
q = q.order_by(OtxStateLog.date.asc())
|
||||||
logs = q.all()
|
logs = q.all()
|
||||||
|
|
||||||
assert logs[0].status == StatusEnum.PENDING
|
assert logs[0].status == StatusEnum.PENDING
|
||||||
assert logs[1].status == StatusEnum.WAITFORGAS
|
assert logs[1].status == StatusEnum.WAITFORGAS
|
||||||
assert logs[2].status == StatusEnum.SENT
|
assert logs[2].status & StatusBits.QUEUED
|
||||||
assert logs[3].status == StatusEnum.SUCCESS
|
assert logs[3].status & StatusBits.IN_NETWORK
|
||||||
|
assert not is_alive(logs[4].status)
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
# standard imports
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# third-party imports
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from cic_eth.db import Otx
|
|
||||||
from cic_eth.db.error import TxStateChangeError
|
|
||||||
|
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
# Check that invalid transitions throw exceptions
|
|
||||||
# sent
|
|
||||||
def test_db_queue_states(
|
|
||||||
init_database,
|
|
||||||
):
|
|
||||||
|
|
||||||
session = init_database
|
|
||||||
|
|
||||||
# these values are completely arbitary
|
|
||||||
tx_hash = '0xF182DFA3AD48723E7E222FE7B4C2C44C23CD4D7FF413E8999DFA15ECE53F'
|
|
||||||
address = '0x38C5559D6EDDDA1F705D3AB1A664CA1B397EB119'
|
|
||||||
signed_tx = '0xA5866A5383249AE843546BDA46235A1CA1614F538FB486140693C2EF1956FC53213F6AEF0F99F44D7103871AF3A12B126DCF9BFB7AF11143FAB3ECE2B452EE35D1320C4C7C6F999C8DF4EB09E729715B573F6672ED852547F552C4AE99D17DCD14C810'
|
|
||||||
o = Otx(
|
|
||||||
nonce=42,
|
|
||||||
address=address[2:],
|
|
||||||
tx_hash=tx_hash[2:],
|
|
||||||
signed_tx=signed_tx[2:],
|
|
||||||
)
|
|
||||||
session.add(o)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
o.sent(session=session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
# send after sent is ok
|
|
||||||
o.sent(session=session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
o.sendfail(session=session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
with pytest.raises(TxStateChangeError):
|
|
||||||
o.sendfail(session=session)
|
|
||||||
|
|
||||||
o.sent(session=session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
o.minefail(1234, session=session)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
with pytest.raises(TxStateChangeError):
|
|
||||||
o.sent(session=session)
|
|
97
apps/cic-eth/tests/unit/db/test_status.py
Normal file
97
apps/cic-eth/tests/unit/db/test_status.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# third-party imports
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from cic_eth.db.models.otx import Otx
|
||||||
|
from cic_eth.db.enum import (
|
||||||
|
StatusEnum,
|
||||||
|
StatusBits,
|
||||||
|
is_alive,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def otx(
|
||||||
|
init_database,
|
||||||
|
):
|
||||||
|
|
||||||
|
bogus_hash = '0x' + os.urandom(32).hex()
|
||||||
|
bogus_address = '0x' + os.urandom(20).hex()
|
||||||
|
bogus_tx_raw = '0x' + os.urandom(128).hex()
|
||||||
|
return Otx(0, bogus_address, bogus_hash, bogus_tx_raw)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_gas(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.waitforgas(init_database)
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.sent(init_database)
|
||||||
|
otx.success(1024, init_database)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_straight_success(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.sent(init_database)
|
||||||
|
otx.success(1024, init_database)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_straight_revert(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.sent(init_database)
|
||||||
|
otx.minefail(1024, init_database)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_nodeerror(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.sendfail(init_database)
|
||||||
|
otx.retry(init_database)
|
||||||
|
otx.sent(init_database)
|
||||||
|
otx.success(1024, init_database)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_nodeerror_multiple(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.sendfail(init_database)
|
||||||
|
otx.retry(init_database)
|
||||||
|
otx.sendfail(init_database)
|
||||||
|
otx.retry(init_database)
|
||||||
|
otx.sent(init_database)
|
||||||
|
otx.success(1024, init_database)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_chain_nodeerror(
|
||||||
|
init_database,
|
||||||
|
otx,
|
||||||
|
):
|
||||||
|
|
||||||
|
otx.readysend(init_database)
|
||||||
|
otx.reject(init_database)
|
||||||
|
assert not is_alive(otx.status)
|
@ -16,8 +16,14 @@ from cic_eth.db.models.otx import OtxSync
|
|||||||
from cic_eth.db.models.tx import TxCache
|
from cic_eth.db.models.tx import TxCache
|
||||||
from cic_eth.db.models.lock import Lock
|
from cic_eth.db.models.lock import Lock
|
||||||
from cic_eth.db.models.base import SessionBase
|
from cic_eth.db.models.base import SessionBase
|
||||||
from cic_eth.db.enum import StatusEnum
|
from cic_eth.db.enum import (
|
||||||
from cic_eth.db.enum import LockEnum
|
StatusEnum,
|
||||||
|
LockEnum,
|
||||||
|
StatusBits,
|
||||||
|
is_alive,
|
||||||
|
is_error_status,
|
||||||
|
status_str,
|
||||||
|
)
|
||||||
from cic_eth.queue.tx import create as queue_create
|
from cic_eth.queue.tx import create as queue_create
|
||||||
from cic_eth.queue.tx import set_final_status
|
from cic_eth.queue.tx import set_final_status
|
||||||
from cic_eth.queue.tx import set_sent_status
|
from cic_eth.queue.tx import set_sent_status
|
||||||
@ -63,13 +69,14 @@ def test_finalize(
|
|||||||
set_sent_status(tx_hash.hex())
|
set_sent_status(tx_hash.hex())
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[0]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[0]).first()
|
||||||
assert otx.status == StatusEnum.OBSOLETED
|
assert otx.status & StatusBits.OBSOLETE
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[1]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[1]).first()
|
||||||
assert otx.status == StatusEnum.OBSOLETED
|
assert otx.status & StatusBits.OBSOLETE
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[2]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[2]).first()
|
||||||
assert otx.status == StatusEnum.OBSOLETED
|
assert otx.status & StatusBits.OBSOLETE
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[3]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[3]).first()
|
||||||
assert otx.status == StatusEnum.PENDING
|
assert otx.status == StatusEnum.PENDING
|
||||||
@ -82,19 +89,22 @@ def test_finalize(
|
|||||||
set_final_status(tx_hashes[3], 1024)
|
set_final_status(tx_hashes[3], 1024)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[0]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[0]).first()
|
||||||
assert otx.status == StatusEnum.CANCELLED
|
assert otx.status & (StatusBits.OBSOLETE | StatusBits.FINAL)
|
||||||
|
assert not is_alive(otx.status)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[1]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[1]).first()
|
||||||
assert otx.status == StatusEnum.CANCELLED
|
assert otx.status & (StatusBits.OBSOLETE | StatusBits.FINAL)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[2]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[2]).first()
|
||||||
assert otx.status == StatusEnum.CANCELLED
|
assert otx.status & (StatusBits.OBSOLETE | StatusBits.FINAL)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[3]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[3]).first()
|
||||||
assert otx.status == StatusEnum.SUCCESS
|
assert otx.status & (StatusBits.IN_NETWORK | StatusBits.FINAL)
|
||||||
|
assert not is_error_status(otx.status)
|
||||||
|
|
||||||
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[4]).first()
|
otx = init_database.query(Otx).filter(Otx.tx_hash==tx_hashes[4]).first()
|
||||||
assert otx.status == StatusEnum.SENT
|
assert otx.status & (StatusBits.IN_NETWORK | StatusBits.FINAL)
|
||||||
|
assert not is_error_status(otx.status)
|
||||||
|
|
||||||
|
|
||||||
def test_expired(
|
def test_expired(
|
||||||
@ -404,7 +414,7 @@ def test_obsoletion(
|
|||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
q = q.filter(Otx.status==StatusEnum.OBSOLETED)
|
q = q.filter(Otx.status.op('&')(StatusEnum.OBSOLETED.value)==StatusEnum.OBSOLETED.value)
|
||||||
z = 0
|
z = 0
|
||||||
for o in q.all():
|
for o in q.all():
|
||||||
z += o.nonce
|
z += o.nonce
|
||||||
@ -416,13 +426,13 @@ def test_obsoletion(
|
|||||||
|
|
||||||
session = SessionBase.create_session()
|
session = SessionBase.create_session()
|
||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
q = q.filter(Otx.status==StatusEnum.OBSOLETED)
|
q = q.filter(Otx.status.op('&')(StatusEnum.CANCELLED.value)==StatusEnum.OBSOLETED.value)
|
||||||
zo = 0
|
zo = 0
|
||||||
for o in q.all():
|
for o in q.all():
|
||||||
zo += o.nonce
|
zo += o.nonce
|
||||||
|
|
||||||
q = session.query(Otx)
|
q = session.query(Otx)
|
||||||
q = q.filter(Otx.status==StatusEnum.CANCELLED)
|
q = q.filter(Otx.status.op('&')(StatusEnum.CANCELLED.value)==StatusEnum.CANCELLED.value)
|
||||||
zc = 0
|
zc = 0
|
||||||
for o in q.all():
|
for o in q.all():
|
||||||
zc += o.nonce
|
zc += o.nonce
|
||||||
@ -450,16 +460,20 @@ def test_retry(
|
|||||||
q = q.filter(Otx.tx_hash==tx_hash)
|
q = q.filter(Otx.tx_hash==tx_hash)
|
||||||
otx = q.first()
|
otx = q.first()
|
||||||
|
|
||||||
assert otx.status == StatusEnum.RETRY
|
assert (otx.status & StatusEnum.RETRY.value) == StatusEnum.RETRY.value
|
||||||
|
assert is_error_status(otx.status)
|
||||||
|
|
||||||
set_sent_status(tx_hash, False)
|
set_sent_status(tx_hash, False)
|
||||||
set_ready(tx_hash)
|
set_ready(tx_hash)
|
||||||
|
|
||||||
|
init_database.commit()
|
||||||
|
|
||||||
q = init_database.query(Otx)
|
q = init_database.query(Otx)
|
||||||
q = q.filter(Otx.tx_hash==tx_hash)
|
q = q.filter(Otx.tx_hash==tx_hash)
|
||||||
otx = q.first()
|
otx = q.first()
|
||||||
|
|
||||||
assert otx.status == StatusEnum.RETRY
|
assert (otx.status & StatusEnum.RETRY.value) == StatusBits.QUEUED.value
|
||||||
|
assert not is_error_status(otx.status)
|
||||||
|
|
||||||
|
|
||||||
def test_get_account_tx(
|
def test_get_account_tx(
|
||||||
|
Loading…
Reference in New Issue
Block a user