diff --git a/chainqueue/state.py b/chainqueue/state.py index e5be424..ebb2ae7 100644 --- a/chainqueue/state.py +++ b/chainqueue/state.py @@ -39,7 +39,7 @@ class Verify: if from_state & state_store.RESERVED: return 'not reserved' if from_state & state_store.mask_error: - return 'already finalized' + return 'already in error state' def NODE_ERROR(self, state_store, from_state): @@ -50,7 +50,14 @@ class Verify: if from_state & state_store.RESERVED: return 'not reserved' if from_state & state_store.mask_error: + return 'already in error state' + + + def NETWORK_ERROR(self, state_store, from_state): + if from_state & state_store.FINAL: return 'already finalized' + if from_state & state_store.IN_NETWORK: + return 'already in network' def OBSOLETE(self, state_store, from_state): @@ -75,7 +82,28 @@ class Verify: return 'not in error state' elif from_state & state_store.mask_error: return 'no first send on error state' - + + + def SENDFAIL(self, state_store, from_state): + return self.NODE_ERROR(state_store, from_state) + + + def FINAL(self, state_store, from_state): + if from_state & state_store.FINAL: + return 'already finalized' + + + def _MINEFAIL(self, state_store, from_state): + return self.NETWORK_ERROR(state_store, from_state) + + + def _CANCEL(self, state_store, from_state): + if from_state: + if from_state & state_store.FINAL: + return 'already finalized' + if not from_state & (state_store.OBSOLETE | state_store.IN_NETWORK): + return 'can only cancel state having OBSOLETE and/or IN_NETWORK' + class Status(shep.persist.PersistedState): @@ -103,7 +131,9 @@ class Status(shep.persist.PersistedState): self.alias('CANCELLED', self.IN_NETWORK | self.FINAL | self.OBSOLETE) self.alias('OVERRIDDEN', self.FINAL | self.OBSOLETE | self.MANUAL) self.alias('REJECTED', self.NODE_ERROR | self.FINAL) - self.alias('REVERTED', self.IN_NETWORK | self.FINAL, self.NETWORK_ERROR) + self.alias('REVERTED', self.IN_NETWORK | self.FINAL | self.NETWORK_ERROR) self.alias('SUCCESS', self.IN_NETWORK | self.FINAL) + self.alias('_MINEFAIL', self.FINAL | self.NETWORK_ERROR) + self.alias('_CANCEL', self.FINAL | self.OBSOLETE) self.mask_error = self.LOCAL_ERROR | self.NODE_ERROR | self.NETWORK_ERROR | self.UNKNOWN_ERROR diff --git a/chainqueue/tx.py b/chainqueue/tx.py index 384223f..4c51d36 100644 --- a/chainqueue/tx.py +++ b/chainqueue/tx.py @@ -6,21 +6,34 @@ logg = logging.getLogger(__name__) class Tx: - def __init__(self, store, seq, tx_hash, signed_tx, cache=None): - self.store = store - self.seq = seq - self.tx_hash = tx_hash - self.signed_tx = signed_tx + def __init__(self, state_store, index_store, tx_hash, cache=None): + self.state_store = state_store + self.index_store = index_store self.cache = cache - self.k = self.__to_key(str(seq), tx_hash) + self.tx_hash = tx_hash + self.signed_tx = None + self.seq = None + self.k = None + self.synced = False def __to_key(self, k, v): return '{:>010s}_{}'.format(k, v) - def create(self): - self.store.put(self.k, self.signed_tx) + def create(self, seq, signed_tx): + n = str(seq) + self.k = self.__to_key(n, self.tx_hash) + self.state_store.put(self.k, signed_tx) + self.index_store.put(self.tx_hash, n) + self.synced = True + + + def load(self): + seq = self.index_store.get(self.tx_hash) + self.k = self.__to_key(seq, self.tx_hash) + self.signed_tx = self.state_store.get(self.k) + self.synced = True def __match_state(self, state): @@ -45,8 +58,11 @@ class Tx: self.state.set(self.k, self.store.NODE_ERROR) - def override(self): - self.state.set(self.k, self.store.OBSOLETE) + def override(self, manual=False): + if manual: + self.state.set(self.k, self.store.OBSOLETE | self.store.MANUAL) + else: + self.state.set(self.k, self.store.OBSOLETE) def manual(self): @@ -64,3 +80,52 @@ class Tx: return self.state.change(self.k, self.store.QUEUED, self.store.INSUFFICIENT_FUNDS) + + def sent(self): + if self.__match_state(self.store.IN_NETWORK): + return + self.state.change(self.k, self.state.IN_NETWORK, self.state.RESERVED | self.state.DEFERRED | self.state.QUEUED | self.state.LOCAL_ERROR | self.state.NODE_ERROR) + + + def sendfail(self): + if self.__match_state(self.store.NODE_ERROR): + return + self.state.change(self.k, self.state.LOCAL_ERROR | self.state.DEFERRED, self.state.RESERVED | self.state.QUEUED | self.state.INSUFFICIENT_FUNDS) + + + def reserve(self): + if self.__match_state(self.store.RESERVED): + return + self.state.change(self.k, self.state.RESERVED, self.state.QUEUED) + + + def minefail(self, block): + if self.__match_state(self.store.NETWORK_ERROR): + return + self.state.set(self.k, self.state.NETWORK_ERROR) + if self.cache: + self.cache.set_block(self.tx_hash, block) + + + def cancel(self, confirmed=False): + if confirmed: + self.state.change(self.k, self.state.OBSOLETE | self.state.FINAL, self.state.RESERVED | self.state.QUEUED) + else: + self.state.change(self.k, self.state.OBSOLETE, self.state.RESERVED | self.state.QUEUED) + + + def success(self, block): + self.state.set(self.state.FINAL) + if self.cache: + self.cache.set_block(self.tx_hash, block) + + + def get(status=0, limit=4096, status_exact=True): + hashes = [] + i = 0 + for k in self.state.list(status): + if status_exact: + if self.state.status(k) != status: + continue + hashes.append(k) + return k diff --git a/tests/test_shep.py b/tests/test_shep.py index 67ef305..796b9f2 100644 --- a/tests/test_shep.py +++ b/tests/test_shep.py @@ -17,6 +17,20 @@ logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() +class MockIndexStore: + + def __init__(self): + self.store = {} + + + def put(self, k, v): + self.store[k] = v + + + def get(self, k): + return self.store.get(k) + + class TestShepBase(unittest.TestCase): def setUp(self): @@ -35,9 +49,13 @@ class TestShep(TestShepBase): tx_hash = add_0x(os.urandom(20).hex()) signed_tx = add_0x(os.urandom(128).hex()) nonce = 42 - tx = Tx(self.state, nonce, tx_hash, signed_tx) - tx.create() - logg.debug('file {}'.format(self.path)) + mock_store = MockIndexStore() + tx = Tx(self.state, mock_store, tx_hash) + tx.create(nonce, signed_tx) + + tx_retrieved = Tx(self.state, mock_store, tx_hash) + tx_retrieved.load() + self.assertEqual(tx_retrieved.signed_tx, signed_tx) def test_shep_valid(self):