Function isInvalidReason

Get result of transaction data and signature verification

string isInvalidReason (
  in ref const(Transaction) tx,
  Engine engine,
  scope nothrow @safe bool delegate(in ref geod24.bitblob.BitBlob!(64L), out UTXO) findUTXO,
  in const(Height) height,
  scope nothrow @safe string delegate(in ref Transaction, Amount) checkFee,
  scope nothrow @safe Amount delegate(geod24.bitblob.BitBlob!(64L)) getPenaltyDeposit
) nothrow @safe;

Parameters

NameDescription
tx Transaction
engine script execution engine
findUTXO delegate for finding Output
height height of block
checkFee delegate for checking tx fee

Return

null if the transaction is valid, a string explaining the reason it is invalid otherwise.

Example

verify transaction data

scope engine = new Engine();
scope storage = new MemoryUTXOSet;
KeyPair[] key_pairs = [KeyPair.random, KeyPair.random, KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager();
scope checker = &payload_checker.check;

// Creates the first transaction.
Transaction previousTx = Transaction([ Output(Amount(100), key_pairs[0].address) ]);

// Save
Hash previousHash = hashFull(previousTx);
storage.put(previousTx);

// Creates the second transaction.
Transaction secondTx = Transaction(
    [
        Input(previousHash, 0)
    ],
    [
        Output(Amount(50), key_pairs[1].address)
    ]
);

secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

// It is validated. (the sum of `Output` < the sum of `Input`)
assert(secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker),
       format("Transaction data is not validated %s", secondTx));

secondTx.outputs ~= Output(Amount(50), key_pairs[2].address);
secondTx.outputs.sort;
secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

// It is validated. (the sum of `Output` == the sum of `Input`)
assert(secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker),
       format("Transaction data is not validated %s", secondTx));

secondTx.outputs ~= Output(Amount(50), key_pairs[3].address);
secondTx.outputs.sort;
secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

// It isn't validated. (the sum of `Output` > the sum of `Input`)
assert(!secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker),
       format("Transaction data is not validated %s", secondTx));

Example

negative output amounts disallowed

scope engine = new Engine();
KeyPair[] key_pairs = [KeyPair.random(), KeyPair.random()];
Transaction tx_1 = Transaction([ Output(Amount(1000), key_pairs[0].address) ]);
Hash tx_1_hash = hashFull(tx_1);

scope payload_checker = new FeeManager();
scope checker = &payload_checker.check;

scope storage = new MemoryUTXOSet;
storage.put(tx_1);

// Creates the second transaction.
Transaction tx_2 = Transaction(
    [Input(tx_1_hash, 0)],
    // oops
    [Output(Amount.invalid(-400_000), key_pairs[1].address)]);

tx_2.inputs[0].unlock = signUnlock(key_pairs[0], tx_2);

assert(!tx_2.isValid(engine, storage.getUTXOFinder(), Height(0), checker));

// Creates the third transaction.
// Reject a transaction whose output value is zero
Transaction tx_3 = Transaction(
    [Input(tx_1_hash, 0)],
    [Output(Amount.invalid(0), key_pairs[1].address)]);

tx_3.inputs[0].unlock = signUnlock(key_pairs[0], tx_3);

assert(!tx_3.isValid(engine, storage.getUTXOFinder(), Height(0), checker));

Example

This creates a new transaction and signs it as a publickey of the previous transaction to create and validate the input.

scope engine = new Engine();
scope storage = new MemoryUTXOSet;

immutable(KeyPair)[] key_pairs;
key_pairs ~= KeyPair.random();
key_pairs ~= KeyPair.random();
key_pairs ~= KeyPair.random();

scope payload_checker = new FeeManager();
scope checker = &payload_checker.check;

// Create the first transaction.
Transaction genesisTx = Transaction([ Output(Amount(100_000), key_pairs[0].address) ]);
Hash genesisHash = hashFull(genesisTx);
storage.put(genesisTx);

// Create the second transaction.
Transaction tx1 = Transaction(
    [
        Input(genesisHash, 0)
    ],
    [
        Output(Amount(1_000), key_pairs[1].address)
    ]
);

