def _execute(self, request): # Given tail hash, fetches the bundle from the tangle # and validates it. # Returns List[List[TransactionTrytes]] # (outer list has one item in current implementation) bundle = GetBundlesCommand( self.adapter)(transactions=[request['tail_hash']]) BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0]) return { 'trytes': bundle[0], }
def _execute(self, request): depth = request['depth'] # type: int min_weight_magnitude = request['minWeightMagnitude'] # type: int transaction = request['transaction'] # type: TransactionHash gb_response = GetBundlesCommand(self.adapter)(transaction=transaction) # Note that we only replay the first bundle returned by # ``getBundles``. bundle = gb_response['bundles'][0] # type: Bundle return SendTrytesCommand(self.adapter)( depth=depth, minWeightMagnitude=min_weight_magnitude, trytes=bundle.as_tryte_strings(), )
class GetBundlesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetBundlesCommand(MockAdapter()).get_request_filter skip_value_check = True def setUp(self): super(GetBundlesRequestFilterTestCase, self).setUp() # noinspection SpellCheckingInspection self.transaction = ('TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' 'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ') def test_pass_happy_path(self): """ Request is valid. """ # Raw trytes are extracted to match the IRI's JSON protocol. request = { 'transaction': self.transaction, } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ filter_ = self._filter({ # Any TrytesCompatible value will work here. 'transaction': TransactionHash(self.transaction), }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'transaction': self.transaction, }, ) def test_fail_empty(self): """ Request is empty. """ self.assertFilterErrors( {}, { 'transaction': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'transaction': TransactionHash(self.transaction), # SAY "WHAT" AGAIN! 'what': 'augh!', }, { 'what': [f.FilterMapper.CODE_EXTRA_KEY], }, ) def test_fail_transaction_wrong_type(self): """ ``transaction`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'transaction': 42, }, { 'transaction': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_transaction_not_trytes(self): """ ``transaction`` contains invalid characters. """ self.assertFilterErrors( { 'transaction': b'not valid; must contain only uppercase and "9"', }, { 'transaction': [Trytes.CODE_NOT_TRYTES], }, )
def setUp(self): super(GetBundlesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetBundlesCommand(self.adapter)
def get_bundles_from_transaction_hashes( adapter, transaction_hashes, inclusion_states, ): # type: (BaseAdapter, Iterable[TransactionHash], bool) -> List[Bundle] """ Given a set of transaction hashes, returns the corresponding bundles, sorted by tail transaction timestamp. """ transaction_hashes = list(transaction_hashes) if not transaction_hashes: return [] my_bundles = [] # type: List[Bundle] # Sort transactions into tail and non-tail. tail_transaction_hashes = set() non_tail_bundle_hashes = set() gt_response = GetTrytesCommand(adapter)(hashes=transaction_hashes) all_transactions = list( map( Transaction.from_tryte_string, gt_response['trytes'], )) # type: List[Transaction] for txn in all_transactions: if txn.is_tail: tail_transaction_hashes.add(txn.hash) else: # Capture the bundle ID instead of the transaction hash so # that we can query the node to find the tail transaction # for that bundle. non_tail_bundle_hashes.add(txn.bundle_hash) if non_tail_bundle_hashes: for txn in find_transaction_objects( adapter=adapter, bundles=list(non_tail_bundle_hashes), ): if txn.is_tail: if txn.hash not in tail_transaction_hashes: all_transactions.append(txn) tail_transaction_hashes.add(txn.hash) # Filter out all non-tail transactions. tail_transactions = [ txn for txn in all_transactions if txn.hash in tail_transaction_hashes ] # Attach inclusion states, if requested. if inclusion_states: gli_response = GetLatestInclusionCommand(adapter)( hashes=list(tail_transaction_hashes), ) for txn in tail_transactions: txn.is_confirmed = gli_response['states'].get(txn.hash) # Find the bundles for each transaction. for txn in tail_transactions: gb_response = GetBundlesCommand(adapter)(transaction=txn.hash) txn_bundles = gb_response['bundles'] # type: List[Bundle] if inclusion_states: for bundle in txn_bundles: bundle.is_confirmed = txn.is_confirmed my_bundles.extend(txn_bundles) return list( sorted( my_bundles, key=lambda bundle_: bundle_.tail_transaction.timestamp, ))
def setUp(self): super(GetBundlesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetBundlesCommand(self.adapter) # Tail transaction hash self.tx_hash = TransactionHash( 'TOYJPHKMLQNDVLDHDILARUJCCIUMQBLUSWPCTIVA' 'DRXICGYDGSVPXFTILFFGAPICYHGGJ9OHXINFX9999' ) self.bundle_trytes = [ # Order is important if we don't convert to bundle representation. # Tail transaction should be the first. TransactionTrytes( 'NBTCPCFDEACCPCBDVC9DTCQAJ9RBTC9D9DCDQAEAKDCDFD9DSCFAJ9VBCDJDTCQAJ9' 'ZBMDYBCCKB99999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999SYRABNN9JD9PNDL' 'IKUNCECUELTOHNLFMVD99999999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX' 'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXFSEWUNJOEGNU' 'I9QOCRFMYSIFAZLJHKZBPQZZYFG9ORYCRDX9TOMJPFCRB9R9KPUUGFPVOWYXFIWEW9' '999BGUEHHGAIWWQBCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJL' 'EDAMYVRGABAWBY9999SYRABNN9JD9PNDLIKUNCECUELTOQZPSBDILVHJQVCEOICFAD' 'YKZVGMOAXJRQNTCKMHGTAUMPGJJMX9LNF' ), TransactionTrytes( '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999WUQXEGBVIECGIWO9IGSYKWWPYCIVUJJGSJPWGIAFJPYSF9NSQOHWAHS9P' '9PWQHOBXNNQIF9IRHVQXKPZW999999999999999999999999999XZUIENOTTBKJMDP' 'RXWGQYG9PWGTHNLFMVD99A99999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX' 'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXBGUEHHGAIWWQ' 'BCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJLEDAMYVRGABAWBY9' '999MYIYBTGIOQYYZFJBLIAWMPSZEFFTXUZPCDIXSLLQDQSFYGQSQOGSPKCZNLVSZ9L' 'MCUWVNGEN9EJEW9999XZUIENOTTBKJMDPRXWGQYG9PWGTXUO9AXMP9FLMDRMADLRPW' 'CZCJBROYCDRJMYU9HDYJM9NDBFUPIZVTR' ), ] # Add a spam tx. When this is returned, traverse_bundle knows it hit a # different bundle and should stop. self.spam_trytes = TransactionTrytes( 'SPAMSPAMSPAM999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999JECDITWO9999999' '999999999999ONLFMVD99999999999999999999VVCHSQSRVFKSBONDWB9EAQEMQOY' 'YRBIZHTBJLYNAVDHZPUZAZ9LYHXWKBEJ9IPR9FAMFLT9EEOHVYWUPRHHSRCILCLWFD' 'GBYBFFOKMCSAPVD9VGZZRRGBLGMZMXD9RMZQDBLMGN9BATWZGULRBCYQEIKIRBPHC9' '999KTLTRSYOWBD9HVNP9GCUABARNGMYXUZKXWRPGOPETZLKYYC9Z9EYXIWVARUBMBM' 'BPXGORN9WPBLY99999ZRBVQWULRFXDNDYZKRKIXPZQT9JJJH9FZU9PVWZJWLXBPODP' 'EHMKTTAGEPLPHUQCZNLDSHERONOMHJCOI' )
class GetBundlesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetBundlesCommand(MockAdapter()).get_request_filter skip_value_check = True def setUp(self): super(GetBundlesRequestFilterTestCase, self).setUp() self.transactions = [ ( 'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' 'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ' ), ( 'TESTVALUE9DONTUSEINPRODUCTION99999TAXQBF' 'ZMUQLZ9RXRRXQOUSAMGAPEKTZNERIKSDYGHQA9999' ), ] def test_pass_happy_path(self): """ Request is valid. """ # Raw trytes are extracted to match the IRI's JSON protocol. request = { 'transactions': self.transactions, } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ # Convert first to TranscationHash tx_hashes = [] for tx in self.transactions: tx_hashes.append(TransactionHash(tx)) filter_ = self._filter({ # Any TrytesCompatible value will work here. 'transactions': tx_hashes, }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'transactions': self.transactions, }, ) def test_fail_empty(self): """ Request is empty. """ self.assertFilterErrors( {}, { 'transactions': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'transactions': self.transactions, # SAY "WHAT" AGAIN! 'what': 'augh!', }, { 'what': [f.FilterMapper.CODE_EXTRA_KEY], }, ) def test_fail_transaction_wrong_type(self): """ ``transactions`` contains no TrytesCompatible value. """ self.assertFilterErrors( { 'transactions': [42], }, { 'transactions.0': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_transaction_not_trytes(self): """ ``transactions`` contains invalid characters. """ self.assertFilterErrors( { 'transactions': [b'not valid; must contain only uppercase and "9"'], }, { 'transactions.0': [Trytes.CODE_NOT_TRYTES], }, ) def test_fail_no_list(self): """ ``transactions`` has one hash rather than a list of hashes. """ self.assertFilterErrors( { 'transactions': self.transactions[0], }, { 'transactions': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_transactions_contents_invalid(self): """ ``transactions`` is a non-empty array, but it contains invlaid values. """ self.assertFilterErrors( { 'transactions': [ b'', True, None, b'not valid transaction hash', # A valid tx hash, this should not produce error TransactionHash(self.transactions[0]), 65498731, b'9' * (TransactionHash.LEN +1), ], }, { 'transactions.0': [f.Required.CODE_EMPTY], 'transactions.1': [f.Type.CODE_WRONG_TYPE], 'transactions.2': [f.Required.CODE_EMPTY], 'transactions.3': [Trytes.CODE_NOT_TRYTES], 'transactions.5': [f.Type.CODE_WRONG_TYPE], 'transactions.6': [Trytes.CODE_WRONG_FORMAT], }, )
def _execute(self, request): stop = request['stop'] # type: Optional[int] inclusion_states = request['inclusionStates'] # type: bool seed = request['seed'] # type: Seed start = request['start'] # type: int generator = AddressGenerator(seed) ft_command = FindTransactionsCommand(self.adapter) # Determine the addresses we will be scanning, and pull their # transaction hashes. if stop is None: # This is similar to the ``getNewAddresses`` command, except it # is interested in all the addresses that `getNewAddresses` # skips. hashes = [] for addy in generator.create_generator(start): ft_response = ft_command(addresses=[addy]) if ft_response.get('hashes'): hashes += ft_response['hashes'] else: break # Reset the command so that we can call it again. ft_command.reset() else: ft_response =\ ft_command(addresses=generator.get_addresses(start, stop - start)) hashes = ft_response.get('hashes') or [] all_bundles = [] # type: List[Bundle] if hashes: # Sort transactions into tail and non-tail. tail_transaction_hashes = set() non_tail_bundle_hashes = set() gt_response = GetTrytesCommand(self.adapter)(hashes=hashes) all_transactions = list( map( Transaction.from_tryte_string, gt_response['trytes'], )) # type: List[Transaction] for txn in all_transactions: if txn.is_tail: tail_transaction_hashes.add(txn.hash) else: # Capture the bundle ID instead of the transaction hash so that # we can query the node to find the tail transaction for that # bundle. non_tail_bundle_hashes.add(txn.bundle_hash) if non_tail_bundle_hashes: for txn in self._find_transactions( bundles=list(non_tail_bundle_hashes)): if txn.is_tail: if txn.hash not in tail_transaction_hashes: all_transactions.append(txn) tail_transaction_hashes.add(txn.hash) # Filter out all non-tail transactions. tail_transactions = [ txn for txn in all_transactions if txn.hash in tail_transaction_hashes ] # Attach inclusion states, if requested. if inclusion_states: gli_response = GetLatestInclusionCommand(self.adapter)( hashes=list(tail_transaction_hashes), ) for txn in tail_transactions: txn.is_confirmed = gli_response['states'].get(txn.hash) # Find the bundles for each transaction. for txn in tail_transactions: gb_response = GetBundlesCommand( self.adapter)(transaction=txn.hash) txn_bundles = gb_response['bundles'] # type: List[Bundle] if inclusion_states: for bundle in txn_bundles: bundle.is_confirmed = txn.is_confirmed all_bundles.extend(txn_bundles) return { # Sort bundles by tail transaction timestamp. 'bundles': list( sorted( all_bundles, key=lambda bundle_: bundle_.tail_transaction.timestamp, )), }
class GetBundlesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetBundlesCommand(MockAdapter()).get_request_filter skip_value_check = True def setUp(self): super(GetBundlesRequestFilterTestCase, self).setUp() # noinspection SpellCheckingInspection self.transaction = ( b'ORLSCIMM9ZONOUSPYYWLOEMXQZLYEHCBEDQSHZOG' b'OPZCZCDZYTDPGEEUXWUZ9FQYCT9OGS9PICOOX9999' ) def test_pass_happy_path(self): """ Request is valid. """ request = { 'transaction': TransactionHash(self.transaction) } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ filter_ = self._filter({ # Any TrytesCompatible value will work here. 'transaction': binary_type(self.transaction), }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'transaction': TransactionHash(self.transaction), }, ) def test_fail_empty(self): """ Request is empty. """ self.assertFilterErrors( {}, { 'transaction': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'transaction': TransactionHash(self.transaction), # SAY "WHAT" AGAIN! 'what': 'augh!', }, { 'what': [f.FilterMapper.CODE_EXTRA_KEY], }, ) def test_fail_transaction_wrong_type(self): """ ``transaction`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'transaction': text_type(self.transaction, 'ascii'), }, { 'transaction': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_transaction_not_trytes(self): """ ``transaction`` contains invalid characters. """ self.assertFilterErrors( { 'transaction': b'not valid; must contain only uppercase and "9"', }, { 'transaction': [Trytes.CODE_NOT_TRYTES], }, )