diff --git a/rai/core_test/block.cpp b/rai/core_test/block.cpp index 4374385a..ecc56788 100644 --- a/rai/core_test/block.cpp +++ b/rai/core_test/block.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -452,3 +453,239 @@ TEST (block_uniquer, cleanup) ASSERT_LT (iterations++, 200); } } + +TEST (block_builder, zeroed_state_block) +{ + std::error_code ec; + rai::block_builder builder; + rai::keypair key; + // Make sure manually- and builder constructed all-zero blocks have equal hashes, and check signature. + auto zero_block_manual (std::make_shared (0, 0, 0, 0, 0, key.prv, key.pub, 0)); + auto zero_block_build = builder.state ().zero ().sign (key.prv, key.pub).build (); + ASSERT_TRUE (zero_block_manual->hash () == zero_block_build->hash ()); + ASSERT_FALSE (rai::validate_message (key.pub, zero_block_build->hash (), zero_block_build->signature)); +} + +TEST (block_builder, state) +{ + // Test against a random hash from the live network + std::error_code ec; + rai::block_builder builder; + auto block = builder + .state () + .account_address ("xrb_15nhh1kzw3x8ohez6s75wy3jr6dqgq65oaede1fzk5hqxk4j8ehz7iqtb3to") + .previous_hex ("FEFBCE274E75148AB31FF63EFB3082EF1126BF72BF3FA9C76A97FD5A9F0EBEC5") + .balance_dec ("2251569974100400000000000000000000") + .representative_address ("xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou") + .link_hex ("E16DD58C1EFA8B521545B0A74375AA994D9FC43828A4266D75ECF57F07A7EE86") + .build (ec); + ASSERT_EQ (block->hash ().to_string (), "2D243F8F92CDD0AD94A1D456A6B15F3BE7A6FCBD98D4C5831D06D15C818CD81F"); +} + +TEST (block_builder, state_missing_rep) +{ + // Test against a random hash from the live network + std::error_code ec; + rai::block_builder builder; + auto block = builder + .state () + .account_address ("xrb_15nhh1kzw3x8ohez6s75wy3jr6dqgq65oaede1fzk5hqxk4j8ehz7iqtb3to") + .previous_hex ("FEFBCE274E75148AB31FF63EFB3082EF1126BF72BF3FA9C76A97FD5A9F0EBEC5") + .balance_dec ("2251569974100400000000000000000000") + .link_hex ("E16DD58C1EFA8B521545B0A74375AA994D9FC43828A4266D75ECF57F07A7EE86") + .sign_zero () + .work (0) + .build (ec); + ASSERT_EQ (ec, nano::error_common::missing_representative); +} + +TEST (block_builder, state_equality) +{ + std::error_code ec; + rai::block_builder builder; + + // With constructor + rai::keypair key1, key2; + rai::state_block block1 (key1.pub, 1, key2.pub, 2, 4, key1.prv, key1.pub, 5); + + // With builder + auto block2 = builder + .state () + .account (key1.pub) + .previous (1) + .representative (key2.pub) + .balance (2) + .link (4) + .sign (key1.prv, key1.pub) + .work (5) + .build (ec); + + ASSERT_NO_ERROR (ec); + ASSERT_EQ (block1.hash (), block2->hash ()); + ASSERT_EQ (block1.work, block2->work); +} + +TEST (block_builder, state_errors) +{ + std::error_code ec; + rai::block_builder builder; + + // Make sure we assert when building a block without an std::error_code + EXPECT_DEATH (builder + .state () + .account_hex ("xyz") + .build (), + ".*"); + + // Ensure the proper error is generated + builder.state ().account_hex ("xrb_bad").build (ec); + ASSERT_EQ (ec, nano::error_common::bad_account_number); + + builder.state ().zero ().account_address ("xrb_1111111111111111111111111111111111111111111111111111hifc8npp").build (ec); + ASSERT_NO_ERROR (ec); +} + +TEST (block_builder, open) +{ + // Test built block's hash against the Genesis open block from the live network + std::error_code ec; + rai::block_builder builder; + auto block = builder + .open () + .account_address ("xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3") + .representative_address ("xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3") + .source_hex ("E89208DD038FBB269987689621D52292AE9C35941A7484756ECCED92A65093BA") + .build (ec); + ASSERT_EQ (block->hash ().to_string (), "991CF190094C00F0B68E2E5F75F6BEE95A2E0BD93CEAA4A6734DB9F19B728948"); +} + +TEST (block_builder, open_equality) +{ + std::error_code ec; + rai::block_builder builder; + + // With constructor + rai::keypair key1, key2; + rai::open_block block1 (1, key1.pub, key2.pub, key1.prv, key1.pub, 5); + + // With builder + auto block2 = builder + .open () + .source (1) + .account (key2.pub) + .representative (key1.pub) + .sign (key1.prv, key1.pub) + .work (5) + .build (ec); + + ASSERT_NO_ERROR (ec); + ASSERT_EQ (block1.hash (), block2->hash ()); + ASSERT_EQ (block1.work, block2->work); +} + +TEST (block_builder, change) +{ + std::error_code ec; + rai::block_builder builder; + auto block = builder + .change () + .representative_address ("xrb_3rropjiqfxpmrrkooej4qtmm1pueu36f9ghinpho4esfdor8785a455d16nf") + .previous_hex ("088EE46429CA936F76C4EAA20B97F6D33E5D872971433EE0C1311BCB98764456") + .build (ec); + ASSERT_EQ (block->hash ().to_string (), "13552AC3928E93B5C6C215F61879358E248D4A5246B8B3D1EEC5A566EDCEE077"); +} + +TEST (block_builder, change_equality) +{ + std::error_code ec; + rai::block_builder builder; + + // With constructor + rai::keypair key1, key2; + rai::change_block block1 (1, key1.pub, key1.prv, key1.pub, 5); + + // With builder + auto block2 = builder + .change () + .previous (1) + .representative (key1.pub) + .sign (key1.prv, key1.pub) + .work (5) + .build (ec); + + ASSERT_NO_ERROR (ec); + ASSERT_EQ (block1.hash (), block2->hash ()); + ASSERT_EQ (block1.work, block2->work); +} + +TEST (block_builder, send) +{ + std::error_code ec; + rai::block_builder builder; + auto block = builder + .send () + .destination_address ("xrb_1gys8r4crpxhp94n4uho5cshaho81na6454qni5gu9n53gksoyy1wcd4udyb") + .previous_hex ("F685856D73A488894F7F3A62BC3A88E17E985F9969629FF3FDD4A0D4FD823F24") + .balance_hex ("00F035A9C7D818E7C34148C524FFFFEE") + .build (ec); + ASSERT_EQ (block->hash ().to_string (), "4560E7B1F3735D082700CFC2852F5D1F378F7418FD24CEF1AD45AB69316F15CD"); +} + +TEST (block_builder, send_equality) +{ + std::error_code ec; + rai::block_builder builder; + + // With constructor + rai::keypair key1, key2; + rai::send_block block1 (1, key1.pub, 2, key1.prv, key1.pub, 5); + + // With builder + auto block2 = builder + .send () + .previous (1) + .destination (key1.pub) + .balance (2) + .sign (key1.prv, key1.pub) + .work (5) + .build (ec); + + ASSERT_NO_ERROR (ec); + ASSERT_EQ (block1.hash (), block2->hash ()); + ASSERT_EQ (block1.work, block2->work); +} + +TEST (block_builder, receive_equality) +{ + std::error_code ec; + rai::block_builder builder; + + // With constructor + rai::keypair key1; + rai::receive_block block1 (1, 2, key1.prv, key1.pub, 5); + + // With builder + auto block2 = builder + .receive () + .previous (1) + .source (2) + .sign (key1.prv, key1.pub) + .work (5) + .build (ec); + + ASSERT_NO_ERROR (ec); + ASSERT_EQ (block1.hash (), block2->hash ()); + ASSERT_EQ (block1.work, block2->work); +} + +TEST (block_builder, receive) +{ + std::error_code ec; + rai::block_builder builder; + auto block = builder + .receive () + .previous_hex ("59660153194CAC5DAC08509D87970BF86F6AEA943025E2A7ED7460930594950E") + .source_hex ("7B2B0A29C1B235FDF9B4DEF2984BB3573BD1A52D28246396FBB3E4C5FE662135") + .build (ec); + ASSERT_EQ (block->hash ().to_string (), "6C004BF911D9CF2ED75CF6EC45E795122AD5D093FF5A83EDFBA43EC4A3EDC722"); +} diff --git a/rai/core_test/testutil.hpp b/rai/core_test/testutil.hpp index d5c82ce3..cc058eb2 100644 --- a/rai/core_test/testutil.hpp +++ b/rai/core_test/testutil.hpp @@ -13,3 +13,8 @@ #define ASSERT_NO_ERROR(condition) \ GTEST_TEST_ERROR_CODE (!(condition), #condition, condition.message ().c_str (), "", \ GTEST_FATAL_FAILURE_) + +/** Extends gtest with a std::error_code assert that expects an error */ +#define ASSERT_IS_ERROR(condition) \ + GTEST_TEST_ERROR_CODE ((condition.value () > 0), #condition, "An error was expected", "", \ + GTEST_FATAL_FAILURE_) diff --git a/rai/lib/CMakeLists.txt b/rai/lib/CMakeLists.txt index aaf40bc9..4cce6170 100644 --- a/rai/lib/CMakeLists.txt +++ b/rai/lib/CMakeLists.txt @@ -15,6 +15,8 @@ add_library (rai_lib errors.hpp errors.cpp expected.hpp + blockbuilders.cpp + blockbuilders.hpp blocks.cpp blocks.hpp config.hpp diff --git a/rai/lib/blockbuilders.cpp b/rai/lib/blockbuilders.cpp new file mode 100644 index 00000000..f0ef6ce1 --- /dev/null +++ b/rai/lib/blockbuilders.cpp @@ -0,0 +1,632 @@ +#include +#include +#include +#include + +namespace +{ +template +void previous_hex_impl (std::string const & previous_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::block_hash previous; + if (!previous.decode_hex (previous_hex)) + { + block->hashables.previous = previous; + } + else + { + ec = nano::error_common::bad_previous; + } +} + +template +void account_hex_impl (std::string const & account_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_hex (account_hex)) + { + block->hashables.account = account; + } + else + { + ec = nano::error_common::bad_account_number; + } +} + +template +void account_address_impl (std::string const & address, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_account (address)) + { + block->hashables.account = account; + } + else + { + ec = nano::error_common::bad_account_number; + } +} + +template +void representative_hex_impl (std::string const & account_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_hex (account_hex)) + { + block->hashables.representative = account; + } + else + { + ec = nano::error_common::bad_representative_number; + } +} + +template +void representative_address_impl (std::string const & address, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_account (address)) + { + block->hashables.representative = account; + } + else + { + ec = nano::error_common::bad_representative_number; + } +} + +template +void destination_hex_impl (std::string const & account_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_hex (account_hex)) + { + block->hashables.destination = account; + } + else + { + ec = nano::error_common::bad_account_number; + } +} + +template +void destination_address_impl (std::string const & address, std::error_code & ec, BLOCKTYPE & block) +{ + rai::account account; + if (!account.decode_account (address)) + { + block->hashables.destination = account; + } + else + { + ec = nano::error_common::bad_account_number; + } +} + +template +void source_hex_impl (std::string const & source_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::block_hash source; + if (!source.decode_hex (source_hex)) + { + block->hashables.source = source; + } + else + { + ec = nano::error_common::bad_source; + } +} + +template +void balance_dec_impl (std::string const & balance_decimal, std::error_code & ec, BLOCKTYPE & block) +{ + rai::amount balance; + if (!balance.decode_dec (balance_decimal)) + { + block->hashables.balance = balance; + } + else + { + ec = nano::error_common::bad_balance; + } +} + +template +void balance_hex_impl (std::string const & balance_hex, std::error_code & ec, BLOCKTYPE & block) +{ + rai::amount balance; + if (!balance.decode_hex (balance_hex)) + { + block->hashables.balance = balance; + } + else + { + ec = nano::error_common::bad_balance; + } +} + +/* The cost of looking up the error_code map is only taken if field-presence checks fail */ +std::unordered_map ec_map = { + { static_cast (rai::build_flags::account_present), nano::error_common::missing_account }, + { static_cast (rai::build_flags::balance_present), nano::error_common::missing_balance }, + { static_cast (rai::build_flags::link_present), nano::error_common::missing_link }, + { static_cast (rai::build_flags::previous_present), nano::error_common::missing_previous }, + { static_cast (rai::build_flags::representative_present), nano::error_common::missing_representative }, + { static_cast (rai::build_flags::signature_present), nano::error_common::missing_signature }, + { static_cast (rai::build_flags::work_present), nano::error_common::missing_work } +}; + +/** Find first set bit as a mask, e.g. 10101000 => 0x08. Returns -1 if no bit is set. */ +inline signed ffs_mask (uint8_t num) +{ + for (signed i = 0; i < 8; i++) + { + if ((num >> i) & 1) + { + return 1 << i; + } + } + return -1; +} + +/** + * Check if \p build_state contains all the flags in \p block_all_flags. + * If not, return the corresponding nano::error_common::missing_<...> value. + */ +std::error_code check_fields_set (uint8_t block_all_flags, uint8_t build_state) +{ + std::error_code ec; + + // Figure out which fields are not set. Note that static typing ensures we cannot set values + // not applicable to a given block type, we can only forget to set fields. This will be zero + // if all fields are set and thus succeed. + uint8_t res = block_all_flags ^ build_state; + if (res) + { + // Convert the first bit set to a field mask and look up the error code. + auto build_flags_mask = ffs_mask (res); + assert (ec_map.find (build_flags_mask) != ec_map.end ()); + ec = ec_map[build_flags_mask]; + } + return ec; +} +} // anonymous namespace + +rai::state_block_builder::state_block_builder () +{ + make_block (); +} + +rai::state_block_builder & rai::state_block_builder::make_block () +{ + construct_block (); + return *this; +} + +void rai::state_block_builder::validate () +{ + if (!ec) + { + ec = check_fields_set (required_fields, build_state); + } +} + +rai::state_block_builder & rai::state_block_builder::zero () +{ + block->work = uint64_t (0); + block->signature.clear (); + block->hashables.account.clear (); + block->hashables.balance.clear (); + block->hashables.link.clear (); + block->hashables.previous.clear (); + block->hashables.representative.clear (); + build_state = required_fields; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::account (rai::account account) +{ + block->hashables.account = account; + build_state |= build_flags::account_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::account_hex (std::string account_hex) +{ + account_hex_impl (account_hex, ec, block); + build_state |= build_flags::account_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::account_address (std::string address) +{ + account_address_impl (address, ec, block); + build_state |= build_flags::account_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::representative (rai::account account) +{ + block->hashables.representative = account; + build_state |= build_flags::representative_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::representative_hex (std::string account_hex) +{ + representative_hex_impl (account_hex, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::representative_address (std::string address) +{ + representative_address_impl (address, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::previous (rai::block_hash previous) +{ + block->hashables.previous = previous; + build_state |= build_flags::previous_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::previous_hex (std::string previous_hex) +{ + previous_hex_impl (previous_hex, ec, block); + build_state |= build_flags::previous_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::balance (rai::amount balance) +{ + block->hashables.balance = balance; + build_state |= build_flags::balance_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::balance_dec (std::string balance_decimal) +{ + balance_dec_impl (balance_decimal, ec, block); + build_state |= build_flags::balance_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::balance_hex (std::string balance_hex) +{ + balance_hex_impl (balance_hex, ec, block); + build_state |= build_flags::balance_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::link (rai::uint256_union link) +{ + block->hashables.link = link; + build_state |= build_flags::link_present; + return *this; +} + +rai::state_block_builder & rai::state_block_builder::link_hex (std::string link_hex) +{ + rai::uint256_union link; + if (!link.decode_hex (link_hex)) + { + block->hashables.link = link; + build_state |= build_flags::link_present; + } + else + { + ec = nano::error_common::bad_link; + } + return *this; +} + +rai::state_block_builder & rai::state_block_builder::link_address (std::string link_address) +{ + rai::account link; + if (!link.decode_account (link_address)) + { + block->hashables.link = link; + build_state |= build_flags::link_present; + } + else + { + ec = nano::error_common::bad_link; + } + return *this; +} + +rai::open_block_builder::open_block_builder () +{ + make_block (); +} + +rai::open_block_builder & rai::open_block_builder::make_block () +{ + construct_block (); + return *this; +} + +void rai::open_block_builder::validate () +{ + if (!ec) + { + ec = check_fields_set (required_fields, build_state); + } +} + +rai::open_block_builder & rai::open_block_builder::zero () +{ + block->work = uint64_t (0); + block->signature.clear (); + block->hashables.account.clear (); + block->hashables.representative.clear (); + block->hashables.source.clear (); + build_state = required_fields; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::account (rai::account account) +{ + block->hashables.account = account; + build_state |= build_flags::account_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::account_hex (std::string account_hex) +{ + account_hex_impl (account_hex, ec, block); + build_state |= build_flags::account_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::account_address (std::string address) +{ + account_address_impl (address, ec, block); + build_state |= build_flags::account_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::representative (rai::account account) +{ + block->hashables.representative = account; + build_state |= build_flags::representative_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::representative_hex (std::string account_hex) +{ + representative_hex_impl (account_hex, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::representative_address (std::string address) +{ + representative_address_impl (address, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::source (rai::block_hash source) +{ + block->hashables.source = source; + build_state |= build_flags::link_present; + return *this; +} + +rai::open_block_builder & rai::open_block_builder::source_hex (std::string source_hex) +{ + source_hex_impl (source_hex, ec, block); + build_state |= build_flags::link_present; + return *this; +} + +rai::change_block_builder::change_block_builder () +{ + make_block (); +} + +rai::change_block_builder & rai::change_block_builder::make_block () +{ + construct_block (); + return *this; +} + +void rai::change_block_builder::validate () +{ + if (!ec) + { + ec = check_fields_set (required_fields, build_state); + } +} + +rai::change_block_builder & rai::change_block_builder::zero () +{ + block->work = uint64_t (0); + block->signature.clear (); + block->hashables.previous.clear (); + block->hashables.representative.clear (); + build_state = required_fields; + return *this; +} + +rai::change_block_builder & rai::change_block_builder::representative (rai::account account) +{ + block->hashables.representative = account; + build_state |= build_flags::representative_present; + return *this; +} + +rai::change_block_builder & rai::change_block_builder::representative_hex (std::string account_hex) +{ + representative_hex_impl (account_hex, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::change_block_builder & rai::change_block_builder::representative_address (std::string address) +{ + representative_address_impl (address, ec, block); + build_state |= build_flags::representative_present; + return *this; +} + +rai::change_block_builder & rai::change_block_builder::previous (rai::block_hash previous) +{ + block->hashables.previous = previous; + build_state |= build_flags::previous_present; + return *this; +} + +rai::change_block_builder & rai::change_block_builder::previous_hex (std::string previous_hex) +{ + previous_hex_impl (previous_hex, ec, block); + build_state |= build_flags::previous_present; + return *this; +} + +rai::send_block_builder::send_block_builder () +{ + make_block (); +} + +rai::send_block_builder & rai::send_block_builder::make_block () +{ + construct_block (); + return *this; +} + +void rai::send_block_builder::validate () +{ + if (!ec) + { + ec = check_fields_set (required_fields, build_state); + } +} + +rai::send_block_builder & rai::send_block_builder::zero () +{ + block->work = uint64_t (0); + block->signature.clear (); + block->hashables.previous.clear (); + block->hashables.destination.clear (); + block->hashables.balance.clear (); + build_state = required_fields; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::destination (rai::account account) +{ + block->hashables.destination = account; + build_state |= build_flags::link_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::destination_hex (std::string account_hex) +{ + destination_hex_impl (account_hex, ec, block); + build_state |= build_flags::link_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::destination_address (std::string address) +{ + destination_address_impl (address, ec, block); + build_state |= build_flags::link_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::previous (rai::block_hash previous) +{ + block->hashables.previous = previous; + build_state |= build_flags::previous_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::previous_hex (std::string previous_hex) +{ + previous_hex_impl (previous_hex, ec, block); + build_state |= build_flags::previous_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::balance (rai::amount balance) +{ + block->hashables.balance = balance; + build_state |= build_flags::balance_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::balance_dec (std::string balance_decimal) +{ + balance_dec_impl (balance_decimal, ec, block); + build_state |= build_flags::balance_present; + return *this; +} + +rai::send_block_builder & rai::send_block_builder::balance_hex (std::string balance_hex) +{ + balance_hex_impl (balance_hex, ec, block); + build_state |= build_flags::balance_present; + return *this; +} + +rai::receive_block_builder::receive_block_builder () +{ + make_block (); +} + +rai::receive_block_builder & rai::receive_block_builder::make_block () +{ + construct_block (); + return *this; +} + +void rai::receive_block_builder::validate () +{ + if (!ec) + { + ec = check_fields_set (required_fields, build_state); + } +} + +rai::receive_block_builder & rai::receive_block_builder::zero () +{ + block->work = uint64_t (0); + block->signature.clear (); + block->hashables.previous.clear (); + block->hashables.source.clear (); + build_state = required_fields; + return *this; +} + +rai::receive_block_builder & rai::receive_block_builder::previous (rai::block_hash previous) +{ + block->hashables.previous = previous; + build_state |= build_flags::previous_present; + return *this; +} + +rai::receive_block_builder & rai::receive_block_builder::previous_hex (std::string previous_hex) +{ + previous_hex_impl (previous_hex, ec, block); + build_state |= build_flags::previous_present; + return *this; +} + +rai::receive_block_builder & rai::receive_block_builder::source (rai::block_hash source) +{ + block->hashables.source = source; + build_state |= build_flags::link_present; + return *this; +} + +rai::receive_block_builder & rai::receive_block_builder::source_hex (std::string source_hex) +{ + source_hex_impl (source_hex, ec, block); + build_state |= build_flags::link_present; + return *this; +} diff --git a/rai/lib/blockbuilders.hpp b/rai/lib/blockbuilders.hpp new file mode 100644 index 00000000..bbb83a7e --- /dev/null +++ b/rai/lib/blockbuilders.hpp @@ -0,0 +1,331 @@ +#pragma once + +#include +#include +#include + +namespace rai +{ +/** Flags to track builder state */ +enum class build_flags : uint8_t +{ + signature_present = 1, + work_present = 2, + account_present = 4, + balance_present = 8, + /* link also covers source and destination for legacy blocks */ + link_present = 16, + previous_present = 32, + representative_present = 64 +}; + +inline rai::build_flags operator| (rai::build_flags a, rai::build_flags b) +{ + return static_cast (static_cast (a) | static_cast (b)); +} +inline uint8_t operator| (uint8_t a, rai::build_flags b) +{ + return static_cast (a | static_cast (b)); +} +inline uint8_t operator& (uint8_t a, rai::build_flags b) +{ + return static_cast (a & static_cast (b)); +} +inline uint8_t operator|= (uint8_t & a, rai::build_flags b) +{ + return a = static_cast (a | static_cast (b)); +} + +/** + * Base type for block builder implementations. We employ static polymorphism + * to pass validation through subtypes without incurring the vtable cost. + */ +template +class abstract_builder +{ +public: + /** Returns the built block as a unique_ptr */ + inline std::unique_ptr build () + { + if (!ec) + { + static_cast (this)->validate (); + } + assert (!ec); + return std::move (block); + } + + /** Returns the built block as a unique_ptr. Any errors are placed in \p ec */ + inline std::unique_ptr build (std::error_code & ec) + { + if (!this->ec) + { + static_cast (this)->validate (); + } + ec = this->ec; + return std::move (block); + } + + /** Set work value */ + inline abstract_builder & work (uint64_t work) + { + block->work = work; + build_state |= build_flags::work_present; + return *this; + } + + /** Sign the block using the \p private_key and \p public_key */ + inline abstract_builder & sign (rai::raw_key const & private_key, rai::public_key const & public_key) + { + block->signature = rai::sign_message (private_key, public_key, block->hash ()); + build_state |= build_flags::signature_present; + return *this; + } + + /** Set signature to zero to pass build() validation, allowing block to be signed at a later point. This is mostly useful for tests. */ + inline abstract_builder & sign_zero () + { + block->signature.clear (); + build_state |= build_flags::signature_present; + return *this; + } + +protected: + abstract_builder () + { + } + + /** Create a new block and resets the internal builder state */ + inline void construct_block () + { + block = std::make_unique (); + ec.clear (); + build_state = 0; + } + + /** The block we're building. Clients can convert this to shared_ptr as needed. */ + std::unique_ptr block; + + /** + * Set if any builder functions fail. This will be output via the build(std::error_code) function, + * or cause an assert for the parameter-less overload. + */ + std::error_code ec; + + /** Bitset to track build state */ + uint8_t build_state{ 0 }; + + /** Required field shared by all block types*/ + uint8_t base_fields = static_cast (rai::build_flags::work_present | rai::build_flags::signature_present); +}; + +/** Builder for state blocks */ +class state_block_builder : public abstract_builder +{ +public: + /** Creates a state block builder by calling make_block() */ + state_block_builder (); + /** Creates a new block with fields, signature and work set to sentinel values. All fields must be set or zeroed for build() to succeed. */ + state_block_builder & make_block (); + /** Sets all hashables, signature and work to zero. */ + state_block_builder & zero (); + /** Set account */ + state_block_builder & account (rai::account account); + /** Set account from hex representation of public key */ + state_block_builder & account_hex (std::string account_hex); + /** Set account from an xrb_ or nano_ address */ + state_block_builder & account_address (std::string account_address); + /** Set representative */ + state_block_builder & representative (rai::account account); + /** Set representative from hex representation of public key */ + state_block_builder & representative_hex (std::string account_hex); + /** Set representative from an xrb_ or nano_ address */ + state_block_builder & representative_address (std::string account_address); + /** Set previous block hash */ + state_block_builder & previous (rai::block_hash previous); + /** Set previous block hash from hex representation */ + state_block_builder & previous_hex (std::string previous_hex); + /** Set balance */ + state_block_builder & balance (rai::amount balance); + /** Set balance from decimal string */ + state_block_builder & balance_dec (std::string balance_decimal); + /** Set balance from hex string */ + state_block_builder & balance_hex (std::string balance_hex); + /** Set link */ + state_block_builder & link (rai::uint256_union link); + /** Set link from hex representation */ + state_block_builder & link_hex (std::string link_hex); + /** Set link from an xrb_ or nano_ address */ + state_block_builder & link_address (std::string link_address); + /** Provides validation for build() */ + void validate (); + +private: + uint8_t required_fields = base_fields | static_cast (rai::build_flags::account_present | rai::build_flags::balance_present | rai::build_flags::link_present | rai::build_flags::previous_present | rai::build_flags::representative_present); +}; + +/** Builder for open blocks */ +class open_block_builder : public abstract_builder +{ +public: + /** Creates an open block builder by calling make_block() */ + open_block_builder (); + /** Creates a new block with fields, signature and work set to sentinel values. All fields must be set or zeroed for build() to succeed. */ + open_block_builder & make_block (); + /** Sets all hashables, signature and work to zero. */ + open_block_builder & zero (); + /** Set account */ + open_block_builder & account (rai::account account); + /** Set account from hex representation of public key */ + open_block_builder & account_hex (std::string account_hex); + /** Set account from an xrb_ or nano_ address */ + open_block_builder & account_address (std::string account_address); + /** Set representative */ + open_block_builder & representative (rai::account account); + /** Set representative from hex representation of public key */ + open_block_builder & representative_hex (std::string account_hex); + /** Set representative from an xrb_ or nano_ address */ + open_block_builder & representative_address (std::string account_address); + /** Set source block hash */ + open_block_builder & source (rai::block_hash source); + /** Set source block hash from hex representation */ + open_block_builder & source_hex (std::string source_hex); + /** Provides validation for build() */ + void validate (); + +private: + uint8_t required_fields = base_fields | static_cast (rai::build_flags::account_present | rai::build_flags::representative_present | rai::build_flags::link_present); +}; + +/** Builder for change blocks */ +class change_block_builder : public abstract_builder +{ +public: + /** Create a change block builder by calling make_block() */ + change_block_builder (); + /** Creates a new block with fields, signature and work set to sentinel values. All fields must be set or zeroed for build() to succeed. */ + change_block_builder & make_block (); + /** Sets all hashables, signature and work to zero. */ + change_block_builder & zero (); + /** Set representative */ + change_block_builder & representative (rai::account account); + /** Set representative from hex representation of public key */ + change_block_builder & representative_hex (std::string account_hex); + /** Set representative from an xrb_ or nano_ address */ + change_block_builder & representative_address (std::string account_address); + /** Set previous block hash */ + change_block_builder & previous (rai::block_hash previous); + /** Set previous block hash from hex representation */ + change_block_builder & previous_hex (std::string previous_hex); + /** Provides validation for build() */ + void validate (); + +private: + uint8_t required_fields = base_fields | static_cast (rai::build_flags::previous_present | rai::build_flags::representative_present); +}; + +/** Builder for send blocks */ +class send_block_builder : public abstract_builder +{ +public: + /** Creates a send block builder by calling make_block() */ + send_block_builder (); + /** Creates a new block with fields, signature and work set to sentinel values. All fields must be set or zeroed for build() to succeed. */ + send_block_builder & make_block (); + /** Sets all hashables, signature and work to zero. */ + send_block_builder & zero (); + /** Set destination */ + send_block_builder & destination (rai::account account); + /** Set destination from hex representation of public key */ + send_block_builder & destination_hex (std::string account_hex); + /** Set destination from an xrb_ or nano_ address */ + send_block_builder & destination_address (std::string account_address); + /** Set previous block hash */ + send_block_builder & previous (rai::block_hash previous); + /** Set previous block hash from hex representation */ + send_block_builder & previous_hex (std::string previous_hex); + /** Set balance */ + send_block_builder & balance (rai::amount balance); + /** Set balance from decimal string */ + send_block_builder & balance_dec (std::string balance_decimal); + /** Set balance from hex string */ + send_block_builder & balance_hex (std::string balance_hex); + /** Provides validation for build() */ + void validate (); + +private: + uint8_t required_fields = base_fields | static_cast (build_flags::previous_present | build_flags::link_present | build_flags::balance_present); +}; + +/** Builder for receive blocks */ +class receive_block_builder : public abstract_builder +{ +public: + /** Creates a receive block by calling make_block() */ + receive_block_builder (); + /** Creates a new block with fields, signature and work set to sentinel values. All fields must be set or zeroed for build() to succeed. */ + receive_block_builder & make_block (); + /** Sets all hashables, signature and work to zero. */ + receive_block_builder & zero (); + /** Set previous block hash */ + receive_block_builder & previous (rai::block_hash previous); + /** Set previous block hash from hex representation */ + receive_block_builder & previous_hex (std::string previous_hex); + /** Set source block hash */ + receive_block_builder & source (rai::block_hash source); + /** Set source block hash from hex representation */ + receive_block_builder & source_hex (std::string source_hex); + /** Provides validation for build() */ + void validate (); + +private: + uint8_t required_fields = base_fields | static_cast (build_flags::previous_present | build_flags::link_present); +}; + +/** Block builder to simplify construction of the various block types */ +class block_builder +{ +public: + /** Prepares a new state block and returns a block builder */ + inline rai::state_block_builder & state () + { + state_builder.make_block (); + return state_builder; + } + + /** Prepares a new open block and returns a block builder */ + inline rai::open_block_builder & open () + { + open_builder.make_block (); + return open_builder; + } + + /** Prepares a new change block and returns a block builder */ + inline rai::change_block_builder & change () + { + change_builder.make_block (); + return change_builder; + } + + /** Prepares a new send block and returns a block builder */ + inline rai::send_block_builder & send () + { + send_builder.make_block (); + return send_builder; + } + + /** Prepares a new receive block and returns a block builder */ + inline rai::receive_block_builder & receive () + { + receive_builder.make_block (); + return receive_builder; + } + +private: + rai::state_block_builder state_builder; + rai::open_block_builder open_builder; + rai::change_block_builder change_builder; + rai::send_block_builder send_builder; + rai::receive_block_builder receive_builder; +}; +} diff --git a/rai/lib/blocks.hpp b/rai/lib/blocks.hpp index ab5374b4..e4dbb125 100644 --- a/rai/lib/blocks.hpp +++ b/rai/lib/blocks.hpp @@ -73,6 +73,7 @@ public: class send_hashables { public: + send_hashables () = default; send_hashables (rai::account const &, rai::block_hash const &, rai::amount const &); send_hashables (bool &, rai::stream &); send_hashables (bool &, boost::property_tree::ptree const &); @@ -84,6 +85,7 @@ public: class send_block : public rai::block { public: + send_block () = default; send_block (rai::block_hash const &, rai::account const &, rai::amount const &, rai::raw_key const &, rai::public_key const &, uint64_t); send_block (bool &, rai::stream &); send_block (bool &, boost::property_tree::ptree const &); @@ -116,6 +118,7 @@ public: class receive_hashables { public: + receive_hashables () = default; receive_hashables (rai::block_hash const &, rai::block_hash const &); receive_hashables (bool &, rai::stream &); receive_hashables (bool &, boost::property_tree::ptree const &); @@ -126,6 +129,7 @@ public: class receive_block : public rai::block { public: + receive_block () = default; receive_block (rai::block_hash const &, rai::block_hash const &, rai::raw_key const &, rai::public_key const &, uint64_t); receive_block (bool &, rai::stream &); receive_block (bool &, boost::property_tree::ptree const &); @@ -158,6 +162,7 @@ public: class open_hashables { public: + open_hashables () = default; open_hashables (rai::block_hash const &, rai::account const &, rai::account const &); open_hashables (bool &, rai::stream &); open_hashables (bool &, boost::property_tree::ptree const &); @@ -169,6 +174,7 @@ public: class open_block : public rai::block { public: + open_block () = default; open_block (rai::block_hash const &, rai::account const &, rai::account const &, rai::raw_key const &, rai::public_key const &, uint64_t); open_block (rai::block_hash const &, rai::account const &, rai::account const &, std::nullptr_t); open_block (bool &, rai::stream &); @@ -202,6 +208,7 @@ public: class change_hashables { public: + change_hashables () = default; change_hashables (rai::block_hash const &, rai::account const &); change_hashables (bool &, rai::stream &); change_hashables (bool &, boost::property_tree::ptree const &); @@ -212,6 +219,7 @@ public: class change_block : public rai::block { public: + change_block () = default; change_block (rai::block_hash const &, rai::account const &, rai::raw_key const &, rai::public_key const &, uint64_t); change_block (bool &, rai::stream &); change_block (bool &, boost::property_tree::ptree const &); @@ -244,6 +252,7 @@ public: class state_hashables { public: + state_hashables () = default; state_hashables (rai::account const &, rai::block_hash const &, rai::account const &, rai::amount const &, rai::uint256_union const &); state_hashables (bool &, rai::stream &); state_hashables (bool &, boost::property_tree::ptree const &); @@ -266,6 +275,7 @@ public: class state_block : public rai::block { public: + state_block () = default; state_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); state_block (bool &, rai::stream &); state_block (bool &, boost::property_tree::ptree const &); diff --git a/rai/lib/errors.cpp b/rai/lib/errors.cpp index 87caeeef..27ed4702 100644 --- a/rai/lib/errors.cpp +++ b/rai/lib/errors.cpp @@ -6,6 +6,20 @@ std::string nano::error_common_messages::message (int ev) const { case nano::error_common::generic: return "Unknown error"; + case nano::error_common::missing_account: + return "Missing account"; + case nano::error_common::missing_balance: + return "Missing balance"; + case nano::error_common::missing_link: + return "Missing link, source or destination"; + case nano::error_common::missing_previous: + return "Missing previous"; + case nano::error_common::missing_representative: + return "Missing representative"; + case nano::error_common::missing_signature: + return "Missing signature"; + case nano::error_common::missing_work: + return "Missing work"; case nano::error_common::account_exists: return "Account already exists"; case nano::error_common::account_not_found: @@ -14,6 +28,18 @@ std::string nano::error_common_messages::message (int ev) const return "Account not found in wallet"; case nano::error_common::bad_account_number: return "Bad account number"; + case nano::error_common::bad_balance: + return "Bad balance"; + case nano::error_common::bad_link: + return "Bad link value"; + case nano::error_common::bad_previous: + return "Bad previous hash"; + case nano::error_common::bad_representative_number: + return "Bad representative"; + case nano::error_common::bad_source: + return "Bad source"; + case nano::error_common::bad_signature: + return "Bad signature"; case nano::error_common::bad_private_key: return "Bad private key"; case nano::error_common::bad_public_key: diff --git a/rai/lib/errors.hpp b/rai/lib/errors.hpp index ec3d883a..c947dcf5 100644 --- a/rai/lib/errors.hpp +++ b/rai/lib/errors.hpp @@ -32,12 +32,25 @@ enum class error_common account_not_found_wallet, account_exists, bad_account_number, + bad_balance, + bad_link, + bad_previous, + bad_representative_number, + bad_source, + bad_signature, bad_private_key, bad_public_key, bad_seed, bad_threshold, bad_wallet_number, bad_work_format, + missing_account, + missing_balance, + missing_link, + missing_previous, + missing_representative, + missing_signature, + missing_work, invalid_amount, invalid_amount_big, invalid_count, diff --git a/rai/lib/numbers.cpp b/rai/lib/numbers.cpp index 6434d05c..d551add5 100644 --- a/rai/lib/numbers.cpp +++ b/rai/lib/numbers.cpp @@ -310,6 +310,12 @@ rai::uint512_union::uint512_union (rai::uint512_t const & number_a) } } +bool rai::uint512_union::is_zero () const +{ + return qwords[0] == 0 && qwords[1] == 0 && qwords[2] == 0 && qwords[3] == 0 + && qwords[4] == 0 && qwords[5] == 0 && qwords[6] == 0 && qwords[7] == 0; +} + void rai::uint512_union::clear () { bytes.fill (0); diff --git a/rai/lib/numbers.hpp b/rai/lib/numbers.hpp index 06df32d1..0fa38e42 100644 --- a/rai/lib/numbers.hpp +++ b/rai/lib/numbers.hpp @@ -119,6 +119,7 @@ union uint512_union std::array qwords; std::array uint256s; void clear (); + bool is_zero () const; rai::uint512_t number () const; std::string to_string () const; }; diff --git a/rai/secure/common.hpp b/rai/secure/common.hpp index 185c673e..cd7aab06 100644 --- a/rai/secure/common.hpp +++ b/rai/secure/common.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include