// Signs the previous hash value.
Hash tx1Hash = hashFull(tx1);
tx1.inputs[0].unlock = signUnlock(key_pairs[0], tx1);
storage.put(tx1);

assert(tx1.isValid(engine, storage.getUTXOFinder(), Height(0), checker),
       format("Transaction signature is not validated %s", tx1));

import agora.serialization.Serializer;
auto dupe = tx1.serializeFull.deserializeFull!Transaction;
assert(dupe.isValid(engine, storage.getUTXOFinder(), Height(0), checker),
       format("Transaction signature is not validated %s", tx1));
dupe.outputs[0].lock.bytes.length = 0;
assert(dupe.isInvalidReason(engine, storage.getUTXOFinder(), Height(0), checker, toDelegate(utGetPenaltyDeposit))
    == "LockType.Key requires 32-byte key argument in the lock script");

Transaction tx2 = Transaction(
    [
        Input(tx1Hash, 0)
    ],
    [
        Output(Amount(1_000), key_pairs[1].address)
    ]
);

Hash tx2Hash = hashFull(tx2);
// Sign with incorrect key
tx2.inputs[0].unlock = signUnlock(key_pairs[2], tx2);
storage.put(tx2);
assert(!tx2.isValid(engine, storage.getUTXOFinder(), Height(0), checker));

Example

verify transactions associated with freezing

scope engine = new Engine();
scope storage = new MemoryUTXOSet();
KeyPair[] key_pairs = [KeyPair.random, KeyPair.random, KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager();
scope checker = &payload_checker.check;

Transaction secondTx;
Hash previousHash;

// When the privious transaction type is `Payment`, second transaction type is `Freeze`.
// Second transaction is valid.
{
    storage.clear;
    // Create the previous transaction with type `OutputType.Payment`
    Transaction previousTx = Transaction([ Output(Amount.MinFreezeAmount +
    10_000.coins, key_pairs[0].address) ]);
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {
            unlock_height: 0,
            output: output
        };
        storage[utxo_hash] = utxo_value;
    }

    // Creates the freezing transaction.
    secondTx = Transaction([Input(previousHash, 0)],
        [Output(Amount.MinFreezeAmount, key_pairs[1].address, OutputType.Freeze)]
    );
    secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

    // Second Transaction is valid.
    assert(secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker));
}

// When the privious transaction type is `Freeze`, second transaction type is `Freeze`.
// Second transaction is invalid.
{
    storage.clear;
    // Create the previous transaction with type `OutputType.Payment`
    Transaction previousTx = Transaction([ Output(Amount.MinFreezeAmount, key_pairs[0].address, OutputType.Freeze) ]);
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {
            unlock_height: 0,
            output: output
        };
        storage[utxo_hash] = utxo_value;
    }

    // Creates the freezing transaction.
    secondTx = Transaction(
        [Input(previousHash, 0)],
        [Output(Amount.MinFreezeAmount, key_pairs[1].address, OutputType.Freeze)]
    );
    secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

    // Second Transaction is invalid.
    assert(!secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker));
}

// When the privious transaction with not enough amount at freezing.
// Second transaction is invalid.
{
    storage.clear;
    // Create the previous transaction with type `OutputType.Payment`
    Transaction previousTx = Transaction([ Output(Amount(100_000_000_000L), key_pairs[0].address) ]);
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {
            unlock_height: 0,
            output: output
        };
        storage[utxo_hash] = utxo_value;
    }

    // Creates the freezing transaction.
    secondTx = Transaction(
        [Input(previousHash, 0)],
        [Output(Amount(100_000_000_000L), key_pairs[1].address, OutputType.Freeze)]
    );
    secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

    // Second Transaction is invalid.
    assert(!secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker));
}

// When the privious transaction with too many amount at freezings.
// Second transaction is valid.
{
    // Create the previous transaction with type `OutputType.Payment`
    Transaction previousTx = Transaction([ Output(Amount(500_000_000_000L), key_pairs[0].address) ]);
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {
            unlock_height: 0,
            output: output
        };
        storage[utxo_hash] = utxo_value;
    }

    // Creates the freezing transaction.
    secondTx = Transaction(
        [Input(previousHash, 0)],
        [Output(Amount(500_000_000_000L) - 10_000.coins, key_pairs[1].address, OutputType.Freeze)]
    );
    secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

    // Second Transaction is valid.
    assert(secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker));
}

