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

White flag #1806

Open
wants to merge 8 commits into
base: white-flag
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,47 @@ Feature: Test transaction confirmation
| keys | values | type |
| states | False | boolListMixed |

Scenario: Conflicting bundles are ignored
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a scenario for invalid bundles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already done in my PR, theyre in the disabled regression tests file in machine 3

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was just about to tag you here @kwek20 but I see you are very alert :-)

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 |

4 changes: 2 additions & 2 deletions python-regression/tests/features/machine3/config.yml
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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""\
Expand Down
55 changes: 54 additions & 1 deletion python-regression/tests/features/steps/transaction_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,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, full_reference, reference_transaction)

milestones.update_latest_milestone(world.config, node, milestone)

Expand Down Expand Up @@ -193,6 +195,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
Expand All @@ -206,3 +245,17 @@ def wait_for_update(index, api):
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
9 changes: 7 additions & 2 deletions python-regression/util/milestone_logic/milestones.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

logger = log.getLogger(__name__)

def issue_milestone(address, api, index, *reference_transaction):
def issue_milestone(address, api, index, full_reference=False, reference_transaction=None):
txn1 = ProposedTransaction(
address=Address(address),
value=0
Expand All @@ -27,7 +27,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']

Expand Down
3 changes: 3 additions & 0 deletions python-regression/util/static_vals.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9NINE99999999999999999999999999999999999999",
"THIS9TEST9ADDRESS9HAS9ONE9HUNDRED9IOTA9TEN999999999999999999999999999999999999999"]

DOUBLE_SPEND_SEED = "THIS9DOUBLE9SPEND9ADDRESS9HAS9ONE9THOUSAND9IOTA9999999999999999999999999999999999"
DOUBLE_SPEND_ADDRESSES = ["YIYPLJDLF9MNSCAORRGFNJNDXOFQZXEXTPDD9TROXZCJPY9AWDTIJY9RKIPLUDPFPLKZRP9NKHPKBJAYA",
"DHGTQLJRYMJTHSYGKCAJYMMWHYYM9XKGYMMKYLEUWOQOAMORGTSWMRHVZ9VKPRTDUGUPBKRB9WMQOBRHY"]

SIDE_TANGLE_ADDRESS = "SIDE9TANGLE9999999999999999999999999999999999999999999999999999999999999999999999"
STITCHING_ADDRESS = "STITCHING9TRANSACTIONS99999999999999999999999999999999999999999999999999999999999"
Expand Down
1 change: 1 addition & 0 deletions python-regression/util/test_logic/api_test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion python-regression/util/test_logic/value_fetch_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
from util import logger as log

logger = log.getLogger(__name__)
Expand Down Expand Up @@ -110,7 +112,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]
Expand Down Expand Up @@ -143,3 +145,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]
16 changes: 16 additions & 0 deletions src/main/java/com/iota/iri/controllers/TransactionViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,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 setConflicting(Tangle tangle, Snapshot initialSnapshot, final boolean isConflicting) throws Exception {
if(isConflicting != transaction.conflicting.get()){
transaction.conflicting.set(isConflicting);
update(tangle, initialSnapshot, "conflicting");
}
}

/**
* This method gets the {@link Transaction#milestone}.
*
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/iota/iri/model/persistables/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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;
Expand Down Expand Up @@ -79,6 +84,11 @@ public class Transaction implements Persistable {
*/
public AtomicBoolean solid = new AtomicBoolean(false);

/**
* This flag indicates whether the transaction is conflicting and was ignored in balance computation
*/
public AtomicBoolean conflicting = new AtomicBoolean(false);

/**
* This flag indicates if the transaction is a coordinator issued milestone.
*/
Expand Down Expand Up @@ -146,6 +156,7 @@ public byte[] metadata() {
byte flags = 0;
flags |= solid.get() ? IS_SOLID_BITMASK : 0;
flags |= milestone.get() ? IS_MILESTONE_BITMASK : 0;
flags |= conflicting.get() ? IS_CONFLICTING_BITMASK : 0;
buffer.put(flags);

buffer.put(Serializer.serialize(snapshot.get()));
Expand Down Expand Up @@ -209,6 +220,7 @@ public void readMetadata(byte[] bytes) {
// decode the boolean byte by checking the bitmasks
solid.set((bytes[i] & IS_SOLID_BITMASK) != 0);
milestone.set((bytes[i] & IS_MILESTONE_BITMASK) != 0);
conflicting.set((bytes[i] & IS_CONFLICTING_BITMASK) != 0);
i++;

snapshot.set(Serializer.getInteger(bytes, i));
Expand Down
Loading