2021-04-02 10:41:38 +02:00
# standard imports
import datetime
import logging
# external imports
from sqlalchemy import Column , Enum , String , Integer , DateTime , Text , or_ , ForeignKey
from sqlalchemy . ext . hybrid import hybrid_property , hybrid_method
from hexathon import (
strip_0x ,
)
# local imports
from . base import SessionBase
2021-04-02 11:51:00 +02:00
from . state import OtxStateLog
2021-04-02 10:41:38 +02:00
from chainqueue . db . enum import (
StatusEnum ,
StatusBits ,
status_str ,
is_error_status ,
)
2021-04-02 11:51:00 +02:00
from chainqueue . error import TxStateChangeError
2021-04-02 10:41:38 +02:00
logg = logging . getLogger ( ) . getChild ( __name__ )
class Otx ( SessionBase ) :
""" Outgoing transactions with local origin.
: param nonce : Transaction nonce
: type nonce : number
: param tx_hash : Tranasction hash
: type tx_hash : str , 0 x - hex
: param signed_tx : Signed raw transaction data
: type signed_tx : str , 0 x - hex
"""
__tablename__ = ' otx '
tracing = False
""" Whether to enable queue state tracing """
nonce = Column ( Integer )
2021-08-27 09:44:12 +02:00
""" Transaction nonce """
2021-04-02 10:41:38 +02:00
date_created = Column ( DateTime , default = datetime . datetime . utcnow )
2021-08-27 09:44:12 +02:00
""" Datetime when record was created """
2021-04-06 10:33:23 +02:00
date_updated = Column ( DateTime , default = datetime . datetime . utcnow )
2021-08-27 09:44:12 +02:00
""" Datetime when record was last updated """
2021-04-02 10:41:38 +02:00
tx_hash = Column ( String ( 66 ) )
2021-08-27 09:44:12 +02:00
""" Tranasction hash """
2021-04-02 10:41:38 +02:00
signed_tx = Column ( Text )
2021-08-27 09:44:12 +02:00
""" Signed raw transaction data """
2021-04-02 10:41:38 +02:00
status = Column ( Integer )
2021-08-27 09:44:12 +02:00
""" The status bit field of the transaction """
2021-04-02 10:41:38 +02:00
block = Column ( Integer )
2021-08-27 09:44:12 +02:00
""" The block number in which the transaction has been included """
def __init__ ( self , nonce , tx_hash , signed_tx ) :
self . nonce = nonce
self . tx_hash = strip_0x ( tx_hash )
self . signed_tx = strip_0x ( signed_tx )
self . status = StatusEnum . PENDING
2021-04-02 10:41:38 +02:00
def __set_status ( self , status , session ) :
self . status | = status
2021-04-06 10:33:23 +02:00
self . date_updated = datetime . datetime . utcnow ( )
2021-04-02 10:41:38 +02:00
session . add ( self )
session . flush ( )
def __reset_status ( self , status , session ) :
status_edit = ~ status & self . status
self . status & = status_edit
2021-04-06 10:33:23 +02:00
self . date_updated = datetime . datetime . utcnow ( )
2021-04-02 10:41:38 +02:00
session . add ( self )
session . flush ( )
def __status_already_set ( self , status ) :
r = bool ( self . status & status )
if r :
logg . warning ( ' status bit {} already set on {} ' . format ( status . name , self . tx_hash ) )
return r
def __status_not_set ( self , status ) :
r = not ( self . status & status )
if r :
logg . warning ( ' status bit {} not set on {} ' . format ( status . name , self . tx_hash ) )
return r
def set_block ( self , block , session = None ) :
""" Set block number transaction was mined in.
Only manipulates object , does not transaction or commit to backend .
: param block : Block number
: type block : number
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
session = SessionBase . bind_session ( session )
if self . block != None :
SessionBase . release_session ( session )
raise TxStateChangeError ( ' Attempted set block {} when block was already {} ' . format ( block , self . block ) )
self . block = block
2021-04-06 10:33:23 +02:00
self . date_updated = datetime . datetime . utcnow ( )
2021-04-02 10:41:38 +02:00
session . add ( self )
session . flush ( )
SessionBase . release_session ( session )
def waitforgas ( self , session = None ) :
""" Marks transaction as suspended pending gas funding.
Only manipulates object , does not transaction or commit to backend .
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
if self . __status_already_set ( StatusBits . GAS_ISSUES ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' GAS_ISSUES cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' GAS_ISSUES cannot be set on an entry with IN_NETWORK state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
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 ) :
""" Marks transaction as " fubar. " Any transaction marked this way is an anomaly and may be a symptom of a serious problem.
Only manipulates object , does not transaction or commit to backend .
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
2021-04-02 10:41:38 +02:00
"""
if self . __status_already_set ( StatusBits . UNKNOWN_ERROR ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' FUBAR cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if is_error_status ( self . status ) :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' FUBAR cannot be set on an entry with an error state already set ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
if not self . status & StatusBits . RESERVED :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 13:04:31 +02:00
SessionBase . release_session ( session )
2021-05-28 11:13:01 +02:00
raise TxStateChangeError ( ' FUBAR on tx that has not been RESERVED ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . UNKNOWN_ERROR | StatusBits . FINAL , session )
2021-08-27 11:10:21 +02:00
self . __reset_status ( StatusBits . QUEUED | StatusBits . RESERVED , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
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.
Only manipulates object , does not transaction or commit to backend .
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
2021-04-02 10:41:38 +02:00
"""
if self . __status_already_set ( StatusBits . NODE_ERROR ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' REJECTED cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' REJECTED cannot be set on an entry already IN_NETWORK ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if is_error_status ( self . status ) :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' REJECTED cannot be set on an entry with an error state already set ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
if not self . status & StatusBits . RESERVED :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 13:04:31 +02:00
SessionBase . release_session ( session )
2021-05-28 11:13:01 +02:00
raise TxStateChangeError ( ' REJECTED on tx that has not been RESERVED ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . NODE_ERROR | StatusBits . FINAL , session )
2021-08-26 17:07:53 +02:00
self . __reset_status ( StatusBits . QUEUED | StatusBits . RESERVED , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def override ( self , manual = False , session = None ) :
""" Marks transaction as manually overridden.
Only manipulates object , does not transaction or commit to backend .
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
2021-04-02 10:41:38 +02:00
"""
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' OVERRIDDEN/OBSOLETED cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . IN_NETWORK :
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' OVERRIDDEN/OBSOLETED cannot be set on an entry already IN_NETWORK ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . OBSOLETE :
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' OVERRIDDEN/OBSOLETED cannot be set on an entry already OBSOLETE ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . OBSOLETE , session )
2021-05-28 11:13:01 +02:00
if manual :
self . manual ( session = session )
2021-08-26 17:07:53 +02:00
self . __reset_status ( StatusBits . QUEUED | StatusBits . IN_NETWORK | StatusBits . RESERVED , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def manual ( self , session = None ) :
2021-08-27 09:44:12 +02:00
""" Marks transaction as having been manually overridden.
2021-04-02 10:41:38 +02:00
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
2021-04-02 10:41:38 +02:00
2021-08-27 09:44:12 +02:00
session = SessionBase . bind_session ( session )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' OVERRIDDEN/OBSOLETED cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . MANUAL , session )
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
2021-05-28 11:13:01 +02:00
2021-04-02 10:41:38 +02:00
def retry ( self , session = None ) :
2021-08-27 09:44:12 +02:00
""" Marks transaction as ready to retry after a timeout following a sendfail or a completed fee funding.
2021-04-02 10:41:38 +02:00
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
if self . __status_already_set ( StatusBits . QUEUED ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = status_str ( self . status )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' RETRY cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if not is_error_status ( self . status ) and not StatusBits . IN_NETWORK & self . status > 0 :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' RETRY cannot be set on an entry that has no error ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
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 ) :
""" Marks transaction as ready for initial send attempt.
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
if self . __status_already_set ( StatusBits . QUEUED ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' READYSEND cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if is_error_status ( self . status ) :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' READYSEND cannot be set on an errored state ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
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 ) :
""" Marks transaction as having been sent to network.
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
if self . __status_already_set ( StatusBits . IN_NETWORK ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SENT cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
2021-04-02 13:04:31 +02:00
if not self . status & StatusBits . RESERVED :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 13:04:31 +02:00
SessionBase . release_session ( session )
2021-05-28 11:13:01 +02:00
raise TxStateChangeError ( ' SENT on tx that has not been RESERVED ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . IN_NETWORK , session )
2021-04-02 11:51:00 +02:00
self . __reset_status ( StatusBits . RESERVED | StatusBits . DEFERRED | StatusBits . QUEUED | StatusBits . LOCAL_ERROR | StatusBits . NODE_ERROR , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def sendfail ( self , session = None ) :
""" Marks that an attempt to send the transaction to the network has failed.
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
if self . __status_already_set ( StatusBits . NODE_ERROR ) :
return
session = SessionBase . bind_session ( session )
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SENDFAIL cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SENDFAIL cannot be set on an entry with IN_NETWORK state set ( {} ) ' . format ( status ) )
2021-04-02 13:04:31 +02:00
if not self . status & StatusBits . RESERVED :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 13:04:31 +02:00
SessionBase . release_session ( session )
2021-05-28 11:13:01 +02:00
raise TxStateChangeError ( ' SENDFAIL on tx that has not been RESERVED ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
self . __set_status ( StatusBits . LOCAL_ERROR | StatusBits . DEFERRED , session )
2021-04-02 11:51:00 +02:00
self . __reset_status ( StatusBits . RESERVED | StatusBits . QUEUED | StatusBits . GAS_ISSUES , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
2021-04-02 11:51:00 +02:00
def reserve ( self , session = None ) :
2021-04-02 10:41:38 +02:00
""" Marks that a process to execute send attempt is underway
Only manipulates object , does not transaction or commit to backend .
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: raises cic_eth . db . error . TxStateChangeError : State change represents a sequence of events that should not exist .
"""
2021-04-02 13:04:31 +02:00
if self . __status_already_set ( StatusBits . RESERVED ) :
2021-04-02 10:41:38 +02:00
return
session = SessionBase . bind_session ( session )
2021-04-02 13:04:31 +02:00
if self . status & StatusBits . QUEUED == 0 :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 13:04:31 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' RESERVED cannot be set on an entry without QUEUED state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . FINAL :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' RESERVED cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' RESERVED cannot be set on an entry with IN_NETWORK state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
self . __reset_status ( StatusBits . QUEUED , session )
2021-04-02 11:51:00 +02:00
self . __set_status ( StatusBits . RESERVED , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def minefail ( self , block , session = None ) :
""" Marks that transaction was mined but code execution did not succeed.
Only manipulates object , does not transaction or commit to backend .
: param block : Block number transaction was mined in .
: type block : number
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: 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 :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' REVERTED cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if not self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' REVERTED cannot be set on an entry without IN_NETWORK state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if block != None :
self . block = block
self . __set_status ( StatusBits . NETWORK_ERROR | StatusBits . FINAL , session )
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def cancel ( self , confirmed = False , session = None ) :
""" Marks that the transaction has been succeeded by a new transaction with same nonce.
If set to confirmed , the previous state must be OBSOLETED , and will transition to CANCELLED - a finalized state . Otherwise , the state must follow a non - finalized state , and will be set to OBSOLETED .
Only manipulates object , does not transaction or commit to backend .
: param confirmed : Whether transition is to a final state .
: type confirmed : bool
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: 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 :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' CANCEL cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if confirmed :
2021-04-06 10:33:23 +02:00
status = self . status
2021-05-02 18:09:37 +02:00
if self . status > 0 and self . status & ( StatusBits . OBSOLETE | StatusBits . IN_NETWORK ) == 0 :
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-05-02 18:09:37 +02:00
raise TxStateChangeError ( ' CANCEL can only be set on an entry marked OBSOLETE or IN_NETWORK ( {} ) ' . format ( status ) )
self . __set_status ( StatusBits . FINAL , session )
self . __set_status ( StatusBits . OBSOLETE , session )
2021-04-06 06:53:07 +02:00
self . __reset_status ( StatusBits . RESERVED | StatusBits . QUEUED , session )
2021-04-02 10:41:38 +02:00
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
def success ( self , block , session = None ) :
""" Marks that transaction was successfully mined.
Only manipulates object , does not transaction or commit to backend .
: param block : Block number transaction was mined in .
: type block : number
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: 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 :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SUCCESS cannot be set on an entry with FINAL state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if not self . status & StatusBits . IN_NETWORK :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SUCCESS cannot be set on an entry without IN_NETWORK state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if is_error_status ( self . status ) :
2021-04-06 10:33:23 +02:00
status = self . status
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
2021-04-06 10:33:23 +02:00
raise TxStateChangeError ( ' SUCCESS cannot be set on an entry with error state set ( {} ) ' . format ( status ) )
2021-04-02 10:41:38 +02:00
if block != None :
self . block = block
self . __set_status ( StatusEnum . SUCCESS , session )
if self . tracing :
self . __state_log ( session = session )
SessionBase . release_session ( session )
@staticmethod
def get ( status = 0 , limit = 4096 , status_exact = True , session = None ) :
""" Returns outgoing transaction lists by status.
Status may either be matched exactly , or be an upper bound of the integer value of the status enum .
: param status : Status value to use in query
: type status : cic_eth . db . enum . StatusEnum
: param limit : Max results to return
: type limit : number
2021-08-27 09:44:12 +02:00
: param status_exact : If false , records where status integer value is less than or equal to the argument will be returned
: type status_exact : bool
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
2021-04-02 10:41:38 +02:00
: returns : List of transaction hashes
: rtype : tuple , where first element is transaction hash
2021-08-27 09:44:12 +02:00
: todo : This approach is obsolete and this method may return unexpected results ; the original status enum was organized so that higher status values matched state of processing towards final state . This is no longer the case .
2021-04-02 10:41:38 +02:00
"""
e = None
session = SessionBase . bind_session ( session )
if status_exact :
e = session . query ( Otx . tx_hash ) . filter ( Otx . status == status ) . order_by ( Otx . date_created . asc ( ) ) . limit ( limit ) . all ( )
else :
e = session . query ( Otx . tx_hash ) . filter ( Otx . status < = status ) . order_by ( Otx . date_created . asc ( ) ) . limit ( limit ) . all ( )
SessionBase . release_session ( session )
return e
@staticmethod
def load ( tx_hash , session = None ) :
""" Retrieves the outgoing transaction record by transaction hash.
: param tx_hash : Transaction hash
: type tx_hash : str , 0 x - hex
2021-08-27 09:44:12 +02:00
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: rtype : chainqueue . db . models . otx . Otx
: returns : Matching otx record
2021-04-02 10:41:38 +02:00
"""
session = SessionBase . bind_session ( session )
q = session . query ( Otx )
2021-04-02 11:51:00 +02:00
q = q . filter ( Otx . tx_hash == strip_0x ( tx_hash ) )
2021-04-02 10:41:38 +02:00
SessionBase . release_session ( session )
return q . first ( )
def __state_log ( self , session ) :
l = OtxStateLog ( self )
session . add ( l )
# TODO: it is not safe to return otx here unless session has been passed in
@staticmethod
2021-04-02 11:51:00 +02:00
def add ( nonce , tx_hash , signed_tx , session = None ) :
2021-08-27 09:44:12 +02:00
""" Add a new otx record to database.
The resulting Otx object will only be returned if the database session is provided by the caller . Otherwise , the returnvalue of the method will be None .
: param tx_hash : Transaction hash , in hex
: type tx_hash : str
: param signed_tx : Signed transaction data , in hex
: type signed_tx : str
: param session : Sqlalchemy database session
: type session : sqlalchemy . orm . Session
: rtype : chainqueue . db . models . otx . Otx
: returns : Matching otx record
"""
2021-04-02 10:41:38 +02:00
external_session = session != None
session = SessionBase . bind_session ( session )
2021-04-02 11:51:00 +02:00
otx = Otx ( nonce , tx_hash , signed_tx )
2021-04-02 10:41:38 +02:00
session . add ( otx )
session . flush ( )
if otx . tracing :
otx . __state_log ( session = session )
session . flush ( )
SessionBase . release_session ( session )
if not external_session :
return None
return otx