// When the freeze TX does not include enough freezing fees
{
    storage.clear;
    // Create the previous transaction with type `OutputType.Payment`
    Transaction previousTx = Transaction([ Output((Amount.MinFreezeAmount + 9_999.coins) * 2, key_pairs[0].address) ]);
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {
            unlock_height: 0,
            output: output
        };
        storage[utxo_hash] = utxo_value;
    }

    // Creates the freezing transaction.
    secondTx = Transaction(
        [Input(previousHash, 0)],
        [Output(Amount.MinFreezeAmount, key_pairs[1].address, OutputType.Freeze),
        Output(Amount.MinFreezeAmount, key_pairs[1].address, OutputType.Freeze)]
    );
    secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);
    // Second Transaction is invalid.
    assert(!secondTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker));
}

Example

Test validation of transactions associated with freezing

Table of freezing status changes over time

freezing status     / melted     / frozen     / melting    / melted

block height / N1 / N2 / N3 / N4

condition to use    /            / N2 >= N1+1 / N3 >= N2+1 / N4 >= N3+2016

utxo unlock height / N1+1 / N2+1 / N3+2016 / N4+1

utxo type           / Payment    / Freeze     / Payment    / Payment
scope engine = new Engine();
scope storage = new MemoryUTXOSet;
KeyPair[] key_pairs = [KeyPair.random, KeyPair.random, KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager();
scope checker = &payload_checker.check;

Height height;

Transaction secondTx;
Transaction thirdTx;
Transaction fourthTx;
Transaction fifthTx;

Hash previousHash;
Hash secondHash;
Hash thirdHash;
Hash fifthHash;

// Create the previous transaction with type <code class="lang-d"><span class="typ">OutputType<wbr/></span><span class="pun">.</span><span class="typ">Payment</span></code>
// Expected height : 0
// Expected Status : melted
{
    height = 0;
    Transaction previousTx = Transaction(
        [ Output(Amount.MinFreezeAmount + 10_000.coins, key_pairs[0].address) ]);

    // Save to UTXOSet
    previousHash = hashFull(previousTx);
    foreach (idx, output; previousTx.outputs)
    {
        const Hash utxo_hash = hashMulti(previousHash, idx);
        const UTXO utxo_value = {

unlock height

height+1,

output

output }; storage[utxo_hash] = utxo_value; } }

// Creates the second freezing transaction // Current height : 0 // Current Status : melted // Expected height : 1 // Expected Status : frozen { height = 1; secondTx = Transaction( [Input(previousHash, 0)], [Output(Amount.MinFreezeAmount, key_pairs[1].address, OutputType.Freeze)] ); secondTx.inputs[0].unlock = signUnlock(key_pairs[0], secondTx);

// Second Transaction is VALID. assert(secondTx.isValid(engine, storage.getUTXOFinder(), height, checker));

// Save to UTXOSet secondHash = hashFull(secondTx); foreach (idx, output; secondTx.outputs) { const Hash utxo_hash = hashMulti(secondHash, idx); const UTXO utxo_value = {

unlock height

height+1,

output

output }; storage[utxo_hash] = utxo_value; } }

// Creates the third payment transaction // Current height : 1 // Current Status : frozen // Expected height : 2 // Expected Status : melting { height = 2; thirdTx = Transaction([Input(secondHash, 0)], [Output(Amount.MinFreezeAmount, key_pairs[2].address)] ); thirdTx.inputs[0].unlock = signUnlock(key_pairs[1], thirdTx);

// Third Transaction is VALID. assert(thirdTx.isValid(engine, storage.getUTXOFinder(), height, checker));

// Save to UTXOSet thirdHash = hashFull(thirdTx); foreach (idx, output; thirdTx.outputs) { const Hash utxo_hash = hashMulti(thirdHash, idx); const UTXO utxo_value = {

unlock height

height+2016,

output

output }; storage[utxo_hash] = utxo_value; } }

