diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 6b233f7d55..7427128e1f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1860,6 +1860,18 @@ struct controller_impl { ); } + digest_type get_strong_digest_by_id( const block_id_type& id ) const { + return fork_db.apply( + overloaded{ + [](const fork_database_legacy_t&) -> digest_type { return digest_type{}; }, + [&](const fork_database_if_t& forkdb) -> digest_type { + auto bsp = forkdb.get_block(id); + return bsp ? bsp->strong_digest : digest_type{}; + } + } + ); + } + fc::sha256 calculate_integrity_hash() { fc::sha256::encoder enc; auto hash_writer = std::make_shared(enc); @@ -4463,6 +4475,10 @@ block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try return id; } FC_CAPTURE_AND_RETHROW( (block_num) ) } +digest_type controller::get_strong_digest_by_id( const block_id_type& id ) const { + return my->get_strong_digest_by_id(id); +} + fc::sha256 controller::calculate_integrity_hash() { try { return my->calculate_integrity_hash(); } FC_LOG_AND_RETHROW() } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c67071f9a6..9a5dd18634 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -289,6 +289,8 @@ namespace eosio::chain { std::optional fetch_block_header_by_id( const block_id_type& id )const; // thread-safe block_id_type get_block_id_for_num( uint32_t block_num )const; + // thread-safe + digest_type get_strong_digest_by_id( const block_id_type& id ) const; // used in unittests fc::sha256 calculate_integrity_hash(); void write_snapshot( const snapshot_writer_ptr& snapshot ); diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index f3be8fe97e..c5bf8b7923 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -255,7 +255,7 @@ namespace eosio { namespace testing { // libtester uses 1 as weight of each of the finalizer, sets (2/3 finalizers + 1) // as threshold, and makes all finalizers vote QC - transaction_trace_ptr set_finalizers(const vector& finalizer_names); + std::pair> set_finalizers(const vector& finalizer_names); // Finalizer policy input to set up a test: weights, threshold and local finalizers // which participate voting. @@ -269,7 +269,7 @@ namespace eosio { namespace testing { uint64_t threshold {0}; std::vector local_finalizers; }; - transaction_trace_ptr set_finalizers(const finalizer_policy_input& input); + std::pair> set_finalizers(const finalizer_policy_input& input); void link_authority( account_name account, account_name code, permission_name req, action_name type = {} ); void unlink_authority( account_name account, account_name code, action_name type = {} ); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index a91659170c..d2c943912c 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -1174,7 +1174,7 @@ namespace eosio { namespace testing { } - transaction_trace_ptr base_tester::set_finalizers(const vector& finalizer_names) { + std::pair> base_tester::set_finalizers(const vector& finalizer_names) { auto num_finalizers = finalizer_names.size(); std::vector finalizers_info; finalizers_info.reserve(num_finalizers); @@ -1191,9 +1191,10 @@ namespace eosio { namespace testing { return set_finalizers(policy_input); } - transaction_trace_ptr base_tester::set_finalizers(const finalizer_policy_input& input) { + std::pair> base_tester::set_finalizers(const finalizer_policy_input& input) { chain::bls_pub_priv_key_map_t local_finalizer_keys; fc::variants finalizer_auths; + std::vector priv_keys; for (const auto& f: input.finalizers) { auto [privkey, pubkey, pop] = get_bls_key( f.name ); @@ -1201,6 +1202,7 @@ namespace eosio { namespace testing { // if it is a local finalizer, set up public to private key mapping for voting if( auto it = std::ranges::find_if(input.local_finalizers, [&](const auto& name) { return name == f.name; }); it != input.local_finalizers.end()) { local_finalizer_keys[pubkey.to_string()] = privkey.to_string(); + priv_keys.emplace_back(privkey); }; finalizer_auths.emplace_back( @@ -1217,8 +1219,9 @@ namespace eosio { namespace testing { fin_policy_variant("threshold", input.threshold); fin_policy_variant("finalizers", std::move(finalizer_auths)); - return push_action( config::system_account_name, "setfinalizer"_n, config::system_account_name, - fc::mutable_variant_object()("finalizer_policy", std::move(fin_policy_variant))); + return { push_action( config::system_account_name, "setfinalizer"_n, config::system_account_name, + fc::mutable_variant_object()("finalizer_policy", std::move(fin_policy_variant))), + priv_keys }; } const table_id_object* base_tester::find_table( name code, name scope, name table ) { diff --git a/unittests/finality_test_cluster.cpp b/unittests/finality_test_cluster.cpp index 69d8d8b17d..dff037028e 100644 --- a/unittests/finality_test_cluster.cpp +++ b/unittests/finality_test_cluster.cpp @@ -158,7 +158,11 @@ void finality_test_cluster::setup_node(node_info& node, eosio::chain::account_na .threshold = 2, .local_finalizers = {local_finalizer} }; - node.node.set_finalizers(policy_input); + + auto [trace_ptr, priv_keys] = node.node.set_finalizers(policy_input); + FC_ASSERT( priv_keys.size() == 1, "number of private keys should be 1" ); + node.priv_key = priv_keys[0]; // we only have one private key + auto block = node.node.produce_block(); // this block contains the header extension for the instant finality @@ -178,7 +182,13 @@ eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, s vote.strong = true; } else { vote.strong = false; + + // fetch the strong digest + auto strong_digest = node.node.control->get_strong_digest_by_id(vote.proposal_id); + // convert the strong digest to weak and sign it + vote.sig = node.priv_key.sign(eosio::chain::create_weak_digest(strong_digest)); } + return node0.node.control->process_vote_message( vote ); } diff --git a/unittests/finality_test_cluster.hpp b/unittests/finality_test_cluster.hpp index 8cbcd8ac9c..97ab1aa4f0 100644 --- a/unittests/finality_test_cluster.hpp +++ b/unittests/finality_test_cluster.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" @@ -78,6 +79,7 @@ class finality_test_cluster { eosio::testing::tester node; uint32_t prev_lib_num{0}; std::vector votes; + fc::crypto::blslib::bls_private_key priv_key; }; std::array nodes; diff --git a/unittests/finality_tests.cpp b/unittests/finality_tests.cpp index 64956c861d..6ee03919fb 100644 --- a/unittests/finality_tests.cpp +++ b/unittests/finality_tests.cpp @@ -188,7 +188,6 @@ BOOST_AUTO_TEST_CASE(long_delayed_votes) { try { cluster.process_node1_vote(); // the vote makes a strong QC for the current block, prompting LIB advance on node0 BOOST_REQUIRE(cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); for (auto i = 2; i < 100; ++i) { @@ -221,14 +220,11 @@ BOOST_AUTO_TEST_CASE(lost_votes) { try { // the vote makes a strong QC for the current block, prompting LIB advance on node0 BOOST_REQUIRE(cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); } FC_LOG_AND_RETHROW() } -#warning "Re-enable these tests" -#if 0 BOOST_AUTO_TEST_CASE(one_weak_vote) { try { finality_test_cluster cluster; @@ -236,7 +232,6 @@ BOOST_AUTO_TEST_CASE(one_weak_vote) { try { cluster.produce_and_push_block(); // Change the vote to a weak vote and process it cluster.process_node1_vote(0, finality_test_cluster::vote_mode::weak); - // A weak QC is created and LIB does not advance on node0 BOOST_REQUIRE(!cluster.node0_lib_advancing()); // The strong QC extension for prior block makes LIB advance on node1 @@ -248,13 +243,12 @@ BOOST_AUTO_TEST_CASE(one_weak_vote) { try { // its final_on_strong_qc_block_num is nullopt due to previous QC was weak. // Cannot advance LIB. BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block + // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); cluster.produce_and_push_block(); cluster.process_node1_vote(); BOOST_REQUIRE(cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); cluster.produce_and_push_block(); @@ -262,7 +256,6 @@ BOOST_AUTO_TEST_CASE(one_weak_vote) { try { // the vote makes a strong QC and a higher final_on_strong_qc, // prompting LIB advance on node0 BOOST_REQUIRE(cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(cluster.node1_lib_advancing()); // now a 3 chain has formed. @@ -285,28 +278,26 @@ BOOST_AUTO_TEST_CASE(two_weak_votes) { try { cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); // A weak QC cannot advance LIB on node0 BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block + // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); cluster.produce_and_push_block(); cluster.process_node1_vote(); // the vote makes a strong QC for the current block, prompting LIB advance on node0 BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); cluster.produce_and_push_block(); cluster.process_node1_vote(); // the vote makes a strong QC for the current block, prompting LIB advance on node0 BOOST_REQUIRE(cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // now a 3 chain has formed. BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(interwined_weak_votes) { try { +BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { finality_test_cluster cluster; // Weak vote @@ -324,7 +315,7 @@ BOOST_AUTO_TEST_CASE(interwined_weak_votes) { try { // its final_on_strong_qc_block_num is nullopt due to previous QC was weak. // Cannot advance LIB. BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block + // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // Weak vote @@ -332,7 +323,6 @@ BOOST_AUTO_TEST_CASE(interwined_weak_votes) { try { cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); // A weak QC cannot advance LIB on node0 BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // Strong vote @@ -340,7 +330,7 @@ BOOST_AUTO_TEST_CASE(interwined_weak_votes) { try { cluster.process_node1_vote(); // the vote makes a strong QC for the current block, prompting LIB advance on node0 BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block + // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // Strong vote @@ -374,13 +364,11 @@ BOOST_AUTO_TEST_CASE(weak_delayed_lost_vote) { try { // The vote makes a strong QC, but final_on_strong_qc is null. // Do not advance LIB BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // A lost vote cluster.produce_and_push_block(); BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // The delayed vote arrives @@ -429,13 +417,11 @@ BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { // The vote makes a strong QC, but final_on_strong_qc is null. // LIB did not advance. BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // A lost vote cluster.produce_and_push_block(); BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // the block does not has a QC extension as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); // The delayed vote arrives @@ -455,7 +441,6 @@ BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); } FC_LOG_AND_RETHROW() } -#endif // verify duplicate votes do not affect LIB advancing BOOST_AUTO_TEST_CASE(duplicate_votes) { try {