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 31a6236f1e..931979dcf8 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 2d1f7dc4bc..b6d3a4fb38 100644 --- a/python-regression/tests/features/machine3/3_transaction_tests.feature +++ b/python-regression/tests/features/machine3/3_transaction_tests.feature @@ -3,7 +3,7 @@ Feature: Test transaction confirmation Scenario: Zero Value Transactions are confirmed In this test, a number of zero value transactions will be made to a specified node. A milestone will be issued that references these transactions, and this should - confirm the transations. + confirm the transactions. Given "10" transactions are issued on "nodeA-m3" with: |keys |values |type | @@ -12,7 +12,7 @@ Feature: Test transaction confirmation |tag |ZERO9VALUE |string | #In the default DB, the current index is 50. The next milestone issued should be 51. - When a milestone is issued with index 51 and references: + Then a milestone is issued with index 51 and references: |keys |values |type | |transactions |evaluate_and_send |responseValue | @@ -43,11 +43,10 @@ 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 - confirm the transations. + confirm the transactions. Given "10" transactions are issued on "nodeA-m3" with: |keys |values |type | @@ -57,7 +56,7 @@ Feature: Test transaction confirmation |tag |VALUE9TRANSACTION |string | #In the default test, the latest sent index will be 51. The next milestone issued should be 52. - When a milestone is issued with index 52 and references: + Then a milestone is issued with index 52 and references: |keys |values |type | |transactions |evaluate_and_send |responseValue | @@ -87,3 +86,73 @@ 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 + + Given "1" transaction is issued on "nodeA-m3" with: + |keys |values |type | + |address |TEST_ADDRESS |staticValue | + |value |0 |int | + |tag |ZERO9VALUE |string | + + And 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 | + + And 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. + Then 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 + + When "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 + + Given "1" transaction is issued on "nodeA-m3" with: + |keys |values |type | + |address |TEST_ADDRESS |staticValue | + |value |0 |int | + |tag |ZERO9VALUE |string | + + And 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. + Then 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 + + When "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 | \ 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 3674917cb6..44a9319c92 100644 --- a/python-regression/tests/features/machine4/4_api_tests.feature +++ b/python-regression/tests/features/machine4/4_api_tests.feature @@ -162,7 +162,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 c0c52584a1..0810e5942d 100644 --- a/python-regression/tests/features/steps/response_handling_steps.py +++ b/python-regression/tests/features/steps/response_handling_steps.py @@ -79,13 +79,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..aa881ef977 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 @@ -32,6 +32,78 @@ def generate_transaction_and_attach(step, node): setattr(static, "TEST_STORE_TRANSACTION", transaction.get('trytes')) return transaction +@step(r'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,7 @@ 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 stitching transaction is issued on "([^"]*)" with the tag "([^"]*)"') def issue_stitching_transaction(step, node, tag): @@ -84,10 +153,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') def reference_stitch_transaction(step): @@ -99,13 +165,16 @@ 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'] = {} @@ -154,7 +223,6 @@ def issue_a_milestone_with_reference(step, index): milestones.update_latest_milestone(world.config, node, milestone) - @step(r'the next (\d+) milestones are issued') def issue_several_milestones(step, num_milestones): node = world.config['nodeId'] @@ -170,7 +238,6 @@ def issue_several_milestones(step, num_milestones): #Give node a moment to update solid milestone wait_for_update(index, api) - @step(r'milestone (\d+) is issued on "([^"]+)"') def issue_a_milestone(step, index, node): """ @@ -193,6 +260,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 035ea262ae..429d24ac9e 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 9f662cb37c..eaabdaee00 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..d176af1f13 --- /dev/null +++ b/python-regression/util/transaction_bundle_logic/bundle_scenario_setup.py @@ -0,0 +1,82 @@ +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 \ 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