// Creates the fourth payment transaction : didn't change to melted not yet // Current height : 2+2014 // Current Status : melting // Expected height : 2+2015 // Expected Status : melting { height = 2+2015; // this is melting, not melted fourthTx = Transaction([Input(thirdHash, 0)], [Output(Amount.MinFreezeAmount, key_pairs[3].address)] ); fourthTx.inputs[0].unlock = signUnlock(key_pairs[2], fourthTx);

// Third Transaction is INVALID. assert(!fourthTx.isValid(engine, storage.getUTXOFinder(), height, checker)); }

// Creates the fifth payment transaction // Current height : 2+2015 // Current Status : melting // Expected height : 2+2016 // Expected Status : melted { height = 2+2016; // this is melted fifthTx = Transaction( [Input(thirdHash, 0)], [Output(Amount.MinFreezeAmount, key_pairs[3].address)] ); fifthTx.inputs[0].unlock = signUnlock(key_pairs[2], fourthTx);

// Third Transaction is VALID. assert(fifthTx.isValid(engine, storage.getUTXOFinder(), height, checker));

// Save to UTXOSet fifthHash = hashFull(fifthTx); foreach (idx, output; fifthTx.outputs) { const Hash utxo_hash = hashMulti(fifthHash, idx); const UTXO utxo_value = {

unlock height

height+1,

output

output }; storage[utxo_hash] = utxo_value; } }


Example:
test for transactions having no input or no output

import std.string; import std.algorithm.searching;

scope engine = new Engine(); scope storage = new MemoryUTXOSet; KeyPair key_pair = KeyPair.random;

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create a transaction having no input Transaction oneTx = Transaction( [], [Output(Amount(50), key_pair.address)] ); storage.put(oneTx);

// test for Payment transaction having no input assert(canFind(toLower(oneTx.isInvalidReason(engine, storage.getUTXOFinder(), Height(0), checker, toDelegate(utGetPenaltyDeposit))), "no input"), format("Tx having no input should not pass validation. tx: %s", oneTx));

// create a transaction Transaction firstTx = Transaction([ Output(Amount(100_1000), key_pair.address) ]); Hash firstHash = hashFull(firstTx); storage.put(firstTx);

// create a transaction having no output Transaction secondTx = Transaction( [Input(firstHash, 0)], [] ); storage.put(secondTx);

// test for Freeze transaction having no output assert(canFind(toLower(secondTx.isInvalidReason(engine, storage.getUTXOFinder(), Height(0), checker, toDelegate(utGetPenaltyDeposit))), "no output"), format("Tx having no output should not pass validation. tx: %s", secondTx));


Example:
test for transaction having combined inputs

