2021-02-01 18:12:51 +01:00
|
|
|
|
# standard imports
|
|
|
|
|
import logging
|
2021-03-06 18:55:51 +01:00
|
|
|
|
import datetime
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
|
|
|
|
# third-party imports
|
2021-03-06 18:55:51 +01:00
|
|
|
|
from sqlalchemy import Column, String, Integer, DateTime
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
|
|
|
|
# local imports
|
|
|
|
|
from .base import SessionBase
|
2021-03-06 18:55:51 +01:00
|
|
|
|
from cic_eth.error import (
|
|
|
|
|
InitializationError,
|
|
|
|
|
IntegrityError,
|
|
|
|
|
)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
|
|
|
|
logg = logging.getLogger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Nonce(SessionBase):
|
|
|
|
|
"""Provides thread-safe nonce increments.
|
|
|
|
|
"""
|
|
|
|
|
__tablename__ = 'nonce'
|
|
|
|
|
|
|
|
|
|
nonce = Column(Integer)
|
|
|
|
|
address_hex = Column(String(42))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get(address, session=None):
|
2021-03-01 21:15:17 +01:00
|
|
|
|
session = SessionBase.bind_session(session)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
2021-03-01 21:15:17 +01:00
|
|
|
|
q = session.query(Nonce)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
q = q.filter(Nonce.address_hex==address)
|
|
|
|
|
nonce = q.first()
|
|
|
|
|
|
|
|
|
|
nonce_value = None
|
|
|
|
|
if nonce != None:
|
|
|
|
|
nonce_value = nonce.nonce;
|
|
|
|
|
|
2021-03-01 21:15:17 +01:00
|
|
|
|
SessionBase.release_session(session)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
|
|
|
|
return nonce_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-03-06 18:55:51 +01:00
|
|
|
|
def __get(conn, address):
|
|
|
|
|
r = conn.execute("SELECT nonce FROM nonce WHERE address_hex = '{}'".format(address))
|
2021-02-01 18:12:51 +01:00
|
|
|
|
nonce = r.fetchone()
|
|
|
|
|
if nonce == None:
|
|
|
|
|
return None
|
|
|
|
|
return nonce[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-03-06 18:55:51 +01:00
|
|
|
|
def __set(conn, address, nonce):
|
|
|
|
|
conn.execute("UPDATE nonce set nonce = {} WHERE address_hex = '{}'".format(nonce, address))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def __init(conn, address, nonce):
|
|
|
|
|
conn.execute("INSERT INTO nonce (nonce, address_hex) VALUES ({}, '{}')".format(nonce, address))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def init(address, nonce=0, session=None):
|
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
|
|
|
|
|
|
q = session.query(Nonce)
|
|
|
|
|
q = q.filter(Nonce.address_hex==address)
|
|
|
|
|
o = q.first()
|
|
|
|
|
if o != None:
|
|
|
|
|
session.flush()
|
|
|
|
|
raise InitializationError('nonce on {} already exists ({})'.format(address, o.nonce))
|
2021-03-01 21:15:17 +01:00
|
|
|
|
session.flush()
|
2021-03-06 18:55:51 +01:00
|
|
|
|
Nonce.__init(session, address, nonce)
|
|
|
|
|
|
|
|
|
|
SessionBase.release_session(session)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
|
|
|
|
|
|
2021-03-06 18:55:51 +01:00
|
|
|
|
# TODO: Incrementing nonce MUST be done by separate tasks.
|
2021-02-01 18:12:51 +01:00
|
|
|
|
@staticmethod
|
2021-03-06 18:55:51 +01:00
|
|
|
|
def next(address, initial_if_not_exists=0):
|
2021-02-01 18:12:51 +01:00
|
|
|
|
"""Generate next nonce for the given address.
|
|
|
|
|
|
|
|
|
|
If there is no previous nonce record for the address, the nonce may be initialized to a specified value, or 0 if no value has been given.
|
|
|
|
|
|
|
|
|
|
:param address: Associate Ethereum address
|
|
|
|
|
:type address: str, 0x-hex
|
|
|
|
|
:param initial_if_not_exists: Initial nonce value to set if no record exists
|
|
|
|
|
:type initial_if_not_exists: number
|
|
|
|
|
:returns: Nonce
|
|
|
|
|
:rtype: number
|
|
|
|
|
"""
|
2021-03-06 18:55:51 +01:00
|
|
|
|
#session = SessionBase.bind_session(session)
|
2021-03-01 21:15:17 +01:00
|
|
|
|
|
2021-03-06 18:55:51 +01:00
|
|
|
|
#session.begin_nested()
|
|
|
|
|
conn = Nonce.engine.connect()
|
2021-02-01 18:12:51 +01:00
|
|
|
|
if Nonce.transactional:
|
2021-03-06 18:55:51 +01:00
|
|
|
|
conn.execute('BEGIN')
|
|
|
|
|
conn.execute('LOCK TABLE nonce IN SHARE ROW EXCLUSIVE MODE')
|
|
|
|
|
logg.debug('locking nonce table for address {}'.format(address))
|
|
|
|
|
nonce = Nonce.__get(conn, address)
|
2021-02-01 18:12:51 +01:00
|
|
|
|
logg.debug('get nonce {} for address {}'.format(nonce, address))
|
|
|
|
|
if nonce == None:
|
|
|
|
|
nonce = initial_if_not_exists
|
|
|
|
|
logg.debug('setting default nonce to {} for address {}'.format(nonce, address))
|
2021-03-06 18:55:51 +01:00
|
|
|
|
Nonce.__init(conn, address, nonce)
|
|
|
|
|
Nonce.__set(conn, address, nonce+1)
|
|
|
|
|
if Nonce.transactional:
|
|
|
|
|
conn.execute('COMMIT')
|
|
|
|
|
logg.debug('unlocking nonce table for address {}'.format(address))
|
|
|
|
|
conn.close()
|
|
|
|
|
#session.commit()
|
|
|
|
|
|
|
|
|
|
#SessionBase.release_session(session)
|
|
|
|
|
return nonce
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NonceReservation(SessionBase):
|
|
|
|
|
|
|
|
|
|
__tablename__ = 'nonce_task_reservation'
|
|
|
|
|
|
|
|
|
|
nonce = Column(Integer)
|
|
|
|
|
key = Column(String)
|
|
|
|
|
date_created = Column(DateTime, default=datetime.datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def peek(key, session=None):
|
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
|
|
|
|
|
|
q = session.query(NonceReservation)
|
|
|
|
|
q = q.filter(NonceReservation.key==key)
|
|
|
|
|
o = q.first()
|
|
|
|
|
|
|
|
|
|
nonce = None
|
|
|
|
|
if o != None:
|
|
|
|
|
nonce = o.nonce
|
|
|
|
|
|
|
|
|
|
session.flush()
|
2021-03-01 21:15:17 +01:00
|
|
|
|
|
|
|
|
|
SessionBase.release_session(session)
|
2021-03-06 18:55:51 +01:00
|
|
|
|
|
2021-02-01 18:12:51 +01:00
|
|
|
|
return nonce
|
|
|
|
|
|
|
|
|
|
|
2021-03-06 18:55:51 +01:00
|
|
|
|
@staticmethod
|
|
|
|
|
def release(key, session=None):
|
|
|
|
|
|
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
|
|
|
|
|
|
nonce = NonceReservation.peek(key, session=session)
|
|
|
|
|
|
|
|
|
|
q = session.query(NonceReservation)
|
|
|
|
|
q = q.filter(NonceReservation.key==key)
|
|
|
|
|
o = q.first()
|
|
|
|
|
|
|
|
|
|
if o == None:
|
|
|
|
|
raise IntegrityError('nonce for key {}'.format(nonce))
|
|
|
|
|
SessionBase.release_session(session)
|
|
|
|
|
|
|
|
|
|
session.delete(o)
|
|
|
|
|
session.flush()
|
|
|
|
|
|
|
|
|
|
SessionBase.release_session(session)
|
|
|
|
|
|
|
|
|
|
return nonce
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def next(address, key, session=None):
|
|
|
|
|
session = SessionBase.bind_session(session)
|
|
|
|
|
|
|
|
|
|
if NonceReservation.peek(key, session) != None:
|
|
|
|
|
raise IntegrityError('nonce for key {}'.format(key))
|
|
|
|
|
|
|
|
|
|
nonce = Nonce.next(address)
|
|
|
|
|
|
|
|
|
|
o = NonceReservation()
|
|
|
|
|
o.nonce = nonce
|
|
|
|
|
o.key = key
|
|
|
|
|
session.add(o)
|
|
|
|
|
|
|
|
|
|
SessionBase.release_session(session)
|
|
|
|
|
|
|
|
|
|
return nonce
|