diff --git a/python-regression/setup.py b/python-regression/setup.py index 834f184bdf..8502b95cd7 100644 --- a/python-regression/setup.py +++ b/python-regression/setup.py @@ -6,7 +6,7 @@ author='DyrellC', packages=['util','tests'], install_requires=[ - 'pyota', + 'pyota[pow]', 'aloe', 'pyyaml', ] diff --git a/python-regression/tests/features/machine1/1_local_snapshots_tests.feature b/python-regression/tests/features/machine1/1_local_snapshots_tests.feature index 120f18cd8f..9895b4617f 100644 --- a/python-regression/tests/features/machine1/1_local_snapshots_tests.feature +++ b/python-regression/tests/features/machine1/1_local_snapshots_tests.feature @@ -75,7 +75,6 @@ Feature: Test Bootstrapping With LS |keys |values |type | |state |True |bool | - Scenario: Old transactions are pruned Takes a node with a large db and transaction pruning enabled, and checks to make sure that the transactions below the pruning depth are no longer present. diff --git a/python-regression/tests/features/machine3/3_transaction_tests.feature b/python-regression/tests/features/machine3/3_transaction_tests.feature index 6280fa0758..d5ce04686c 100644 --- a/python-regression/tests/features/machine3/3_transaction_tests.feature +++ b/python-regression/tests/features/machine3/3_transaction_tests.feature @@ -43,7 +43,6 @@ Feature: Test transaction confirmation | keys | values | type | | states | False | boolListMixed | - Scenario: Value Transactions are confirmed In this test, a number of value transactions will be made to a specified node. A milestone will be issued that references these transactions, and this should @@ -87,3 +86,114 @@ Feature: Test transaction confirmation | keys | values | type | | states | False | boolListMixed | + Scenario: Valid value transfer bundle that doesnt affect ledger state + We want to ascertain that ledger state is always calculated correctly. + Even in the presence of a bundle that handles funds but without changing address + + Then "1" transaction is issued on "nodeA-m3" with: + |keys |values |type | + |address |TEST_ADDRESS |staticValue | + |value |0 |int | + |tag |ZERO9VALUE |string | + + Then a value bundle which moves funds back and forth from an address is generated referencing the previous transaction with: + |keys |values |type | + |seed |THE_BANK |staticList | + |value |100 |int | + |tag |FAKE9VALUE |string | + + Then a transaction is issued referencing the previous transaction + |keys |values |type | + |seed |THE_BANK |staticList | + |address |TEST_ADDRESS |staticValue | + |value |11 |int | + |tag |VALUE9TRANSACTION |string | + + #In the default test, the latest sent index will be 52. The next milestone issued should be 53. + When a milestone is issued with index 53 and references: + |keys |values |type | + |transactions |previousTransaction |responseValue | + + #Give the node time to solidify the milestone + And we wait "15" second/seconds + + Given "getBalances" is called on "nodeA-m3" with: + |keys |values |type | + |addresses |FAKE_SPEND_ADDRESSES |staticList | + + Then the response for "getBalances" should return with: + |keys |values |type | + |balances |0 |int | + + Scenario: Double spend only affects the ledger once + We want to ascertain that ledger state is always calculated correctly. + Even in the presence of double spend, the confirmed state should have spent only once + + Then "1" transaction is issued on "nodeA-m3" with: + |keys |values |type | + |address |TEST_ADDRESS |staticValue | + |value |0 |int | + |tag |ZERO9VALUE |string | + + Then a double spend is generated referencing the previous transaction with: + |keys |values |type | + |seed |DOUBLE_SPEND_SEED |staticValue | + |value |1000 |int | + |tag |FAKE9VALUE |string | + + #In the default test, the latest sent index will be 53. The next milestone issued should be 54. + When a milestone is issued with index 54 and references: + |keys |values |type | + |transactions |firstDoubleSpend |responseValue | + + #Give the node time to solidify the milestone + And we wait "15" second/seconds + + Given "getBalances" is called on "nodeA-m3" with: + |keys |values |type | + |addresses |DOUBLE_SPEND_ADDRESSES |staticList | + + Then the response for "getBalances" should return with: + |keys |values |type | + |balances |1000 0 |intList | + + Scenario: Split transaction over 2 bundles + We want to ascertain that ledger state is always calculated correctly. + Even when there is a transaction used in 2 different bundles. A split bundle is + a bundle that uses a transaction from another bundle. + + Then "1" transaction is issued on "nodeA-m3" with: + |keys |values |type | + |address |TEST_ADDRESS |staticValue | + |value |0 |int | + |tag |ZERO9VALUE |string | + + Then a split bundle is generated referencing the previous transaction with: + |keys |values |type | + |seed |SPLIT_BUNDLE_SEED |staticValue | + |value |2000 |int | + |tag |FAKE9VALUE |string | + |address |SPLIT_TO_ADDRESS |staticValue | + + Then a transaction is issued referencing the previous transaction + |keys |values |type | + |seed |THE_BANK |staticList | + |address |TEST_ADDRESS |staticValue | + |value |11 |int | + |tag |VALUE9TRANSACTION |string | + + #In the default test, the latest sent index will be 54. The next milestone issued should be 55. + When a milestone is issued with index 55 and references: + |keys |values |type | + |transactions |previousTransaction |responseValue | + + #Give the node time to solidify the milestone + And we wait "15" second/seconds + + Given "getBalances" is called on "nodeA-m3" with: + |keys |values |type | + |addresses |SPLIT_TO_ADDRESS |staticList | + + Then the response for "getBalances" should return with: + |keys |values |type | + |balances |2000 |intList | \ No newline at end of file diff --git a/python-regression/tests/features/machine3/config.yml b/python-regression/tests/features/machine3/config.yml index 194bc1b2b0..91bf574c48 100644 --- a/python-regression/tests/features/machine3/config.yml +++ b/python-regression/tests/features/machine3/config.yml @@ -1,6 +1,6 @@ defaults: &transaction_tests_config_files - db: https://s3.eu-central-1.amazonaws.com/iotaledger-dbfiles/dev/Transactions_Tests_db.tar - db_checksum: 756237276479da4b01deaa0c1211ca65a4c8ec6f081452ea7e8153648c53bd67 + db: https://s3.eu-central-1.amazonaws.com/iotaledger-dbfiles/dev/TransactionsTestsDb.tar + db_checksum: 4d94ae65b38ea0f8461d5ec24e5140b20eb86590d54e87e30aa9a72b26a131be iri_args: ['--testnet-coordinator', 'EFPNKGPCBXXXLIBYFGIGYBYTFFPIOQVNNVVWTTIYZO9NFREQGVGDQQHUUQ9CLWAEMXVDFSSMOTGAHVIBH', '--milestone-start', diff --git a/python-regression/tests/features/machine4/4_api_tests.feature b/python-regression/tests/features/machine4/4_api_tests.feature index b9d7a71912..9aaf8cdeb4 100644 --- a/python-regression/tests/features/machine4/4_api_tests.feature +++ b/python-regression/tests/features/machine4/4_api_tests.feature @@ -173,7 +173,7 @@ Feature: Test API calls on Machine 1 Given "getBalances" is called on "nodeA-m4" with: |keys |values |type | |addresses |TEST_EMPTY_ADDRESS |staticList | - |threshold |100 |int | + |threshold |100 |int | Then the response for "getBalances" should return with: |keys |values |type | diff --git a/python-regression/tests/features/steps/response_handling_steps.py b/python-regression/tests/features/steps/response_handling_steps.py index b663009f1a..849680ba76 100644 --- a/python-regression/tests/features/steps/response_handling_steps.py +++ b/python-regression/tests/features/steps/response_handling_steps.py @@ -78,13 +78,12 @@ def check_response_for_value(step, api_call): expected_values = {} args = step.hashes api_utils.prepare_options(args, expected_values) - for expected_value_key in expected_values: if expected_value_key in response_values: expected_value = expected_values[expected_value_key] response_value = response_values[expected_value_key] - if isinstance(response_value, list) and api_call != 'getTrytes' and api_call != 'getInclusionStates': + if isinstance(response_value, list) and isinstance(expected_value, list) != True and api_call != 'getTrytes' and api_call != 'getInclusionStates': response_value = response_value[0] assert expected_value == response_value, "The expected value {} does not match""\ diff --git a/python-regression/tests/features/steps/transaction_steps.py b/python-regression/tests/features/steps/transaction_steps.py index a1e758e66f..9d1fe3f11b 100644 --- a/python-regression/tests/features/steps/transaction_steps.py +++ b/python-regression/tests/features/steps/transaction_steps.py @@ -3,7 +3,7 @@ from util import static_vals as static from util import logger as log from util.test_logic import api_test_logic as api_utils -from util.transaction_bundle_logic import transaction_logic as transactions +from util.transaction_bundle_logic import bundle_scenario_setup, transaction_logic as transactions from util.milestone_logic import milestones from time import sleep @@ -31,7 +31,79 @@ def generate_transaction_and_attach(step, node): setattr(static, "TEST_STORE_TRANSACTION", transaction.get('trytes')) return transaction + +@step(r'Then a value bundle which moves funds back and forth from an address is generated referencing the previous transaction with:') +def fake_value_transaction(step): + """ + Creates a bundle that both receives and sends value between 2 addresses. + This makes the total ledger change zero + :param step.hashes: A gherkin table present in the feature file specifying the + arguments and the associated type. + """ + + node = world.config['nodeId'] + previous = world.responses['evaluate_and_send'][node][0] + + seed = get_step_value(step, "seed")[0] + api = api_utils.prepare_api_call(node, seed=seed) + + logger.info('Finding Transactions') + gtta_transactions = api.get_transactions_to_approve(depth=3) + trunk = previous + branch = gtta_transactions['branchTransaction'] + + value = int(get_step_value(step, "value")) + tag = get_step_value(step, "tag") + + bundle = bundle_scenario_setup.create_fake_transfer_bundle(api, seed, tag, value) + + argument_list = {'trunk_transaction': trunk, 'branch_transaction': branch, + 'trytes': bundle.as_tryte_strings(), 'min_weight_magnitude': 14} + + bundle = transactions.attach_store_and_broadcast(api, argument_list) + transaction_trytes = bundle.get('trytes') + transaction_hash = Transaction.from_tryte_string(transaction_trytes[0]) + set_previous_transaction(node, [transaction_hash.hash]) + +@step(r'a double spend is generated referencing the previous transaction with:') +def create_double_spent(step): + """ + Creates two bundles which both try to spend the same address. + This test fails if they are both confirmed + :param step.hashes: A gherkin table present in the feature file specifying the + arguments and the associated type. + """ + node = world.config['nodeId'] + previous = world.responses['evaluate_and_send'][node][0] + seed = get_step_value(step, "seed") + api = api_utils.prepare_api_call(node, seed=seed) + + tag = get_step_value(step, "tag")[0] + value = int(get_step_value(step, "value")) + + response = api.get_inputs(start=0, stop=1, threshold=0, security_level=2) + addressFrom = response['inputs'][0] + + bundles = bundle_scenario_setup.create_double_spend_bundles(seed, addressFrom, static.DOUBLE_SPEND_ADDRESSES[0], static.DOUBLE_SPEND_ADDRESSES[1], tag, value) + + logger.info('Finding Transactions') + gtta_transactions = api.get_transactions_to_approve(depth=3) + trunk1 = previous + branch1 = gtta_transactions['branchTransaction'] + trunk2 = previous + branch2 = gtta_transactions['trunkTransaction'] + + argument_list = {'trunk_transaction': trunk1, 'branch_transaction': branch1, + 'trytes': bundles[0].as_tryte_strings(), 'min_weight_magnitude': 14} + firstDoubleSpend = Transaction.from_tryte_string( transactions.attach_store_and_broadcast(api, argument_list).get('trytes')[0] ) + + argument_list = {'trunk_transaction': trunk2, 'branch_transaction': branch2, + 'trytes': bundles[1].as_tryte_strings(), 'min_weight_magnitude': 14} + secondDoubleSpend = Transaction.from_tryte_string( transactions.attach_store_and_broadcast(api, argument_list).get('trytes')[0] ) + + set_previous_transaction(node, [firstDoubleSpend.hash]) + set_world_object(node, "firstDoubleSpend", [firstDoubleSpend.hash]) @step(r'an inconsistent transaction is generated on "([^"]+)"') def create_inconsistent_transaction(step, node): @@ -55,10 +127,36 @@ def create_inconsistent_transaction(step, node): transaction_trytes = transaction.get('trytes') transaction_hash = Transaction.from_tryte_string(transaction_trytes[0]) - if 'inconsistentTransactions' not in world.responses: - world.responses['inconsistentTransactions'] = {} - world.responses['inconsistentTransactions'][node] = transaction_hash.hash + set_world_object(node, 'inconsistentTransactions', transaction_hash.hash) +@step(r'a split bundle is generated referencing the previous transaction with:') +def create_split_bundle(step): + """ + Create a bundle that reuses the last transaction from another bundle in its own + + This test fails if we do not find the correct balance after confirming the second bundle(reattachment) + :param step.hashes: A gherkin table present in the feature file specifying the + arguments and the associated type. + """ + node = world.config['nodeId'] + previous = world.responses['evaluate_and_send'][node][0] + seed = get_step_value(step, "seed") + api = api_utils.prepare_api_call(node, seed=seed) + + tag = get_step_value(step, "tag")[0] + value = int(get_step_value(step, "value")) + addressTo = get_step_value(step, "address")[0] + + response = api.get_inputs(start=0, stop=1, threshold=0, security_level=3) + addressFrom = response['inputs'][0] + + bundles = bundle_scenario_setup.create_split_bundles(api, seed, addressFrom, addressTo, static.SPLIT_REST_ADDRESS, tag, value, previous) + + api.broadcast_and_store(bundles[0].as_tryte_strings()) + api.broadcast_and_store(bundles[1].as_tryte_strings()) + + set_previous_transaction(node, [bundles[1][0].hash]) + set_world_object(node, "reattachSplitSpend", [bundles[1][0].hash]) @step(r'a stitching transaction is issued on "([^"]*)" with the tag "([^"]*)"') def issue_stitching_transaction(step, node, tag): @@ -84,9 +182,7 @@ def issue_stitching_transaction(step, node, tag): # Finds transaction hash and stores it in world bundlehash = api.find_transactions(bundles=[bundle.hash]) - if 'previousTransaction' not in world.responses: - world.responses['previousTransaction'] = {} - world.responses['previousTransaction'][node] = bundlehash['hashes'][0] + set_previous_transaction(node, bundlehash['hashes'][0]) @step(r'a transaction is issued referencing the previous transaction') @@ -99,13 +195,17 @@ def reference_stitch_transaction(step): transaction_bundle = transactions.create_transaction_bundle(referencing_address, 'REFERENCE9TAG', 0) branch = api.get_transactions_to_approve(depth=3)['branchTransaction'] - options = {'trunk_transaction': stitch, 'branch_transaction': branch, 'trytes': + options = {'trunk_transaction': stitch[0], 'branch_transaction': branch, 'trytes': transaction_bundle.as_tryte_strings(), 'min_weight_magnitude': 9} - transactions.attach_store_and_broadcast(api, options) + transaction = transactions.attach_store_and_broadcast(api, options) + transaction_trytes = transaction.get('trytes') + transaction_hash = Transaction.from_tryte_string(transaction_trytes[0]) + + set_previous_transaction(node, [transaction_hash.hash]) -@step(r'"(\d+)" transactions are issued on "([^"]+)" with:') +@step(r'"(\d+)" transactions? (?:is|are) issued on "([^"]+)" with:') def issue_multiple_transactions(step, num_transactions, node): transactions_to_store = [] world.responses['evaluate_and_send'] = {} @@ -193,6 +293,22 @@ def issue_a_milestone(step, index, node): milestone_hash2 = Transaction.from_tryte_string(milestone['trytes'][1]).hash world.config['latestMilestone'][node] = [milestone_hash, milestone_hash2] +def set_previous_transaction(node, txHash): + set_world_object(node, 'previousTransaction', txHash) + +def set_world_object(node, objectName, value): + if objectName not in world.responses: + world.responses[objectName] = {} + world.responses[objectName][node] = value + +def get_step_value(step, key_name): + for arg_index, arg in enumerate(step.hashes): + if arg['keys'] == key_name : + if arg['type'] == "staticValue" or arg['type'] == "staticList": + return getattr(static, arg['values']) + else: + return arg['values'] + return 0 def wait_for_update(index, api): updated = False diff --git a/python-regression/util/static_vals.py b/python-regression/util/static_vals.py index a4d332bd50..59f85bce52 100644 --- a/python-regression/util/static_vals.py +++ b/python-regression/util/static_vals.py @@ -33,6 +33,16 @@ "THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9NINE99999999999999999999999999999999999999", "THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9TEN999999999999999999999999999999999999999"] +FAKE_SPEND_ADDRESSES = ["CTCFZQHZ9MVOQVKOASZJFFQYCYSUZOIXFUDGBNQQWNUVNYXJVOYHMQPJVVKNICNRCUDEWXJIEDKXCLVWY", + "DRDSKHQ9XHRFMXXHTGZPGUIKWVQYSDQDPUTHEUXEROVUTAQDRCJIWLARCTAQHMYAUVNYNVCEBD9QNC9SD"] + +DOUBLE_SPEND_SEED = "THIS9DOUBLE9SPEND9ADDRESS9HAS9ONE9THOUSAND9IOTA9999999999999999999999999999999999" +DOUBLE_SPEND_ADDRESSES = ["YIYPLJDLF9MNSCAORRGFNJNDXOFQZXEXTPDD9TROXZCJPY9AWDTIJY9RKIPLUDPFPLKZRP9NKHPKBJAYA", + "DHGTQLJRYMJTHSYGKCAJYMMWHYYM9XKGYMMKYLEUWOQOAMORGTSWMRHVZ9VKPRTDUGUPBKRB9WMQOBRHY"] + +SPLIT_BUNDLE_SEED = "THIS9SPLIT9BUNDLE9ADDRESS9HAS9ONE9THOUSAND9IOTA9999999999999999999999999999999999" +SPLIT_TO_ADDRESS = ["9JMLFZINDAQ99YYDWSYDZMADO9PHWTIZOXHKTSNYFMFMFOQVNAWCATWECJBRYRGPHMBZHYAPTQFFEWZKC"] +SPLIT_REST_ADDRESS = "USUXAWWGZSEJICZQJSMJSEYBBDOPGBJFEOYAMDMXKZAOPDDJBMUXIETNYXGRFWWROIOTRDYODJEALZRE9" SIDE_TANGLE_ADDRESS = "SIDE9TANGLE9999999999999999999999999999999999999999999999999999999999999999999999" STITCHING_ADDRESS = "STITCHING9TRANSACTIONS99999999999999999999999999999999999999999999999999999999999" diff --git a/python-regression/util/test_logic/api_test_logic.py b/python-regression/util/test_logic/api_test_logic.py index f6fe3f858d..51114c5229 100644 --- a/python-regression/util/test_logic/api_test_logic.py +++ b/python-regression/util/test_logic/api_test_logic.py @@ -77,6 +77,7 @@ def prepare_options(args, option_list): fetch_list = { 'int': value_fetch.fetch_int, + 'intList': value_fetch.fetch_int_list, 'string': value_fetch.fetch_string, 'list': value_fetch.fetch_list, 'nodeAddress': value_fetch.fetch_node_address, @@ -136,7 +137,8 @@ def fetch_call(api_call, api, options): try: response = call_list[api_call](**options) except ValueError as e: - logger.error(str(e)) + if "filter_errors" in e.context: + logger.info(e.context["filter_errors"]) response = None return response diff --git a/python-regression/util/test_logic/value_fetch_logic.py b/python-regression/util/test_logic/value_fetch_logic.py index 3dede4a655..55b5465509 100644 --- a/python-regression/util/test_logic/value_fetch_logic.py +++ b/python-regression/util/test_logic/value_fetch_logic.py @@ -28,6 +28,14 @@ def fetch_int(value): """ return int(value) +def fetch_int_list(value): + """ + Returns an array of int representations of the input value. + :param value: The input value + :return: The int list + """ + int_list = value.split() + return [int(x) for x in int_list] def fetch_string(value): """ @@ -95,7 +103,10 @@ def fetch_static_list(value): :return: The stored object in list format """ static_value = getattr(static, value) - return [static_value] + if isinstance(static_value, list): + return static_value + else: + return [static_value] def fetch_bool(value): diff --git a/python-regression/util/threading_logic/pool_logic.py b/python-regression/util/threading_logic/pool_logic.py index 6722f12be2..e6601eae55 100644 --- a/python-regression/util/threading_logic/pool_logic.py +++ b/python-regression/util/threading_logic/pool_logic.py @@ -42,5 +42,4 @@ def fetch_results(future_result, timeout): logger.debug('Response: {}'.format(response)) return response except Exception as err: - logger.debug(err) - logger.info(err) + logger.error(err) diff --git a/python-regression/util/transaction_bundle_logic/bundle_scenario_setup.py b/python-regression/util/transaction_bundle_logic/bundle_scenario_setup.py new file mode 100644 index 0000000000..6d8364ce17 --- /dev/null +++ b/python-regression/util/transaction_bundle_logic/bundle_scenario_setup.py @@ -0,0 +1,186 @@ +from iota.crypto.signing import KeyGenerator + +from iota import Iota, ProposedTransaction, Address, Bundle, TransactionHash, \ + Transaction, TryteString, Tag, ProposedBundle + +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def create_double_spend_bundles(seedFrom, addressFrom, address1, address2, tag, value): + """ + Create 2 bundles with conflicting value transfers + + :param seedFrom: The seed used for signing the budles + :param addressFrom: The address which we will use for input + :param address1: The address we will use with the first bundle + :param address2: The address we will use with the second bundle + :param tag: The tag that will be associated with the transaction + :param value: The value we will send + """ + + bundle1 = ProposedBundle() + bundle1.add_transaction(ProposedTransaction( + address = Address(address1), + tag = Tag(tag), + value = value + )) + bundle1.add_inputs([Address( + addressFrom, + balance = addressFrom.balance, + key_index = addressFrom.key_index, + security_level = addressFrom.security_level + ), + ]) + bundle1.send_unspent_inputs_to(Address(addressFrom)) + bundle1.finalize() + bundle1.sign_inputs(KeyGenerator(seedFrom)) + + bundle2 = ProposedBundle() + bundle2.add_transaction(ProposedTransaction( + address = Address(address2), + tag = Tag(tag), + value = value + )) + bundle2.add_inputs([Address( + addressFrom, + balance = addressFrom.balance, + key_index = addressFrom.key_index, + security_level = addressFrom.security_level + ), + ]) + bundle2.send_unspent_inputs_to(Address(addressFrom)) + bundle2.finalize() + bundle2.sign_inputs(KeyGenerator(seedFrom)) + return [bundle1, bundle2] + +def create_fake_transfer_bundle(api, seed, tag, value): + """ + Create a bundle that sends the specified value back and forth. + + :param api: The seed used to generate the bundle, addresses and signing + :param seed: The seed used to generate the bundle, addresses and signing + :param tag: The tag that will be associated with the transaction + :param value: The value of the transaction + """ + gna_result = api.get_new_addresses(count=2, security_level=1) + addresses = gna_result['addresses'] + bundle = ProposedBundle() + bundle.add_transaction(ProposedTransaction( + address=Address(addresses[0]), + tag=Tag(tag), + value=value + )) + bundle.add_transaction(ProposedTransaction( + address=Address(addresses[1]), + tag=Tag(tag), + value=value + )) + bundle.add_inputs([Address(addresses[0], value, 0, 1), Address(addresses[1], value, 1, 1)]) + bundle.finalize() + bundle.sign_inputs(KeyGenerator(seed)) + return bundle + +def create_split_bundles(api, seedFrom, addressFrom, addressTo, addressRest, tag, value, reference): + """ + Create a bundle that reuses the last transaction form another bundle in its own + + :param api: Api used to create and attach the first bundle + :param seedfrom: The seed used for signing, addressFrom should be created form this + :param addressFrom: The address we will use as input + :param addressTo: The address we will use to send value to + :param addressRest: Where the rest of the balance gets send to, if any + :param tag: The tag that will be associated with the transaction + :param value: The value we will send + :param reference: The reference we use to connect the trunk with + """ + bundle1 = ProposedBundle() + bundle1.add_transaction(ProposedTransaction( + address = Address(addressTo), + tag = Tag(tag), + value = value + )) + bundle1.add_inputs([Address( + addressFrom, + balance = addressFrom.balance, + key_index = addressFrom.key_index, + security_level = addressFrom.security_level + ), + ]) + bundle1.send_unspent_inputs_to(Address(addressRest)) + bundle1.finalize() + bundle1.sign_inputs(KeyGenerator(seedFrom)) + + gtta_response = api.get_transactions_to_approve(3) + + trunk = reference + branch = gtta_response.get('branchTransaction') + + attached_original_trytes = api.attach_to_tangle(trunk, branch, bundle1.as_tryte_strings()).get('trytes') + + # So we have the original bundle attached, time to construct the new one + # We need to re-attach, but take special care, so we dont use the api, rather we do it ourself + + re_attached_trytes = custom_attach(attached_original_trytes, 14) + + original_bundle = Bundle.from_tryte_strings(attached_original_trytes) + + re_attached_bundle = Bundle.from_tryte_strings(re_attached_trytes) + return [original_bundle, re_attached_bundle] + +def custom_attach(trytes, mwm): + """ + Custom attach to to tangle. + + Takes already attached bundle trytes, and except for the the head transaction, + updates `attachment_timestamp` and re-does the pow, resulting in a new + nonce and transaction hash. + + The head transaction remains the same as in the original bundle. + + :param trytes: List[TransactionTrytes] + :param mwm: int + """ + # Install the pow package together with pyota: + # $ pip install pyota[pow] + from pow.ccurl_interface import get_powed_tx_trytes, get_hash_trytes, \ + get_current_ms + + previoustx = None + + # Construct bundle object + bundle = Bundle.from_tryte_strings(trytes) + + # and we need the head tx first + for txn in reversed(bundle.transactions): + if (not previoustx): # this is the head transaction + # head tx stays the same, it is the original + previoustx = txn.hash + continue + + # It is not a head transaction + # only updae the trunk reference + txn.trunk_transaction_hash = previoustx # the previous transaction + txn.attachment_timestamp = get_current_ms() + + # Let's do the pow locally + txn_string = txn.as_tryte_string().__str__() + # returns a python unicode string + powed_txn_string = get_powed_tx_trytes(txn_string, mwm) + # construct trytestring from python string + powed_txn_trytes = TryteString(powed_txn_string) + # compute transaction hash + hash_string = get_hash_trytes(powed_txn_string) + hash_trytes = TryteString(hash_string) + hash_= TransactionHash(hash_trytes) + + # Create powed txn object + powed_txn = Transaction.from_tryte_string( + trytes=powed_txn_trytes, + hash_=hash_ + ) + + previoustx = powed_txn.hash + # put that back in the bundle + bundle.transactions[txn.current_index] = powed_txn + return bundle.as_tryte_strings() \ No newline at end of file diff --git a/python-regression/util/transaction_bundle_logic/transaction_logic.py b/python-regression/util/transaction_bundle_logic/transaction_logic.py index 7b100aac11..a610fc5bb8 100644 --- a/python-regression/util/transaction_bundle_logic/transaction_logic.py +++ b/python-regression/util/transaction_bundle_logic/transaction_logic.py @@ -2,6 +2,8 @@ from util import static_vals as static from util.test_logic import api_test_logic as api_utils from util.test_logic import value_fetch_logic as value_fetch + +from util.transaction_bundle_logic import bundle_logic as bundle_logic from util import logger as log logger = log.getLogger(__name__) @@ -22,7 +24,7 @@ def create_transaction_bundle(address, tag, value): ) bundle = ProposedBundle() bundle.add_transaction(txn) - bundle.finalize() + bundle_logic.finalize(bundle) return bundle