scope engine = new Engine(); scope storage = new MemoryUTXOSet; KeyPair[] key_pairs = [KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create the first transaction. Transaction firstTx = Transaction( [Input(Hash.init, 0)], [Output(Amount(100), key_pairs[0].address)] ); Hash firstHash = hashFull(firstTx); storage.put(firstTx);

// create the second transaction. Transaction secondTx = Transaction( [Input(Hash.init, 0)], [Output(Amount(100), key_pairs[0].address, OutputType.Freeze)] ); Hash secondHash = hashFull(secondTx); storage.put(secondTx);

// create the third transaction Transaction thirdTx = Transaction( [Input(firstHash, 0), Input(secondHash, 0)], [Output(Amount(100), key_pairs[1].address)] ); Hash thirdHash = hashFull(thirdTx); storage.put(thirdTx); thirdTx.inputs[0].unlock = signUnlock(key_pairs[0], thirdTx); thirdTx.inputs[1].unlock = signUnlock(key_pairs[0], thirdTx);

// test for transaction having combined inputs assert(!thirdTx.isValid(engine, storage.getUTXOFinder(), Height(0), checker), format("Tx having combined inputs should not pass validation. tx: %s", thirdTx));


Example:
test for unknown transaction output type

scope engine = new Engine(); Transaction[Hash] storage; OutputType unknown_type = cast(OutputType)100; // picking value beyond enum range KeyPair key_pair = KeyPair.random;

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create a transaction having unknown transaction type Transaction firstTx = Transaction([Input(Hash.init, 0)], [Output(Amount(100), key_pair.address, unknown_type)] ); Hash firstHash = hashFull(firstTx); storage[firstHash] = firstTx;

// test for unknown transaction type assert(!firstTx.isValid(engine, null, Height(0), checker), format("Tx having unknown type should not pass validation. tx: %s", firstTx));


Example:
test for checking input overflow for Payment and Freeze type transactions

scope engine = new Engine(); scope storage = new MemoryUTXOSet(); KeyPair[] key_pairs = [KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create the first transaction auto firstTx = Transaction( [Input(Hash.init, 0)], [Output(Amount.MaxUnitSupply, key_pairs[0].address)] ); storage.put(firstTx); const firstHash = UTXO.getHash(firstTx.hashFull(), 0);

// create the second transaction auto secondTx = Transaction( [Input(Hash.init, 0)], [Output(Amount(100), key_pairs[0].address)] ); storage.put(secondTx); const secondHash = UTXO.getHash(secondTx.hashFull(), 0);

// create the third transaction auto thirdTx = Transaction( [Input(firstHash, 0), Input(secondHash, 0)], [Output(Amount(100), key_pairs[1].address)] ); storage.put(thirdTx); auto thirdHash = hashFull(thirdTx); thirdTx.inputs[0].unlock = signUnlock(key_pairs[0], thirdTx); thirdTx.inputs[1].unlock = signUnlock(key_pairs[0], thirdTx);

// test for input overflow in Payment transaction assert(!thirdTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("Tx having input overflow should not pass validation. tx: %s", thirdTx));

// create the fourth transaction auto fourthTx = Transaction( [Input(firstHash, 0), Input(secondHash, 0)], [Output(Amount(100), key_pairs[1].address, OutputType.Freeze)] ); storage.put(fourthTx); auto fourthHash = hashFull(fourthTx); fourthTx.inputs[0].unlock = signUnlock(key_pairs[0], fourthTx); fourthTx.inputs[1].unlock = signUnlock(key_pairs[0], fourthTx);

// test for input overflow in Freeze transaction assert(!fourthTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("Tx having input overflow should not pass validation. tx: %s", fourthTx));


Example:
test for checking output overflow for Payment type transaction

scope engine = new Engine(); scope storage = new MemoryUTXOSet(); KeyPair[] key_pairs = [KeyPair.random, KeyPair.random];

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create the first transaction auto firstTx = Transaction( [Input(Hash.init, 0)], [Output(Amount(100), key_pairs[0].address)] ); storage.put(firstTx); const firstHash = UTXO.getHash(firstTx.hashFull(), 0);

// create the second transaction auto secondTx = Transaction( [Input(Hash.init, 0)], [Output(Amount(100), key_pairs[0].address)] ); storage.put(secondTx); const secondHash = UTXO.getHash(secondTx.hashFull(), 0);

// create the third transaction auto thirdTx = Transaction( [Input(firstHash, 0), Input(secondHash, 0)], [Output(Amount.MaxUnitSupply, key_pairs[1].address), Output(Amount(100), key_pairs[1].address)] ); storage.put(thirdTx); auto thirdHash = hashFull(thirdTx); thirdTx.inputs[0].unlock = signUnlock(key_pairs[0], thirdTx); thirdTx.inputs[1].unlock = signUnlock(key_pairs[0], thirdTx);

// test for output overflow in Payment transaction assert(!thirdTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("Tx having output overflow should not pass validation. tx: %s", thirdTx));


Example:
test for transaction to store data

scope engine = new Engine(); scope storage = new MemoryUTXOSet; KeyPair key_pair = KeyPair.random;

scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

// create the payment transaction. Transaction paymentTx = Transaction( [Input(Hash.init)], [Output(80_000.coins, key_pair.address)] ); storage.put(paymentTx); Hash payment_utxo = UTXO.getHash(paymentTx.hashFull(), 0);

// create the frozen transaction. Transaction frozenTx = Transaction( [Input(Hash.init)], [Output(80_000.coins, key_pair.address, OutputType.Freeze)] ); storage.put(frozenTx); Hash frozen_utxo = UTXO.getHash(frozenTx.hashFull(), 0);

// create data with nomal size ubyte[] normal_data; normal_data.length = payload_checker.params.TxPayloadMaxSize; foreach (idx; 0 .. normal_data.length) normal_data[idx] = cast(ubyte)(idx % 256);

// create data with large size ubyte[] large_data; large_data ~= normal_data; large_data ~= cast(ubyte)(0);

// calculate fee Amount normal_data_fee = calculateDataFee(normal_data.length, payload_checker.params.TxPayloadFeeFactor); Amount large_data_fee = calculateDataFee(large_data.length, payload_checker.params.TxPayloadFeeFactor);

Transaction dataTx; Hash dataHash;

// Test 1. Too large data // create a transaction with large data dataTx = Transaction( [Input(payment_utxo)], [ Output(large_data_fee, payload_checker.params.CommonsBudgetAddress), Output(40_000.coins, key_pair.address) ].sort.array, large_data, ); dataHash = hashFull(dataTx); dataTx.inputs[0].unlock = signUnlock(key_pair, dataTx);

// test for the transaction with large data assert(!dataTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("When storing data, tx with large data payload should not pass validation. tx: %s", dataTx));

// Test 2. With not enough fee // create a transaction with not enough fee dataTx = Transaction( [Input(payment_utxo)], [Output(Amount(80_000L * 10_000_000L - normal_data_fee.integral + 1), key_pair.address)], normal_data, ); dataHash = hashFull(dataTx); dataTx.inputs[0].unlock = signUnlock(key_pair, dataTx);

// test for transaction without commons budget assert(!dataTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("When storing data, tx with not enough fee should not pass validation. tx: %s", dataTx));

// Test 3. Nomal // create a transaction with enough fee Amount rem_amount = paymentTx.outputs[0].value; rem_amount.sub(normal_data_fee); dataTx = Transaction( [Input(payment_utxo)], [Output(rem_amount, key_pair.address)], normal_data, ); dataHash = hashFull(dataTx); dataTx.inputs[0].unlock = signUnlock(key_pair, dataTx);

// test for the transaction with enough fee assert(dataTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("When storing data, Transaction data is not validated. tx: %s", dataTx));

// Test 5. Using frozen input // create the data transaction. dataTx = Transaction( [Input(frozen_utxo)], [ Output(40_000.coins, key_pair.address) ], normal_data, ); dataHash = hashFull(dataTx); dataTx.inputs[0].unlock = signUnlock(key_pair, dataTx);

// test for data storage using frozen input assert(dataTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("When storing data, tx with frozen input should pass validation. tx: %s", dataTx));

// Test 6. The transaction with the type of Freeze // create the data transaction. dataTx = Transaction( [Input(payment_utxo)], [ Output(40_000.coins, key_pair.address, OutputType.Freeze) ], normal_data, ); dataHash = hashFull(dataTx); dataTx.inputs[0].unlock = signUnlock(key_pair, dataTx);

// test for data storage using frozen input assert(!dataTx.isValid(engine, &storage.peekUTXO, Height(0), checker), format("When storing data, tx with type of Freeze should not pass validation. tx: %s", dataTx));


Example:
transaction-level absolute time lock

scope engine = new Engine(); scope storage = new MemoryUTXOSet; scope payload_checker = new FeeManager(); scope checker = &payload_checker.check;

KeyPair kp = KeyPair.random();

Transaction prev_tx = Transaction([Output(Amount(100), kp.address)]); storage.put(prev_tx);

Transaction tx = Transaction( [Input(hashFull(prev_tx), 0)], [Output(Amount(50), kp.address)]);

// effectively disabled lock tx.lock_height = Height(0); tx.inputs[0].unlock = signUnlock(kp, tx); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(0), checker, toDelegate(utGetPenaltyDeposit)) is null); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(1024), checker, toDelegate(utGetPenaltyDeposit)) is null);

tx.lock_height = Height(10); tx.inputs[0].unlock = signUnlock(kp, tx); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(0), checker, toDelegate(utGetPenaltyDeposit)) == "Transaction: Not unlocked for this height"); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(9), checker, toDelegate(utGetPenaltyDeposit)) == "Transaction: Not unlocked for this height"); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(10), checker, toDelegate(utGetPenaltyDeposit)) == null); assert(tx.isInvalidReason(engine, storage.getUTXOFinder(), Height(1024), checker, toDelegate(utGetPenaltyDeposit)) == null);