Skip to content
This repository was archived by the owner on Aug 23, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@ -204,7 +202,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,10 +2,9 @@
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 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