From 80655fae8ea58b7937645587a7e9927e819f3d92 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Fri, 20 Mar 2020 14:45:57 +0100 Subject: [PATCH 1/7] white-flag --- .../machine3/3_transaction_tests.feature | 44 ++++++++ .../tests/features/machine3/config.yml | 4 +- .../features/steps/response_handling_steps.py | 2 +- .../tests/features/steps/transaction_steps.py | 57 +++++++++- .../util/milestone_logic/milestones.py | 9 +- python-regression/util/static_vals.py | 3 + .../util/test_logic/api_test_logic.py | 1 + .../util/test_logic/value_fetch_logic.py | 12 +- .../transaction_logic.py | 51 ++++++++- .../iri/controllers/TransactionViewModel.java | 16 +++ .../iri/model/persistables/Transaction.java | 12 ++ .../ledger/impl/LedgerServiceImpl.java | 105 ++++++++++-------- 12 files changed, 263 insertions(+), 53 deletions(-) diff --git a/python-regression/tests/features/machine3/3_transaction_tests.feature b/python-regression/tests/features/machine3/3_transaction_tests.feature index 6280fa0758..3875625b75 100644 --- a/python-regression/tests/features/machine3/3_transaction_tests.feature +++ b/python-regression/tests/features/machine3/3_transaction_tests.feature @@ -87,3 +87,47 @@ Feature: Test transaction confirmation | keys | values | type | | states | False | boolListMixed | + Scenario: Conflicting bundles are ignored + We want to ascertain that conflicting bundles are confirmed but only 1 of the conflicts is applied to the ledger + + When "1" transactions are 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 |1000000 |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 53 and references: + |keys |values |type | + |transactions |doubleSpends |responseValue | + |fullReference |True |bool | + + #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 | + |threshold |100 |int | + + + Then the response for "getBalances" should return with: + |keys |values |type | + |balances |1000000 0 |intList | + + Given "getInclusionStates" is called on "nodeA-m3" with: + | keys | values | type | + | transactions | doubleSpends | responseValue | + | tips | latestMilestone | configValue | + + + Then the response for "getInclusionStates" should return with: + | keys | values | type | + | states | True True | boolListMixed | + 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/steps/response_handling_steps.py b/python-regression/tests/features/steps/response_handling_steps.py index 7e1817a7ea..3744fa4dfa 100644 --- a/python-regression/tests/features/steps/response_handling_steps.py +++ b/python-regression/tests/features/steps/response_handling_steps.py @@ -85,7 +85,7 @@ def check_response_for_value(step, api_call): 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 not isinstance(expected_value, list) 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 bb485b5668..40b828b8d9 100644 --- a/python-regression/tests/features/steps/transaction_steps.py +++ b/python-regression/tests/features/steps/transaction_steps.py @@ -152,7 +152,9 @@ def issue_a_milestone_with_reference(step, index): reference_transaction = transactions.fetch_transaction_from_list(step.hashes, node) logger.info('Issuing milestone {}'.format(index)) - milestone = milestones.issue_milestone(address, api, index, reference_transaction) + #To reference both trunk and branch of the milestone from the reference list + full_reference = bool(get_step_value(step,"fullReference")) + milestone = milestones.issue_milestone(address, api, index, reference_transaction, full_reference) milestones.update_latest_milestone(world.config, node, milestone) @@ -195,6 +197,43 @@ 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] +@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. + :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 = transactions.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] ) + + doubleSpends = [firstDoubleSpend.hash, secondDoubleSpend.hash] + set_world_object(node, "doubleSpends", doubleSpends) def wait_for_update(index, api): updated = False @@ -204,7 +243,21 @@ def wait_for_update(index, api): if node_info['latestSolidSubtangleMilestoneIndex'] == index: updated = True break - i += 1; + i += 1 sleep(1) assert updated is True, "The node was unable to update to index {}".format(index) + +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 \ No newline at end of file diff --git a/python-regression/util/milestone_logic/milestones.py b/python-regression/util/milestone_logic/milestones.py index 4eb197ef52..237dafd00b 100644 --- a/python-regression/util/milestone_logic/milestones.py +++ b/python-regression/util/milestone_logic/milestones.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) -def issue_milestone(address, api, index, *reference_transaction): +def issue_milestone(address, api, index, reference_transaction, full_reference=False): txn1 = ProposedTransaction( address=Address(address), value=0 @@ -29,7 +29,12 @@ def issue_milestone(address, api, index, *reference_transaction): tips = api.get_transactions_to_approve(depth=3) trunk = tips['trunkTransaction'] if reference_transaction: - branch = reference_transaction[0] + if full_reference: + if len(reference_transaction) > 1: + trunk = reference_transaction[len(reference_transaction) - 2] + else: + logger.error('Cannot reference 2 txs because is only 1 tx in the reference list') + branch = reference_transaction[len(reference_transaction) - 1] else: branch = tips['branchTransaction'] diff --git a/python-regression/util/static_vals.py b/python-regression/util/static_vals.py index a4d332bd50..de479bdcb9 100644 --- a/python-regression/util/static_vals.py +++ b/python-regression/util/static_vals.py @@ -33,6 +33,9 @@ "THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9NINE99999999999999999999999999999999999999", "THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9TEN999999999999999999999999999999999999999"] +DOUBLE_SPEND_SEED = "THIS9DOUBLE9SPEND9ADDRESS9HAS9ONE9THOUSAND9IOTA9999999999999999999999999999999999" +DOUBLE_SPEND_ADDRESSES = ["YIYPLJDLF9MNSCAORRGFNJNDXOFQZXEXTPDD9TROXZCJPY9AWDTIJY9RKIPLUDPFPLKZRP9NKHPKBJAYA", + "DHGTQLJRYMJTHSYGKCAJYMMWHYYM9XKGYMMKYLEUWOQOAMORGTSWMRHVZ9VKPRTDUGUPBKRB9WMQOBRHY"] 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 95019c053b..84009a8e5b 100644 --- a/python-regression/util/test_logic/api_test_logic.py +++ b/python-regression/util/test_logic/api_test_logic.py @@ -78,6 +78,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, diff --git a/python-regression/util/test_logic/value_fetch_logic.py b/python-regression/util/test_logic/value_fetch_logic.py index 3dede4a655..65a516ac59 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,9 @@ def fetch_static_list(value): :return: The stored object in list format """ static_value = getattr(static, value) - return [static_value] + if not isinstance(static_value, list): + static_value = [static_value] + return static_value def fetch_bool(value): diff --git a/python-regression/util/transaction_bundle_logic/transaction_logic.py b/python-regression/util/transaction_bundle_logic/transaction_logic.py index e82b49e533..988dc98580 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 iota.crypto.signing import KeyGenerator + import logging logging.basicConfig(level=logging.INFO) @@ -111,7 +113,7 @@ def fetch_transaction_from_list(args, node): if args[0]['type'] == 'responseValue': transaction_list = value_fetch.fetch_response(args[0]['values']) - reference_transaction = transaction_list[node][len(transaction_list) - 1] + reference_transaction = transaction_list[node] elif args[0]['type'] == 'staticValue': transaction_list = options['transactions'] reference_transaction = transaction_list[len(transaction_list) - 1] @@ -144,3 +146,50 @@ def evaluate_and_send(api, seed, arg_list): api.broadcast_and_store(transaction.get('trytes')) return transaction + +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 bundles + :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] \ No newline at end of file diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java index 89e78407ed..4104b26cc9 100644 --- a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java +++ b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java @@ -863,6 +863,22 @@ public void isMilestone(Tangle tangle, Snapshot initialSnapshot, final boolean i } } + /** + * This method sets the {@link Transaction#conflicting} flag. + * + * It first checks if the {@link Transaction#conflicting} flag has changed. If so, it issues a database update. + * @param tangle Tangle instance + * @param initialSnapshot The snapshot representing the start of the ledger + * @param isConflicting True if the transaction is conflicting and ignored during balance computation + * @throws Exception If something goes wrong + */ + public void isConflicting(Tangle tangle, Snapshot initialSnapshot, final boolean isConflicting) throws Exception { + if(isConflicting != transaction.conflicting){ + transaction.conflicting = isConflicting; + update(tangle, initialSnapshot, "conflicting"); + } + } + /** * This method gets the {@link Transaction#milestone}. * diff --git a/src/main/java/com/iota/iri/model/persistables/Transaction.java b/src/main/java/com/iota/iri/model/persistables/Transaction.java index a13e6a2e0a..63f1deafca 100644 --- a/src/main/java/com/iota/iri/model/persistables/Transaction.java +++ b/src/main/java/com/iota/iri/model/persistables/Transaction.java @@ -37,6 +37,11 @@ public class Transaction implements Persistable { */ public static final int IS_MILESTONE_BITMASK = 0b10; + /** + * Bitmask used to access and store the conflicting flag. + */ + public static final int IS_CONFLICTING_BITMASK = 0b100; + public byte[] bytes; public Hash address; @@ -75,6 +80,11 @@ public class Transaction implements Persistable { */ public boolean solid = false; + /** + * This flag indicates whether the transaction is conflicting and was ignored in balance computation + */ + public boolean conflicting = false; + /** * This flag indicates if the transaction is a coordinator issued milestone. */ @@ -142,6 +152,7 @@ public byte[] metadata() { byte flags = 0; flags |= solid ? IS_SOLID_BITMASK : 0; flags |= milestone ? IS_MILESTONE_BITMASK : 0; + flags |= conflicting ? IS_CONFLICTING_BITMASK : 0; buffer.put(flags); buffer.put(Serializer.serialize(snapshot)); @@ -205,6 +216,7 @@ public void readMetadata(byte[] bytes) { // decode the boolean byte by checking the bitmasks solid = (bytes[i] & IS_SOLID_BITMASK) != 0; milestone = (bytes[i] & IS_MILESTONE_BITMASK) != 0; + conflicting = (bytes[i] & IS_CONFLICTING_BITMASK) != 0; i++; snapshot = Serializer.getInteger(bytes, i); diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index da0224e62d..4180e5ed0c 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -15,7 +15,6 @@ import com.iota.iri.service.snapshot.impl.SnapshotStateDiffImpl; import com.iota.iri.service.spentaddresses.SpentAddressesService; import com.iota.iri.storage.Tangle; -import com.iota.iri.utils.dag.DAGHelper; import java.util.*; @@ -163,54 +162,77 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s visitedTransactions.add(solidEntryPointHash); }); - final Queue nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(startTransaction)); - Hash transactionPointer; - while ((transactionPointer = nonAnalyzedTransactions.poll()) != null) { - try { - final TransactionViewModel transactionViewModel = TransactionViewModel.fromHash(tangle, - transactionPointer); - if (transactionViewModel.getCurrentIndex() == 0 && visitedTransactions.add(transactionPointer)) { - if (transactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT) { - return null; - } - if (!milestoneService.isTransactionConfirmed(transactionViewModel, milestoneIndex)) { + Stack stack = new Stack<>(); + stack.push(startTransaction); - final List bundleTransactions = bundleValidator.validate(tangle, - snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash()); - - if (bundleTransactions.isEmpty()) { + while(!stack.empty()){ + try { + Map currentState = new HashMap<>(); + Hash transactionPointer = stack.peek(); + TransactionViewModel transactionViewModel = TransactionViewModel.fromHash(tangle, transactionPointer); + TransactionViewModel trunk = transactionViewModel.getTrunkTransaction(tangle); + TransactionViewModel branch = transactionViewModel.getBranchTransaction(tangle); + + + boolean approvedTrunk = (trunk.snapshotIndex() > 0) && (trunk.snapshotIndex() != milestoneIndex); + boolean approvedBranch = (branch.snapshotIndex() > 0) && (branch.snapshotIndex() != milestoneIndex); + if ((visitedTransactions.contains(trunk.getHash()) || approvedTrunk) && + (visitedTransactions.contains(branch.getHash()) || approvedBranch)) { + if (transactionViewModel.getCurrentIndex() == 0) { + if (transactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT) { return null; } + if (!milestoneService.isTransactionConfirmed(transactionViewModel, milestoneIndex)) { + final List bundleTransactions = bundleValidator.validate(tangle, + snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash()); - // ISSUE 1008: generateBalanceDiff should be refactored so we don't have those hidden - // concerns - spentAddressesService.persistValidatedSpentAddressesAsync(bundleTransactions); + if (bundleTransactions.isEmpty()) { + return null; + } - if (BundleValidator.isInconsistent(bundleTransactions)) { - log.error("Encountered an inconsistent bundle with tail {} and bundle hash {}", - bundleTransactions.get(0).getHash(), bundleTransactions.get(0).getBundleHash()); - return null; - } + // ISSUE 1008: generateBalanceDiff should be refactored so we don't have those hidden + // concerns + spentAddressesService.persistValidatedSpentAddressesAsync(bundleTransactions); - for (final TransactionViewModel bundleTransactionViewModel : bundleTransactions) { - if (bundleTransactionViewModel.value() != 0) { + if (BundleValidator.isInconsistent(bundleTransactions)) { + log.error("Encountered an inconsistent bundle with tail {} and bundle hash {}", + bundleTransactions.get(0).getHash(), bundleTransactions.get(0).getBundleHash()); + return null; + } - final Hash address = bundleTransactionViewModel.getAddressHash(); - final Long value = state.get(address); - state.put(address, value == null ? bundleTransactionViewModel.value() - : Math.addExact(value, bundleTransactionViewModel.value())); + for (final TransactionViewModel bundleTransactionViewModel : bundleTransactions) { + if (bundleTransactionViewModel.value() != 0) { + final Hash address = bundleTransactionViewModel.getAddressHash(); + final Long value = currentState.get(address); + currentState.put(address, value == null ? bundleTransactionViewModel.value() + : Math.addExact(value, bundleTransactionViewModel.value())); + } + } + state.forEach((key, value) -> { + if (currentState.computeIfPresent(key, ((hash, aLong) -> value + aLong)) == null) { + currentState.putIfAbsent(key, value); + } + }); + boolean isConsistent = snapshotProvider.getLatestSnapshot().patchedState(new SnapshotStateDiffImpl(currentState)).isConsistent(); + if(isConsistent){ + state = currentState; + }else{ + transactionViewModel.isConflicting(tangle, initialSnapshot, true); } } - nonAnalyzedTransactions.addAll(DAGHelper.get(tangle).findTails(transactionViewModel)); } - + visitedTransactions.add(transactionPointer); + stack.pop(); } - - } catch (Exception e) { + else if((!visitedTransactions.contains(trunk.getHash()) && !approvedTrunk)){ + stack.push(trunk.getHash()); + }else if((!visitedTransactions.contains(branch.getHash()) && !approvedBranch)) { + stack.push(branch.getHash()); + } + }catch(Exception e){ throw new LedgerException("unexpected error while generating the balance diff", e); } } - return state; } @@ -260,15 +282,10 @@ private boolean generateStateDiff(MilestoneViewModel milestone) throws LedgerExc snapshotProvider.getLatestSnapshot().getIndex()); successfullyProcessed = balanceChanges != null; if (successfullyProcessed) { - successfullyProcessed = snapshotProvider.getLatestSnapshot().patchedState( - new SnapshotStateDiffImpl(balanceChanges)).isConsistent(); - if (successfullyProcessed) { - milestoneService.updateMilestoneIndexOfMilestoneTransactions(milestone.getHash(), - milestone.index()); - - if (!balanceChanges.isEmpty()) { - new StateDiffViewModel(balanceChanges, milestone.getHash()).store(tangle); - } + milestoneService.updateMilestoneIndexOfMilestoneTransactions(milestone.getHash(), + milestone.index()); + if (!balanceChanges.isEmpty()) { + new StateDiffViewModel(balanceChanges, milestone.getHash()).store(tangle); } } } finally { From d05b6bc1800f5e58da59a4f0129372d25dbcf171 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Fri, 20 Mar 2020 17:41:42 +0100 Subject: [PATCH 2/7] added check if tvm is empty --- .../com/iota/iri/controllers/TransactionViewModel.java | 8 ++++++++ .../iri/service/ledger/impl/LedgerServiceImpl.java | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java index 4104b26cc9..ccc4fe6fed 100644 --- a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java +++ b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java @@ -879,6 +879,14 @@ public void isConflicting(Tangle tangle, Snapshot initialSnapshot, final boolean } } + /** + * Checks if a transaction is empty. + * @return True if empty. False otherwise. + */ + public boolean isEmpty(){ + return Arrays.equals(getBytes(), new byte[SIZE]); + } + /** * This method gets the {@link Transaction#milestone}. * diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index 4180e5ed0c..03a7fcc6c4 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -5,6 +5,7 @@ import com.iota.iri.controllers.StateDiffViewModel; import com.iota.iri.controllers.TransactionViewModel; import com.iota.iri.model.Hash; +import com.iota.iri.model.persistables.Transaction; import com.iota.iri.service.ledger.LedgerException; import com.iota.iri.service.ledger.LedgerService; import com.iota.iri.service.milestone.MilestoneService; @@ -173,11 +174,10 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s TransactionViewModel trunk = transactionViewModel.getTrunkTransaction(tangle); TransactionViewModel branch = transactionViewModel.getBranchTransaction(tangle); - boolean approvedTrunk = (trunk.snapshotIndex() > 0) && (trunk.snapshotIndex() != milestoneIndex); boolean approvedBranch = (branch.snapshotIndex() > 0) && (branch.snapshotIndex() != milestoneIndex); - if ((visitedTransactions.contains(trunk.getHash()) || approvedTrunk) && - (visitedTransactions.contains(branch.getHash()) || approvedBranch)) { + if ((trunk.isEmpty() || visitedTransactions.contains(trunk.getHash()) || approvedTrunk) && + (branch.isEmpty() || visitedTransactions.contains(branch.getHash()) || approvedBranch)) { if (transactionViewModel.getCurrentIndex() == 0) { if (transactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT) { return null; @@ -224,9 +224,9 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s visitedTransactions.add(transactionPointer); stack.pop(); } - else if((!visitedTransactions.contains(trunk.getHash()) && !approvedTrunk)){ + else if((!trunk.isEmpty() && !visitedTransactions.contains(trunk.getHash()) && !approvedTrunk)){ stack.push(trunk.getHash()); - }else if((!visitedTransactions.contains(branch.getHash()) && !approvedBranch)) { + }else if((!branch.isEmpty() && !visitedTransactions.contains(branch.getHash()) && !approvedBranch)) { stack.push(branch.getHash()); } }catch(Exception e){ From 0a70b70ac83c1b3f97fcc9d891030a106cf09330 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Mon, 23 Mar 2020 12:40:53 +0100 Subject: [PATCH 3/7] refactoring --- .../iri/controllers/TransactionViewModel.java | 14 +---- .../iri/model/persistables/Transaction.java | 7 ++- .../ledger/impl/LedgerServiceImpl.java | 61 ++++++++++--------- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java index ccc4fe6fed..d5b2496c9e 100644 --- a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java +++ b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java @@ -872,21 +872,13 @@ public void isMilestone(Tangle tangle, Snapshot initialSnapshot, final boolean i * @param isConflicting True if the transaction is conflicting and ignored during balance computation * @throws Exception If something goes wrong */ - public void isConflicting(Tangle tangle, Snapshot initialSnapshot, final boolean isConflicting) throws Exception { - if(isConflicting != transaction.conflicting){ - transaction.conflicting = isConflicting; + public void setConflicting(Tangle tangle, Snapshot initialSnapshot, final boolean isConflicting) throws Exception { + if(isConflicting != transaction.conflicting.get()){ + transaction.conflicting.set(isConflicting); update(tangle, initialSnapshot, "conflicting"); } } - /** - * Checks if a transaction is empty. - * @return True if empty. False otherwise. - */ - public boolean isEmpty(){ - return Arrays.equals(getBytes(), new byte[SIZE]); - } - /** * This method gets the {@link Transaction#milestone}. * diff --git a/src/main/java/com/iota/iri/model/persistables/Transaction.java b/src/main/java/com/iota/iri/model/persistables/Transaction.java index 63f1deafca..4bc32ef24e 100644 --- a/src/main/java/com/iota/iri/model/persistables/Transaction.java +++ b/src/main/java/com/iota/iri/model/persistables/Transaction.java @@ -9,6 +9,7 @@ import javax.naming.OperationNotSupportedException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; /** @@ -83,7 +84,7 @@ public class Transaction implements Persistable { /** * This flag indicates whether the transaction is conflicting and was ignored in balance computation */ - public boolean conflicting = false; + public AtomicBoolean conflicting = new AtomicBoolean(false); /** * This flag indicates if the transaction is a coordinator issued milestone. @@ -152,7 +153,7 @@ public byte[] metadata() { byte flags = 0; flags |= solid ? IS_SOLID_BITMASK : 0; flags |= milestone ? IS_MILESTONE_BITMASK : 0; - flags |= conflicting ? IS_CONFLICTING_BITMASK : 0; + flags |= conflicting.get() ? IS_CONFLICTING_BITMASK : 0; buffer.put(flags); buffer.put(Serializer.serialize(snapshot)); @@ -216,7 +217,7 @@ public void readMetadata(byte[] bytes) { // decode the boolean byte by checking the bitmasks solid = (bytes[i] & IS_SOLID_BITMASK) != 0; milestone = (bytes[i] & IS_MILESTONE_BITMASK) != 0; - conflicting = (bytes[i] & IS_CONFLICTING_BITMASK) != 0; + conflicting.set((bytes[i] & IS_CONFLICTING_BITMASK) != 0); i++; snapshot = Serializer.getInteger(bytes, i); diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index 03a7fcc6c4..02b75c0cdb 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -5,7 +5,6 @@ import com.iota.iri.controllers.StateDiffViewModel; import com.iota.iri.controllers.TransactionViewModel; import com.iota.iri.model.Hash; -import com.iota.iri.model.persistables.Transaction; import com.iota.iri.service.ledger.LedgerException; import com.iota.iri.service.ledger.LedgerService; import com.iota.iri.service.milestone.MilestoneService; @@ -163,21 +162,21 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s visitedTransactions.add(solidEntryPointHash); }); - Stack stack = new Stack<>(); + Deque stack = new ArrayDeque<>(); stack.push(startTransaction); - while(!stack.empty()){ + while(stack.size() != 0){ try { Map currentState = new HashMap<>(); Hash transactionPointer = stack.peek(); TransactionViewModel transactionViewModel = TransactionViewModel.fromHash(tangle, transactionPointer); - TransactionViewModel trunk = transactionViewModel.getTrunkTransaction(tangle); - TransactionViewModel branch = transactionViewModel.getBranchTransaction(tangle); + TransactionViewModel trunkTransactionViewModel = transactionViewModel.getTrunkTransaction(tangle); + TransactionViewModel branchTransactionViewModel = transactionViewModel.getBranchTransaction(tangle); - boolean approvedTrunk = (trunk.snapshotIndex() > 0) && (trunk.snapshotIndex() != milestoneIndex); - boolean approvedBranch = (branch.snapshotIndex() > 0) && (branch.snapshotIndex() != milestoneIndex); - if ((trunk.isEmpty() || visitedTransactions.contains(trunk.getHash()) || approvedTrunk) && - (branch.isEmpty() || visitedTransactions.contains(branch.getHash()) || approvedBranch)) { + boolean approvedTrunk = (trunkTransactionViewModel.snapshotIndex() > 0) && (trunkTransactionViewModel.snapshotIndex() != milestoneIndex); + boolean approvedBranch = (branchTransactionViewModel.snapshotIndex() > 0) && (branchTransactionViewModel.snapshotIndex() != milestoneIndex); + if ((trunkTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT || visitedTransactions.contains(trunkTransactionViewModel.getHash()) || approvedTrunk) && + (branchTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT || visitedTransactions.contains(branchTransactionViewModel.getHash()) || approvedBranch)) { if (transactionViewModel.getCurrentIndex() == 0) { if (transactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT) { return null; @@ -200,34 +199,38 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s return null; } - for (final TransactionViewModel bundleTransactionViewModel : bundleTransactions) { - if (bundleTransactionViewModel.value() != 0) { - final Hash address = bundleTransactionViewModel.getAddressHash(); - final Long value = currentState.get(address); - currentState.put(address, value == null ? bundleTransactionViewModel.value() - : Math.addExact(value, bundleTransactionViewModel.value())); + try{ + for (final TransactionViewModel bundleTransactionViewModel : bundleTransactions) { + if (bundleTransactionViewModel.value() != 0) { + final Hash address = bundleTransactionViewModel.getAddressHash(); + final Long value = currentState.get(address); + currentState.put(address, value == null ? bundleTransactionViewModel.value() + : Math.addExact(value, bundleTransactionViewModel.value())); + } } - } - state.forEach((key, value) -> { - if (currentState.computeIfPresent(key, ((hash, aLong) -> value + aLong)) == null) { - currentState.putIfAbsent(key, value); + state.forEach((key, value) -> { + if (currentState.computeIfPresent(key, ((hash, aLong) -> value + aLong)) == null) { + currentState.putIfAbsent(key, value); + } + }); + boolean isConsistent = snapshotProvider.getLatestSnapshot().patchedState(new SnapshotStateDiffImpl(currentState)).isConsistent(); + if(isConsistent){ + state = currentState; + }else{ + transactionViewModel.setConflicting(tangle, initialSnapshot, true); } - }); - boolean isConsistent = snapshotProvider.getLatestSnapshot().patchedState(new SnapshotStateDiffImpl(currentState)).isConsistent(); - if(isConsistent){ - state = currentState; - }else{ - transactionViewModel.isConflicting(tangle, initialSnapshot, true); + }catch(ArithmeticException e){ + transactionViewModel.setConflicting(tangle, initialSnapshot, true); } } } visitedTransactions.add(transactionPointer); stack.pop(); } - else if((!trunk.isEmpty() && !visitedTransactions.contains(trunk.getHash()) && !approvedTrunk)){ - stack.push(trunk.getHash()); - }else if((!branch.isEmpty() && !visitedTransactions.contains(branch.getHash()) && !approvedBranch)) { - stack.push(branch.getHash()); + else if((trunkTransactionViewModel.getType() != TransactionViewModel.PREFILLED_SLOT && !visitedTransactions.contains(trunkTransactionViewModel.getHash()) && !approvedTrunk)){ + stack.addFirst(trunkTransactionViewModel.getHash()); + }else if((branchTransactionViewModel.getType() != TransactionViewModel.PREFILLED_SLOT && !visitedTransactions.contains(branchTransactionViewModel.getHash()) && !approvedBranch)) { + stack.addFirst(branchTransactionViewModel.getHash()); } }catch(Exception e){ throw new LedgerException("unexpected error while generating the balance diff", e); From 7c430ced7433dc80d919a7b59c1b39cf3f861fe4 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Mon, 23 Mar 2020 13:48:32 +0100 Subject: [PATCH 4/7] fix milestone issuance error --- python-regression/tests/features/steps/transaction_steps.py | 2 +- python-regression/util/milestone_logic/milestones.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-regression/tests/features/steps/transaction_steps.py b/python-regression/tests/features/steps/transaction_steps.py index 40b828b8d9..e3add96eac 100644 --- a/python-regression/tests/features/steps/transaction_steps.py +++ b/python-regression/tests/features/steps/transaction_steps.py @@ -154,7 +154,7 @@ def issue_a_milestone_with_reference(step, index): logger.info('Issuing milestone {}'.format(index)) #To reference both trunk and branch of the milestone from the reference list full_reference = bool(get_step_value(step,"fullReference")) - milestone = milestones.issue_milestone(address, api, index, reference_transaction, full_reference) + milestone = milestones.issue_milestone(address, api, index, full_reference, reference_transaction) milestones.update_latest_milestone(world.config, node, milestone) diff --git a/python-regression/util/milestone_logic/milestones.py b/python-regression/util/milestone_logic/milestones.py index 237dafd00b..1a915dfda6 100644 --- a/python-regression/util/milestone_logic/milestones.py +++ b/python-regression/util/milestone_logic/milestones.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) -def issue_milestone(address, api, index, reference_transaction, full_reference=False): +def issue_milestone(address, api, index, full_reference=False, reference_transaction=None): txn1 = ProposedTransaction( address=Address(address), value=0 From 6a2653f8cddb1c703f1f5c6a16d69a6c1af08769 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Wed, 25 Mar 2020 01:22:06 +0100 Subject: [PATCH 5/7] refactor: extra complicated conditions to variables --- .../service/ledger/impl/LedgerServiceImpl.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index 02b75c0cdb..e782db2ada 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -173,10 +173,14 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s TransactionViewModel trunkTransactionViewModel = transactionViewModel.getTrunkTransaction(tangle); TransactionViewModel branchTransactionViewModel = transactionViewModel.getBranchTransaction(tangle); - boolean approvedTrunk = (trunkTransactionViewModel.snapshotIndex() > 0) && (trunkTransactionViewModel.snapshotIndex() != milestoneIndex); - boolean approvedBranch = (branchTransactionViewModel.snapshotIndex() > 0) && (branchTransactionViewModel.snapshotIndex() != milestoneIndex); - if ((trunkTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT || visitedTransactions.contains(trunkTransactionViewModel.getHash()) || approvedTrunk) && - (branchTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT || visitedTransactions.contains(branchTransactionViewModel.getHash()) || approvedBranch)) { + boolean isEmptyTrunk = trunkTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; + boolean isEmptyBranch = branchTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; + boolean isApprovedTrunk = (trunkTransactionViewModel.snapshotIndex() > 0) && (trunkTransactionViewModel.snapshotIndex() != milestoneIndex); + boolean isApprovedBranch = (branchTransactionViewModel.snapshotIndex() > 0) && (branchTransactionViewModel.snapshotIndex() != milestoneIndex); + boolean isLeafTrunk = isEmptyTrunk || visitedTransactions.contains(trunkTransactionViewModel.getHash()) || isApprovedTrunk; + boolean isLeafBranch = isEmptyBranch || visitedTransactions.contains(branchTransactionViewModel.getHash()) || isApprovedBranch; + + if (isLeafTrunk && isLeafBranch) { if (transactionViewModel.getCurrentIndex() == 0) { if (transactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT) { return null; @@ -227,9 +231,9 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s visitedTransactions.add(transactionPointer); stack.pop(); } - else if((trunkTransactionViewModel.getType() != TransactionViewModel.PREFILLED_SLOT && !visitedTransactions.contains(trunkTransactionViewModel.getHash()) && !approvedTrunk)){ + else if(!isLeafTrunk){ stack.addFirst(trunkTransactionViewModel.getHash()); - }else if((branchTransactionViewModel.getType() != TransactionViewModel.PREFILLED_SLOT && !visitedTransactions.contains(branchTransactionViewModel.getHash()) && !approvedBranch)) { + }else if(!isLeafBranch) { stack.addFirst(branchTransactionViewModel.getHash()); } }catch(Exception e){ From a0532cb3d9d82a34b3bba4c4db7667897dafd949 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Tue, 31 Mar 2020 11:54:37 +0100 Subject: [PATCH 6/7] do not confirm non-solid txs --- .../iota/iri/service/ledger/LedgerService.java | 3 ++- .../service/ledger/impl/LedgerServiceImpl.java | 18 +++++++++++++++--- .../ledger/impl/LedgerServiceImplTest.java | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/iota/iri/service/ledger/LedgerService.java b/src/main/java/com/iota/iri/service/ledger/LedgerService.java index b785cdafc9..f07e6a6cf5 100644 --- a/src/main/java/com/iota/iri/service/ledger/LedgerService.java +++ b/src/main/java/com/iota/iri/service/ledger/LedgerService.java @@ -101,10 +101,11 @@ public interface LedgerService { * @param visitedTransactions a set of transaction hashes that shall be considered to be visited already * @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees * examined + * @param allowGenesisReference Allows for confirmation of a transaction that references genesis * @return a map of the balance changes (addresses associated to their balance) or {@code null} if the balance could * not be generated due to inconsistencies * @throws LedgerException if anything unexpected happens while generating the balance changes */ - Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) + Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, boolean allowGenesisReference) throws LedgerException; } diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index e782db2ada..30dbfa8ddd 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -132,7 +132,7 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } Set visitedHashes = new HashSet<>(approvedHashes); Map currentState = generateBalanceDiff(visitedHashes, tip, - snapshotProvider.getLatestSnapshot().getIndex()); + snapshotProvider.getLatestSnapshot().getIndex(), true); if (currentState == null) { return false; } @@ -151,7 +151,7 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } @Override - public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) + public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, boolean allowGenesisReference) throws LedgerException { Map state = new HashMap<>(); @@ -175,6 +175,18 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s boolean isEmptyTrunk = trunkTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; boolean isEmptyBranch = branchTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; + + //Don't confirm non-solid txs. + Hash genesisHash = Hash.NULL_HASH; + if(isEmptyTrunk && !allowGenesisReference){ + return null; + } + //proceed only if empty trunk or branch is genesis + if((isEmptyTrunk && !trunkTransactionViewModel.getHash().equals(genesisHash))|| + (isEmptyBranch && !branchTransactionViewModel.getHash().equals(genesisHash))){ + return null; + } + boolean isApprovedTrunk = (trunkTransactionViewModel.snapshotIndex() > 0) && (trunkTransactionViewModel.snapshotIndex() != milestoneIndex); boolean isApprovedBranch = (branchTransactionViewModel.snapshotIndex() > 0) && (branchTransactionViewModel.snapshotIndex() != milestoneIndex); boolean isLeafTrunk = isEmptyTrunk || visitedTransactions.contains(trunkTransactionViewModel.getHash()) || isApprovedTrunk; @@ -286,7 +298,7 @@ private boolean generateStateDiff(MilestoneViewModel milestone) throws LedgerExc try { Hash tail = transactionViewModel.getHash(); Map balanceChanges = generateBalanceDiff(new HashSet<>(), tail, - snapshotProvider.getLatestSnapshot().getIndex()); + snapshotProvider.getLatestSnapshot().getIndex(), true); successfullyProcessed = balanceChanges != null; if (successfullyProcessed) { milestoneService.updateMilestoneIndexOfMilestoneTransactions(milestone.getHash(), diff --git a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java index 72dbb53539..6ddffa18e1 100644 --- a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java +++ b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java @@ -8,7 +8,10 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; +import com.iota.iri.model.Hash; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -71,7 +74,19 @@ public void generateBalanceDiffWithPersistsSpentAddresses() throws Exception { int milestoneIndex = 1; when(milestoneService.isTransactionConfirmed(tailTx, milestoneIndex)).thenReturn(false); when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(Collections.emptyMap()); - ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex); + ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex, true); verify(spentAddressesService, times(1)).persistValidatedSpentAddressesAsync(eq(bundle)); } + + @Test + public void generateBalanceDiffWithGenesisReference() throws Exception { + List bundle = TangleMockUtils.mockValidBundle(tangle, bundleValidator, 1, + "A", "Z"); + TransactionViewModel tailTx = bundle.get(0); + int milestoneIndex = 1; + when(milestoneService.isTransactionConfirmed(tailTx, milestoneIndex)).thenReturn(false); + when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(Collections.emptyMap()); + Map diffMap = ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex, false); + Assert.assertNull("Diff map should be null because genesis trunk reference is not allowed", diffMap); + } } \ No newline at end of file From 2d4cc70d4d31ede80e5a56a6ee21c0095f3e8fb0 Mon Sep 17 00:00:00 2001 From: Acha Bill Date: Wed, 1 Apr 2020 15:46:15 +0100 Subject: [PATCH 7/7] remove geneis reference flag and isempty check --- .../iri/service/ledger/LedgerService.java | 3 +-- .../ledger/impl/LedgerServiceImpl.java | 24 ++++------------- .../ledger/impl/LedgerServiceImplTest.java | 26 +++++-------------- 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/iota/iri/service/ledger/LedgerService.java b/src/main/java/com/iota/iri/service/ledger/LedgerService.java index f07e6a6cf5..b785cdafc9 100644 --- a/src/main/java/com/iota/iri/service/ledger/LedgerService.java +++ b/src/main/java/com/iota/iri/service/ledger/LedgerService.java @@ -101,11 +101,10 @@ public interface LedgerService { * @param visitedTransactions a set of transaction hashes that shall be considered to be visited already * @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees * examined - * @param allowGenesisReference Allows for confirmation of a transaction that references genesis * @return a map of the balance changes (addresses associated to their balance) or {@code null} if the balance could * not be generated due to inconsistencies * @throws LedgerException if anything unexpected happens while generating the balance changes */ - Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, boolean allowGenesisReference) + Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) throws LedgerException; } diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index 30dbfa8ddd..065a7bdfd0 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -132,7 +132,7 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } Set visitedHashes = new HashSet<>(approvedHashes); Map currentState = generateBalanceDiff(visitedHashes, tip, - snapshotProvider.getLatestSnapshot().getIndex(), true); + snapshotProvider.getLatestSnapshot().getIndex()); if (currentState == null) { return false; } @@ -151,7 +151,7 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } @Override - public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, boolean allowGenesisReference) + public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) throws LedgerException { Map state = new HashMap<>(); @@ -173,24 +173,10 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s TransactionViewModel trunkTransactionViewModel = transactionViewModel.getTrunkTransaction(tangle); TransactionViewModel branchTransactionViewModel = transactionViewModel.getBranchTransaction(tangle); - boolean isEmptyTrunk = trunkTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; - boolean isEmptyBranch = branchTransactionViewModel.getType() == TransactionViewModel.PREFILLED_SLOT; - - //Don't confirm non-solid txs. - Hash genesisHash = Hash.NULL_HASH; - if(isEmptyTrunk && !allowGenesisReference){ - return null; - } - //proceed only if empty trunk or branch is genesis - if((isEmptyTrunk && !trunkTransactionViewModel.getHash().equals(genesisHash))|| - (isEmptyBranch && !branchTransactionViewModel.getHash().equals(genesisHash))){ - return null; - } - boolean isApprovedTrunk = (trunkTransactionViewModel.snapshotIndex() > 0) && (trunkTransactionViewModel.snapshotIndex() != milestoneIndex); boolean isApprovedBranch = (branchTransactionViewModel.snapshotIndex() > 0) && (branchTransactionViewModel.snapshotIndex() != milestoneIndex); - boolean isLeafTrunk = isEmptyTrunk || visitedTransactions.contains(trunkTransactionViewModel.getHash()) || isApprovedTrunk; - boolean isLeafBranch = isEmptyBranch || visitedTransactions.contains(branchTransactionViewModel.getHash()) || isApprovedBranch; + boolean isLeafTrunk = visitedTransactions.contains(trunkTransactionViewModel.getHash()) || isApprovedTrunk; + boolean isLeafBranch = visitedTransactions.contains(branchTransactionViewModel.getHash()) || isApprovedBranch; if (isLeafTrunk && isLeafBranch) { if (transactionViewModel.getCurrentIndex() == 0) { @@ -298,7 +284,7 @@ private boolean generateStateDiff(MilestoneViewModel milestone) throws LedgerExc try { Hash tail = transactionViewModel.getHash(); Map balanceChanges = generateBalanceDiff(new HashSet<>(), tail, - snapshotProvider.getLatestSnapshot().getIndex(), true); + snapshotProvider.getLatestSnapshot().getIndex()); successfullyProcessed = balanceChanges != null; if (successfullyProcessed) { milestoneService.updateMilestoneIndexOfMilestoneTransactions(milestone.getHash(), diff --git a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java index 6ddffa18e1..a72277a0cb 100644 --- a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java +++ b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java @@ -5,10 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import com.iota.iri.model.Hash; import org.junit.Assert; @@ -40,7 +37,7 @@ public class LedgerServiceImplTest { @Mock private Tangle tangle; - @Mock(answer = Answers.RETURNS_MOCKS) + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SnapshotProvider snapshotProvider; @Mock @@ -72,21 +69,12 @@ public void generateBalanceDiffWithPersistsSpentAddresses() throws Exception { "A", "Z"); TransactionViewModel tailTx = bundle.get(0); int milestoneIndex = 1; + Map solidEntryPoints = new HashMap(){{ + put(Hash.NULL_HASH, 1); + }}; when(milestoneService.isTransactionConfirmed(tailTx, milestoneIndex)).thenReturn(false); - when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(Collections.emptyMap()); - ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex, true); + when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(solidEntryPoints); + ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex); verify(spentAddressesService, times(1)).persistValidatedSpentAddressesAsync(eq(bundle)); } - - @Test - public void generateBalanceDiffWithGenesisReference() throws Exception { - List bundle = TangleMockUtils.mockValidBundle(tangle, bundleValidator, 1, - "A", "Z"); - TransactionViewModel tailTx = bundle.get(0); - int milestoneIndex = 1; - when(milestoneService.isTransactionConfirmed(tailTx, milestoneIndex)).thenReturn(false); - when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(Collections.emptyMap()); - Map diffMap = ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex, false); - Assert.assertNull("Diff map should be null because genesis trunk reference is not allowed", diffMap); - } } \ No newline at end of file