diff --git a/rai/blockstore.cpp b/rai/blockstore.cpp index b3619d15..c83ebee6 100644 --- a/rai/blockstore.cpp +++ b/rai/blockstore.cpp @@ -42,6 +42,13 @@ public: { fill_value (block_a); } + void utx_block (rai::utx_block const & block_a) override + { + if (!block_a.previous ().is_zero ()) + { + fill_value (block_a); + } + } MDB_txn * transaction; rai::block_store & store; }; @@ -256,6 +263,7 @@ checksum (0) error_a |= mdb_dbi_open (transaction, "receive", MDB_CREATE, &receive_blocks) != 0; error_a |= mdb_dbi_open (transaction, "open", MDB_CREATE, &open_blocks) != 0; error_a |= mdb_dbi_open (transaction, "change", MDB_CREATE, &change_blocks) != 0; + error_a |= mdb_dbi_open (transaction, "utx", MDB_CREATE, &utx_blocks) != 0; error_a |= mdb_dbi_open (transaction, "pending", MDB_CREATE, &pending) != 0; error_a |= mdb_dbi_open (transaction, "blocks_info", MDB_CREATE, &blocks_info) != 0; error_a |= mdb_dbi_open (transaction, "representation", MDB_CREATE, &representation) != 0; @@ -558,6 +566,9 @@ MDB_dbi rai::block_store::block_database (rai::block_type type_a) case rai::block_type::change: result = change_blocks; break; + case rai::block_type::utx: + result = utx_blocks; + break; default: assert (false); break; @@ -603,7 +614,20 @@ MDB_val rai::block_store::block_get_raw (MDB_txn * transaction_a, rai::block_has { auto status (mdb_get (transaction_a, change_blocks, rai::mdb_val (hash_a), result)); assert (status == 0 || status == MDB_NOTFOUND); - if (status == 0) + if (status != 0) + { + auto status (mdb_get (transaction_a, utx_blocks, rai::mdb_val (hash_a), result)); + assert (status == 0 || status == MDB_NOTFOUND); + if (status != 0) + { + // Block not found + } + else + { + type_a = rai::block_type::utx; + } + } + else { type_a = rai::block_type::change; } @@ -663,8 +687,15 @@ std::unique_ptr rai::block_store::block_random (MDB_txn * transactio } else { - // change - result = block_random (transaction_a, change_blocks); + region -= count.open; + if (region < count.change) + { + result = block_random (transaction_a, change_blocks); + } + else + { + result = block_random (transaction_a, utx_blocks); + } } } } @@ -712,20 +743,25 @@ std::unique_ptr rai::block_store::block_get (MDB_txn * transaction_a void rai::block_store::block_del (MDB_txn * transaction_a, rai::block_hash const & hash_a) { - auto status (mdb_del (transaction_a, send_blocks, rai::mdb_val (hash_a), nullptr)); + auto status (mdb_del (transaction_a, utx_blocks, rai::mdb_val (hash_a), nullptr)); assert (status == 0 || status == MDB_NOTFOUND); if (status != 0) { - auto status (mdb_del (transaction_a, receive_blocks, rai::mdb_val (hash_a), nullptr)); + auto status (mdb_del (transaction_a, send_blocks, rai::mdb_val (hash_a), nullptr)); assert (status == 0 || status == MDB_NOTFOUND); if (status != 0) { - auto status (mdb_del (transaction_a, open_blocks, rai::mdb_val (hash_a), nullptr)); + auto status (mdb_del (transaction_a, receive_blocks, rai::mdb_val (hash_a), nullptr)); assert (status == 0 || status == MDB_NOTFOUND); if (status != 0) { - auto status (mdb_del (transaction_a, change_blocks, rai::mdb_val (hash_a), nullptr)); - assert (status == 0); + auto status (mdb_del (transaction_a, open_blocks, rai::mdb_val (hash_a), nullptr)); + assert (status == 0 || status == MDB_NOTFOUND); + if (status != 0) + { + auto status (mdb_del (transaction_a, change_blocks, rai::mdb_val (hash_a), nullptr)); + assert (status == 0); + } } } } @@ -753,6 +789,12 @@ bool rai::block_store::block_exists (MDB_txn * transaction_a, rai::block_hash co auto status (mdb_get (transaction_a, change_blocks, rai::mdb_val (hash_a), junk)); assert (status == 0 || status == MDB_NOTFOUND); exists = status == 0; + if (!exists) + { + auto status (mdb_get (transaction_a, utx_blocks, rai::mdb_val (hash_a), junk)); + assert (status == 0 || status == MDB_NOTFOUND); + exists = status == 0; + } } } } @@ -774,10 +816,14 @@ rai::block_counts rai::block_store::block_count (MDB_txn * transaction_a) MDB_stat change_stats; auto status4 (mdb_stat (transaction_a, change_blocks, &change_stats)); assert (status4 == 0); + MDB_stat utx_stats; + auto status5 (mdb_stat (transaction_a, utx_blocks, &utx_stats)); + assert (status5 == 0); result.send = send_stats.ms_entries; result.receive = receive_stats.ms_entries; result.open = open_stats.ms_entries; result.change = change_stats.ms_entries; + result.utx = utx_stats.ms_entries; return result; } diff --git a/rai/blockstore.hpp b/rai/blockstore.hpp index 004b1fda..77ecce89 100644 --- a/rai/blockstore.hpp +++ b/rai/blockstore.hpp @@ -162,6 +162,8 @@ public: MDB_dbi open_blocks; // block_hash -> change_block MDB_dbi change_blocks; + // block_hash -> utx_block + MDB_dbi utx_blocks; // block_hash -> sender, amount, destination // Pending blocks to sender account, amount, destination account MDB_dbi pending; // block_hash -> account, balance // Blocks info diff --git a/rai/common.cpp b/rai/common.cpp index 53d8f78c..a6d32f83 100644 --- a/rai/common.cpp +++ b/rai/common.cpp @@ -89,6 +89,7 @@ size_t constexpr rai::send_block::size; size_t constexpr rai::receive_block::size; size_t constexpr rai::open_block::size; size_t constexpr rai::change_block::size; +size_t constexpr rai::utx_block::size; rai::keypair const & rai::zero_key (globals.zero_key); rai::keypair const & rai::test_genesis_key (globals.test_genesis_key); @@ -255,7 +256,7 @@ change (0) size_t rai::block_counts::sum () { - return send + receive + open + change; + return send + receive + open + change + utx; } rai::pending_info::pending_info () : @@ -439,6 +440,14 @@ void rai::amount_visitor::open_block (rai::open_block const & block_a) } } +void rai::amount_visitor::utx_block (rai::utx_block const & block_a) +{ + balance_visitor prev (transaction, store); + prev.compute (block_a.hashables.previous); + result = block_a.hashables.balance.number (); + result = result < prev.result ? prev.result - result : result - prev.result; +} + void rai::amount_visitor::change_block (rai::change_block const & block_a) { result = 0; @@ -525,6 +534,12 @@ void rai::balance_visitor::change_block (rai::change_block const & block_a) } } +void rai::balance_visitor::utx_block (rai::utx_block const & block_a) +{ + result = block_a.hashables.balance.number (); + current = 0; +} + void rai::balance_visitor::compute (rai::block_hash const & block_hash) { current = block_hash; @@ -574,6 +589,11 @@ void rai::representative_visitor::change_block (rai::change_block const & block_ result = block_a.hash (); } +void rai::representative_visitor::utx_block (rai::utx_block const & block_a) +{ + result = block_a.hash (); +} + rai::vote::vote (rai::vote const & other_a) : sequence (other_a.sequence), block (other_a.block), diff --git a/rai/common.hpp b/rai/common.hpp index 3a05ebf9..b8e545a8 100644 --- a/rai/common.hpp +++ b/rai/common.hpp @@ -37,6 +37,7 @@ public: void receive_block (rai::receive_block const &) override; void open_block (rai::open_block const &) override; void change_block (rai::change_block const &) override; + void utx_block (rai::utx_block const &) override; MDB_txn * transaction; rai::block_store & store; rai::block_hash current; @@ -56,6 +57,7 @@ public: void receive_block (rai::receive_block const &) override; void open_block (rai::open_block const &) override; void change_block (rai::change_block const &) override; + void utx_block (rai::utx_block const &) override; void from_send (rai::block_hash const &); MDB_txn * transaction; rai::block_store & store; @@ -75,6 +77,7 @@ public: void receive_block (rai::receive_block const & block_a) override; void open_block (rai::open_block const & block_a) override; void change_block (rai::change_block const & block_a) override; + void utx_block (rai::utx_block const & block_a) override; MDB_txn * transaction; rai::block_store & store; rai::block_hash current; @@ -170,6 +173,7 @@ public: size_t receive; size_t open; size_t change; + size_t utx; }; class vote { @@ -217,9 +221,12 @@ enum class process_result unreceivable, // Source block doesn't exist or has already been received gap_previous, // Block marked as previous is unknown gap_source, // Block marked as source is unknown + utx_disabled, // Awaiting UTX canary block not_receive_from_send, // Receive does not have a send source account_mismatch, // Account number in open block doesn't match send destination - opened_burn_account // The impossible happened, someone found the private key associated with the public key '0'. + opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. + balance_mismatch, // Balance and amount delta don't match + block_position // This block cannot follow the previous block }; class process_return { @@ -228,6 +235,7 @@ public: rai::account account; rai::amount amount; rai::account pending_account; + boost::optional utx_is_send; }; enum class tally_result { diff --git a/rai/core_test/block.cpp b/rai/core_test/block.cpp index 99052728..35e73863 100644 --- a/rai/core_test/block.cpp +++ b/rai/core_test/block.cpp @@ -325,3 +325,81 @@ TEST (block, confirm_req_serialization) ASSERT_EQ (req, req2); ASSERT_EQ (*req.block, *req2.block); } + +TEST (utx, serialization) +{ + rai::keypair key1; + rai::keypair key2; + rai::utx_block block1 (key1.pub, 1, key2.pub, 2, 4, key1.prv, key1.pub, 5); + ASSERT_EQ (key1.pub, block1.hashables.account); + ASSERT_EQ (rai::block_hash (1), block1.previous ()); + ASSERT_EQ (key2.pub, block1.hashables.representative); + ASSERT_EQ (rai::amount (2), block1.hashables.balance); + ASSERT_EQ (rai::uint256_union (4), block1.hashables.link); + std::vector bytes; + { + rai::vectorstream stream (bytes); + block1.serialize (stream); + } + ASSERT_EQ (rai::utx_block::size, bytes.size ()); + bool error1; + rai::bufferstream stream (bytes.data (), bytes.size ()); + rai::utx_block block2 (error1, stream); + ASSERT_FALSE (error1); + ASSERT_EQ (block1, block2); + block2.hashables.account.clear (); + block2.hashables.previous.clear (); + block2.hashables.representative.clear (); + block2.hashables.balance.clear (); + block2.hashables.link.clear (); + block2.signature.clear (); + block2.work = 0; + rai::bufferstream stream2 (bytes.data (), bytes.size ()); + ASSERT_FALSE (block2.deserialize (stream2)); + ASSERT_EQ (block1, block2); + std::string json; + block1.serialize_json (json); + std::stringstream body (json); + boost::property_tree::ptree tree; + boost::property_tree::read_json (body, tree); + bool error2; + rai::utx_block block3 (error2, tree); + ASSERT_FALSE (error2); + ASSERT_EQ (block1, block3); + block3.hashables.account.clear (); + block3.hashables.previous.clear (); + block3.hashables.representative.clear (); + block3.hashables.balance.clear (); + block3.hashables.link.clear (); + block3.signature.clear (); + block3.work = 0; + ASSERT_FALSE (block3.deserialize_json (tree)); + ASSERT_EQ (block1, block3); +} + +TEST (utx, hashing) +{ + rai::keypair key; + rai::utx_block block (key.pub, 0, key.pub, 0, 0, key.prv, key.pub, 0); + auto hash (block.hash ()); + block.hashables.account.bytes[0] ^= 0x1; + ASSERT_NE (hash, block.hash ()); + block.hashables.account.bytes[0] ^= 0x1; + ASSERT_EQ (hash, block.hash ()); + block.hashables.previous.bytes[0] ^= 0x1; + ASSERT_NE (hash, block.hash ()); + block.hashables.previous.bytes[0] ^= 0x1; + ASSERT_EQ (hash, block.hash ()); + block.hashables.representative.bytes[0] ^= 0x1; + ASSERT_NE (hash, block.hash ()); + block.hashables.representative.bytes[0] ^= 0x1; + ASSERT_EQ (hash, block.hash ()); + block.hashables.balance.bytes[0] ^= 0x1; + ASSERT_NE (hash, block.hash ()); + block.hashables.balance.bytes[0] ^= 0x1; + ASSERT_EQ (hash, block.hash ()); + block.hashables.link.bytes[0] ^= 0x1; + ASSERT_NE (hash, block.hash ()); + block.hashables.link.bytes[0] ^= 0x1; + ASSERT_EQ (hash, block.hash ()); +} diff --git a/rai/core_test/block_store.cpp b/rai/core_test/block_store.cpp index 3fc7bdf7..d46cb6e3 100644 --- a/rai/core_test/block_store.cpp +++ b/rai/core_test/block_store.cpp @@ -986,3 +986,27 @@ TEST (block_store, upgrade_v9_v10) ASSERT_EQ (block_info.account, rai::test_genesis_key.pub); ASSERT_EQ (block_info.balance.number (), rai::genesis_amount - rai::Gxrb_ratio * 31); } + +TEST (block_store, utx_block) +{ + bool error (false); + rai::block_store store (error, rai::unique_path ()); + ASSERT_FALSE (error); + rai::genesis genesis; + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair key1; + rai::utx_block block1 (1, genesis.hash (), 3, 4, 6, key1.prv, key1.pub, 7); + ASSERT_EQ (rai::block_type::utx, block1.type ()); + store.block_put (transaction, block1.hash (), block1); + ASSERT_TRUE (store.block_exists (transaction, block1.hash ())); + auto block2 (store.block_get (transaction, block1.hash ())); + ASSERT_NE (nullptr, block2); + ASSERT_EQ (block1, *block2); + auto count (store.block_count (transaction)); + ASSERT_EQ (1, count.utx); + store.block_del (transaction, block1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, block1.hash ())); + auto count2 (store.block_count (transaction)); + ASSERT_EQ (0, count2.utx); +} diff --git a/rai/core_test/ledger.cpp b/rai/core_test/ledger.cpp index 6f9fcbca..364f17c2 100644 --- a/rai/core_test/ledger.cpp +++ b/rai/core_test/ledger.cpp @@ -322,6 +322,22 @@ TEST (ledger, rollback_representation) ASSERT_EQ (0, ledger.weight (transaction, key3.pub)); } +TEST (ledger, receive_rollback) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::send_block send (genesis.hash (), rai::test_genesis_key.pub, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send).code); + rai::receive_block receive (send.hash (), send.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive).code); + ledger.rollback (transaction, receive.hash ()); +} + TEST (ledger, process_duplicate) { bool init (false); @@ -1454,3 +1470,684 @@ TEST (ledger, bootstrap_rep_weight) ASSERT_EQ (0, ledger.weight (transaction, key2.pub)); } } + +TEST (ledger, block_destination_source) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair dest; + rai::uint128_t balance (rai::genesis_amount); + balance -= rai::Gxrb_ratio; + rai::send_block block1 (genesis.hash (), dest.pub, balance, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + balance -= rai::Gxrb_ratio; + rai::send_block block2 (block1.hash (), rai::genesis_account, balance, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + balance += rai::Gxrb_ratio; + rai::receive_block block3 (block2.hash (), block2.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + balance -= rai::Gxrb_ratio; + rai::utx_block block4 (rai::genesis_account, block3.hash (), rai::genesis_account, balance, dest.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + balance -= rai::Gxrb_ratio; + rai::utx_block block5 (rai::genesis_account, block4.hash (), rai::genesis_account, balance, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + balance += rai::Gxrb_ratio; + rai::utx_block block6 (rai::genesis_account, block5.hash (), rai::genesis_account, balance, block5.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block1).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block2).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block3).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block4).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block5).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, block6).code); + ASSERT_EQ (balance, ledger.balance (transaction, block6.hash ())); + ASSERT_EQ (dest.pub, ledger.block_destination (transaction, block1)); + ASSERT_TRUE (ledger.block_source (transaction, block1).is_zero ()); + ASSERT_EQ (rai::genesis_account, ledger.block_destination (transaction, block2)); + ASSERT_TRUE (ledger.block_source (transaction, block2).is_zero ()); + ASSERT_TRUE (ledger.block_destination (transaction, block3).is_zero ()); + ASSERT_EQ (block2.hash (), ledger.block_source (transaction, block3)); + ASSERT_EQ (dest.pub, ledger.block_destination (transaction, block4)); + ASSERT_TRUE (ledger.block_source (transaction, block4).is_zero ()); + ASSERT_EQ (rai::genesis_account, ledger.block_destination (transaction, block5)); + ASSERT_TRUE (ledger.block_source (transaction, block5).is_zero ()); + ASSERT_TRUE (ledger.block_destination (transaction, block6).is_zero ()); + ASSERT_EQ (block5.hash (), ledger.block_source (transaction, block6)); +} + +TEST (ledger, utx_account) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_EQ (rai::genesis_account, ledger.account (transaction, send1.hash ())); +} + +TEST (ledger, utx_send_receive) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + ASSERT_TRUE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, send1.hash ()))); + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_TRUE (store.block_exists (transaction, receive1.hash ())); + auto receive2 (store.block_get (transaction, receive1.hash ())); + ASSERT_NE (nullptr, receive2); + ASSERT_EQ (receive1, *receive2); + ASSERT_EQ (rai::genesis_amount, ledger.balance (transaction, receive1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, send1.hash ()))); +} + +TEST (ledger, utx_receive) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::send_block send1 (genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_TRUE (store.block_exists (transaction, receive1.hash ())); + auto receive2 (store.block_get (transaction, receive1.hash ())); + ASSERT_NE (nullptr, receive2); + ASSERT_EQ (receive1, *receive2); + ASSERT_EQ (rai::genesis_amount, ledger.balance (transaction, receive1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); +} + +TEST (ledger, utx_rep_change) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair rep; + rai::utx_block change1 (rai::genesis_account, genesis.hash (), rep.pub, rai::genesis_amount, 0, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, change1).code); + ASSERT_TRUE (store.block_exists (transaction, change1.hash ())); + auto change2 (store.block_get (transaction, change1.hash ())); + ASSERT_NE (nullptr, change2); + ASSERT_EQ (change1, *change2); + ASSERT_EQ (rai::genesis_amount, ledger.balance (transaction, change1.hash ())); + ASSERT_EQ (0, ledger.amount (transaction, change1.hash ())); + ASSERT_EQ (0, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_open) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + ASSERT_TRUE (store.pending_exists (transaction, rai::pending_key (destination.pub, send1.hash ()))); + rai::utx_block open1 (destination.pub, 0, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (destination.pub, send1.hash ()))); + ASSERT_TRUE (store.block_exists (transaction, open1.hash ())); + auto open2 (store.block_get (transaction, open1.hash ())); + ASSERT_NE (nullptr, open2); + ASSERT_EQ (open1, *open2); + ASSERT_EQ (rai::Gxrb_ratio, ledger.balance (transaction, open1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, open1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); +} + +// Make sure old block types can't be inserted after a utx block. +TEST (ledger, send_after_utx_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::send_block send2 (send1.hash (), rai::genesis_account, rai::genesis_amount - (2 * rai::Gxrb_ratio), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::block_position, ledger.process (transaction, send2).code); +} + +// Make sure old block types can't be inserted after a utx block. +TEST (ledger, receive_after_utx_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::receive_block receive1 (send1.hash (), send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::block_position, ledger.process (transaction, receive1).code); +} + +// Make sure old block types can't be inserted after a utx block. +TEST (ledger, change_after_utx_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::keypair rep; + rai::change_block change1 (send1.hash (), rep.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::block_position, ledger.process (transaction, change1).code); +} + +TEST (ledger, utx_unreceivable_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::send_block send1 (genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount, 1, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::gap_source, ledger.process (transaction, receive1).code); +} + +TEST (ledger, utx_receive_bad_amount_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::send_block send1 (genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::balance_mismatch, ledger.process (transaction, receive1).code); +} + +TEST (ledger, utx_no_link_amount_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::keypair rep; + rai::utx_block change1 (rai::genesis_account, send1.hash (), rep.pub, rai::genesis_amount, 0, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::balance_mismatch, ledger.process (transaction, change1).code); +} + +TEST (ledger, utx_receive_wrong_account_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::keypair key; + rai::utx_block receive1 (key.pub, 0, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), key.prv, key.pub, 0); + ASSERT_EQ (rai::process_result::unreceivable, ledger.process (transaction, receive1).code); +} + +TEST (ledger, utx_open_utx_fork) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block open1 (destination.pub, 0, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + rai::open_block open2 (send1.hash (), rai::genesis_account, destination.pub, destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::fork, ledger.process (transaction, open2).code); + ASSERT_EQ (open1.root (), open2.root ()); +} + +TEST (ledger, utx_utx_open_fork) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::open_block open1 (send1.hash (), rai::genesis_account, destination.pub, destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + rai::utx_block open2 (destination.pub, 0, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::fork, ledger.process (transaction, open2).code); + ASSERT_EQ (open1.root (), open2.root ()); +} + +TEST (ledger, utx_open_previous_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block open1 (destination.pub, destination.pub, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::gap_previous, ledger.process (transaction, open1).code); +} + +TEST (ledger, utx_open_source_fail) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block open1 (destination.pub, 0, rai::genesis_account, 0, 0, destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::gap_source, ledger.process (transaction, open1).code); +} + +TEST (ledger, utx_send_change) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair rep; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rep.pub, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (0, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_receive_change) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.balance (transaction, send1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::keypair rep; + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rep.pub, rai::genesis_amount, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_TRUE (store.block_exists (transaction, receive1.hash ())); + auto receive2 (store.block_get (transaction, receive1.hash ())); + ASSERT_NE (nullptr, receive2); + ASSERT_EQ (receive1, *receive2); + ASSERT_EQ (rai::genesis_amount, ledger.balance (transaction, receive1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); + ASSERT_EQ (0, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_open_old) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::open_block open1 (send1.hash (), rai::genesis_account, destination.pub, destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + ASSERT_EQ (rai::Gxrb_ratio, ledger.balance (transaction, open1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, open1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); +} + +TEST (ledger, utx_receive_old) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block send2 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount - (2 * rai::Gxrb_ratio), destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send2).code); + rai::open_block open1 (send1.hash (), rai::genesis_account, destination.pub, destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + rai::receive_block receive1 (open1.hash (), send2.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_EQ (2 * rai::Gxrb_ratio, ledger.balance (transaction, receive1.hash ())); + ASSERT_EQ (rai::Gxrb_ratio, ledger.amount (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); +} + +TEST (ledger, utx_rollback_send) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store.block_exists (transaction, send1.hash ())); + auto send2 (store.block_get (transaction, send1.hash ())); + ASSERT_NE (nullptr, send2); + ASSERT_EQ (send1, *send2); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::pending_info info; + ASSERT_FALSE (store.pending_get (transaction, rai::pending_key (rai::genesis_account, send1.hash ()), info)); + ASSERT_EQ (rai::genesis_account, info.source); + ASSERT_EQ (rai::Gxrb_ratio, info.amount.number ()); + ledger.rollback (transaction, send1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, send1.hash ()))); + ASSERT_TRUE (store.block_successor (transaction, genesis.hash ()).is_zero ()); +} + +TEST (ledger, utx_rollback_receive) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rai::genesis_account, rai::genesis_amount, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, receive1.hash ()))); + ledger.rollback (transaction, receive1.hash ()); + rai::pending_info info; + ASSERT_FALSE (store.pending_get (transaction, rai::pending_key (rai::genesis_account, send1.hash ()), info)); + ASSERT_EQ (rai::genesis_account, info.source); + ASSERT_EQ (rai::Gxrb_ratio, info.amount.number ()); + ASSERT_FALSE (store.block_exists (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); +} + +TEST (ledger, utx_rollback_received_send) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair key; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, key.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block receive1 (key.pub, 0, key.pub, rai::Gxrb_ratio, send1.hash (), key.prv, key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, receive1.hash ()))); + ledger.rollback (transaction, send1.hash ()); + ASSERT_FALSE (store.pending_exists (transaction, rai::pending_key (rai::genesis_account, send1.hash ()))); + ASSERT_FALSE (store.block_exists (transaction, send1.hash ())); + ASSERT_FALSE (store.block_exists (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (0, ledger.account_balance (transaction, key.pub)); + ASSERT_EQ (0, ledger.weight (transaction, key.pub)); +} + +TEST (ledger, utx_rep_change_rollback) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair rep; + rai::utx_block change1 (rai::genesis_account, genesis.hash (), rep.pub, rai::genesis_amount, 0, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, change1).code); + ledger.rollback (transaction, change1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, change1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (0, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_open_rollback) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair destination; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, destination.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::utx_block open1 (destination.pub, 0, rai::genesis_account, rai::Gxrb_ratio, send1.hash (), destination.prv, destination.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, open1).code); + ledger.rollback (transaction, open1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, open1.hash ())); + ASSERT_EQ (0, ledger.account_balance (transaction, destination.pub)); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + rai::pending_info info; + ASSERT_FALSE (store.pending_get (transaction, rai::pending_key (destination.pub, send1.hash ()), info)); + ASSERT_EQ (rai::genesis_account, info.source); + ASSERT_EQ (rai::Gxrb_ratio, info.amount.number ()); +} + +TEST (ledger, utx_send_change_rollback) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::keypair rep; + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rep.pub, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + ledger.rollback (transaction, send1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, send1.hash ())); + ASSERT_EQ (rai::genesis_amount, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (0, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_receive_change_rollback) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::ledger ledger (store); + rai::genesis genesis; + ledger.utx_parse_canary = genesis.hash (); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block send1 (rai::genesis_account, genesis.hash (), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, send1).code); + rai::keypair rep; + rai::utx_block receive1 (rai::genesis_account, send1.hash (), rep.pub, rai::genesis_amount, send1.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, receive1).code); + ledger.rollback (transaction, receive1.hash ()); + ASSERT_FALSE (store.block_exists (transaction, receive1.hash ())); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.account_balance (transaction, rai::genesis_account)); + ASSERT_EQ (rai::genesis_amount - rai::Gxrb_ratio, ledger.weight (transaction, rai::genesis_account)); + ASSERT_EQ (0, ledger.weight (transaction, rep.pub)); +} + +TEST (ledger, utx_canary_blocks) +{ + bool init (false); + rai::block_store store (init, rai::unique_path ()); + ASSERT_TRUE (!init); + rai::genesis genesis; + rai::send_block parse_canary (genesis.hash (), rai::test_genesis_key.pub, rai::genesis_amount, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + rai::send_block generate_canary (parse_canary.hash (), rai::test_genesis_key.pub, rai::genesis_amount, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + rai::ledger ledger (store, 0, parse_canary.hash (), generate_canary.hash ()); + rai::transaction transaction (store.environment, nullptr, true); + genesis.initialize (transaction, store); + rai::utx_block utx (rai::test_genesis_key.pub, genesis.hash (), rai::test_genesis_key.pub, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_FALSE (ledger.utx_parsing_enabled (transaction)); + ASSERT_FALSE (ledger.utx_generation_enabled (transaction)); + ASSERT_EQ (rai::process_result::utx_disabled, ledger.process (transaction, utx).code); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, parse_canary).code); + ASSERT_TRUE (ledger.utx_parsing_enabled (transaction)); + ASSERT_FALSE (ledger.utx_generation_enabled (transaction)); + ASSERT_EQ (rai::process_result::progress, ledger.process (transaction, generate_canary).code); + ASSERT_TRUE (ledger.utx_parsing_enabled (transaction)); + ASSERT_TRUE (ledger.utx_generation_enabled (transaction)); +} diff --git a/rai/core_test/message.cpp b/rai/core_test/message.cpp index f565f460..2d886625 100644 --- a/rai/core_test/message.cpp +++ b/rai/core_test/message.cpp @@ -54,8 +54,8 @@ TEST (message, publish_serialization) ASSERT_EQ (8, bytes.size ()); ASSERT_EQ (0x52, bytes[0]); ASSERT_EQ (0x41, bytes[1]); - ASSERT_EQ (0x06, bytes[2]); - ASSERT_EQ (0x06, bytes[3]); + ASSERT_EQ (0x07, bytes[2]); + ASSERT_EQ (0x07, bytes[3]); ASSERT_EQ (0x01, bytes[4]); ASSERT_EQ (static_cast (rai::message_type::publish), bytes[5]); ASSERT_EQ (0x02, bytes[6]); @@ -68,8 +68,8 @@ TEST (message, publish_serialization) std::bitset<16> extensions; ASSERT_FALSE (rai::message::read_header (stream, version_max, version_using, version_min, type, extensions)); ASSERT_EQ (0x01, version_min); - ASSERT_EQ (0x06, version_using); - ASSERT_EQ (0x06, version_max); + ASSERT_EQ (0x07, version_using); + ASSERT_EQ (0x07, version_max); ASSERT_EQ (rai::message_type::publish, type); } diff --git a/rai/core_test/network.cpp b/rai/core_test/network.cpp index 7737dfb2..480e902c 100644 --- a/rai/core_test/network.cpp +++ b/rai/core_test/network.cpp @@ -550,6 +550,38 @@ TEST (bootstrap_processor, process_two) node1->stop (); } +// Bootstrap can pull universal blocks +TEST (bootstrap_processor, process_utx) +{ + rai::system system (24000, 1); + rai::genesis genesis; + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + auto node0 (system.nodes[0]); + node0->ledger.utx_parse_canary = genesis.hash (); + std::unique_ptr block1 (new rai::utx_block (rai::test_genesis_key.pub, node0->latest (rai::test_genesis_key.pub), rai::test_genesis_key.pub, rai::genesis_amount - 100, rai::test_genesis_key.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0)); + std::unique_ptr block2 (new rai::utx_block (rai::test_genesis_key.pub, block1->hash (), rai::test_genesis_key.pub, rai::genesis_amount, block1->hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0)); + node0->generate_work (*block1); + node0->generate_work (*block2); + node0->process (*block1); + node0->process (*block2); + rai::node_init init1; + auto node1 (std::make_shared (init1, system.service, 24001, rai::unique_path (), system.alarm, system.logging, system.work)); + node1->ledger.utx_parse_canary = genesis.hash (); + ASSERT_EQ (node0->latest (rai::test_genesis_key.pub), block2->hash ()); + ASSERT_NE (node1->latest (rai::test_genesis_key.pub), block2->hash ()); + node1->bootstrap_initiator.bootstrap (node0->network.endpoint ()); + auto iterations (0); + ASSERT_NE (node1->latest (rai::test_genesis_key.pub), node0->latest (rai::test_genesis_key.pub)); + while (node1->latest (rai::test_genesis_key.pub) != node0->latest (rai::test_genesis_key.pub)) + { + system.poll (); + ++iterations; + ASSERT_LT (iterations, 200); + } + ASSERT_EQ (0, node1->active.roots.size ()); + node1->stop (); +} + TEST (bootstrap_processor, process_new) { rai::system system (24000, 2); diff --git a/rai/core_test/node.cpp b/rai/core_test/node.cpp index 7d6ca1ff..508f72cf 100644 --- a/rai/core_test/node.cpp +++ b/rai/core_test/node.cpp @@ -42,6 +42,23 @@ TEST (node, inactive_supply) node->stop (); } +TEST (node, utx_canaries) +{ + rai::node_init init; + auto service (boost::make_shared ()); + rai::alarm alarm (*service); + auto path (rai::unique_path ()); + rai::node_config config; + config.logging.init (path); + rai::work_pool work (std::numeric_limits::max (), nullptr); + config.utx_parse_canary = 10; + config.utx_generate_canary = 20; + auto node (std::make_shared (init, *service, path, alarm, config, work)); + ASSERT_EQ (rai::block_hash (10), node->ledger.utx_parse_canary); + ASSERT_EQ (rai::block_hash (20), node->ledger.utx_generate_canary); + node->stop (); +} + TEST (node, password_fanout) { rai::node_init init; @@ -495,6 +512,8 @@ TEST (node_config, serialization) config1.callback_port = 10; config1.callback_target = "test"; config1.lmdb_max_dbs = 256; + config1.utx_parse_canary = 10; + config1.utx_generate_canary = 10; boost::property_tree::ptree tree; config1.serialize_json (tree); rai::logging logging2; @@ -510,6 +529,9 @@ TEST (node_config, serialization) ASSERT_NE (config2.callback_address, config1.callback_address); ASSERT_NE (config2.callback_port, config1.callback_port); ASSERT_NE (config2.callback_target, config1.callback_target); + ASSERT_NE (config2.lmdb_max_dbs, config1.lmdb_max_dbs); + ASSERT_NE (config2.utx_parse_canary, config1.utx_parse_canary); + ASSERT_NE (config2.utx_generate_canary, config1.utx_generate_canary); bool upgraded (false); config2.deserialize_json (upgraded, tree); @@ -524,6 +546,8 @@ TEST (node_config, serialization) ASSERT_EQ (config2.callback_port, config1.callback_port); ASSERT_EQ (config2.callback_target, config1.callback_target); ASSERT_EQ (config2.lmdb_max_dbs, config1.lmdb_max_dbs); + ASSERT_EQ (config2.utx_parse_canary, config1.utx_parse_canary); + ASSERT_EQ (config2.utx_generate_canary, config1.utx_generate_canary); } TEST (node_config, v1_v2_upgrade) @@ -655,50 +679,6 @@ TEST (node_config, random_rep) ASSERT_NE (config1.preconfigured_representatives.end (), std::find (config1.preconfigured_representatives.begin (), config1.preconfigured_representatives.end (), rep)); } -TEST (node, block_replace) -{ - rai::system system (24000, 2); - system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); - auto block1 (system.wallet (0)->send_action (rai::test_genesis_key.pub, 0, rai::Gxrb_ratio)); - auto block3 (system.wallet (0)->send_action (rai::test_genesis_key.pub, 0, rai::Gxrb_ratio)); - ASSERT_NE (nullptr, block1); - auto initial_work (block1->block_work ()); - while (rai::work_value (block1->root (), block1->block_work ()) <= rai::work_value (block1->root (), initial_work)) - { - system.nodes[1]->generate_work (*block1); - } - { - rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); - ASSERT_EQ (block3->hash (), system.nodes[0]->store.block_successor (transaction, block1->hash ())); - } - for (auto i (0); i < 1; ++i) - { - rai::transaction transaction_a (system.nodes[1]->store.environment, nullptr, false); - system.nodes[1]->network.republish_block (transaction_a, block1); - } - auto iterations1 (0); - std::unique_ptr block2; - while (block2 == nullptr) - { - system.poll (); - ++iterations1; - ASSERT_LT (iterations1, 200); - rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); - auto block (system.nodes[0]->store.block_get (transaction, block1->hash ())); - if (block->block_work () != initial_work) - { - block2 = std::move (block); - } - } - { - rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); - ASSERT_EQ (block3->hash (), system.nodes[0]->store.block_successor (transaction, block1->hash ())); - } - ASSERT_NE (initial_work, block1->block_work ()); - ASSERT_EQ (block1->block_work (), block2->block_work ()); - ASSERT_GT (rai::work_value (block2->root (), block2->block_work ()), rai::work_value (block1->root (), initial_work)); -} - TEST (node, fork_publish) { std::weak_ptr node0; @@ -1032,7 +1012,7 @@ TEST (node, coherent_observer) { rai::system system (24000, 1); auto & node1 (*system.nodes[0]); - node1.observers.blocks.add ([&node1](std::shared_ptr block_a, rai::account const & account_a, rai::amount const &) { + node1.observers.blocks.add ([&node1](std::shared_ptr block_a, rai::process_return const &) { rai::transaction transaction (node1.store.environment, nullptr, false); ASSERT_TRUE (node1.store.block_exists (transaction, block_a->hash ())); }); diff --git a/rai/core_test/rpc.cpp b/rai/core_test/rpc.cpp index 320aded6..004cb01f 100644 --- a/rai/core_test/rpc.cpp +++ b/rai/core_test/rpc.cpp @@ -879,11 +879,23 @@ TEST (rpc, history) ASSERT_NE (nullptr, send); auto receive (system.wallet (0)->receive_action (static_cast (*send), rai::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); ASSERT_NE (nullptr, receive); - rai::rpc rpc (system.service, *system.nodes[0], rai::rpc_config (true)); + auto node0 (system.nodes[0]); + rai::genesis genesis; + node0->ledger.utx_parse_canary = genesis.hash (); + rai::utx_block usend (rai::genesis_account, node0->latest (rai::genesis_account), rai::genesis_account, rai::genesis_amount - rai::Gxrb_ratio, rai::genesis_account, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + rai::utx_block ureceive (rai::genesis_account, usend.hash (), rai::genesis_account, rai::genesis_amount, usend.hash (), rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + rai::utx_block uchange (rai::genesis_account, ureceive.hash (), rai::keypair ().pub, rai::genesis_amount, 0, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + { + rai::transaction transaction (node0->store.environment, nullptr, true); + ASSERT_EQ (rai::process_result::progress, node0->ledger.process (transaction, usend).code); + ASSERT_EQ (rai::process_result::progress, node0->ledger.process (transaction, ureceive).code); + ASSERT_EQ (rai::process_result::progress, node0->ledger.process (transaction, uchange).code); + } + rai::rpc rpc (system.service, *node0, rai::rpc_config (true)); rpc.start (); boost::property_tree::ptree request; request.put ("action", "history"); - request.put ("hash", receive->hash ().to_string ()); + request.put ("hash", uchange.hash ().to_string ()); request.put ("count", 100); test_response response (request, rpc, system.service); while (response.status == 0) @@ -897,20 +909,28 @@ TEST (rpc, history) { history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account"), i->second.get ("amount"), i->second.get ("hash"))); } - ASSERT_EQ (3, history_l.size ()); + ASSERT_EQ (5, history_l.size ()); ASSERT_EQ ("receive", std::get<0> (history_l[0])); + ASSERT_EQ (ureceive.hash ().to_string (), std::get<3> (history_l[0])); ASSERT_EQ (rai::test_genesis_key.pub.to_account (), std::get<1> (history_l[0])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[0])); - ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (rai::Gxrb_ratio.convert_to (), std::get<2> (history_l[0])); + ASSERT_EQ (5, history_l.size ()); ASSERT_EQ ("send", std::get<0> (history_l[1])); + ASSERT_EQ (usend.hash ().to_string (), std::get<3> (history_l[1])); ASSERT_EQ (rai::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); - ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[1])); - ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[1])); - rai::genesis genesis; + ASSERT_EQ (rai::Gxrb_ratio.convert_to (), std::get<2> (history_l[1])); ASSERT_EQ ("receive", std::get<0> (history_l[2])); ASSERT_EQ (rai::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); - ASSERT_EQ (rai::genesis_amount.convert_to (), std::get<2> (history_l[2])); - ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ ("send", std::get<0> (history_l[3])); + ASSERT_EQ (rai::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ ("receive", std::get<0> (history_l[4])); + ASSERT_EQ (rai::test_genesis_key.pub.to_account (), std::get<1> (history_l[4])); + ASSERT_EQ (rai::genesis_amount.convert_to (), std::get<2> (history_l[4])); + ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[4])); } TEST (rpc, history_count) @@ -2923,6 +2943,8 @@ TEST (rpc, block_count_type) ASSERT_EQ ("1", open_count); std::string change_count (response.json.get ("change")); ASSERT_EQ ("0", change_count); + std::string utx_count (response.json.get ("utx")); + ASSERT_EQ ("0", utx_count); } TEST (rpc, ledger) @@ -3146,6 +3168,86 @@ TEST (rpc, block_create) ASSERT_EQ (receive_hash, latest.to_string ()); } +TEST (rpc, block_create_utx) +{ + rai::system system (24000, 1); + rai::keypair key; + rai::genesis genesis; + system.nodes[0]->ledger.utx_parse_canary = genesis.hash (); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "utx"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", rai::test_genesis_key.pub.to_account ()); + request.put ("previous", genesis.hash ().to_string ()); + request.put ("representative", rai::test_genesis_key.pub.to_account ()); + request.put ("balance", (rai::genesis_amount - rai::Gxrb_ratio).convert_to ()); + request.put ("link", key.pub.to_account ()); + request.put ("work", rai::to_string_hex (system.nodes[0]->generate_work (genesis.hash ()))); + rai::rpc rpc (system.service, *system.nodes[0], rai::rpc_config (true)); + rpc.start (); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string utx_hash (response.json.get ("hash")); + auto utx_text (response.json.get ("block")); + std::stringstream block_stream (utx_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto utx_block (rai::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, utx_block); + ASSERT_EQ (rai::block_type::utx, utx_block->type ()); + ASSERT_EQ (utx_hash, utx_block->hash ().to_string ()); + auto process_result (system.nodes[0]->process (*utx_block)); + ASSERT_EQ (rai::process_result::progress, process_result.code); +} +TEST (rpc, block_create_utx_open) +{ + rai::system system (24000, 1); + rai::keypair key; + rai::genesis genesis; + system.nodes[0]->ledger.utx_parse_canary = genesis.hash (); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto send_block (system.wallet (0)->send_action (rai::test_genesis_key.pub, key.pub, rai::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "utx"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", key.pub.to_account ()); + request.put ("previous", 0); + request.put ("representative", rai::test_genesis_key.pub.to_account ()); + request.put ("balance", rai::Gxrb_ratio.convert_to ()); + request.put ("link", send_block->hash ().to_string ()); + request.put ("work", rai::to_string_hex (system.nodes[0]->generate_work (send_block->hash ()))); + rai::rpc rpc (system.service, *system.nodes[0], rai::rpc_config (true)); + rpc.start (); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string utx_hash (response.json.get ("hash")); + auto utx_text (response.json.get ("block")); + std::stringstream block_stream (utx_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto utx_block (rai::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, utx_block); + ASSERT_EQ (rai::block_type::utx, utx_block->type ()); + ASSERT_EQ (utx_hash, utx_block->hash ().to_string ()); + ASSERT_TRUE (system.nodes[0]->latest (key.pub).is_zero ()); + auto process_result (system.nodes[0]->process (*utx_block)); + ASSERT_EQ (rai::process_result::progress, process_result.code); + ASSERT_FALSE (system.nodes[0]->latest (key.pub).is_zero ()); +} + TEST (rpc, wallet_lock) { rai::system system (24000, 1); diff --git a/rai/core_test/wallet.cpp b/rai/core_test/wallet.cpp index ee4e84d2..d9d11108 100644 --- a/rai/core_test/wallet.cpp +++ b/rai/core_test/wallet.cpp @@ -978,3 +978,26 @@ TEST (wallet, password_race_corrupt_seed) } } } + +TEST (wallet, utx_implicit_generate) +{ + rai::system system (24000, 1); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + rai::genesis genesis; + system.nodes [0]->ledger.utx_parse_canary = genesis.hash (); + { + rai::transaction transaction (system.nodes [0]->store.environment, nullptr, true); + ASSERT_FALSE (system.wallet (0)->should_generate_utx (transaction, genesis.hash ())); + rai::utx_block block (rai::test_genesis_key.pub, genesis.hash (), rai::test_genesis_key.pub, rai::genesis_amount - rai::Gxrb_ratio, rai::test_genesis_key.pub, rai::test_genesis_key.prv, rai::test_genesis_key.pub, 0); + ASSERT_EQ (rai::process_result::progress, system.nodes [0]->ledger.process (transaction, block).code); + ASSERT_TRUE (system.wallet (0)->should_generate_utx (transaction, block.hash ())); + } + ASSERT_FALSE (system.wallet (0)->search_pending ()); + auto iterations (0); + while (system.nodes [0]->balance (rai::test_genesis_key.pub) != rai::genesis_amount) + { + system.poll (); + ++iterations; + ASSERT_LT (iterations, 200); + } +} diff --git a/rai/core_test/wallets.cpp b/rai/core_test/wallets.cpp index 8f08492b..f8ede908 100644 --- a/rai/core_test/wallets.cpp +++ b/rai/core_test/wallets.cpp @@ -76,7 +76,7 @@ TEST (wallets, wallet_create_max) rai::system system (24000, 1); bool error (false); rai::wallets wallets (error, *system.nodes[0]); - const int nonWalletDbs = 16; + const int nonWalletDbs = 17; for (int i = 0; i < system.nodes[0]->config.lmdb_max_dbs - nonWalletDbs; i++) { rai::keypair key; diff --git a/rai/ledger.cpp b/rai/ledger.cpp index 1461b2db..2def6910 100644 --- a/rai/ledger.cpp +++ b/rai/ledger.cpp @@ -46,13 +46,14 @@ public: auto representative (ledger.representative (transaction, block_a.hashables.previous)); auto amount (ledger.amount (transaction, block_a.hashables.source)); auto destination_account (ledger.account (transaction, hash)); + auto source_account (ledger.account (transaction, block_a.hashables.source)); rai::account_info info; auto error (ledger.store.account_get (transaction, destination_account, info)); assert (!error); ledger.store.representation_add (transaction, ledger.representative (transaction, hash), 0 - amount); ledger.change_latest (transaction, destination_account, block_a.hashables.previous, representative, ledger.balance (transaction, block_a.hashables.previous), info.block_count - 1); ledger.store.block_del (transaction, hash); - ledger.store.pending_put (transaction, rai::pending_key (destination_account, block_a.hashables.source), { ledger.account (transaction, block_a.hashables.source), amount }); + ledger.store.pending_put (transaction, rai::pending_key (destination_account, block_a.hashables.source), { source_account, amount }); ledger.store.frontier_del (transaction, hash); ledger.store.frontier_put (transaction, block_a.hashables.previous, destination_account); ledger.store.block_successor_clear (transaction, block_a.hashables.previous); @@ -66,10 +67,11 @@ public: auto hash (block_a.hash ()); auto amount (ledger.amount (transaction, block_a.hashables.source)); auto destination_account (ledger.account (transaction, hash)); + auto source_account (ledger.account (transaction, block_a.hashables.source)); ledger.store.representation_add (transaction, ledger.representative (transaction, hash), 0 - amount); ledger.change_latest (transaction, destination_account, 0, 0, 0, 0); ledger.store.block_del (transaction, hash); - ledger.store.pending_put (transaction, rai::pending_key (destination_account, block_a.hashables.source), { ledger.account (transaction, block_a.hashables.source), amount }); + ledger.store.pending_put (transaction, rai::pending_key (destination_account, block_a.hashables.source), { source_account, amount }); ledger.store.frontier_del (transaction, hash); } void change_block (rai::change_block const & block_a) override @@ -93,6 +95,64 @@ public: ledger.store.block_info_del (transaction, hash); } } + void utx_block (rai::utx_block const & block_a) override + { + auto hash (block_a.hash ()); + rai::block_hash representative (0); + if (!block_a.hashables.previous.is_zero ()) + { + representative = ledger.representative (transaction, block_a.hashables.previous); + } + auto balance (ledger.balance (transaction, block_a.hashables.previous)); + auto is_send (block_a.hashables.balance < balance); + // Add in amount delta + ledger.store.representation_add (transaction, hash, 0 - block_a.hashables.balance.number ()); + if (!representative.is_zero ()) + { + // Move existing representation + ledger.store.representation_add (transaction, representative, balance); + } + + if (is_send) + { + rai::pending_key key (block_a.hashables.link, hash); + while (!ledger.store.pending_exists (transaction, key)) + { + ledger.rollback (transaction, ledger.latest (transaction, block_a.hashables.link)); + } + ledger.store.pending_del (transaction, key); + } + else if (!block_a.hashables.link.is_zero ()) + { + rai::pending_info info (ledger.account (transaction, block_a.hashables.link), block_a.hashables.balance.number () - balance); + ledger.store.pending_put (transaction, rai::pending_key (block_a.hashables.account, block_a.hashables.link), info); + } + + rai::account_info info; + auto error (ledger.store.account_get (transaction, block_a.hashables.account, info)); + assert (!error); + ledger.change_latest (transaction, block_a.hashables.account, block_a.hashables.previous, representative, balance, info.block_count - 1); + + auto previous (ledger.store.block_get (transaction, block_a.hashables.previous)); + if (previous != nullptr) + { + ledger.store.block_successor_clear (transaction, block_a.hashables.previous); + switch (previous->type ()) + { + case rai::block_type::send: + case rai::block_type::receive: + case rai::block_type::open: + case rai::block_type::change: + { + ledger.store.frontier_put (transaction, block_a.hashables.previous, block_a.hashables.account); + break; + } + default: + break; + } + } + ledger.store.block_del (transaction, hash); + } MDB_txn * transaction; rai::ledger & ledger; }; @@ -106,11 +166,125 @@ public: void receive_block (rai::receive_block const &) override; void open_block (rai::open_block const &) override; void change_block (rai::change_block const &) override; + void utx_block (rai::utx_block const &) override; + void utx_block_impl (rai::utx_block const &); rai::ledger & ledger; MDB_txn * transaction; rai::process_return result; }; +void ledger_processor::utx_block (rai::utx_block const & block_a) +{ + result.code = ledger.utx_parsing_enabled (transaction) ? rai::process_result::progress : rai::process_result::utx_disabled; + if (result.code == rai::process_result::progress) + { + utx_block_impl (block_a); + } +} + +void ledger_processor::utx_block_impl (rai::utx_block const & block_a) +{ + auto hash (block_a.hash ()); + auto existing (ledger.store.block_exists (transaction, hash)); + result.code = existing ? rai::process_result::old : rai::process_result::progress; // Have we seen this block before? (Unambiguous) + if (result.code == rai::process_result::progress) + { + result.code = validate_message (block_a.hashables.account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is this block signed correctly (Unambiguous) + if (result.code == rai::process_result::progress) + { + result.code = block_a.hashables.account.is_zero () ? rai::process_result::opened_burn_account : rai::process_result::progress; // Is this for the burn account? (Unambiguous) + if (result.code == rai::process_result::progress) + { + rai::account_info info; + result.amount = block_a.hashables.balance; + auto is_send (false); + auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info)); + if (!account_error) + { + // Account already exists + result.code = block_a.hashables.previous.is_zero () ? rai::process_result::fork : rai::process_result::progress; // Has this account already been opened? (Ambigious) + if (result.code == rai::process_result::progress) + { + result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? rai::process_result::progress : rai::process_result::gap_previous; // Does the previous block exist in the ledger? (Unambigious) + if (result.code == rai::process_result::progress) + { + is_send = block_a.hashables.balance < info.balance; + result.amount = result.amount.number () - info.balance.number (); + result.code = block_a.hashables.previous == info.head ? rai::process_result::progress : rai::process_result::fork; // Is the previous block the account's head block? (Ambigious) + } + } + } + else + { + // Account does not yet exists + result.code = block_a.previous ().is_zero () ? rai::process_result::progress : rai::process_result::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious) + if (result.code == rai::process_result::progress) + { + result.code = !block_a.hashables.link.is_zero () ? rai::process_result::progress : rai::process_result::gap_source; // Is the first block receiving from a send ? (Unambigious) + } + } + if (result.code == rai::process_result::progress) + { + if (!is_send) + { + if (!block_a.hashables.link.is_zero ()) + { + result.code = ledger.store.block_exists (transaction, block_a.hashables.link) ? rai::process_result::progress : rai::process_result::gap_source; // Have we seen the source block already? (Harmless) + if (result.code == rai::process_result::progress) + { + rai::pending_key key (block_a.hashables.account, block_a.hashables.link); + rai::pending_info pending; + result.code = ledger.store.pending_get (transaction, key, pending) ? rai::process_result::unreceivable : rai::process_result::progress; // Has this source already been received (Malformed) + if (result.code == rai::process_result::progress) + { + result.code = result.amount == pending.amount ? rai::process_result::progress : rai::process_result::balance_mismatch; + } + } + } + else + { + // If there's no link, the balance must remain the same, only the representative can change + result.code = result.amount.is_zero () ? rai::process_result::progress : rai::process_result::balance_mismatch; + } + } + } + if (result.code == rai::process_result::progress) + { + result.utx_is_send = is_send; + ledger.store.block_put (transaction, hash, block_a); + + if (!info.rep_block.is_zero ()) + { + // Move existing representation + ledger.store.representation_add (transaction, info.rep_block, 0 - info.balance.number ()); + } + // Add in amount delta + ledger.store.representation_add (transaction, hash, block_a.hashables.balance.number ()); + + if (is_send) + { + rai::pending_key key (block_a.hashables.link, hash); + rai::pending_info info (block_a.hashables.account, 0 - result.amount.number ()); + ledger.store.pending_put (transaction, key, info); + } + else if (!block_a.hashables.link.is_zero ()) + { + ledger.store.pending_del (transaction, rai::pending_key (block_a.hashables.account, block_a.hashables.link)); + } + + ledger.change_latest (transaction, block_a.hashables.account, hash, hash, block_a.hashables.balance, info.block_count + 1, true); + if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + { + ledger.store.frontier_del (transaction, info.head); + } + // Frontier table is unnecessary for utx blocks and this also prevents old blocks from being inserted on top of utx blocks + result.account = block_a.hashables.account; + } + } + } + } +} + void ledger_processor::change_block (rai::change_block const & block_a) { auto hash (block_a.hash ()); @@ -118,30 +292,34 @@ void ledger_processor::change_block (rai::change_block const & block_a) result.code = existing ? rai::process_result::old : rai::process_result::progress; // Have we seen this block before? (Harmless) if (result.code == rai::process_result::progress) { - auto previous (ledger.store.block_exists (transaction, block_a.hashables.previous)); - result.code = previous ? rai::process_result::progress : rai::process_result::gap_previous; // Have we seen the previous block already? (Harmless) + auto previous (ledger.store.block_get (transaction, block_a.hashables.previous)); + result.code = previous != nullptr ? rai::process_result::progress : rai::process_result::gap_previous; // Have we seen the previous block already? (Harmless) if (result.code == rai::process_result::progress) { - auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); - result.code = account.is_zero () ? rai::process_result::fork : rai::process_result::progress; + result.code = block_a.valid_predecessor (*previous) ? rai::process_result::progress : rai::process_result::block_position; if (result.code == rai::process_result::progress) { - rai::account_info info; - auto latest_error (ledger.store.account_get (transaction, account, info)); - assert (!latest_error); - assert (info.head == block_a.hashables.previous); - result.code = validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is this block signed correctly (Malformed) + auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); + result.code = account.is_zero () ? rai::process_result::fork : rai::process_result::progress; if (result.code == rai::process_result::progress) { - ledger.store.block_put (transaction, hash, block_a); - auto balance (ledger.balance (transaction, block_a.hashables.previous)); - ledger.store.representation_add (transaction, hash, balance); - ledger.store.representation_add (transaction, info.rep_block, 0 - balance); - ledger.change_latest (transaction, account, hash, hash, info.balance, info.block_count + 1); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = 0; + rai::account_info info; + auto latest_error (ledger.store.account_get (transaction, account, info)); + assert (!latest_error); + assert (info.head == block_a.hashables.previous); + result.code = validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is this block signed correctly (Malformed) + if (result.code == rai::process_result::progress) + { + ledger.store.block_put (transaction, hash, block_a); + auto balance (ledger.balance (transaction, block_a.hashables.previous)); + ledger.store.representation_add (transaction, hash, balance); + ledger.store.representation_add (transaction, info.rep_block, 0 - balance); + ledger.change_latest (transaction, account, hash, hash, info.balance, info.block_count + 1); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = 0; + } } } } @@ -155,34 +333,38 @@ void ledger_processor::send_block (rai::send_block const & block_a) result.code = existing ? rai::process_result::old : rai::process_result::progress; // Have we seen this block before? (Harmless) if (result.code == rai::process_result::progress) { - auto previous (ledger.store.block_exists (transaction, block_a.hashables.previous)); - result.code = previous ? rai::process_result::progress : rai::process_result::gap_previous; // Have we seen the previous block already? (Harmless) + auto previous (ledger.store.block_get (transaction, block_a.hashables.previous)); + result.code = previous != nullptr ? rai::process_result::progress : rai::process_result::gap_previous; // Have we seen the previous block already? (Harmless) if (result.code == rai::process_result::progress) { - auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); - result.code = account.is_zero () ? rai::process_result::fork : rai::process_result::progress; + result.code = block_a.valid_predecessor (*previous) ? rai::process_result::progress : rai::process_result::block_position; if (result.code == rai::process_result::progress) { - result.code = validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is this block signed correctly (Malformed) + auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); + result.code = account.is_zero () ? rai::process_result::fork : rai::process_result::progress; if (result.code == rai::process_result::progress) { - rai::account_info info; - auto latest_error (ledger.store.account_get (transaction, account, info)); - assert (!latest_error); - assert (info.head == block_a.hashables.previous); - result.code = info.balance.number () >= block_a.hashables.balance.number () ? rai::process_result::progress : rai::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + result.code = validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is this block signed correctly (Malformed) if (result.code == rai::process_result::progress) { - auto amount (info.balance.number () - block_a.hashables.balance.number ()); - ledger.store.representation_add (transaction, info.rep_block, 0 - amount); - ledger.store.block_put (transaction, hash, block_a); - ledger.change_latest (transaction, account, hash, info.rep_block, block_a.hashables.balance, info.block_count + 1); - ledger.store.pending_put (transaction, rai::pending_key (block_a.hashables.destination, hash), { account, amount }); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = amount; - result.pending_account = block_a.hashables.destination; + rai::account_info info; + auto latest_error (ledger.store.account_get (transaction, account, info)); + assert (!latest_error); + assert (info.head == block_a.hashables.previous); + result.code = info.balance.number () >= block_a.hashables.balance.number () ? rai::process_result::progress : rai::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + if (result.code == rai::process_result::progress) + { + auto amount (info.balance.number () - block_a.hashables.balance.number ()); + ledger.store.representation_add (transaction, info.rep_block, 0 - amount); + ledger.store.block_put (transaction, hash, block_a); + ledger.change_latest (transaction, account, hash, info.rep_block, block_a.hashables.balance, info.block_count + 1); + ledger.store.pending_put (transaction, rai::pending_key (block_a.hashables.destination, hash), { account, amount }); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = amount; + result.pending_account = block_a.hashables.destination; + } } } } @@ -197,46 +379,55 @@ void ledger_processor::receive_block (rai::receive_block const & block_a) result.code = existing ? rai::process_result::old : rai::process_result::progress; // Have we seen this block already? (Harmless) if (result.code == rai::process_result::progress) { - result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? rai::process_result::progress : rai::process_result::gap_source; // Have we seen the source block already? (Harmless) + auto previous (ledger.store.block_get (transaction, block_a.hashables.previous)); + result.code = previous != nullptr ? rai::process_result::progress : rai::process_result::gap_previous; if (result.code == rai::process_result::progress) { - auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); - result.code = account.is_zero () ? rai::process_result::gap_previous : rai::process_result::progress; //Have we seen the previous block? No entries for account at all (Harmless) + result.code = block_a.valid_predecessor (*previous) ? rai::process_result::progress : rai::process_result::block_position; if (result.code == rai::process_result::progress) { - result.code = rai::validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is the signature valid (Malformed) + result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? rai::process_result::progress : rai::process_result::gap_source; // Have we seen the source block already? (Harmless) if (result.code == rai::process_result::progress) { - rai::account_info info; - ledger.store.account_get (transaction, account, info); - result.code = info.head == block_a.hashables.previous ? rai::process_result::progress : rai::process_result::gap_previous; // Block doesn't immediately follow latest block (Harmless) + auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous)); + result.code = account.is_zero () ? rai::process_result::gap_previous : rai::process_result::progress; //Have we seen the previous block? No entries for account at all (Harmless) if (result.code == rai::process_result::progress) { - rai::pending_key key (account, block_a.hashables.source); - rai::pending_info pending; - result.code = ledger.store.pending_get (transaction, key, pending) ? rai::process_result::unreceivable : rai::process_result::progress; // Has this source already been received (Malformed) + result.code = rai::validate_message (account, hash, block_a.signature) ? rai::process_result::bad_signature : rai::process_result::progress; // Is the signature valid (Malformed) if (result.code == rai::process_result::progress) { - auto new_balance (info.balance.number () + pending.amount.number ()); - rai::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - assert (!error); - ledger.store.pending_del (transaction, key); - ledger.store.block_put (transaction, hash, block_a); - ledger.change_latest (transaction, account, hash, info.rep_block, new_balance, info.block_count + 1); - ledger.store.representation_add (transaction, info.rep_block, pending.amount.number ()); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = pending.amount; + rai::account_info info; + ledger.store.account_get (transaction, account, info); + result.code = info.head == block_a.hashables.previous ? rai::process_result::progress : rai::process_result::gap_previous; // Block doesn't immediately follow latest block (Harmless) + if (result.code == rai::process_result::progress) + { + rai::pending_key key (account, block_a.hashables.source); + rai::pending_info pending; + result.code = ledger.store.pending_get (transaction, key, pending) ? rai::process_result::unreceivable : rai::process_result::progress; // Has this source already been received (Malformed) + if (result.code == rai::process_result::progress) + { + auto new_balance (info.balance.number () + pending.amount.number ()); + rai::account_info source_info; + auto error (ledger.store.account_get (transaction, pending.source, source_info)); + assert (!error); + ledger.store.pending_del (transaction, key); + ledger.store.block_put (transaction, hash, block_a); + ledger.change_latest (transaction, account, hash, info.rep_block, new_balance, info.block_count + 1); + ledger.store.representation_add (transaction, info.rep_block, pending.amount.number ()); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = pending.amount; + } + } } } + else + { + result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? rai::process_result::fork : rai::process_result::gap_previous; // If we have the block but it's not the latest we have a signed fork (Malicious) + } } } - else - { - result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? rai::process_result::fork : rai::process_result::gap_previous; // If we have the block but it's not the latest we have a signed fork (Malicious) - } } } } @@ -304,10 +495,12 @@ bool rai::shared_ptr_block_hash::operator() (std::shared_ptr const & return *lhs == *rhs; } -rai::ledger::ledger (rai::block_store & store_a, rai::uint128_t const & inactive_supply_a) : +rai::ledger::ledger (rai::block_store & store_a, rai::uint128_t const & inactive_supply_a, rai::block_hash const & utx_parse_canary_a, rai::block_hash const & utx_generate_canary_a) : store (store_a), inactive_supply (inactive_supply_a), -check_bootstrap_weights (true) +check_bootstrap_weights (true), +utx_parse_canary (utx_parse_canary_a), +utx_generate_canary (utx_generate_canary_a) { } @@ -432,6 +625,49 @@ std::string rai::ledger::block_text (rai::block_hash const & hash_a) return result; } +bool rai::ledger::is_utx_send (MDB_txn * transaction_a, rai::utx_block const & block_a) +{ + bool result (false); + rai::block_hash previous (block_a.hashables.previous); + if (!previous.is_zero ()) + { + if (block_a.hashables.balance < balance (transaction_a, previous)) + { + result = true; + } + } + return result; +} + +rai::block_hash rai::ledger::block_destination (MDB_txn * transaction_a, rai::block const & block_a) +{ + rai::block_hash result (0); + rai::send_block const * send_block (dynamic_cast (&block_a)); + rai::utx_block const * utx_block (dynamic_cast (&block_a)); + if (send_block != nullptr) + { + result = send_block->hashables.destination; + } + else if (utx_block != nullptr && is_utx_send (transaction_a, *utx_block)) + { + result = utx_block->hashables.link; + } + return result; +} + +rai::block_hash rai::ledger::block_source (MDB_txn * transaction_a, rai::block const & block_a) +{ + // If block_a.source () is nonzero, then we have our source. + // However, universal blocks will always return zero. + rai::block_hash result (block_a.source ()); + rai::utx_block const * utx_block (dynamic_cast (&block_a)); + if (utx_block != nullptr && !is_utx_send (transaction_a, *utx_block)) + { + result = utx_block->hashables.link; + } + return result; +} + // Vote weight of an account rai::uint128_t rai::ledger::weight (MDB_txn * transaction_a, rai::account const & account_a) { @@ -473,20 +709,26 @@ void rai::ledger::rollback (MDB_txn * transaction_a, rai::block_hash const & blo // Return account containing hash rai::account rai::ledger::account (MDB_txn * transaction_a, rai::block_hash const & hash_a) { - assert (store.block_exists (transaction_a, hash_a)); + rai::account result; auto hash (hash_a); rai::block_hash successor (1); rai::block_info block_info; - while (!successor.is_zero () && store.block_info_get (transaction_a, successor, block_info)) + std::unique_ptr block (store.block_get (transaction_a, hash)); + while (!successor.is_zero () && block->type () != rai::block_type::utx && store.block_info_get (transaction_a, successor, block_info)) { successor = store.block_successor (transaction_a, hash); if (!successor.is_zero ()) { hash = successor; + block = store.block_get (transaction_a, hash); } } - rai::account result; - if (successor.is_zero ()) + if (block->type () == rai::block_type::utx) + { + auto utx_block (dynamic_cast (block.get ())); + result = utx_block->hashables.account; + } + else if (successor.is_zero ()) { result = store.frontier_get (transaction_a, hash); } @@ -552,6 +794,16 @@ void rai::ledger::dump_account_chain (rai::account const & account_a) } } +bool rai::ledger::utx_parsing_enabled (MDB_txn * transaction_a) +{ + return store.block_exists (transaction_a, utx_parse_canary); +} + +bool rai::ledger::utx_generation_enabled (MDB_txn * transaction_a) +{ + return utx_parsing_enabled (transaction_a) && store.block_exists (transaction_a, utx_generate_canary); +} + void rai::ledger::checksum_update (MDB_txn * transaction_a, rai::block_hash const & hash_a) { rai::checksum value; @@ -561,7 +813,7 @@ void rai::ledger::checksum_update (MDB_txn * transaction_a, rai::block_hash cons store.checksum_put (transaction_a, 0, 0, value); } -void rai::ledger::change_latest (MDB_txn * transaction_a, rai::account const & account_a, rai::block_hash const & hash_a, rai::block_hash const & rep_block_a, rai::amount const & balance_a, uint64_t block_count_a) +void rai::ledger::change_latest (MDB_txn * transaction_a, rai::account const & account_a, rai::block_hash const & hash_a, rai::block_hash const & rep_block_a, rai::amount const & balance_a, uint64_t block_count_a, bool is_utx) { rai::account_info info; auto exists (!store.account_get (transaction_a, account_a, info)); @@ -571,7 +823,7 @@ void rai::ledger::change_latest (MDB_txn * transaction_a, rai::account const & a } else { - assert (dynamic_cast (store.block_get (transaction_a, hash_a).get ()) != nullptr); + assert (store.block_get (transaction_a, hash_a)->previous ().is_zero ()); info.open_block = hash_a; } if (!hash_a.is_zero ()) @@ -582,7 +834,7 @@ void rai::ledger::change_latest (MDB_txn * transaction_a, rai::account const & a info.modified = rai::seconds_since_epoch (); info.block_count = block_count_a; store.account_put (transaction_a, account_a, info); - if (!(block_count_a % store.block_info_max)) + if (!(block_count_a % store.block_info_max) && !is_utx) { rai::block_info block_info; block_info.account = account_a; diff --git a/rai/ledger.hpp b/rai/ledger.hpp index eb16f999..a2c49ee3 100644 --- a/rai/ledger.hpp +++ b/rai/ledger.hpp @@ -16,7 +16,7 @@ public: class ledger { public: - ledger (rai::block_store &, rai::uint128_t const & = 0); + ledger (rai::block_store &, rai::uint128_t const & = 0, rai::block_hash const & = 0, rai::block_hash const & = 0); std::pair> winner (MDB_txn *, rai::votes const & votes_a); // Map of weight -> associated block, ordered greatest to least std::map, std::greater> tally (MDB_txn *, rai::votes const &); @@ -35,18 +35,25 @@ public: bool block_exists (rai::block_hash const &); std::string block_text (char const *); std::string block_text (rai::block_hash const &); + bool is_utx_send (MDB_txn *, rai::utx_block const &); + rai::block_hash block_destination (MDB_txn *, rai::block const &); + rai::block_hash block_source (MDB_txn *, rai::block const &); rai::uint128_t supply (MDB_txn *); rai::process_return process (MDB_txn *, rai::block const &); void rollback (MDB_txn *, rai::block_hash const &); - void change_latest (MDB_txn *, rai::account const &, rai::block_hash const &, rai::account const &, rai::uint128_union const &, uint64_t); + void change_latest (MDB_txn *, rai::account const &, rai::block_hash const &, rai::account const &, rai::uint128_union const &, uint64_t, bool = false); void checksum_update (MDB_txn *, rai::block_hash const &); rai::checksum checksum (MDB_txn *, rai::account const &, rai::account const &); void dump_account_chain (rai::account const &); + bool utx_parsing_enabled (MDB_txn *); + bool utx_generation_enabled (MDB_txn *); static rai::uint128_t const unit; rai::block_store & store; rai::uint128_t inactive_supply; std::unordered_map bootstrap_weights; uint64_t bootstrap_weight_max_blocks; std::atomic check_bootstrap_weights; + rai::block_hash utx_parse_canary; + rai::block_hash utx_generate_canary; }; }; diff --git a/rai/lib/blocks.cpp b/rai/lib/blocks.cpp index 5d1b7c7e..cc8e9fc3 100644 --- a/rai/lib/blocks.cpp +++ b/rai/lib/blocks.cpp @@ -270,6 +270,24 @@ bool rai::send_block::operator== (rai::block const & other_a) const return result; } +bool rai::send_block::valid_predecessor (rai::block const & block_a) const +{ + bool result; + switch (block_a.type ()) + { + case rai::block_type::send: + case rai::block_type::receive: + case rai::block_type::open: + case rai::block_type::change: + result = true; + break; + default: + result = false; + break; + } + return result; +} + rai::block_type rai::send_block::type () const { return rai::block_type::send; @@ -541,6 +559,11 @@ bool rai::open_block::operator== (rai::open_block const & other_a) const return hashables.source == other_a.hashables.source && hashables.representative == other_a.hashables.representative && hashables.account == other_a.hashables.account && work == other_a.work && signature == other_a.signature; } +bool rai::open_block::valid_predecessor (rai::block const & block_a) const +{ + return false; +} + rai::block_hash rai::open_block::source () const { return hashables.source; @@ -765,6 +788,24 @@ bool rai::change_block::operator== (rai::change_block const & other_a) const return hashables.previous == other_a.hashables.previous && hashables.representative == other_a.hashables.representative && work == other_a.work && signature == other_a.signature; } +bool rai::change_block::valid_predecessor (rai::block const & block_a) const +{ + bool result; + switch (block_a.type ()) + { + case rai::block_type::send: + case rai::block_type::receive: + case rai::block_type::open: + case rai::block_type::change: + result = true; + break; + default: + result = false; + break; + } + return result; +} + rai::block_hash rai::change_block::source () const { return 0; @@ -790,6 +831,309 @@ void rai::change_block::signature_set (rai::uint512_union const & signature_a) signature = signature_a; } +rai::utx_hashables::utx_hashables (rai::account const & account_a, rai::block_hash const & previous_a, rai::account const & representative_a, rai::amount const & balance_a, rai::uint256_union const & link_a) : +account (account_a), +previous (previous_a), +representative (representative_a), +balance (balance_a), +link (link_a) +{ +} + +rai::utx_hashables::utx_hashables (bool & error_a, rai::stream & stream_a) +{ + error_a = rai::read (stream_a, account); + if (!error_a) + { + error_a = rai::read (stream_a, previous); + if (!error_a) + { + error_a = rai::read (stream_a, representative); + if (!error_a) + { + error_a = rai::read (stream_a, balance); + if (!error_a) + { + error_a = rai::read (stream_a, link); + } + } + } + } +} + +rai::utx_hashables::utx_hashables (bool & error_a, boost::property_tree::ptree const & tree_a) +{ + try + { + auto account_l (tree_a.get ("account")); + auto previous_l (tree_a.get ("previous")); + auto representative_l (tree_a.get ("representative")); + auto balance_l (tree_a.get ("balance")); + auto link_l (tree_a.get ("link")); + error_a = account.decode_account (account_l); + if (!error_a) + { + error_a = previous.decode_hex (previous_l); + if (!error_a) + { + error_a = representative.decode_account (representative_l); + if (!error_a) + { + error_a = balance.decode_dec (balance_l); + if (!error_a) + { + error_a = link.decode_account (link_l) && link.decode_hex (link_l); + } + } + } + } + } + catch (std::runtime_error const &) + { + error_a = true; + } +} + +void rai::utx_hashables::hash (blake2b_state & hash_a) const +{ + blake2b_update (&hash_a, account.bytes.data (), sizeof (account.bytes)); + blake2b_update (&hash_a, previous.bytes.data (), sizeof (previous.bytes)); + blake2b_update (&hash_a, representative.bytes.data (), sizeof (representative.bytes)); + blake2b_update (&hash_a, balance.bytes.data (), sizeof (balance.bytes)); + blake2b_update (&hash_a, link.bytes.data (), sizeof (link.bytes)); +} + +rai::utx_block::utx_block (rai::account const & account_a, rai::block_hash const & previous_a, rai::account const & representative_a, rai::amount const & balance_a, rai::uint256_union const & link_a, rai::raw_key const & prv_a, rai::public_key const & pub_a, uint64_t work_a) : +hashables (account_a, previous_a, representative_a, balance_a, link_a), +signature (rai::sign_message (prv_a, pub_a, hash ())), +work (work_a) +{ +} + +rai::utx_block::utx_block (bool & error_a, rai::stream & stream_a) : +hashables (error_a, stream_a) +{ + if (!error_a) + { + error_a = rai::read (stream_a, signature); + if (!error_a) + { + error_a = rai::read (stream_a, work); + } + } +} + +rai::utx_block::utx_block (bool & error_a, boost::property_tree::ptree const & tree_a) : +hashables (error_a, tree_a) +{ + if (!error_a) + { + try + { + auto type_l (tree_a.get ("type")); + auto signature_l (tree_a.get ("signature")); + auto work_l (tree_a.get ("work")); + error_a = type_l != "utx"; + if (!error_a) + { + error_a = rai::from_string_hex (work_l, work); + if (!error_a) + { + error_a = signature.decode_hex (signature_l); + } + } + } + catch (std::runtime_error const &) + { + error_a = true; + } + } +} + +void rai::utx_block::hash (blake2b_state & hash_a) const +{ + rai::uint256_union preamble (static_cast (rai::block_type::utx)); + blake2b_update (&hash_a, preamble.bytes.data (), preamble.bytes.size ()); + hashables.hash (hash_a); +} + +uint64_t rai::utx_block::block_work () const +{ + return work; +} + +void rai::utx_block::block_work_set (uint64_t work_a) +{ + work = work_a; +} + +rai::block_hash rai::utx_block::previous () const +{ + return hashables.previous; +} + +void rai::utx_block::serialize (rai::stream & stream_a) const +{ + write (stream_a, hashables.account); + write (stream_a, hashables.previous); + write (stream_a, hashables.representative); + write (stream_a, hashables.balance); + write (stream_a, hashables.link); + write (stream_a, signature); + write (stream_a, work); +} + +void rai::utx_block::serialize_json (std::string & string_a) const +{ + boost::property_tree::ptree tree; + tree.put ("type", "utx"); + tree.put ("account", hashables.account.to_account ()); + tree.put ("previous", hashables.previous.to_string ()); + tree.put ("representative", representative ().to_account ()); + tree.put ("balance", hashables.balance.to_string_dec ()); + tree.put ("link", hashables.link.to_string ()); + tree.put ("link_as_account", hashables.link.to_account ()); + std::string signature_l; + signature.encode_hex (signature_l); + tree.put ("signature", signature_l); + tree.put ("work", rai::to_string_hex (work)); + std::stringstream ostream; + boost::property_tree::write_json (ostream, tree); + string_a = ostream.str (); +} + +bool rai::utx_block::deserialize (rai::stream & stream_a) +{ + auto error (read (stream_a, hashables.account)); + if (!error) + { + error = read (stream_a, hashables.previous); + if (!error) + { + error = read (stream_a, hashables.representative); + if (!error) + { + error = read (stream_a, hashables.balance); + if (!error) + { + error = read (stream_a, hashables.link); + if (!error) + { + error = read (stream_a, signature); + if (!error) + { + error = read (stream_a, work); + } + } + } + } + } + } + return error; +} + +bool rai::utx_block::deserialize_json (boost::property_tree::ptree const & tree_a) +{ + auto error (false); + try + { + assert (tree_a.get ("type") == "utx"); + auto account_l (tree_a.get ("account")); + auto previous_l (tree_a.get ("previous")); + auto representative_l (tree_a.get ("representative")); + auto balance_l (tree_a.get ("balance")); + auto link_l (tree_a.get ("link")); + auto work_l (tree_a.get ("work")); + auto signature_l (tree_a.get ("signature")); + error = hashables.account.decode_account (account_l); + if (!error) + { + error = hashables.previous.decode_hex (previous_l); + if (!error) + { + error = hashables.representative.decode_account (representative_l); + if (!error) + { + error = hashables.balance.decode_dec (balance_l); + if (!error) + { + error = hashables.link.decode_account (link_l) && hashables.link.decode_hex (link_l); + if (!error) + { + error = rai::from_string_hex (work_l, work); + if (!error) + { + error = signature.decode_hex (signature_l); + } + } + } + } + } + } + } + catch (std::runtime_error const &) + { + error = true; + } + return error; +} + +void rai::utx_block::visit (rai::block_visitor & visitor_a) const +{ + visitor_a.utx_block (*this); +} + +rai::block_type rai::utx_block::type () const +{ + return rai::block_type::utx; +} + +bool rai::utx_block::operator== (rai::block const & other_a) const +{ + auto other_l (dynamic_cast (&other_a)); + auto result (other_l != nullptr); + if (result) + { + result = *this == *other_l; + } + return result; +} + +bool rai::utx_block::operator== (rai::utx_block const & other_a) const +{ + return hashables.account == other_a.hashables.account && hashables.previous == other_a.hashables.previous && hashables.representative == other_a.hashables.representative && hashables.balance == other_a.hashables.balance && hashables.link == other_a.hashables.link && signature == other_a.signature && work == other_a.work; +} + +bool rai::utx_block::valid_predecessor (rai::block const & block_a) const +{ + return true; +} + +rai::block_hash rai::utx_block::source () const +{ + return 0; +} + +rai::block_hash rai::utx_block::root () const +{ + return !hashables.previous.is_zero () ? hashables.previous : hashables.account; +} + +rai::account rai::utx_block::representative () const +{ + return hashables.representative; +} + +rai::signature rai::utx_block::block_signature () const +{ + return signature; +} + +void rai::utx_block::signature_set (rai::uint512_union const & signature_a) +{ + signature = signature_a; +} + std::unique_ptr rai::deserialize_block_json (boost::property_tree::ptree const & tree_a) { std::unique_ptr result; @@ -832,6 +1176,15 @@ std::unique_ptr rai::deserialize_block_json (boost::property_tree::p result = std::move (obj); } } + else if (type == "utx") + { + bool error; + std::unique_ptr obj (new rai::utx_block (error, tree_a)); + if (!error) + { + result = std::move (obj); + } + } } catch (std::runtime_error const &) { @@ -896,6 +1249,16 @@ std::unique_ptr rai::deserialize_block (rai::stream & stream_a, rai: } break; } + case rai::block_type::utx: + { + bool error; + std::unique_ptr obj (new rai::utx_block (error, stream_a)); + if (!error) + { + result = std::move (obj); + } + break; + } default: assert (false); break; @@ -1059,6 +1422,24 @@ bool rai::receive_block::operator== (rai::block const & other_a) const return result; } +bool rai::receive_block::valid_predecessor (rai::block const & block_a) const +{ + bool result; + switch (block_a.type ()) + { + case rai::block_type::send: + case rai::block_type::receive: + case rai::block_type::open: + case rai::block_type::change: + result = true; + break; + default: + result = false; + break; + } + return result; +} + rai::block_hash rai::receive_block::previous () const { return hashables.previous; diff --git a/rai/lib/blocks.hpp b/rai/lib/blocks.hpp index 73971e86..1d641dbb 100644 --- a/rai/lib/blocks.hpp +++ b/rai/lib/blocks.hpp @@ -31,12 +31,13 @@ void write (rai::stream & stream_a, T const & value) class block_visitor; enum class block_type : uint8_t { - invalid, - not_a_block, - send, - receive, - open, - change + invalid = 0, + not_a_block = 1, + send = 2, + receive = 3, + open = 4, + change = 5, + utx = 6 }; class block { @@ -62,6 +63,7 @@ public: virtual rai::signature block_signature () const = 0; virtual void signature_set (rai::uint512_union const &) = 0; virtual ~block () = default; + virtual bool valid_predecessor (rai::block const &) const = 0; }; class send_hashables { @@ -99,6 +101,7 @@ public: void signature_set (rai::uint512_union const &) override; bool operator== (rai::block const &) const override; bool operator== (rai::send_block const &) const; + bool valid_predecessor (rai::block const &) const override; static size_t constexpr size = sizeof (rai::account) + sizeof (rai::block_hash) + sizeof (rai::amount) + sizeof (rai::signature) + sizeof (uint64_t); send_hashables hashables; rai::signature signature; @@ -139,6 +142,7 @@ public: void signature_set (rai::uint512_union const &) override; bool operator== (rai::block const &) const override; bool operator== (rai::receive_block const &) const; + bool valid_predecessor (rai::block const &) const override; static size_t constexpr size = sizeof (rai::block_hash) + sizeof (rai::block_hash) + sizeof (rai::signature) + sizeof (uint64_t); receive_hashables hashables; rai::signature signature; @@ -181,6 +185,7 @@ public: void signature_set (rai::uint512_union const &) override; bool operator== (rai::block const &) const override; bool operator== (rai::open_block const &) const; + bool valid_predecessor (rai::block const &) const override; static size_t constexpr size = sizeof (rai::block_hash) + sizeof (rai::account) + sizeof (rai::account) + sizeof (rai::signature) + sizeof (uint64_t); rai::open_hashables hashables; rai::signature signature; @@ -221,11 +226,65 @@ public: void signature_set (rai::uint512_union const &) override; bool operator== (rai::block const &) const override; bool operator== (rai::change_block const &) const; + bool valid_predecessor (rai::block const &) const override; static size_t constexpr size = sizeof (rai::block_hash) + sizeof (rai::account) + sizeof (rai::signature) + sizeof (uint64_t); rai::change_hashables hashables; rai::signature signature; uint64_t work; }; +class utx_hashables +{ +public: + utx_hashables (rai::account const &, rai::block_hash const &, rai::account const &, rai::amount const &, rai::uint256_union const &); + utx_hashables (bool &, rai::stream &); + utx_hashables (bool &, boost::property_tree::ptree const &); + void hash (blake2b_state &) const; + // Account# / public key that operates this account + // Uses: + // Bulk signature validation in advance of further ledger processing + // Arranging uncomitted transactions by account + rai::account account; + // Previous transaction in this chain + rai::block_hash previous; + // Representative of this account + rai::account representative; + // Current balance of this account + // Allows lookup of account balance simply by looking at the head block + rai::amount balance; + // Link field contains source block_hash if receiving, destination account if sending + rai::uint256_union link; +}; +class utx_block : public rai::block +{ +public: + utx_block (rai::account const &, rai::block_hash const &, rai::account const &, rai::amount const &, rai::uint256_union const &, rai::raw_key const &, rai::public_key const &, uint64_t); + utx_block (bool &, rai::stream &); + utx_block (bool &, boost::property_tree::ptree const &); + virtual ~utx_block () = default; + using rai::block::hash; + void hash (blake2b_state &) const override; + uint64_t block_work () const override; + void block_work_set (uint64_t) override; + rai::block_hash previous () const override; + rai::block_hash source () const override; + rai::block_hash root () const override; + rai::account representative () const override; + void serialize (rai::stream &) const override; + void serialize_json (std::string &) const override; + bool deserialize (rai::stream &); + bool deserialize_json (boost::property_tree::ptree const &); + void visit (rai::block_visitor &) const override; + rai::block_type type () const override; + rai::signature block_signature () const override; + void signature_set (rai::uint512_union const &) override; + bool operator== (rai::block const &) const override; + bool operator== (rai::utx_block const &) const; + bool valid_predecessor (rai::block const &) const override; + static size_t constexpr size = sizeof (rai::account) + sizeof (rai::block_hash) + sizeof (rai::account) + sizeof (rai::amount) + sizeof (rai::uint256_union) + sizeof (rai::signature) + sizeof (uint64_t); + rai::utx_hashables hashables; + rai::signature signature; + uint64_t work; // Only least 48 least significant bits are encoded +}; class block_visitor { public: @@ -233,6 +292,7 @@ public: virtual void receive_block (rai::receive_block const &) = 0; virtual void open_block (rai::open_block const &) = 0; virtual void change_block (rai::change_block const &) = 0; + virtual void utx_block (rai::utx_block const &) = 0; virtual ~block_visitor () = default; }; std::unique_ptr deserialize_block (rai::stream &); diff --git a/rai/node/bootstrap.cpp b/rai/node/bootstrap.cpp index 165e149c..fcad9509 100644 --- a/rai/node/bootstrap.cpp +++ b/rai/node/bootstrap.cpp @@ -52,6 +52,18 @@ public: { add_dependency (block_a.hashables.previous); } + void utx_block (rai::utx_block const & block_a) override + { + if (!block_a.hashables.previous.is_zero ()) + { + add_dependency (block_a.hashables.previous); + } + if (complete) + { + // Might not be a dependency block (if this is a send) but that's okay + add_dependency (block_a.hashables.link); + } + } void add_dependency (rai::block_hash const & hash_a) { if (!sync.synchronized (transaction, hash_a) && sync.retrieve (transaction, hash_a) != nullptr) @@ -582,6 +594,15 @@ void rai::bulk_pull_client::received_type () }); break; } + case rai::block_type::utx: + { + connection->start_timeout (); + boost::asio::async_read (connection->socket, boost::asio::buffer (connection->receive_buffer.data () + 1, rai::utx_block::size), [this_l](boost::system::error_code const & ec, size_t size_a) { + this_l->connection->stop_timeout (); + this_l->received_block (ec, size_a); + }); + break; + } case rai::block_type::not_a_block: { // Avoid re-using slow peers, or peers that sent the wrong blocks. @@ -2039,6 +2060,13 @@ void rai::bulk_push_server::received_type () }); break; } + case rai::block_type::utx: + { + boost::asio::async_read (*connection->socket, boost::asio::buffer (receive_buffer.data () + 1, rai::utx_block::size), [this_l](boost::system::error_code const & ec, size_t size_a) { + this_l->received_block (ec, size_a); + }); + break; + } case rai::block_type::not_a_block: { connection->finish_request (); diff --git a/rai/node/common.cpp b/rai/node/common.cpp index be39a38d..84853c7e 100644 --- a/rai/node/common.cpp +++ b/rai/node/common.cpp @@ -10,8 +10,8 @@ size_t constexpr rai::message::bootstrap_server_position; std::bitset<16> constexpr rai::message::block_type_mask; rai::message::message (rai::message_type type_a) : -version_max (0x06), -version_using (0x06), +version_max (0x07), +version_using (0x07), version_min (0x01), type (type_a) { @@ -389,7 +389,7 @@ bool rai::confirm_ack::deserialize (rai::stream & stream_a) void rai::confirm_ack::serialize (rai::stream & stream_a) { - assert (block_type () == rai::block_type::send || block_type () == rai::block_type::receive || block_type () == rai::block_type::open || block_type () == rai::block_type::change); + assert (block_type () == rai::block_type::send || block_type () == rai::block_type::receive || block_type () == rai::block_type::open || block_type () == rai::block_type::change || block_type () == rai::block_type::utx); write_header (stream_a); vote->serialize (stream_a, block_type ()); } diff --git a/rai/node/node.cpp b/rai/node/node.cpp index f1177db3..ce4ef5fc 100644 --- a/rai/node/node.cpp +++ b/rai/node/node.cpp @@ -803,7 +803,9 @@ enable_voting (true), bootstrap_connections (4), bootstrap_connections_max (64), callback_port (0), -lmdb_max_dbs (128) +lmdb_max_dbs (128), +utx_parse_canary (0), +utx_generate_canary (0) { switch (rai::rai_network) { @@ -813,6 +815,8 @@ lmdb_max_dbs (128) case rai::rai_networks::rai_beta_network: preconfigured_peers.push_back ("rai-beta.raiblocks.net"); preconfigured_representatives.push_back (rai::account ("5DF352F3E7367A17F2ADB52B8123959602F8D94C2F295B23F6BDFFFC5FEFCA5E")); + utx_parse_canary = rai::block_hash ("5005F5283DE8D2DAB0DAC41DE9BD23640F962B4F0EA7D3128C2EA3D78D578E27"); + utx_generate_canary = rai::block_hash ("FC18E2265FB835E8CF60E63531053A768CEDF5194263B01A5C95574944E4660D"); break; case rai::rai_networks::rai_live_network: preconfigured_peers.push_back ("rai.raiblocks.net"); @@ -833,7 +837,7 @@ lmdb_max_dbs (128) void rai::node_config::serialize_json (boost::property_tree::ptree & tree_a) const { - tree_a.put ("version", "9"); + tree_a.put ("version", "10"); tree_a.put ("peering_port", std::to_string (peering_port)); tree_a.put ("bootstrap_fraction_numerator", std::to_string (bootstrap_fraction_numerator)); tree_a.put ("receive_minimum", receive_minimum.to_string_dec ()); @@ -875,6 +879,8 @@ void rai::node_config::serialize_json (boost::property_tree::ptree & tree_a) con tree_a.put ("callback_port", std::to_string (callback_port)); tree_a.put ("callback_target", callback_target); tree_a.put ("lmdb_max_dbs", lmdb_max_dbs); + tree_a.put ("utx_parse_canary", utx_parse_canary.to_string ()); + tree_a.put ("utx_generate_canary", utx_generate_canary.to_string ()); } bool rai::node_config::upgrade_json (unsigned version, boost::property_tree::ptree & tree_a) @@ -949,6 +955,12 @@ bool rai::node_config::upgrade_json (unsigned version, boost::property_tree::ptr tree_a.put ("version", "9"); result = true; case 9: + tree_a.put ("utx_parse_canary", utx_parse_canary.to_string ()); + tree_a.put ("utx_generate_canary", utx_generate_canary.to_string ()); + tree_a.erase ("version"); + tree_a.put ("version", "10"); + result = true; + case 10: break; default: throw std::runtime_error ("Unknown node_config version"); @@ -1022,6 +1034,8 @@ bool rai::node_config::deserialize_json (bool & upgraded_a, boost::property_tree callback_target = tree_a.get ("callback_target"); auto lmdb_max_dbs_l = tree_a.get ("lmdb_max_dbs"); result |= parse_port (callback_port_l, callback_port); + auto utx_parse_canary_l = tree_a.get ("utx_parse_canary"); + auto utx_generate_canary_l = tree_a.get ("utx_generate_canary"); try { peering_port = std::stoul (peering_port_l); @@ -1040,6 +1054,8 @@ bool rai::node_config::deserialize_json (bool & upgraded_a, boost::property_tree result |= password_fanout > 1024 * 1024; result |= io_threads == 0; result |= work_threads == 0; + result |= utx_parse_canary.decode_hex (utx_parse_canary_l); + result |= utx_generate_canary.decode_hex (utx_generate_canary_l); } catch (std::logic_error const &) { @@ -1246,7 +1262,7 @@ void rai::block_processor::process_receive_many (std::deque 0) { node.observers.account_balance (i.second.account, false); @@ -1291,34 +1307,22 @@ rai::process_return rai::block_processor::process_receive_one (MDB_txn * transac { BOOST_LOG (node.log) << boost::str (boost::format ("Gap source for: %1%") % block_a->hash ().to_string ()); } - node.store.unchecked_put (transaction_a, block_a->source (), block_a); + node.store.unchecked_put (transaction_a, node.ledger.block_source (transaction_a, *block_a), block_a); + node.gap_cache.add (transaction_a, block_a); + break; + } + case rai::process_result::utx_disabled: + { + if (node.config.logging.ledger_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("UTX blocks are disabled: %1%") % block_a->hash ().to_string ()); + } + node.store.unchecked_put (transaction_a, node.ledger.utx_parse_canary, block_a); node.gap_cache.add (transaction_a, block_a); break; } case rai::process_result::old: { - { - auto root (block_a->root ()); - auto hash (block_a->hash ()); - auto existing (node.store.block_get (transaction_a, hash)); - if (existing != nullptr) - { - // Replace block with one that has higher work value - if (rai::work_value (root, block_a->block_work ()) > rai::work_value (root, existing->block_work ())) - { - auto account (node.ledger.account (transaction_a, hash)); - if (!rai::validate_message (account, hash, block_a->block_signature ())) - { - node.store.block_put (transaction_a, hash, *block_a, node.store.block_successor (transaction_a, hash)); - BOOST_LOG (node.log) << boost::str (boost::format ("Replacing block %1% with one that has higher work value") % hash.to_string ()); - } - } - } - else - { - // Could have been rolled back, maybe - } - } if (node.config.logging.ledger_duplicate_logging ()) { BOOST_LOG (node.log) << boost::str (boost::format ("Old for: %1%") % block_a->hash ().to_string ()); @@ -1376,10 +1380,28 @@ rai::process_return rai::block_processor::process_receive_one (MDB_txn * transac { BOOST_LOG (node.log) << boost::str (boost::format ("Account mismatch for: %1%") % block_a->hash ().to_string ()); } + break; } case rai::process_result::opened_burn_account: { BOOST_LOG (node.log) << boost::str (boost::format ("*** Rejecting open block for burn account ***: %1%") % block_a->hash ().to_string ()); + break; + } + case rai::process_result::balance_mismatch: + { + if (node.config.logging.ledger_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Balance mismatch for: %1%") % block_a->hash ().to_string ()); + } + break; + } + case rai::process_result::block_position: + { + if (node.config.logging.ledger_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Block %1% cannot follow predecessor %2%") % block_a->hash ().to_string () % block_a->previous ().to_string ()); + } + break; } } return result; @@ -1397,7 +1419,7 @@ alarm (alarm_a), work (work_a), store (init_a.block_store_init, application_path_a / "data.ldb", config_a.lmdb_max_dbs), gap_cache (*this), -ledger (store, config_a.inactive_supply.number ()), +ledger (store, config_a.inactive_supply.number (), config.utx_parse_canary, config.utx_generate_canary), active (*this), wallets (init_a.block_store_init, *this), network (*this, config.peering_port), @@ -1420,27 +1442,31 @@ block_processor_thread ([this]() { this->block_processor.process_blocks (); }) peers.disconnect_observer = [this]() { observers.disconnect (); }; - observers.blocks.add ([this](std::shared_ptr block_a, rai::account const & account_a, rai::amount const & amount_a) { + observers.blocks.add ([this](std::shared_ptr block_a, rai::process_return const & result_a) { if (this->block_arrival.recent (block_a->hash ())) { rai::transaction transaction (store.environment, nullptr, true); active.start (transaction, block_a); } }); - observers.blocks.add ([this](std::shared_ptr block_a, rai::account const & account_a, rai::amount const & amount_a) { + observers.blocks.add ([this](std::shared_ptr block_a, rai::process_return const & result_a) { if (this->block_arrival.recent (block_a->hash ())) { auto node_l (shared_from_this ()); - background ([node_l, block_a, account_a, amount_a]() { + background ([node_l, block_a, result_a]() { if (!node_l->config.callback_address.empty ()) { boost::property_tree::ptree event; - event.add ("account", account_a.to_account ()); + event.add ("account", result_a.account.to_account ()); event.add ("hash", block_a->hash ().to_string ()); std::string block_text; block_a->serialize_json (block_text); event.add ("block", block_text); - event.add ("amount", amount_a.to_string_dec ()); + event.add ("amount", result_a.amount.to_string_dec ()); + if (result_a.utx_is_send) + { + event.add ("is_send", *result_a.utx_is_send); + } std::stringstream ostream; boost::property_tree::write_json (ostream, event); ostream.flush (); @@ -2315,35 +2341,42 @@ public: { } virtual ~confirmed_visitor () = default; - void send_block (rai::send_block const & block_a) override + void scan_receivable (rai::account const & account_a) { for (auto i (node.wallets.items.begin ()), n (node.wallets.items.end ()); i != n; ++i) { auto wallet (i->second); - if (wallet->exists (block_a.hashables.destination)) + if (wallet->exists (account_a)) { rai::account representative; rai::pending_info pending; rai::transaction transaction (node.store.environment, nullptr, false); representative = wallet->store.representative (transaction); - auto error (node.store.pending_get (transaction, rai::pending_key (block_a.hashables.destination, block_a.hash ()), pending)); + auto error (node.store.pending_get (transaction, rai::pending_key (account_a, block->hash ()), pending)); if (!error) { auto node_l (node.shared ()); auto amount (pending.amount.number ()); - assert (block.get () == &block_a); wallet->receive_async (block, representative, amount, [](std::shared_ptr) {}); } else { if (node.config.logging.ledger_duplicate_logging ()) { - BOOST_LOG (node.log) << boost::str (boost::format ("Block confirmed before timeout %1%") % block_a.hash ().to_string ()); + BOOST_LOG (node.log) << boost::str (boost::format ("Block confirmed before timeout %1%") % block->hash ().to_string ()); } } } } } + void utx_block (rai::utx_block const & block_a) override + { + scan_receivable (block_a.hashables.link); + } + void send_block (rai::send_block const & block_a) override + { + scan_receivable (block_a.hashables.destination); + } void receive_block (rai::receive_block const &) override { } diff --git a/rai/node/node.hpp b/rai/node/node.hpp index 4b6792f6..ab603b6d 100644 --- a/rai/node/node.hpp +++ b/rai/node/node.hpp @@ -407,6 +407,8 @@ public: uint16_t callback_port; std::string callback_target; int lmdb_max_dbs; + rai::block_hash utx_parse_canary; + rai::block_hash utx_generate_canary; static std::chrono::seconds constexpr keepalive_period = std::chrono::seconds (60); static std::chrono::seconds constexpr keepalive_cutoff = keepalive_period * 5; static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5); @@ -414,7 +416,7 @@ public: class node_observers { public: - rai::observer_set, rai::account const &, rai::amount const &> blocks; + rai::observer_set, rai::process_return const &> blocks; rai::observer_set wallet; rai::observer_set, rai::endpoint const &> vote; rai::observer_set account_balance; diff --git a/rai/node/rpc.cpp b/rai/node/rpc.cpp index 2d382d1d..01e4774d 100644 --- a/rai/node/rpc.cpp +++ b/rai/node/rpc.cpp @@ -141,8 +141,8 @@ void rai::rpc::start () } acceptor.listen (); - node.observers.blocks.add ([this](std::shared_ptr block_a, rai::account const & account_a, rai::amount const &) { - observer_action (account_a); + node.observers.blocks.add ([this](std::shared_ptr block_a, rai::process_return const & result_a) { + observer_action (result_a.account); }); accept (); @@ -961,18 +961,17 @@ void rai::rpc_handler::blocks_info () entry.put ("contents", contents); if (pending) { - auto block_l (dynamic_cast (block.get ())); bool exists (false); - if (block_l != nullptr) + auto destination (node.ledger.block_destination (transaction, *block)); + if (!destination.is_zero ()) { - auto destination (block_l->hashables.destination); exists = node.store.pending_exists (transaction, rai::pending_key (destination, hash)); } entry.put ("pending", exists ? "1" : "0"); } if (source) { - rai::block_hash source_hash (block->source ()); + rai::block_hash source_hash (node.ledger.block_source (transaction, *block)); std::unique_ptr block_a (node.store.block_get (transaction, source_hash)); if (block_a != nullptr) { @@ -1043,6 +1042,7 @@ void rai::rpc_handler::block_count_type () response_l.put ("receive", std::to_string (count.receive)); response_l.put ("open", std::to_string (count.open)); response_l.put ("change", std::to_string (count.change)); + response_l.put ("utx", std::to_string (count.utx)); response (response_l); } @@ -1125,6 +1125,7 @@ void rai::rpc_handler::block_create () prv.data.clear (); rai::uint256_union previous (0); rai::uint128_union balance (0); + rai::uint256_union link (0); if (wallet != 0 && account != 0) { auto existing (node.wallets.items.find (wallet)); @@ -1183,11 +1184,41 @@ void rai::rpc_handler::block_create () error_response (response, "Bad balance number"); } } + boost::optional link_text (request.get_optional ("link")); + if (link_text.is_initialized ()) + { + auto error_link (link.decode_account (link_text.get ())); + if (error_link) + { + auto error_link (link.decode_hex (link_text.get ())); + if (error_link) + { + error_response (response, "Bad link number"); + } + } + } if (prv.data != 0) { rai::uint256_union pub; ed25519_publickey (prv.data.bytes.data (), pub.bytes.data ()); - if (type == "open") + if (type == "utx") + { + if (!account.is_zero () && previous_text.is_initialized () && !representative.is_zero () && !balance.is_zero () && link_text.is_initialized ()) + { + rai::utx_block utx (account, previous, representative, balance, link, prv, pub, work); + boost::property_tree::ptree response_l; + response_l.put ("hash", utx.hash ().to_string ()); + std::string contents; + utx.serialize_json (contents); + response_l.put ("block", contents); + response (response_l); + } + else + { + error_response (response, "Account, previous, representative, balance, and link are required"); + } + } + else if (type == "open") { if (representative != 0 && source != 0) { @@ -1622,6 +1653,53 @@ public: tree.put ("representative", block_a.hashables.representative.to_account ()); } } + void utx_block (rai::utx_block const & block_a) + { + if (raw) + { + tree.put ("type", "utx"); + tree.put ("representative", block_a.hashables.representative.to_account ()); + tree.put ("link", block_a.hashables.link.to_string ()); + } + auto balance (block_a.hashables.balance.number ()); + auto previous_balance (handler.node.ledger.balance (transaction, block_a.hashables.previous)); + if (balance < previous_balance) + { + if (raw) + { + tree.put ("subtype", "send"); + } + else + { + tree.put ("type", "send"); + } + tree.put ("account", block_a.hashables.account.to_account ()); + tree.put ("amount", (previous_balance - balance).convert_to ()); + } + else + { + if (block_a.hashables.link.is_zero ()) + { + if (raw) + { + tree.put ("subtype", "change"); + } + } + else + { + if (raw) + { + tree.put ("subtype", "receive"); + } + else + { + tree.put ("type", "receive"); + } + tree.put ("account", block_a.hashables.account.to_account ()); + tree.put ("amount", (balance - previous_balance).convert_to ()); + } + } + } rai::rpc_handler & handler; bool raw; rai::transaction & transaction; @@ -2178,12 +2256,11 @@ void rai::rpc_handler::pending_exists () auto block (node.store.block_get (transaction, hash)); if (block != nullptr) { - auto block_l (dynamic_cast (block.get ())); auto exists (false); - if (block_l != nullptr) + auto destination (node.ledger.block_destination (transaction, *block)); + if (!destination.is_zero ()) { - auto account (block_l->hashables.destination); - exists = node.store.pending_exists (transaction, rai::pending_key (account, hash)); + exists = node.store.pending_exists (transaction, rai::pending_key (destination, hash)); } boost::property_tree::ptree response_l; response_l.put ("exists", exists ? "1" : "0"); @@ -2418,7 +2495,7 @@ void rai::rpc_handler::process () { case rai::process_result::progress: { - node.observers.blocks (block_a, result.account, result.amount); + node.observers.blocks (block_a, result); boost::property_tree::ptree response_l; response_l.put ("hash", hash.to_string ()); response (response_l); @@ -2434,6 +2511,11 @@ void rai::rpc_handler::process () error_response (response, "Gap source block"); break; } + case rai::process_result::utx_disabled: + { + error_response (response, "UTX blocks are disabled"); + break; + } case rai::process_result::old: { error_response (response, "Old block"); @@ -2777,7 +2859,7 @@ void rai::rpc_handler::republish () block = node.store.block_get (transaction, hash); if (sources != 0) // Republish source chain { - rai::block_hash source (block->source ()); + rai::block_hash source (node.ledger.block_source (transaction, *block)); std::unique_ptr block_a (node.store.block_get (transaction, source)); std::vector hashes; while (block_a != nullptr && hashes.size () < sources) @@ -2803,10 +2885,9 @@ void rai::rpc_handler::republish () if (destinations != 0) // Republish destination chain { auto block_b (node.store.block_get (transaction, hash)); - auto block_s (dynamic_cast (block_b.get ())); - if (block_s != nullptr) + auto destination (node.ledger.block_destination (transaction, *block_b)); + if (!destination.is_zero ()) { - auto destination (block_s->hashables.destination); auto exists (node.store.pending_exists (transaction, rai::pending_key (destination, hash))); if (!exists) { @@ -2817,7 +2898,7 @@ void rai::rpc_handler::republish () while (block_d != nullptr && hash != source) { hashes.push_back (previous); - source = block_d->source (); + source = node.ledger.block_source (transaction, *block_d); previous = block_d->previous (); block_d = node.store.block_get (transaction, previous); } diff --git a/rai/node/testing.cpp b/rai/node/testing.cpp index dceb9256..9703fd5f 100644 --- a/rai/node/testing.cpp +++ b/rai/node/testing.cpp @@ -128,40 +128,27 @@ void rai::system::generate_usage_traffic (uint32_t count_a, uint32_t wait_a, siz void rai::system::generate_rollback (rai::node & node_a, std::vector & accounts_a) { - rai::block_hash current (node_a.latest (get_random_account (accounts_a))); - rai::block_hash target (current); rai::transaction transaction (node_a.store.environment, nullptr, true); - while (!current.is_zero ()) + auto index (random_pool.GenerateWord32 (0, accounts_a.size () - 1)); + auto account (accounts_a[index]); + rai::account_info info; + auto error (node_a.store.account_get (transaction, account, info)); + if (!error) { - auto block1 (node_a.store.block_get (transaction, current)); - assert (block1 != nullptr); - current = block1->previous (); - auto block2 (node_a.store.block_get (transaction, target)); - assert (block2 != nullptr); - target = block2->previous (); - if (!current.is_zero ()) + auto hash (info.open_block); + rai::genesis genesis; + if (hash != genesis.hash ()) { - auto block2 (node_a.store.block_get (transaction, current)); - current = block2->previous (); + accounts_a[index] = accounts_a[accounts_a.size () - 1]; + accounts_a.pop_back (); + node_a.ledger.rollback (transaction, hash); } - auto open (dynamic_cast (block2.get ())); - if (open != nullptr) - { - if (!node_a.ledger.block_exists (open->hashables.source)) - { - target = 0; - } - } - } - if (!target.is_zero ()) - { - node_a.ledger.rollback (transaction, target); } } void rai::system::generate_receive (rai::node & node_a) { - std::shared_ptr send_block; + std::shared_ptr send_block; { rai::transaction transaction (node_a.store.environment, nullptr, false); rai::uint256_union random_block; @@ -171,14 +158,12 @@ void rai::system::generate_receive (rai::node & node_a) { rai::pending_key send_hash (i->first); rai::pending_info info (i->second); - auto block (node_a.store.block_get (transaction, send_hash.hash)); - assert (dynamic_cast (block.get ()) != nullptr); - send_block.reset (static_cast (block.release ())); + send_block = node_a.store.block_get (transaction, send_hash.hash); } } if (send_block != nullptr) { - auto receive_error (wallet (0)->receive_sync (std::move (send_block), rai::genesis_account, std::numeric_limits::max ())); + auto receive_error (wallet (0)->receive_sync (send_block, rai::genesis_account, std::numeric_limits::max ())); (void)receive_error; } } @@ -186,11 +171,11 @@ void rai::system::generate_receive (rai::node & node_a) void rai::system::generate_activity (rai::node & node_a, std::vector & accounts_a) { auto what (random_pool.GenerateByte ()); - if (what < 0x10) + if (what < 0x1) { generate_rollback (node_a, accounts_a); } - else if (what < 0x1) + else if (what < 0x10) { generate_change_known (node_a, accounts_a); } @@ -306,11 +291,19 @@ void rai::system::generate_mass_activity (uint32_t count_a, rai::node & node_a) auto previous (std::chrono::steady_clock::now ()); for (uint32_t i (0); i < count_a; ++i) { - if ((i & 0xfff) == 0) + if ((i & 0xff) == 0) { auto now (std::chrono::steady_clock::now ()); auto us (std::chrono::duration_cast (now - previous).count ()); - std::cerr << boost::str (boost::format ("Mass activity iteration %1% us %2% us/t %3%\n") % i % us % (us / 256)); + uint64_t count (0); + uint64_t utx (0); + { + rai::transaction transaction (node_a.store.environment, nullptr, false); + auto block_counts (node_a.store.block_count (transaction)); + count = block_counts.sum (); + utx = block_counts.utx; + } + std::cerr << boost::str (boost::format ("Mass activity iteration %1% us %2% us/t %3% utx: %4% old: %5%\n") % i % us % (us / 256) % utx % (count - utx)); previous = now; } generate_activity (node_a, accounts); diff --git a/rai/node/wallet.cpp b/rai/node/wallet.cpp index 2518bc85..59e72aba 100644 --- a/rai/node/wallet.cpp +++ b/rai/node/wallet.cpp @@ -842,38 +842,63 @@ void rai::wallet_store::destroy (MDB_txn * transaction_a) assert (status == 0); } -std::shared_ptr rai::wallet::receive_action (rai::send_block const & send_a, rai::account const & representative_a, rai::uint128_union const & amount_a, bool generate_work_a) +std::shared_ptr rai::wallet::receive_action (rai::block const & send_a, rai::account const & representative_a, rai::uint128_union const & amount_a, bool generate_work_a) { + rai::account account; auto hash (send_a.hash ()); std::shared_ptr block; if (node.config.receive_minimum.number () <= amount_a.number ()) { rai::transaction transaction (node.ledger.store.environment, nullptr, false); - if (node.ledger.store.pending_exists (transaction, rai::pending_key (send_a.hashables.destination, hash))) + rai::pending_info pending_info; + if (node.store.block_exists (transaction, hash)) { - rai::raw_key prv; - if (!store.fetch (transaction, send_a.hashables.destination, prv)) + account = node.ledger.block_destination (transaction, send_a); + if (!node.ledger.store.pending_get (transaction, rai::pending_key (account, hash), pending_info)) { - rai::account_info info; - auto new_account (node.ledger.store.account_get (transaction, send_a.hashables.destination, info)); - if (!new_account) + rai::raw_key prv; + if (!store.fetch (transaction, account, prv)) { - auto receive (new rai::receive_block (info.head, hash, prv, send_a.hashables.destination, generate_work_a ? work_fetch (transaction, send_a.hashables.destination, info.head) : 0)); - block.reset (receive); + rai::account_info info; + auto new_account (node.ledger.store.account_get (transaction, account, info)); + if (!new_account) + { + std::shared_ptr rep_block = node.ledger.store.block_get (transaction, info.rep_block); + assert (rep_block != nullptr); + if (should_generate_utx (transaction, info.head)) + { + block.reset (new rai::utx_block (account, info.head, rep_block->representative (), info.balance.number () + pending_info.amount.number (), hash, prv, account, generate_work_a ? work_fetch (transaction, account, info.head) : 0)); + } + else + { + block.reset (new rai::receive_block (info.head, hash, prv, account, generate_work_a ? work_fetch (transaction, account, info.head) : 0)); + } + } + else + { + if (node.ledger.utx_generation_enabled (transaction)) + { + block.reset (new rai::utx_block (account, 0, representative_a, pending_info.amount, hash, prv, account, generate_work_a ? work_fetch (transaction, account, account) : 0)); + } + else + { + block.reset (new rai::open_block (hash, representative_a, account, prv, account, generate_work_a ? work_fetch (transaction, account, account) : 0)); + } + } } else { - block.reset (new rai::open_block (hash, representative_a, send_a.hashables.destination, prv, send_a.hashables.destination, generate_work_a ? work_fetch (transaction, send_a.hashables.destination, send_a.hashables.destination) : 0)); + BOOST_LOG (node.log) << "Unable to receive, wallet locked"; } } else { - BOOST_LOG (node.log) << "Unable to receive, wallet locked"; + // Ledger doesn't have this marked as available to receive anymore } } else { - // Ledger doesn't have this marked as available to receive anymore + // Ledger doesn't have this block anymore. } } else @@ -890,7 +915,7 @@ std::shared_ptr rai::wallet::receive_action (rai::send_block const & { auto hash (block->hash ()); auto this_l (shared_from_this ()); - auto source (send_a.hashables.destination); + auto source (account); node.wallets.queue_wallet_action (rai::wallets::generate_priority, [this_l, source, hash] { this_l->work_generate (source, hash); }); @@ -915,7 +940,14 @@ std::shared_ptr rai::wallet::change_action (rai::account const & sou rai::raw_key prv; auto error2 (store.fetch (transaction, source_a, prv)); assert (!error2); - block.reset (new rai::change_block (info.head, representative_a, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + if (should_generate_utx (transaction, info.head)) + { + block.reset (new rai::utx_block (source_a, info.head, representative_a, info.balance, 0, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + } + else + { + block.reset (new rai::change_block (info.head, representative_a, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + } } } } @@ -983,7 +1015,16 @@ std::shared_ptr rai::wallet::send_action (rai::account const & sourc rai::raw_key prv; auto error2 (store.fetch (transaction, source_a, prv)); assert (!error2); - block.reset (new rai::send_block (info.head, account_a, balance - amount_a, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + std::shared_ptr rep_block = node.ledger.store.block_get (transaction, info.rep_block); + assert (rep_block != nullptr); + if (should_generate_utx (transaction, info.head)) + { + block.reset (new rai::utx_block (source_a, info.head, rep_block->representative (), balance - amount_a, account_a, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + } + else + { + block.reset (new rai::send_block (info.head, account_a, balance - amount_a, prv, source_a, generate_work_a ? work_fetch (transaction, source_a, info.head) : 0)); + } if (id_mdb_val) { auto status (mdb_put (transaction, node.wallets.send_action_ids, *id_mdb_val, rai::mdb_val (block->hash ()), 0)); @@ -1011,6 +1052,14 @@ std::shared_ptr rai::wallet::send_action (rai::account const & sourc return block; } +bool rai::wallet::should_generate_utx (MDB_txn * transaction_a, rai::block_hash const & hash_a) +{ + auto head (node.store.block_get(transaction_a, hash_a)); + assert (head != nullptr); + auto is_utx (dynamic_cast (head.get ()) != nullptr); + return is_utx || node.ledger.utx_generation_enabled (transaction_a); +} + bool rai::wallet::change_sync (rai::account const & source_a, rai::account const & representative_a) { std::promise result; @@ -1041,9 +1090,9 @@ bool rai::wallet::receive_sync (std::shared_ptr block_a, rai::accoun void rai::wallet::receive_async (std::shared_ptr block_a, rai::account const & representative_a, rai::uint128_t const & amount_a, std::function)> const & action_a, bool generate_work_a) { - assert (dynamic_cast (block_a.get ()) != nullptr); + //assert (dynamic_cast (block_a.get ()) != nullptr); node.wallets.queue_wallet_action (amount_a, [this, block_a, representative_a, amount_a, action_a, generate_work_a]() { - auto block (receive_action (*static_cast (block_a.get ()), representative_a, amount_a, generate_work_a)); + auto block (receive_action (*static_cast (block_a.get ()), representative_a, amount_a, generate_work_a)); action_a (block); }); } diff --git a/rai/node/wallet.hpp b/rai/node/wallet.hpp index 3de60d60..198d552e 100644 --- a/rai/node/wallet.hpp +++ b/rai/node/wallet.hpp @@ -124,7 +124,7 @@ class wallet : public std::enable_shared_from_this { public: std::shared_ptr change_action (rai::account const &, rai::account const &, bool = true); - std::shared_ptr receive_action (rai::send_block const &, rai::account const &, rai::uint128_union const &, bool = true); + std::shared_ptr receive_action (rai::block const &, rai::account const &, rai::uint128_union const &, bool = true); std::shared_ptr send_action (rai::account const &, rai::account const &, rai::uint128_t const &, bool = true, boost::optional = {}); wallet (bool &, rai::transaction &, rai::node &, std::string const &); wallet (bool &, rai::transaction &, rai::node &, std::string const &, std::string const &); @@ -151,6 +151,7 @@ public: void work_ensure (MDB_txn *, rai::account const &); bool search_pending (); void init_free_accounts (MDB_txn *); + bool should_generate_utx (MDB_txn *, rai::block_hash const &); /** Changes the wallet seed and returns the first account */ rai::public_key change_seed (MDB_txn * transaction_a, rai::raw_key const & prv_a); std::unordered_set free_accounts; diff --git a/rai/qt/qt.cpp b/rai/qt/qt.cpp index c21e1cf6..15fc8ea9 100644 --- a/rai/qt/qt.cpp +++ b/rai/qt/qt.cpp @@ -518,6 +518,29 @@ public: amount = 0; account = block_a.hashables.representative; } + void utx_block (rai::utx_block const & block_a) + { + auto balance (block_a.hashables.balance.number ()); + auto previous_balance (ledger.balance (transaction, block_a.hashables.previous)); + account = block_a.hashables.account; + if (balance < previous_balance) + { + type = "Send"; + amount = previous_balance - balance; + } + else + { + if (block_a.hashables.link.is_zero ()) + { + type = "Change"; + } + else + { + type = "Receive"; + } + amount = balance - previous_balance; + } + } MDB_txn * transaction; rai::ledger & ledger; std::string type; @@ -1076,17 +1099,18 @@ void rai_qt::wallet::start () this_l->push_main_stack (this_l->send_blocks_window); } }); - node.observers.blocks.add ([this_w](std::shared_ptr block_a, rai::account const & account_a, rai::amount const &) { + node.observers.blocks.add ([this_w](std::shared_ptr block_a, rai::process_return const & result_a) { if (auto this_l = this_w.lock ()) { - this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w, block_a, account_a]() { + auto account (result_a.account); + this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w, block_a, account]() { if (auto this_l = this_w.lock ()) { - if (this_l->wallet_m->exists (account_a)) + if (this_l->wallet_m->exists (account)) { this_l->accounts.refresh (); } - if (account_a == this_l->account) + if (account == this_l->account) { this_l->history.refresh (); } @@ -1993,10 +2017,10 @@ void rai_qt::block_creation::create_receive () auto block_l (wallet.node.store.block_get (transaction, source_l)); if (block_l != nullptr) { - auto send_block (dynamic_cast (block_l.get ())); - if (send_block != nullptr) + auto destination (wallet.node.ledger.block_destination (transaction, *block_l)); + if (!destination.is_zero ()) { - rai::pending_key pending_key (send_block->hashables.destination, source_l); + rai::pending_key pending_key (destination, source_l); rai::pending_info pending; if (!wallet.node.store.pending_get (transaction, pending_key, pending)) { @@ -2117,10 +2141,10 @@ void rai_qt::block_creation::create_open () auto block_l (wallet.node.store.block_get (transaction, source_l)); if (block_l != nullptr) { - auto send_block (dynamic_cast (block_l.get ())); - if (send_block != nullptr) + auto destination (wallet.node.ledger.block_destination (transaction, *block_l)); + if (!destination.is_zero ()) { - rai::pending_key pending_key (send_block->hashables.destination, source_l); + rai::pending_key pending_key (destination, source_l); rai::pending_info pending; if (!wallet.node.store.pending_get (transaction, pending_key, pending)) { diff --git a/rai/slow_test/node.cpp b/rai/slow_test/node.cpp index 78074667..8b441405 100644 --- a/rai/slow_test/node.cpp +++ b/rai/slow_test/node.cpp @@ -19,26 +19,52 @@ TEST (system, generate_mass_activity) TEST (system, generate_mass_activity_long) { - std::vector threads; + rai::system system (24000, 1); + rai::thread_runner runner (system.service, system.nodes[0]->config.io_threads); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + size_t count (1000000000); + system.generate_mass_activity (count, *system.nodes[0]); + size_t accounts (0); + rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); + for (auto i (system.nodes[0]->store.latest_begin (transaction)), n (system.nodes[0]->store.latest_end ()); i != n; ++i) { - rai::system system (24000, 1); - rai::thread_runner runner (system.service, system.nodes[0]->config.io_threads); - system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); - size_t count (1000000000); - system.generate_mass_activity (count, *system.nodes[0]); - size_t accounts (0); - rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); - for (auto i (system.nodes[0]->store.latest_begin (transaction)), n (system.nodes[0]->store.latest_end ()); i != n; ++i) - { - ++accounts; - } - system.stop (); - runner.join (); + ++accounts; } - for (auto i (threads.begin ()), n (threads.end ()); i != n; ++i) + system.stop (); + runner.join (); +} + +TEST (system, generate_mass_activity_utx_enable) +{ + rai::system system (24000, 1); + rai::thread_runner runner (system.service, system.nodes[0]->config.io_threads); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + system.nodes[0]->alarm.add (std::chrono::steady_clock::now () + std::chrono::minutes (1), [&system]() { + std::cerr << boost::str (boost::format ("Enabling utx block parsing\n")); + rai::transaction transaction (system.nodes[0]->store.environment, nullptr, true); + ASSERT_FALSE (system.nodes[0]->ledger.utx_parsing_enabled (transaction)); + rai::genesis genesis; + system.nodes[0]->ledger.utx_parse_canary = genesis.hash (); + ASSERT_TRUE (system.nodes[0]->ledger.utx_parsing_enabled (transaction)); + }); + system.nodes[0]->alarm.add (std::chrono::steady_clock::now () + std::chrono::minutes (2), [&system]() { + std::cerr << boost::str (boost::format ("Enabling utx block generation\n")); + rai::transaction transaction (system.nodes[0]->store.environment, nullptr, true); + ASSERT_FALSE (system.nodes[0]->ledger.utx_generation_enabled (transaction)); + rai::genesis genesis; + system.nodes[0]->ledger.utx_generate_canary = genesis.hash (); + ASSERT_TRUE (system.nodes[0]->ledger.utx_generation_enabled (transaction)); + }); + size_t count (1000000000); + system.generate_mass_activity (count, *system.nodes[0]); + size_t accounts (0); + rai::transaction transaction (system.nodes[0]->store.environment, nullptr, false); + for (auto i (system.nodes[0]->store.latest_begin (transaction)), n (system.nodes[0]->store.latest_end ()); i != n; ++i) { - i->join (); + ++accounts; } + system.stop (); + runner.join (); } TEST (system, receive_while_synchronizing)