Skip to content
This repository has been archived by the owner on Aug 23, 2020. It is now read-only.

Commit

Permalink
Merge branch 'dev' into invalid-bundle-regression
Browse files Browse the repository at this point in the history
  • Loading branch information
Brord van Wierst committed Mar 25, 2020
2 parents 58db440 + a62dff0 commit 3fda0e0
Show file tree
Hide file tree
Showing 39 changed files with 343 additions and 97 deletions.
7 changes: 7 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
1.8.5

Protocol-Change:
Added bundle validity rules - check that bundles confirm only tails and 2 bundles at most (#1786)
Check validity rules on tip-sel and check-consistency only (#1802)


1.8.4

Hotfix: Ensure proper creation of solid entrypoints (#1702)
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.iota</groupId>
<artifactId>iri</artifactId>
<version>1.8.4</version>
<version>1.8.5</version>
<name>IRI</name>
<description>IOTA Reference Implementation</description>

Expand Down
5 changes: 2 additions & 3 deletions python-regression/tests/features/steps/api_test_steps.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from aloe import *
from util import static_vals
from util import logger as log
from util.test_logic import api_test_logic as api_utils
from util.threading_logic import pool_logic as pool
from util.neighbor_logic import neighbor_logic as neighbors
from util.response_logic import response_handling as responses
from time import sleep, time

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = log.getLogger(__name__)

testAddress = static_vals.TEST_ADDRESS

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from aloe import step
from util.test_logic import api_test_logic as api_utils
from util import logger as log

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = log.getLogger(__name__)


@step(r'A local snapshot was taken on "([^"]+)" at index (\d+)')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import logging
from aloe import world, step
from util.response_logic import response_handling as response_handling
from util.test_logic import api_test_logic as api_utils
from util.test_logic import value_fetch_logic

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
from util import logger as log
logger = log.getLogger(__name__)

world.test_vars = {}

Expand Down
8 changes: 3 additions & 5 deletions python-regression/tests/features/steps/transaction_steps.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from aloe import world, step
from iota import Transaction
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 bundle_scenario_setup, transaction_logic as transactions
from util.milestone_logic import milestones
from time import sleep

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger = log.getLogger(__name__)

@step(r'a transaction is generated and attached on "([^"]+)" with:')
def generate_transaction_and_attach(step, node):
Expand Down Expand Up @@ -292,7 +290,7 @@ 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)
5 changes: 5 additions & 0 deletions python-regression/util/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s: %(message)s')

def getLogger(name):
return logging.getLogger(name)
6 changes: 2 additions & 4 deletions python-regression/util/milestone_logic/milestones.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from iota import ProposedTransaction, ProposedBundle, Tag, Address, Transaction
from util import conversion as converter
from util import logger as log
from util.transaction_bundle_logic import bundle_logic

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger = log.getLogger(__name__)

def issue_milestone(address, api, index, *reference_transaction):
txn1 = ProposedTransaction(
Expand Down
5 changes: 2 additions & 3 deletions python-regression/util/neighbor_logic/neighbor_logic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
from util import logger as log
logger = log.getLogger(__name__)


def check_if_neighbors(api, neighbors, expected_neighbor):
Expand Down
5 changes: 2 additions & 3 deletions python-regression/util/response_logic/response_handling.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import logging
from util.threading_logic import pool_logic as pool
from util import logger as log

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = log.getLogger(__name__)


def find_in_response(key, response):
Expand Down
5 changes: 2 additions & 3 deletions python-regression/util/test_logic/api_test_logic.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import json
import logging
import urllib3
from aloe import world
from iota import Iota, Address, Tag, TryteString

from . import value_fetch_logic as value_fetch
from util import logger as log

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = log.getLogger(__name__)


def prepare_api_call(node_name, **kwargs):
Expand Down
5 changes: 2 additions & 3 deletions python-regression/util/threading_logic/pool_logic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from multiprocessing.dummy import Pool
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
from util import logger as log
logger = log.getLogger(__name__)


def start_pool(function, iterations, args):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
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
import logging

from util.transaction_bundle_logic import bundle_logic as bundle_logic
from util import logger as log

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = log.getLogger(__name__)


def create_transaction_bundle(address, tag, value):
Expand Down
126 changes: 113 additions & 13 deletions src/main/java/com/iota/iri/BundleValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.iota.iri.utils.Converter;

import java.util.*;
import com.google.common.annotations.VisibleForTesting;

/**
* Validates bundles.
Expand Down Expand Up @@ -55,22 +56,33 @@ public enum Validity {
*/
public static final int MODE_VALIDATE_SEMANTICS = 1 << 2;

/**
* Instructs the validation code to validate all transactions within the bundle approve via their branch the trunk
* transaction of the head transaction
*/
public static final int MODE_VALIDATE_BUNDLE_TX_APPROVAL = 1 << 3;

/**
* Instructs the validation code to validate that the bundle only approves tail txs.
*/
public static final int MODE_VALIDATE_TAIL_APPROVAL = 1 << 4;

/**
* Instructs the validation code to fully validate the semantics, bundle hash and signatures of the given bundle.
*/
public static final int MODE_VALIDATE_ALL = MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS;
public static final int MODE_VALIDATE_ALL = MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS | MODE_VALIDATE_TAIL_APPROVAL | MODE_VALIDATE_BUNDLE_TX_APPROVAL;

/**
* Instructs the validation code to skip checking the bundle's already computed validity and instead to proceed to
* validate the bundle further.
*/
public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 3;
public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 5;

/**
* Instructs the validation code to skip checking whether the tail transaction is present or a tail transaction was
* given as the start transaction.
*/
public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 4;
public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 6;

/**
* Fetches a bundle of transactions identified by the {@code tailHash} and validates the transactions. Bundle is a
Expand All @@ -85,6 +97,8 @@ public enum Validity {
* <li>Total bundle value is 0 (inputs and outputs are balanced)</li>
* <li>Recalculate the bundle hash by absorbing and squeezing the transactions' essence</li>
* <li>Validate the signature on input transactions</li>
* <li>The bundle must only approve tail transactions</li>
* <li>All transactions within the bundle approve via their branch the trunk transaction of the head transaction.</li>
* </ol>
* <p>
* As well as the following syntactic checks:
Expand All @@ -96,9 +110,11 @@ public enum Validity {
* we lose the last trit in the process</li>
* </ol>
*
* @param tangle used to fetch the bundle's transactions from the persistence layer
* @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
* @param tailHash the hash of the last transaction in a bundle.
* @param tangle used to fetch the bundle's transactions from the persistence layer
* @param enforceExtraRules true if enforce {@link #validateBundleTailApproval(Tangle, List)} and
* {@link #validateBundleTransactionsApproval(List)}
* @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
* @param tailHash the hash of the last transaction in a bundle.
* @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail
* transaction's {@link TransactionViewModel#getValidity()} will return 1, else {@link
* TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list will be
Expand All @@ -108,9 +124,32 @@ public enum Validity {
* validate it again.
* </p>
*/
public List<TransactionViewModel> validate(Tangle tangle, Snapshot initialSnapshot, Hash tailHash) throws Exception {
public List<TransactionViewModel> validate(Tangle tangle, boolean enforceExtraRules, Snapshot initialSnapshot,
Hash tailHash) throws Exception {
int mode = getMode(enforceExtraRules);
return validate(tangle, initialSnapshot, tailHash, mode);
}

/**
* Does {@link #validate(Tangle, boolean, Snapshot, Hash)} but with an option of skipping some checks according to
* the give {@code mode}
*
* @param tangle used to fetch the bundle's transactions from the persistence layer
* @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
* @param tailHash the hash of the last transaction in a bundle.
* @param mode flags that specify which validation checks to perform
* @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail
* transaction's {@link TransactionViewModel#getValidity()} will return 1, else
* {@link TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list
* will be returned.
* @throws Exception if a persistence error occurred
* @implNote if {@code tailHash} was already invalidated/validated by a previous call to this method then we don't
* validate it again.
* @see #validate(Tangle, boolean, Snapshot, Hash)
*/
private List<TransactionViewModel> validate(Tangle tangle, Snapshot initialSnapshot, Hash tailHash, int mode) throws Exception {
List<TransactionViewModel> bundleTxs = new LinkedList<>();
switch (validate(tangle, tailHash, MODE_VALIDATE_ALL, bundleTxs)) {
switch (validate(tangle, tailHash, mode, bundleTxs)) {
case VALID:
if (bundleTxs.get(0).getValidity() != 1) {
bundleTxs.get(0).setValidity(tangle, initialSnapshot, 1);
Expand All @@ -126,7 +165,14 @@ public List<TransactionViewModel> validate(Tangle tangle, Snapshot initialSnapsh
}
}

private static boolean hasMode(int mode, int has) {
private static int getMode(boolean enforceExtraRules) {
if (enforceExtraRules) {
return MODE_VALIDATE_ALL;
}
return MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS;
}

private static boolean hasMode(int mode, int has) {
return (mode & has) == has;
}

Expand All @@ -147,7 +193,9 @@ private static boolean hasMode(int mode, int has) {
* @return whether the validation criteria were passed or not
* @throws Exception if an error occurred in the persistence layer
*/
public static Validity validate(Tangle tangle, Hash startTxHash, int validationMode, List<TransactionViewModel> bundleTxs) throws Exception {
@VisibleForTesting
Validity validate(Tangle tangle, Hash startTxHash, int validationMode, List<TransactionViewModel> bundleTxs)
throws Exception {
TransactionViewModel startTx = TransactionViewModel.fromHash(tangle, startTxHash);
if (startTx == null || (!hasMode(validationMode, MODE_SKIP_TAIL_TX_EXISTENCE) &&
(startTx.getCurrentIndex() != 0 || startTx.getValidity() == -1))) {
Expand All @@ -160,7 +208,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
!hasMode(validationMode, MODE_VALIDATE_SEMANTICS));

// check the semantics of the bundle: total sum, semantics per tx (current/last index), missing txs, supply
Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, validationMode);
Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs,
validationMode);
if (hasMode(validationMode, MODE_VALIDATE_SEMANTICS) && bundleSemanticsValidity != Validity.VALID) {
return bundleSemanticsValidity;
}
Expand All @@ -177,6 +226,21 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
return bundleHashValidity;
}

//verify that the bundle only approves tail txs
if (hasMode(validationMode, MODE_VALIDATE_TAIL_APPROVAL)) {
Validity bundleTailApprovalValidity = validateBundleTailApproval(tangle, bundleTxs);
if (bundleTailApprovalValidity != Validity.VALID) {
return bundleTailApprovalValidity;
}
}

//verify all transactions within the bundle approve via their branch the trunk transaction of the head transaction
if (hasMode(validationMode, MODE_VALIDATE_BUNDLE_TX_APPROVAL)) {
Validity bundleTransactionsApprovalValidity = validateBundleTransactionsApproval(bundleTxs);
if (bundleTransactionsApprovalValidity != Validity.VALID) {
return bundleTransactionsApprovalValidity;
}
}

// verify the signatures of input transactions
if (hasMode(validationMode, MODE_VALIDATE_SIGNATURES)) {
Expand All @@ -198,7 +262,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
* @param bundleTxs an empty list which gets filled with the transactions in order of trunk ordering
* @return whether the bundle is semantically valid
*/
public static Validity validateBundleSemantics(TransactionViewModel startTx, Map<Hash, TransactionViewModel> bundleTxsMapping, List<TransactionViewModel> bundleTxs) {
public Validity validateBundleSemantics(TransactionViewModel startTx,
Map<Hash, TransactionViewModel> bundleTxsMapping, List<TransactionViewModel> bundleTxs) {
return validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, MODE_VALIDATE_SEMANTICS);
}

Expand All @@ -221,7 +286,9 @@ public static Validity validateBundleSemantics(TransactionViewModel startTx, Map
* @param validationMode the used validation mode
* @return whether the bundle is semantically valid
*/
private static Validity validateBundleSemantics(TransactionViewModel startTx, Map<Hash, TransactionViewModel> bundleTxsMapping, List<TransactionViewModel> bundleTxs, int validationMode) {
private Validity validateBundleSemantics(TransactionViewModel startTx,
Map<Hash, TransactionViewModel> bundleTxsMapping, List<TransactionViewModel> bundleTxs,
int validationMode) {
TransactionViewModel tvm = startTx;
final long lastIndex = tvm.lastIndex();
long bundleValue = 0;
Expand Down Expand Up @@ -379,6 +446,39 @@ public static boolean isInconsistent(Collection<TransactionViewModel> transactio
return (sum != 0 || transactionViewModels.isEmpty());
}

/**
* A bundle is invalid if The branch transaction hash of the non head transactions within a bundle, is not the same
* as the trunk transaction hash of the head transaction.
*
* @param bundleTxs list of transactions that are in a bundle.
* @return Whether the bundle tx chain is valid.
*/
@VisibleForTesting
Validity validateBundleTransactionsApproval(List<TransactionViewModel> bundleTxs) {
Hash headTrunkTransactionHash = bundleTxs.get(bundleTxs.size() - 1).getTrunkTransactionHash();
for(int i = 0; i < bundleTxs.size() - 1; i++){
if(!bundleTxs.get(i).getBranchTransactionHash().equals(headTrunkTransactionHash)){
return Validity.INVALID;
}
}
return Validity.VALID;
}

/**
* A bundle is invalid if the trunk and branch transactions approved by the bundle are non tails.
*
* @param bundleTxs The txs in the bundle.
* @return Whether the bundle approves only tails.
*/
@VisibleForTesting
Validity validateBundleTailApproval(Tangle tangle, List<TransactionViewModel> bundleTxs) throws Exception {
TransactionViewModel headTx = bundleTxs.get(bundleTxs.size() - 1);
TransactionViewModel bundleTrunkTvm = headTx.getTrunkTransaction(tangle);
TransactionViewModel bundleBranchTvm = headTx.getBranchTransaction(tangle);
return bundleTrunkTvm != null && bundleBranchTvm != null && bundleBranchTvm.getCurrentIndex() == 0
&& bundleTrunkTvm.getCurrentIndex() == 0 ? Validity.VALID : Validity.INVALID;
}

/**
* Traverses down the given {@code tail} trunk until all transactions that belong to the same bundle (identified by
* the bundle hash) are found and loaded.
Expand Down
Loading

0 comments on commit 3fda0e0

Please sign in to comment.