diff --git a/.cicd/defaults.json b/.cicd/defaults.json index 09326de0b9..9dfc3e4c94 100644 --- a/.cicd/defaults.json +++ b/.cicd/defaults.json @@ -1,9 +1,9 @@ { "cdt":{ - "target":"4", + "target":"hotstuff_integration", "prerelease":false }, "referencecontracts":{ - "ref":"main" + "ref":"instant-finality" } } diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7f42f51044..997ee4ef2b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -236,7 +236,7 @@ jobs: error-log-paths: '["build/etc", "build/var", "build/leap-ignition-wd", "build/TestLogs"]' log-tarball-prefix: ${{matrix.cfg.name}} tests-label: long_running_tests - test-timeout: 1800 + test-timeout: 2700 - name: Export core dumps run: docker run --mount type=bind,source=/var/lib/systemd/coredump,target=/cores alpine sh -c 'tar -C /cores/ -c .' | tar x if: failure() diff --git a/.gitignore b/.gitignore index e8e586a987..bb757e42d0 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,4 @@ node_modules package-lock.json snapshots +!unittests/snapshots diff --git a/CMakeModules/EosioTesterBuild.cmake.in b/CMakeModules/EosioTesterBuild.cmake.in index 91828dc700..97c5f0e99e 100644 --- a/CMakeModules/EosioTesterBuild.cmake.in +++ b/CMakeModules/EosioTesterBuild.cmake.in @@ -110,6 +110,7 @@ target_include_directories(EosioChain INTERFACE @CMAKE_BINARY_DIR@/libraries/chain/include @CMAKE_SOURCE_DIR@/libraries/libfc/include @CMAKE_SOURCE_DIR@/libraries/libfc/libraries/boringssl/boringssl/src/include + @CMAKE_SOURCE_DIR@/libraries/libfc/libraries/bls12-381/include @CMAKE_SOURCE_DIR@/libraries/softfloat/source/include @CMAKE_SOURCE_DIR@/libraries/appbase/include @CMAKE_SOURCE_DIR@/libraries/chainbase/include diff --git a/benchmark/benchmark.cpp b/benchmark/benchmark.cpp index a8ede0f68f..680ede512f 100644 --- a/benchmark/benchmark.cpp +++ b/benchmark/benchmark.cpp @@ -15,7 +15,8 @@ std::map> features { { "key", key_benchmarking }, { "hash", hash_benchmarking }, { "blake2", blake2_benchmarking }, - { "bls", bls_benchmarking } + { "bls", bls_benchmarking }, + { "merkle", merkle_benchmarking } }; // values to control cout format @@ -34,6 +35,10 @@ void set_num_runs(uint32_t runs) { num_runs = runs; } +uint32_t get_num_runs() { + return num_runs; +} + void print_header() { std::cout << std::left << std::setw(name_width) << "function" << std::setw(runs_width) << "runs" @@ -63,12 +68,14 @@ bytes to_bytes(const std::string& source) { return output; }; -void benchmarking(const std::string& name, const std::function& func) { +void benchmarking(const std::string& name, const std::function& func, + std::optional opt_num_runs /* = {} */) { uint64_t total{0}; uint64_t min{std::numeric_limits::max()}; uint64_t max{0}; + uint32_t runs = opt_num_runs ? *opt_num_runs : num_runs; - for (auto i = 0U; i < num_runs; ++i) { + for (auto i = 0U; i < runs; ++i) { auto start_time = std::chrono::high_resolution_clock::now(); func(); auto end_time = std::chrono::high_resolution_clock::now(); @@ -79,7 +86,7 @@ void benchmarking(const std::string& name, const std::function& func) { max = std::max(max, duration); } - print_results(name, num_runs, total, min, max); + print_results(name, runs, total, min, max); } } // benchmark diff --git a/benchmark/benchmark.hpp b/benchmark/benchmark.hpp index 51fe4b6af9..224c924a7b 100644 --- a/benchmark/benchmark.hpp +++ b/benchmark/benchmark.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -11,6 +12,7 @@ namespace eosio::benchmark { using bytes = std::vector; void set_num_runs(uint32_t runs); +uint32_t get_num_runs(); std::map> get_features(); void print_header(); bytes to_bytes(const std::string& source); @@ -21,7 +23,8 @@ void key_benchmarking(); void hash_benchmarking(); void blake2_benchmarking(); void bls_benchmarking(); +void merkle_benchmarking(); -void benchmarking(const std::string& name, const std::function& func); +void benchmarking(const std::string& name, const std::function& func, std::optional num_runs = {}); } // benchmark diff --git a/benchmark/bls.cpp b/benchmark/bls.cpp index b1fcc35e6e..596ce50fd1 100644 --- a/benchmark/bls.cpp +++ b/benchmark/bls.cpp @@ -70,7 +70,8 @@ struct interface_in_benchmark { // build transaction context from the packed transaction timer = std::make_unique(); trx_timer = std::make_unique(*timer); - trx_ctx = std::make_unique(*chain->control.get(), *ptrx, ptrx->id(), std::move(*trx_timer)); + trx_ctx = std::make_unique(*chain->control.get(), *ptrx, ptrx->id(), std::move(*trx_timer), + action_digests_t::store_which_t::legacy); trx_ctx->max_transaction_time_subjective = fc::microseconds::maximum(); trx_ctx->init_for_input_trx( ptrx->get_unprunable_size(), ptrx->get_prunable_size() ); trx_ctx->exec(); // this is required to generate action traces to be used by apply_context constructor diff --git a/benchmark/merkle.cpp b/benchmark/merkle.cpp new file mode 100644 index 0000000000..05106bf74a --- /dev/null +++ b/benchmark/merkle.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +namespace eosio::benchmark { + +using namespace eosio::chain; + +std::vector create_test_digests(size_t n) { + std::vector v; + v.reserve(n); + for (size_t i=0; i digests = create_test_digests(num_digests); + const deque deq { digests.begin(), digests.end() }; + + auto num_str = std::to_string(size_boost); + while(num_str.size() < 4) + num_str.insert(0, 1, ' '); + auto msg_header = "Calc, "s + num_str + ",000 digests, "s; + uint32_t num_runs = std::min(get_num_runs(), std::max(1u, get_num_runs() / size_boost)); + benchmarking(msg_header + "legacy: ", [&]() { calculate_merkle_legacy(deq); }, num_runs); + benchmarking(msg_header + "savanna:", [&]() { calculate_merkle(digests.begin(), digests.end()); }, num_runs); +} + +void benchmark_incr_merkle(uint32_t size_boost) { + using namespace std::string_literals; + const size_t num_digests = size_boost * 1000ull; // don't use exact powers of 2 as it is a special case + + const std::vector digests = create_test_digests(num_digests); + + auto num_str = std::to_string(size_boost); + while(num_str.size() < 4) + num_str.insert(0, 1, ' '); + auto msg_header = "Incr, "s + num_str + ",000 digests, "s; + uint32_t num_runs = std::min(get_num_runs(), std::max(1u, get_num_runs() / size_boost)); + + auto incr = [&](const auto& incr_tree) { + auto work_tree = incr_tree; + for (const auto& d : digests) + work_tree.append(d); + return work_tree.get_root(); + }; + + benchmarking(msg_header + "legacy: ", [&]() { incr(incremental_merkle_tree_legacy()); }, num_runs); + benchmarking(msg_header + "savanna:", [&]() { incr(incremental_merkle_tree()); }, num_runs); +} + +// register benchmarking functions +void merkle_benchmarking() { + benchmark_calc_merkle(1000); // calculate_merkle of very large sequence (1,000,000 digests) + benchmark_calc_merkle(50); // calculate_merkle of large sequence (50,000 digests) + benchmark_calc_merkle(1); // calculate_merkle of small sequence (1000 digests) + std::cout << "\n"; + + benchmark_incr_merkle(100); // incremental_merkle of very large sequence (100,000 digests) + benchmark_incr_merkle(25); // incremental_merkle of large sequence (25,000 digests) + benchmark_incr_merkle(1); // incremental_merkle of small sequence (1000 digests) +} + +} \ No newline at end of file diff --git a/docs/block_production/lifecycle.md b/docs/block_production/lifecycle.md new file mode 100644 index 0000000000..19478ffb2a --- /dev/null +++ b/docs/block_production/lifecycle.md @@ -0,0 +1,27 @@ +The following diagram describes Leap block production, as implemented in `libraries/chain/controller.cpp`: + +```mermaid +flowchart TD + pp[producer_plugin] --> D + A("replay()"):::fun --> B("replay_push_block()"):::fun + B --> E("maybe_switch_forks()"):::fun + C("init()"):::fun ---> E + C --> A + D("push_block()"):::fun ---> E + subgraph G["apply_block()"] + direction TB + start -- "stage = Ø" --> sb + sb("start_block()"):::fun -- "stage = building_block" --> et + et["execute transactions" ] -- "stage = building_block" --> fb("finish_block()"):::fun + fb -- "stage = assembled block" --> cb["add transaction metadata and create completed block"] + cb -- "stage = completed block" --> commit("commit_block() (where we [maybe] add to fork_db and mark valid)"):::fun + + end + B ----> start + E --> G + D --> F("log_irreversible()"):::fun + commit -- "stage = Ø" --> F + F -- "if in irreversible mode" --> G + + classDef fun fill:#f96 +``` \ No newline at end of file diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index ba37c30284..d9d9acf431 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -3,6 +3,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/genesis_state_root_key.cpp.in ${CMAKE file(GLOB HEADERS "include/eosio/chain/*.hpp" "include/eosio/chain/webassembly/*.hpp" + "include/eosio/chain/hotstuff/*.hpp" "${CMAKE_CURRENT_BINARY_DIR}/include/eosio/chain/core_symbol.hpp" ) if((APPLE AND UNIX) OR (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")) @@ -78,18 +79,26 @@ set(CHAIN_WEBASSEMBLY_SOURCES webassembly/transaction.cpp ) +set(CHAIN_HOTSTUFF_SOURCES + hotstuff/finalizer.cpp + hotstuff/instant_finality_extension.cpp + hotstuff/hotstuff.cpp +) + add_library(eosio_rapidjson INTERFACE) target_include_directories(eosio_rapidjson INTERFACE ../rapidjson/include) ## SORT .cpp by most likely to change / break compile add_library( eosio_chain - merkle.cpp name.cpp transaction.cpp block.cpp block_header.cpp + block_header_state.cpp + block_state.cpp block_header_state_legacy.cpp block_state_legacy.cpp + finality_core.cpp fork_database.cpp controller.cpp authorization_manager.cpp @@ -119,6 +128,7 @@ add_library( eosio_chain ${CHAIN_EOSVMOC_SOURCES} ${CHAIN_EOSVM_SOURCES} ${CHAIN_WEBASSEMBLY_SOURCES} + ${CHAIN_HOTSTUFF_SOURCES} authority.cpp trace.cpp @@ -171,6 +181,8 @@ if(EOSVMOC_ENABLE_DEVELOPER_OPTIONS) target_compile_definitions(eosio_chain PUBLIC EOSIO_EOS_VM_OC_DEVELOPER) endif() +add_subdirectory(hotstuff/test) + install( TARGETS eosio_chain RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} COMPONENT dev EXCLUDE_FROM_ALL LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} COMPONENT dev EXCLUDE_FROM_ALL diff --git a/libraries/chain/abi_serializer.cpp b/libraries/chain/abi_serializer.cpp index 1cb039fd4a..e11cf9da39 100644 --- a/libraries/chain/abi_serializer.cpp +++ b/libraries/chain/abi_serializer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -633,6 +634,14 @@ namespace eosio { namespace chain { _variant_to_binary(type, var, ds, ctx); } + void impl::abi_to_variant::add_block_header_instant_finality_extension( mutable_variant_object& mvo, const header_extension_multimap& header_exts ) { + if (header_exts.count(instant_finality_extension::extension_id())) { + const auto& if_extension = + std::get(header_exts.lower_bound(instant_finality_extension::extension_id())->second); + mvo("instant_finality_extension", if_extension); + } + } + type_name abi_serializer::get_action_type(name action)const { auto itr = actions.find(action); if( itr != actions.end() ) return itr->second; diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 75d42dbe35..aba88fbc61 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -14,7 +14,7 @@ using boost::container::flat_set; -namespace eosio { namespace chain { +namespace eosio::chain { static inline void print_debug(account_name receiver, const action_trace& ar) { if (!ar.console.empty()) { @@ -184,7 +184,7 @@ void apply_context::exec_one() r.auth_sequence[auth.actor] = next_auth_sequence( auth.actor ); } - trx_context.executed_action_receipt_digests.emplace_back( r.digest() ); + trx_context.executed_action_receipts.compute_and_append_digests_from(trace); finalize_trace( trace, start ); @@ -218,17 +218,17 @@ void apply_context::exec() exec_one(); } - if( _cfa_inline_actions.size() > 0 || _inline_actions.size() > 0 ) { + if( !_cfa_inline_actions.empty() || !_inline_actions.empty() ) { EOS_ASSERT( recurse_depth < control.get_global_properties().configuration.max_inline_action_depth, transaction_exception, "max inline action depth per transaction reached" ); - } - for( uint32_t ordinal : _cfa_inline_actions ) { - trx_context.execute_action( ordinal, recurse_depth + 1 ); - } + for( uint32_t ordinal : _cfa_inline_actions ) { + trx_context.execute_action( ordinal, recurse_depth + 1 ); + } - for( uint32_t ordinal : _inline_actions ) { - trx_context.execute_action( ordinal, recurse_depth + 1 ); + for( uint32_t ordinal : _inline_actions ) { + trx_context.execute_action( ordinal, recurse_depth + 1 ); + } } } /// exec() @@ -1105,4 +1105,4 @@ bool apply_context::should_use_eos_vm_oc()const { } -} } /// eosio::chain +} /// eosio::chain diff --git a/libraries/chain/block.cpp b/libraries/chain/block.cpp index 31885839db..14af857a88 100644 --- a/libraries/chain/block.cpp +++ b/libraries/chain/block.cpp @@ -20,6 +20,11 @@ namespace eosio { namespace chain { } } + void quorum_certificate_extension::reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, "quorum_certificate_extension expects FC to support reflector_init" ); + static_assert( extension_id() == 3, "extension id for quorum_certificate_extension must be 3" ); + } + flat_multimap signed_block::validate_and_extract_extensions()const { using decompose_t = block_extension_types::decompose_t; diff --git a/libraries/chain/block_header.cpp b/libraries/chain/block_header.cpp index eef0f5bee3..b385016325 100644 --- a/libraries/chain/block_header.cpp +++ b/libraries/chain/block_header.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -24,7 +25,7 @@ namespace eosio { namespace chain { return result; } - flat_multimap block_header::validate_and_extract_header_extensions()const { + header_extension_multimap block_header::validate_and_extract_header_extensions()const { using decompose_t = block_header_extension_types::decompose_t; flat_multimap results; @@ -64,4 +65,34 @@ namespace eosio { namespace chain { return results; } + std::optional block_header::extract_header_extension(uint16_t extension_id)const { + using decompose_t = block_header_extension_types::decompose_t; + + for( size_t i = 0; i < header_extensions.size(); ++i ) { + const auto& e = header_extensions[i]; + auto id = e.first; + + if (id != extension_id) + continue; + + block_header_extension ext; + + auto match = decompose_t::extract( id, e.second, ext ); + EOS_ASSERT( match, invalid_block_header_extension, + "Block header extension with id type ${id} is not supported", + ("id", id) + ); + + return ext; + } + + return {}; + } + + bool block_header::contains_header_extension(uint16_t extension_id)const { + return std::any_of(header_extensions.cbegin(), header_extensions.cend(), [&](const auto& p) { + return p.first == extension_id; + }); + } + } } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp new file mode 100644 index 0000000000..7097e567f8 --- /dev/null +++ b/libraries/chain/block_header_state.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include + +namespace eosio::chain { + +// this is a versioning scheme that is separate from protocol features that only +// gets updated if a protocol feature causes a breaking change to light block +// header validation + +// data for finality_digest +struct finality_digest_data_v1 { + uint32_t major_version{light_header_protocol_version_major}; + uint32_t minor_version{light_header_protocol_version_minor}; + uint32_t active_finalizer_policy_generation {0}; + digest_type finality_tree_digest; + digest_type active_finalizer_policy_and_base_digest; +}; + +// compute base_digest explicitly because of pointers involved. +digest_type block_header_state::compute_base_digest() const { + digest_type::encoder enc; + + fc::raw::pack( enc, header ); + fc::raw::pack( enc, core ); + + for (const auto& fp_pair : finalizer_policies) { + fc::raw::pack( enc, fp_pair.first ); + assert(fp_pair.second); + fc::raw::pack( enc, *fp_pair.second ); + } + + assert(active_proposer_policy); + fc::raw::pack( enc, *active_proposer_policy ); + + for (const auto& pp_pair : proposer_policies) { + assert(pp_pair.second); + fc::raw::pack( enc, *pp_pair.second ); + } + + if (activated_protocol_features) { + fc::raw::pack( enc, *activated_protocol_features ); + } + + return enc.result(); +} + +digest_type block_header_state::compute_finality_digest() const { + assert(active_finalizer_policy); + auto active_finalizer_policy_digest = fc::sha256::hash(*active_finalizer_policy); + auto base_digest = compute_base_digest(); + + std::pair active_and_base{ active_finalizer_policy_digest, base_digest }; + auto afp_base_digest = fc::sha256::hash(active_and_base); + + finality_digest_data_v1 finality_digest_data { + .active_finalizer_policy_generation = active_finalizer_policy->generation, + .finality_tree_digest = finality_mroot(), + .active_finalizer_policy_and_base_digest = afp_base_digest + }; + + return fc::sha256::hash(finality_digest_data); +} + +const producer_authority& block_header_state::get_scheduled_producer(block_timestamp_type t) const { + return detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, t); +} + +const vector& block_header_state::get_new_protocol_feature_activations()const { + return detail::get_new_protocol_feature_activations(header_exts); +} + +#warning Add last_proposed_finalizer_policy_generation to snapshot_block_header_state_v3, see header file TODO + +void finish_next(const block_header_state& prev, + block_header_state& next_header_state, + vector new_protocol_feature_activations, + std::shared_ptr new_proposer_policy, + qc_claim_t qc_claim) { + + // activated protocol features + // --------------------------- + if (!new_protocol_feature_activations.empty()) { + next_header_state.activated_protocol_features = std::make_shared( + *prev.activated_protocol_features, std::move(new_protocol_feature_activations)); + } else { + next_header_state.activated_protocol_features = prev.activated_protocol_features; + } + + // proposer policy + // --------------- + next_header_state.active_proposer_policy = prev.active_proposer_policy; + + if(!prev.proposer_policies.empty()) { + auto it = prev.proposer_policies.begin(); + // +1 since this is called after the block is built, this will be the active schedule for the next block + if (it->first.slot <= next_header_state.header.timestamp.slot + 1) { + next_header_state.active_proposer_policy = it->second; + next_header_state.proposer_policies = { ++it, prev.proposer_policies.end() }; + } else { + next_header_state.proposer_policies = prev.proposer_policies; + } + } + + if (new_proposer_policy) { + // called when assembling the block + next_header_state.proposer_policies[new_proposer_policy->active_time] = std::move(new_proposer_policy); + } + + // finalizer policy + // ---------------- + next_header_state.active_finalizer_policy = prev.active_finalizer_policy; + + // finality_core + // ------------- + block_ref parent_block { + .block_id = prev.block_id, + .timestamp = prev.timestamp() + }; + next_header_state.core = prev.core.next(parent_block, qc_claim); + + // Finally update block id from header + // ----------------------------------- + next_header_state.block_id = next_header_state.header.calculate_id(); +} + +block_header_state block_header_state::next(block_header_state_input& input) const { + block_header_state next_header_state; + + // header + // ------ + next_header_state.header = { + .timestamp = input.timestamp, + .producer = input.producer, + .confirmed = 0, + .previous = input.parent_id, + .transaction_mroot = input.transaction_mroot, + .action_mroot = input.finality_mroot_claim, + .schedule_version = block_header::proper_svnn_schedule_version + }; + + // finality extension + // ------------------ + instant_finality_extension new_if_ext {input.most_recent_ancestor_with_qc, + std::move(input.new_finalizer_policy), + input.new_proposer_policy}; + + uint16_t if_ext_id = instant_finality_extension::extension_id(); + emplace_extension(next_header_state.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); + next_header_state.header_exts.emplace(if_ext_id, std::move(new_if_ext)); + + // add protocol_feature_activation extension + // ----------------------------------------- + if (!input.new_protocol_feature_activations.empty()) { + uint16_t ext_id = protocol_feature_activation::extension_id(); + protocol_feature_activation pfa_ext{.protocol_features = input.new_protocol_feature_activations}; + + emplace_extension(next_header_state.header.header_extensions, ext_id, fc::raw::pack(pfa_ext)); + next_header_state.header_exts.emplace(ext_id, std::move(pfa_ext)); + } + + finish_next(*this, next_header_state, std::move(input.new_protocol_feature_activations), std::move(input.new_proposer_policy), input.most_recent_ancestor_with_qc); + + return next_header_state; +} + +/** + * Transitions the current header state into the next header state given the supplied signed block header. + * + * Given a signed block header, generate the expected template based upon the header time, + * then validate that the provided header matches the template. + */ +block_header_state block_header_state::next(const signed_block_header& h, validator_t& validator) const { + auto producer = detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, h.timestamp).producer_name; + + EOS_ASSERT( h.previous == block_id, unlinkable_block_exception, "previous mismatch ${p} != ${id}", ("p", h.previous)("id", block_id) ); + EOS_ASSERT( h.producer == producer, wrong_producer, "wrong producer specified" ); + EOS_ASSERT( !h.new_producers, producer_schedule_exception, "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" ); + + block_header_state next_header_state; + next_header_state.header = static_cast(h); + next_header_state.header_exts = h.validate_and_extract_header_extensions(); + auto& exts = next_header_state.header_exts; + + // retrieve protocol_feature_activation from incoming block header extension + // ------------------------------------------------------------------------- + vector new_protocol_feature_activations; + if( exts.count(protocol_feature_activation::extension_id() > 0) ) { + auto pfa_entry = exts.lower_bound(protocol_feature_activation::extension_id()); + auto& pfa_ext = std::get(pfa_entry->second); + new_protocol_feature_activations = pfa_ext.protocol_features; + validator( timestamp(), activated_protocol_features->protocol_features, new_protocol_feature_activations ); + } + + // retrieve instant_finality_extension data from block header extension + // -------------------------------------------------------------------- + EOS_ASSERT(exts.count(instant_finality_extension::extension_id()) > 0, invalid_block_header_extension, + "Instant Finality Extension is expected to be present in all block headers after switch to IF"); + auto if_entry = exts.lower_bound(instant_finality_extension::extension_id()); + auto& if_ext = std::get(if_entry->second); + + if (h.is_proper_svnn_block()) { + // if there is no Finality Tree Root associated with the block, + // then this needs to validate that h.action_mroot is the empty digest + auto next_core_metadata = core.next_metadata(if_ext.qc_claim); + bool no_finality_tree_associated = core.is_genesis_block_num(next_core_metadata.final_on_strong_qc_block_num); + + EOS_ASSERT(no_finality_tree_associated == h.action_mroot.empty(), block_validate_exception, + "No Finality Tree Root associated with the block, does not match with empty action_mroot: " + "(${n}), action_mroot empty (${e}), final_on_strong_qc_block_num (${f})", + ("n", no_finality_tree_associated)("e", h.action_mroot.empty())("f", next_core_metadata.final_on_strong_qc_block_num)); + }; + + finish_next(*this, next_header_state, std::move(new_protocol_feature_activations), if_ext.new_proposer_policy, if_ext.qc_claim); + + return next_header_state; +} + +} // namespace eosio::chain + +FC_REFLECT( eosio::chain::finality_digest_data_v1, (major_version)(minor_version)(active_finalizer_policy_generation)(finality_tree_digest)(active_finalizer_policy_and_base_digest) ) diff --git a/libraries/chain/block_header_state_legacy.cpp b/libraries/chain/block_header_state_legacy.cpp index 81ad77373b..a47ca2282f 100644 --- a/libraries/chain/block_header_state_legacy.cpp +++ b/libraries/chain/block_header_state_legacy.cpp @@ -1,25 +1,10 @@ #include +#include +#include #include #include -namespace eosio { namespace chain { - - namespace detail { - bool is_builtin_activated( const protocol_feature_activation_set_ptr& pfa, - const protocol_feature_set& pfs, - builtin_protocol_feature_t feature_codename ) - { - auto digest = pfs.get_builtin_digest(feature_codename); - const auto& protocol_features = pfa->protocol_features; - return digest && protocol_features.find(*digest) != protocol_features.end(); - } - } - - producer_authority block_header_state_legacy::get_scheduled_producer( block_timestamp_type t )const { - auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions); - index /= config::producer_repetitions; - return active_schedule.producers[index]; - } +namespace eosio::chain { uint32_t block_header_state_legacy::calc_dpos_last_irreversible( account_name producer_of_next_block )const { vector blocknums; blocknums.reserve( producer_to_last_implied_irb.size() ); @@ -35,8 +20,12 @@ namespace eosio { namespace chain { return blocknums[ index ]; } + const producer_authority& block_header_state_legacy::get_scheduled_producer( block_timestamp_type t ) const { + return detail::get_scheduled_producer(active_schedule.producers, t); + } + pending_block_header_state_legacy block_header_state_legacy::next( block_timestamp_type when, - uint16_t num_prev_blocks_to_confirm )const + uint16_t num_prev_blocks_to_confirm )const { pending_block_header_state_legacy result; @@ -46,7 +35,7 @@ namespace eosio { namespace chain { (when = header.timestamp).slot++; } - auto proauth = get_scheduled_producer(when); + const auto& proauth = get_scheduled_producer(when); auto itr = producer_to_last_produced.find( proauth.producer_name ); if( itr != producer_to_last_produced.end() ) { @@ -167,6 +156,13 @@ namespace eosio { namespace chain { result.producer_to_last_implied_irb[proauth.producer_name] = dpos_proposed_irreversible_blocknum; } + if (header_exts.count(instant_finality_extension::extension_id())) { // transition to savanna has started + const auto& if_extension = + std::get(header_exts.lower_bound(instant_finality_extension::extension_id())->second); + // copy over qc_claim from IF Genesis Block + result.qc_claim = if_extension.qc_claim; + } + return result; } @@ -174,6 +170,7 @@ namespace eosio { namespace chain { const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, const std::optional& new_producers, + std::optional&& new_finalizer_policy, vector&& new_protocol_feature_activations, const protocol_feature_set& pfs )const @@ -192,7 +189,7 @@ namespace eosio { namespace chain { emplace_extension( h.header_extensions, protocol_feature_activation::extension_id(), - fc::raw::pack( protocol_feature_activation{ std::move(new_protocol_feature_activations) } ) + fc::raw::pack( protocol_feature_activation{ .protocol_features=std::move(new_protocol_feature_activations) } ) ); } @@ -218,20 +215,30 @@ namespace eosio { namespace chain { } } + if (new_finalizer_policy) { + assert(new_finalizer_policy->generation == 1); // only allowed to be set once + // set current block_num as qc_claim.last_qc_block_num in the IF extension + qc_claim_t initial_if_claim { .block_num = block_num, + .is_strong_qc = false }; + emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), + fc::raw::pack(instant_finality_extension{ initial_if_claim, std::move(new_finalizer_policy), {} })); + } else if (qc_claim) { + emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), + fc::raw::pack(instant_finality_extension{ *qc_claim, {}, {} })); + } + return h; } block_header_state_legacy pending_block_header_state_legacy::_finish_next( const signed_block_header& h, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator + validator_t& validator )&& { EOS_ASSERT( h.timestamp == timestamp, block_validate_exception, "timestamp mismatch" ); - EOS_ASSERT( h.previous == previous, unlinkable_block_exception, "previous mismatch" ); + EOS_ASSERT( h.previous == previous, unlinkable_block_exception, "previous mismatch ${p} != ${id}", ("p", h.previous)("id", previous) ); EOS_ASSERT( h.confirmed == confirmed, block_validate_exception, "confirmed mismatch" ); EOS_ASSERT( h.producer == producer, wrong_producer, "wrong producer specified" ); EOS_ASSERT( h.schedule_version == active_schedule_version, producer_schedule_exception, "schedule_version in signed block is corrupted" ); @@ -293,14 +300,14 @@ namespace eosio { namespace chain { block_header_state_legacy result( std::move( *static_cast(this) ) ); - result.id = h.calculate_id(); - result.header = h; + result.id = h.calculate_id(); + result.header = h; result.header_exts = std::move(exts); if( maybe_new_producer_schedule ) { result.pending_schedule.schedule = std::move(*maybe_new_producer_schedule); - result.pending_schedule.schedule_hash = std::move(*maybe_new_producer_schedule_hash); + result.pending_schedule.schedule_hash = *maybe_new_producer_schedule_hash; result.pending_schedule.schedule_lib_num = block_number; } else { if( was_pending_promoted ) { @@ -308,7 +315,7 @@ namespace eosio { namespace chain { } else { result.pending_schedule.schedule = std::move( prev_pending_schedule.schedule ); } - result.pending_schedule.schedule_hash = std::move( prev_pending_schedule.schedule_hash ); + result.pending_schedule.schedule_hash = prev_pending_schedule.schedule_hash ; result.pending_schedule.schedule_lib_num = prev_pending_schedule.schedule_lib_num; } @@ -321,9 +328,7 @@ namespace eosio { namespace chain { const signed_block_header& h, vector&& additional_signatures, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, bool skip_validate_signee )&& { @@ -350,9 +355,7 @@ namespace eosio { namespace chain { block_header_state_legacy pending_block_header_state_legacy::finish_next( signed_block_header& h, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, const signer_callback_type& signer )&& { @@ -380,14 +383,12 @@ namespace eosio { namespace chain { */ block_header_state_legacy block_header_state_legacy::next( const signed_block_header& h, - vector&& _additional_signatures, + vector&& additional_signatures, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, bool skip_validate_signee )const { - return next( h.timestamp, h.confirmed ).finish_next( h, std::move(_additional_signatures), pfs, validator, skip_validate_signee ); + return next( h.timestamp, h.confirmed ).finish_next( h, std::move(additional_signatures), pfs, validator, skip_validate_signee ); } digest_type block_header_state_legacy::sig_digest()const { @@ -433,8 +434,8 @@ namespace eosio { namespace chain { std::tie(is_satisfied, relevant_sig_count) = producer_authority::keys_satisfy_and_relevant(keys, valid_block_signing_authority); EOS_ASSERT(relevant_sig_count == keys.size(), wrong_signing_key, - "block signed by unexpected key", - ("signing_keys", keys)("authority", valid_block_signing_authority)); + "block signed by unexpected key: ${signing_keys}, expected: ${authority}. ${c} != ${s}", + ("signing_keys", keys)("authority", valid_block_signing_authority)("c", relevant_sig_count)("s", keys.size())); EOS_ASSERT(is_satisfied, wrong_signing_key, "block signatures do not satisfy the block signing authority", @@ -442,35 +443,50 @@ namespace eosio { namespace chain { } /** - * Reference cannot outlive *this. Assumes header_exts is not mutated after instatiation. + * Reference cannot outlive *this. Assumes header_exts is not mutated after instantiation. */ const vector& block_header_state_legacy::get_new_protocol_feature_activations()const { - static const vector no_activations{}; - - if( header_exts.count(protocol_feature_activation::extension_id()) == 0 ) - return no_activations; + return detail::get_new_protocol_feature_activations(header_exts); + } - return std::get(header_exts.lower_bound(protocol_feature_activation::extension_id())->second).protocol_features; + block_header_state_legacy::block_header_state_legacy( snapshot_detail::snapshot_block_header_state_legacy_v2&& bhs_v2 ) + { + block_num = bhs_v2.block_num; + dpos_proposed_irreversible_blocknum = bhs_v2.dpos_proposed_irreversible_blocknum; + dpos_irreversible_blocknum = bhs_v2.dpos_irreversible_blocknum; + active_schedule = producer_authority_schedule( bhs_v2.active_schedule ); + blockroot_merkle = std::move(bhs_v2.blockroot_merkle); + producer_to_last_produced = std::move(bhs_v2.producer_to_last_produced); + producer_to_last_implied_irb = std::move(bhs_v2.producer_to_last_implied_irb); + valid_block_signing_authority = block_signing_authority_v0{ 1, {{std::move(bhs_v2.block_signing_key), 1}} }; + confirm_count = std::move(bhs_v2.confirm_count); + id = bhs_v2.id; + header = std::move(bhs_v2.header); + pending_schedule.schedule_lib_num = bhs_v2.pending_schedule.schedule_lib_num; + pending_schedule.schedule_hash = bhs_v2.pending_schedule.schedule_hash; + pending_schedule.schedule = producer_authority_schedule( bhs_v2.pending_schedule.schedule ); + activated_protocol_features = std::move(bhs_v2.activated_protocol_features); } - block_header_state_legacy::block_header_state_legacy( legacy::snapshot_block_header_state_v2&& snapshot ) + block_header_state_legacy::block_header_state_legacy( snapshot_detail::snapshot_block_header_state_legacy_v3&& bhs_v3 ) { - block_num = snapshot.block_num; - dpos_proposed_irreversible_blocknum = snapshot.dpos_proposed_irreversible_blocknum; - dpos_irreversible_blocknum = snapshot.dpos_irreversible_blocknum; - active_schedule = producer_authority_schedule( snapshot.active_schedule ); - blockroot_merkle = std::move(snapshot.blockroot_merkle); - producer_to_last_produced = std::move(snapshot.producer_to_last_produced); - producer_to_last_implied_irb = std::move(snapshot.producer_to_last_implied_irb); - valid_block_signing_authority = block_signing_authority_v0{ 1, {{std::move(snapshot.block_signing_key), 1}} }; - confirm_count = std::move(snapshot.confirm_count); - id = std::move(snapshot.id); - header = std::move(snapshot.header); - pending_schedule.schedule_lib_num = snapshot.pending_schedule.schedule_lib_num; - pending_schedule.schedule_hash = std::move(snapshot.pending_schedule.schedule_hash); - pending_schedule.schedule = producer_authority_schedule( snapshot.pending_schedule.schedule ); - activated_protocol_features = std::move(snapshot.activated_protocol_features); + block_num = bhs_v3.block_num; + dpos_proposed_irreversible_blocknum = bhs_v3.dpos_proposed_irreversible_blocknum; + dpos_irreversible_blocknum = bhs_v3.dpos_irreversible_blocknum; + active_schedule = std::move(bhs_v3.active_schedule); + blockroot_merkle = std::move(bhs_v3.blockroot_merkle); + producer_to_last_produced = std::move(bhs_v3.producer_to_last_produced); + producer_to_last_implied_irb = std::move(bhs_v3.producer_to_last_implied_irb); + valid_block_signing_authority = std::move(bhs_v3.valid_block_signing_authority); + confirm_count = std::move(bhs_v3.confirm_count); + id = bhs_v3.id; + header = std::move(bhs_v3.header); + pending_schedule = std::move(bhs_v3.pending_schedule); + activated_protocol_features = std::move(bhs_v3.activated_protocol_features); + additional_signatures = std::move(bhs_v3.additional_signatures); + + header_exts = header.validate_and_extract_header_extensions(); } -} } /// namespace eosio::chain +} /// namespace eosio::chain diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp new file mode 100644 index 0000000000..4735e81de4 --- /dev/null +++ b/libraries/chain/block_state.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include + +#include + +namespace eosio::chain { + +block_state::block_state(const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs, + const validator_t& validator, bool skip_validate_signee) + : block_header_state(prev.next(*b, validator)) + , block(std::move(b)) + , strong_digest(compute_finality_digest()) + , weak_digest(create_weak_digest(strong_digest)) + , pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold, prev.active_finalizer_policy->max_weak_sum_before_weak_final()) +{ + // ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here + if( !skip_validate_signee ) { + auto sigs = detail::extract_additional_signatures(block); + const auto& valid_block_signing_authority = prev.get_scheduled_producer(timestamp()).authority; + verify_signee(sigs, valid_block_signing_authority); + } +} + +block_state::block_state(const block_header_state& bhs, + deque&& trx_metas, + deque&& trx_receipts, + const std::optional& valid, + const std::optional& qc, + const signer_callback_type& signer, + const block_signing_authority& valid_block_signing_authority, + const digest_type& action_mroot) + : block_header_state(bhs) + , block(std::make_shared(signed_block_header{bhs.header})) + , strong_digest(compute_finality_digest()) + , weak_digest(create_weak_digest(strong_digest)) + , pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold, bhs.active_finalizer_policy->max_weak_sum_before_weak_final()) + , valid(valid) + , pub_keys_recovered(true) // called by produce_block so signature recovery of trxs must have been done + , cached_trxs(std::move(trx_metas)) + , action_mroot(action_mroot) +{ + block->transactions = std::move(trx_receipts); + + if( qc ) { + dlog("integrate qc ${qc} into block ${bn} ${id}", ("qc", qc->to_qc_claim())("bn", block_num())("id", id())); + emplace_extension(block->block_extensions, quorum_certificate_extension::extension_id(), fc::raw::pack( *qc )); + } + + sign(signer, valid_block_signing_authority); +} + +// Used for transition from dpos to Savanna. +block_state_ptr block_state::create_if_genesis_block(const block_state_legacy& bsp) { + assert(bsp.action_mroot_savanna); + + auto result_ptr = std::make_shared(); + auto &result = *result_ptr; + + // set block_header_state data ---- + result.block_id = bsp.id(); + result.header = bsp.header; + result.activated_protocol_features = bsp.activated_protocol_features; + result.core = finality_core::create_core_for_genesis_block(bsp.block_num()); + + assert(bsp.block->contains_header_extension(instant_finality_extension::extension_id())); // required by transition mechanism + instant_finality_extension if_ext = bsp.block->extract_header_extension(); + assert(if_ext.new_finalizer_policy); // required by transition mechanism + result.active_finalizer_policy = std::make_shared(*if_ext.new_finalizer_policy); + result.active_proposer_policy = std::make_shared(); + result.active_proposer_policy->active_time = bsp.timestamp(); + result.active_proposer_policy->proposer_schedule = bsp.active_schedule; + result.proposer_policies = {}; // none pending at IF genesis block + result.finalizer_policies = {}; // none pending at IF genesis block + result.header_exts = bsp.header_exts; + + // set block_state data ---- + result.block = bsp.block; + result.strong_digest = result.compute_finality_digest(); // all block_header_state data populated in result at this point + result.weak_digest = create_weak_digest(result.strong_digest); + + // TODO: https://github.com/AntelopeIO/leap/issues/2057 + // TODO: Do not aggregate votes on blocks created from block_state_legacy. This can be removed when #2057 complete. + result.pending_qc = pending_quorum_certificate{result.active_finalizer_policy->finalizers.size(), result.active_finalizer_policy->threshold, result.active_finalizer_policy->max_weak_sum_before_weak_final()}; + + // build leaf_node and validation_tree + valid_t::finality_leaf_node_t leaf_node { + .block_num = bsp.block_num(), + .finality_digest = result.strong_digest, + .action_mroot = *bsp.action_mroot_savanna + }; + // construct valid structure + incremental_merkle_tree validation_tree; + validation_tree.append(fc::sha256::hash(leaf_node)); + result.valid = valid_t { + .validation_tree = validation_tree, + .validation_mroots = { validation_tree.get_root() } + }; + + result.validated.store(bsp.is_valid()); + result.pub_keys_recovered = bsp._pub_keys_recovered; + result.cached_trxs = bsp._cached_trxs; + result.action_mroot = *bsp.action_mroot_savanna; + result.base_digest = {}; // calculated on demand in get_finality_data() + + return result_ptr; +} + +block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) + : block_header_state { + .block_id = sbs.block_id, + .header = std::move(sbs.header), + .activated_protocol_features = std::move(sbs.activated_protocol_features), + .core = std::move(sbs.core), + .active_finalizer_policy = std::move(sbs.active_finalizer_policy), + .active_proposer_policy = std::move(sbs.active_proposer_policy), + .proposer_policies = std::move(sbs.proposer_policies), + .finalizer_policies = std::move(sbs.finalizer_policies) + } + , strong_digest(compute_finality_digest()) + , weak_digest(create_weak_digest(strong_digest)) + , pending_qc(active_finalizer_policy->finalizers.size(), active_finalizer_policy->threshold, + active_finalizer_policy->max_weak_sum_before_weak_final()) // just in case we receive votes + , valid(std::move(sbs.valid)) +{ + header_exts = header.validate_and_extract_header_extensions(); +} + +deque block_state::extract_trxs_metas() { + pub_keys_recovered = false; + auto result = std::move(cached_trxs); + cached_trxs.clear(); + return result; +} + +void block_state::set_trxs_metas( deque&& trxs_metas, bool keys_recovered ) { + pub_keys_recovered = keys_recovered; + cached_trxs = std::move( trxs_metas ); +} + +// Called from net threads +vote_status block_state::aggregate_vote(const vote_message& vote) { + const auto& finalizers = active_finalizer_policy->finalizers; + auto it = std::find_if(finalizers.begin(), + finalizers.end(), + [&](const auto& finalizer) { return finalizer.public_key == vote.finalizer_key; }); + + if (it != finalizers.end()) { + auto index = std::distance(finalizers.begin(), it); + auto digest = vote.strong ? strong_digest.to_uint8_span() : std::span(weak_digest); + return pending_qc.add_vote(block_num(), + vote.strong, + digest, + index, + vote.finalizer_key, + vote.sig, + finalizers[index].weight); + } else { + wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) ); + return vote_status::unknown_public_key; + } +} + +bool block_state::has_voted(const bls_public_key& key) const { + const auto& finalizers = active_finalizer_policy->finalizers; + auto it = std::find_if(finalizers.begin(), + finalizers.end(), + [&](const auto& finalizer) { return finalizer.public_key == key; }); + + if (it != finalizers.end()) { + auto index = std::distance(finalizers.begin(), it); + return pending_qc.has_voted(index); + } + return false; +} + +// Called from net threads +void block_state::verify_qc(const valid_quorum_certificate& qc) const { + const auto& finalizers = active_finalizer_policy->finalizers; + auto num_finalizers = finalizers.size(); + + // utility to accumulate voted weights + auto weights = [&] ( const hs_bitset& votes_bitset ) -> uint64_t { + uint64_t sum = 0; + auto n = std::min(num_finalizers, votes_bitset.size()); + for (auto i = 0u; i < n; ++i) { + if( votes_bitset[i] ) { // ith finalizer voted + sum += finalizers[i].weight; + } + } + return sum; + }; + + // compute strong and weak accumulated weights + auto strong_weights = qc._strong_votes ? weights( *qc._strong_votes ) : 0; + auto weak_weights = qc._weak_votes ? weights( *qc._weak_votes ) : 0; + + // verfify quorum is met + if( qc.is_strong() ) { + EOS_ASSERT( strong_weights >= active_finalizer_policy->threshold, + invalid_qc_claim, + "strong quorum is not met, strong_weights: ${s}, threshold: ${t}", + ("s", strong_weights)("t", active_finalizer_policy->threshold) ); + } else { + EOS_ASSERT( strong_weights + weak_weights >= active_finalizer_policy->threshold, + invalid_qc_claim, + "weak quorum is not met, strong_weights: ${s}, weak_weights: ${w}, threshold: ${t}", + ("s", strong_weights)("w", weak_weights)("t", active_finalizer_policy->threshold) ); + } + + // no reason to use bls_public_key wrapper + std::vector pubkeys; + pubkeys.reserve(2); + std::vector> digests; + digests.reserve(2); + + // utility to aggregate public keys for verification + auto aggregate_pubkeys = [&](const auto& votes_bitset) -> bls12_381::g1 { + const auto n = std::min(num_finalizers, votes_bitset.size()); + std::vector pubkeys_to_aggregate; + pubkeys_to_aggregate.reserve(n); + for(auto i = 0u; i < n; ++i) { + if (votes_bitset[i]) { // ith finalizer voted + pubkeys_to_aggregate.emplace_back(finalizers[i].public_key.jacobian_montgomery_le()); + } + } + + return bls12_381::aggregate_public_keys(pubkeys_to_aggregate); + }; + + // aggregate public keys and digests for strong and weak votes + if( qc._strong_votes ) { + pubkeys.emplace_back(aggregate_pubkeys(*qc._strong_votes)); + digests.emplace_back(std::vector{strong_digest.data(), strong_digest.data() + strong_digest.data_size()}); + } + + if( qc._weak_votes ) { + pubkeys.emplace_back(aggregate_pubkeys(*qc._weak_votes)); + digests.emplace_back(std::vector{weak_digest.begin(), weak_digest.end()}); + } + + // validate aggregated signature + EOS_ASSERT( bls12_381::aggregate_verify(pubkeys, digests, qc._sig.jacobian_montgomery_le()), + invalid_qc_claim, "signature validation failed" ); +} + +valid_t block_state::new_valid(const block_header_state& next_bhs, const digest_type& action_mroot, const digest_type& strong_digest) const { + assert(valid); + assert(next_bhs.core.last_final_block_num() >= core.last_final_block_num()); + + // Copy parent's validation_tree and validation_mroots. + auto start = next_bhs.core.last_final_block_num() - core.last_final_block_num(); + valid_t next_valid { + .validation_tree = valid->validation_tree, + // Trim roots from the front end, up to block number `next_bhs.core.last_final_block_num()` + .validation_mroots = { valid->validation_mroots.cbegin() + start, valid->validation_mroots.cend() } + }; + + // construct block's finality leaf node. + valid_t::finality_leaf_node_t leaf_node{ + .block_num = next_bhs.block_num(), + .finality_digest = strong_digest, + .action_mroot = action_mroot + }; + auto leaf_node_digest = fc::sha256::hash(leaf_node); + + // append new finality leaf node digest to validation_tree + next_valid.validation_tree.append(leaf_node_digest); + + // append the root of the new Validation Tree to validation_mroots. + next_valid.validation_mroots.emplace_back(next_valid.validation_tree.get_root()); + + // post condition of validation_mroots + assert(next_valid.validation_mroots.size() == (next_bhs.block_num() - next_bhs.core.last_final_block_num() + 1)); + + return next_valid; +} + +digest_type block_state::get_validation_mroot(block_num_type target_block_num) const { + if (!valid) { + return digest_type{}; + } + + assert(valid->validation_mroots.size() > 0); + assert(core.last_final_block_num() <= target_block_num && + target_block_num < core.last_final_block_num() + valid->validation_mroots.size()); + assert(target_block_num - core.last_final_block_num() < valid->validation_mroots.size()); + + return valid->validation_mroots[target_block_num - core.last_final_block_num()]; +} + +digest_type block_state::get_finality_mroot_claim(const qc_claim_t& qc_claim) const { + auto next_core_metadata = core.next_metadata(qc_claim); + + // For proper IF blocks that do not have an associated Finality Tree defined + if (core.is_genesis_block_num(next_core_metadata.final_on_strong_qc_block_num)) { + return digest_type{}; + } + + return get_validation_mroot(next_core_metadata.final_on_strong_qc_block_num); +} + +finality_data_t block_state::get_finality_data() { + if (!base_digest) { + base_digest = compute_base_digest(); // cache it + } + return { + // other fields take the default values set by finality_data_t definition + .active_finalizer_policy_generation = active_finalizer_policy->generation, + .action_mroot = action_mroot, + .base_digest = *base_digest + }; +} + +void inject_additional_signatures( signed_block& b, const std::vector& additional_signatures) +{ + if (!additional_signatures.empty()) { + // as an optimization we don't copy this out into the legitimate extension structure as it serializes + // the same way as the vector of signatures + static_assert(fc::reflector::total_member_count == 1); + static_assert(std::is_same_v>); + + emplace_extension(b.block_extensions, additional_block_signatures_extension::extension_id(), fc::raw::pack( additional_signatures )); + } +} + +void block_state::sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority ) { + auto sigs = signer( block_id ); + + EOS_ASSERT(!sigs.empty(), no_block_signatures, "Signer returned no signatures"); + block->producer_signature = sigs.back(); // last is producer signature, rest are additional signatures to inject in the block extension + sigs.pop_back(); + + verify_signee(sigs, valid_block_signing_authority); + inject_additional_signatures(*block, sigs); +} + +void block_state::verify_signee(const std::vector& additional_signatures, const block_signing_authority& valid_block_signing_authority) const { + auto num_keys_in_authority = std::visit([](const auto &a){ return a.keys.size(); }, valid_block_signing_authority); + EOS_ASSERT(1 + additional_signatures.size() <= num_keys_in_authority, wrong_signing_key, + "number of block signatures (${num_block_signatures}) exceeds number of keys (${num_keys}) in block signing authority: ${authority}", + ("num_block_signatures", 1 + additional_signatures.size()) + ("num_keys", num_keys_in_authority) + ("authority", valid_block_signing_authority) + ); + + std::set keys; + keys.emplace(fc::crypto::public_key( block->producer_signature, block_id, true )); + + for (const auto& s: additional_signatures) { + auto res = keys.emplace(s, block_id, true); + EOS_ASSERT(res.second, wrong_signing_key, "block signed by same key twice: ${key}", ("key", *res.first)); + } + + bool is_satisfied = false; + size_t relevant_sig_count = 0; + + std::tie(is_satisfied, relevant_sig_count) = producer_authority::keys_satisfy_and_relevant(keys, valid_block_signing_authority); + + EOS_ASSERT(relevant_sig_count == keys.size(), wrong_signing_key, + "block signed by unexpected key: ${signing_keys}, expected: ${authority}. ${c} != ${s}", + ("signing_keys", keys)("authority", valid_block_signing_authority)("c", relevant_sig_count)("s", keys.size())); + + EOS_ASSERT(is_satisfied, wrong_signing_key, + "block signatures ${signing_keys} do not satisfy the block signing authority: ${authority}", + ("signing_keys", keys)("authority", valid_block_signing_authority)); +} + +} /// eosio::chain diff --git a/libraries/chain/block_state_legacy.cpp b/libraries/chain/block_state_legacy.cpp index 532e89d16c..28a7735ca6 100644 --- a/libraries/chain/block_state_legacy.cpp +++ b/libraries/chain/block_state_legacy.cpp @@ -1,35 +1,14 @@ #include +#include #include +#include -namespace eosio { namespace chain { + +namespace eosio::chain { namespace { constexpr auto additional_sigs_eid = additional_block_signatures_extension::extension_id(); - /** - * Given a complete signed block, extract the validated additional signatures if present; - * - * @param b complete signed block - * @param pfs protocol feature set for digest access - * @param pfa activated protocol feature set to determine if extensions are allowed - * @return the list of additional signatures - * @throws if additional signatures are present before being supported by protocol feature activations - */ - vector extract_additional_signatures( const signed_block_ptr& b, - const protocol_feature_set& pfs, - const protocol_feature_activation_set_ptr& pfa ) - { - auto exts = b->validate_and_extract_extensions(); - - if ( exts.count(additional_sigs_eid) > 0 ) { - auto& additional_sigs = std::get(exts.lower_bound(additional_sigs_eid)->second); - - return std::move(additional_sigs.signatures); - } - - return {}; - } - /** * Given a pending block header state, wrap the promotion to a block header state such that additional signatures * can be allowed based on activations *prior* to the promoted block and properly injected into the signed block @@ -77,28 +56,32 @@ namespace eosio { namespace chain { block_state_legacy::block_state_legacy( const block_header_state_legacy& prev, signed_block_ptr b, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, - bool skip_validate_signee + const validator_t& validator, + bool skip_validate_signee ) - :block_header_state_legacy( prev.next( *b, extract_additional_signatures(b, pfs, prev.activated_protocol_features), pfs, validator, skip_validate_signee ) ) - ,block( std::move(b) ) + :block_header_state_legacy( prev.next( *b, detail::extract_additional_signatures(b), pfs, validator, skip_validate_signee ) ) + ,block( std::move(b) ) {} block_state_legacy::block_state_legacy( pending_block_header_state_legacy&& cur, signed_block_ptr&& b, deque&& trx_metas, + const std::optional& action_receipt_digests_savanna, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + const validator_t& validator, const signer_callback_type& signer ) :block_header_state_legacy( inject_additional_signatures( std::move(cur), *b, pfs, validator, signer ) ) ,block( std::move(b) ) ,_pub_keys_recovered( true ) // called by produce_block so signature recovery of trxs must have been done ,_cached_trxs( std::move(trx_metas) ) + ,action_mroot_savanna( action_receipt_digests_savanna ? std::optional(calculate_merkle(*action_receipt_digests_savanna)) : std::nullopt ) {} -} } /// eosio::chain + block_state_legacy::block_state_legacy(snapshot_detail::snapshot_block_state_legacy_v7&& sbs) + : block_header_state_legacy(std::move(static_cast(sbs))) + // , valid(std::move(sbs.valid) // [snapshot todo] + { + } + +} /// eosio::chain diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b79210526f..613a7fb16b 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -23,9 +23,14 @@ #include #include #include +#include #include #include +#include #include +#include +#include +#include #include #include @@ -35,11 +40,12 @@ #include #include +#include #include #include #include -namespace eosio { namespace chain { +namespace eosio::chain { using resource_limits::resource_limits_manager; @@ -71,7 +77,7 @@ class maybe_session { public: maybe_session() = default; - maybe_session( maybe_session&& other) + maybe_session( maybe_session&& other) noexcept :_session(std::move(other._session)) { } @@ -97,7 +103,7 @@ class maybe_session { _session->push(); } - maybe_session& operator = ( maybe_session&& mv ) { + maybe_session& operator=( maybe_session&& mv ) noexcept { if (mv._session) { _session.emplace(std::move(*mv._session)); mv._session.reset(); @@ -112,104 +118,767 @@ class maybe_session { std::optional _session; }; -struct building_block { - building_block( const block_header_state_legacy& prev, - block_timestamp_type when, - uint16_t num_prev_blocks_to_confirm, - const vector& new_protocol_feature_activations ) - :_pending_block_header_state_legacy( prev.next( when, num_prev_blocks_to_confirm ) ) - ,_new_protocol_feature_activations( new_protocol_feature_activations ) - ,_trx_mroot_or_receipt_digests( digests_t{} ) - {} +// apply methods of block_handle defined here as access to internal block_handle restricted to controller +template +R apply(const block_handle& bh, F&& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](const block_state_legacy_ptr& head) { std::forward(f)(head); }, + [&](const block_state_ptr& head) { std::forward(f)(head); } + }, bh.internal()); + else + return std::visit(overloaded{[&](const block_state_legacy_ptr& head) -> R { return std::forward(f)(head); }, + [&](const block_state_ptr& head) -> R { return std::forward(f)(head); } + }, bh.internal()); +} + +// apply savanna block_state +template +R apply_s(const block_handle& bh, F&& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](const block_state_legacy_ptr&) {}, + [&](const block_state_ptr& head) { std::forward(f)(head); }}, bh.internal()); + else + return std::visit(overloaded{[&](const block_state_legacy_ptr&) -> R { return {}; }, + [&](const block_state_ptr& head) -> R { return std::forward(f)(head); }}, bh.internal()); +} + +// apply legancy block_state_legacy +template +R apply_l(const block_handle& bh, F&& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](const block_state_legacy_ptr& head) { std::forward(f)(head); }, + [&](const block_state_ptr&) {}}, bh.internal()); + else + return std::visit(overloaded{[&](const block_state_legacy_ptr& head) -> R { return std::forward(f)(head); }, + [&](const block_state_ptr&) -> R { return {}; }}, bh.internal()); +} + +struct completed_block { + block_handle bsp; + + bool is_legacy() const { return std::holds_alternative(bsp.internal()); } + + deque extract_trx_metas() { + return apply>(bsp, [](auto& bsp) { return bsp->extract_trxs_metas(); }); + } + + const flat_set& get_activated_protocol_features() const { + return apply&>(bsp, [](const auto& bsp) -> const flat_set& { + return bsp->get_activated_protocol_features()->protocol_features; + }); + } + + const block_id_type& id() const { return bsp.id(); } + uint32_t block_num() const { return bsp.block_num(); } + block_timestamp_type timestamp() const { return bsp.block_time(); } + account_name producer() const { return bsp.producer(); } + + const producer_authority_schedule& active_producers() const { + return apply(bsp, [](const auto& bsp) -> const producer_authority_schedule& { + return bsp->active_schedule_auth(); + }); + } + + const producer_authority_schedule* next_producers() const { + return apply(bsp, + overloaded{[](const block_state_legacy_ptr& bsp) -> const producer_authority_schedule* { + return bsp->pending_schedule_auth(); + }, + [](const block_state_ptr& bsp) -> const producer_authority_schedule* { + return bsp->proposer_policies.empty() + ? nullptr + : &bsp->proposer_policies.begin()->second->proposer_schedule; + } + }); + } + + const producer_authority_schedule* pending_producers_legacy() const { + return apply(bsp, + overloaded{[](const block_state_legacy_ptr& bsp) -> const producer_authority_schedule* { + return &bsp->pending_schedule.schedule; + }, + [](const block_state_ptr&) -> const producer_authority_schedule* { return nullptr; } + }); + } - pending_block_header_state_legacy _pending_block_header_state_legacy; - std::optional _new_pending_producer_schedule; - vector _new_protocol_feature_activations; - size_t _num_new_protocol_features_that_have_activated = 0; - deque _pending_trx_metas; - deque _pending_trx_receipts; // boost deque in 1.71 with 1024 elements performs better - std::variant _trx_mroot_or_receipt_digests; - digests_t _action_receipt_digests; + bool is_protocol_feature_activated(const digest_type& digest) const { + const auto& activated_features = get_activated_protocol_features(); + return (activated_features.find(digest) != activated_features.end()); + } + + const block_signing_authority& pending_block_signing_authority() const { + // this should never be called on completed_block because `controller::is_building_block()` returns false + assert(0); + static block_signing_authority bsa; return bsa; // just so it builds + } }; struct assembled_block { - block_id_type _id; - pending_block_header_state_legacy _pending_block_header_state_legacy; - deque _trx_metas; - signed_block_ptr _unsigned_block; + // -------------------------------------------------------------------------------- + struct assembled_block_legacy { + block_id_type id; + pending_block_header_state_legacy pending_block_header_state; + deque trx_metas; + signed_block_ptr unsigned_block; + + // if the unsigned_block pre-dates block-signing authorities this may be present. + std::optional new_producer_authority_cache; + + // Passed to completed_block, to be used by Legacy to Savanna transisition + std::optional action_receipt_digests_savanna; + }; - // if the _unsigned_block pre-dates block-signing authorities this may be present. - std::optional _new_producer_authority_cache; -}; + // -------------------------------------------------------------------------------- + struct assembled_block_if { + producer_authority active_producer_authority; + block_header_state bhs; + deque trx_metas; // Comes from building_block::pending_trx_metas + // Carried over to put into block_state (optimization for fork reorgs) + deque trx_receipts; // Comes from building_block::pending_trx_receipts + std::optional valid; // Comes from assemble_block + std::optional qc; // QC to add as block extension to new block + digest_type action_mroot; + + block_header_state& get_bhs() { return bhs; } + }; -struct completed_block { - block_state_legacy_ptr _block_state; + std::variant v; + + bool is_legacy() const { return std::holds_alternative(v); } + + template + R apply_legacy(F&& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](assembled_block_legacy& ab) { std::forward(f)(ab); }, + [&](assembled_block_if& ab) {}}, v); + else + return std::visit(overloaded{[&](assembled_block_legacy& ab) -> R { return std::forward(f)(ab); }, + [&](assembled_block_if& ab) -> R { return {}; }}, v); + } + + deque extract_trx_metas() { + return std::visit([](auto& ab) { return std::move(ab.trx_metas); }, v); + } + + bool is_protocol_feature_activated(const digest_type& digest) const { + // Calling is_protocol_feature_activated during the assembled_block stage is not efficient. + // We should avoid doing it. + // In fact for now it isn't even implemented. + EOS_THROW( misc_exception, + "checking if protocol feature is activated in the assembled_block stage is not yet supported" ); + // TODO: implement this + } + + const block_id_type& id() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) -> const block_id_type& { return ab.id; }, + [](const assembled_block_if& ab) -> const block_id_type& { return ab.bhs.id(); }}, + v); + } + + block_timestamp_type timestamp() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) { return ab.pending_block_header_state.timestamp; }, + [](const assembled_block_if& ab) { return ab.bhs.header.timestamp; }}, + v); + } + + uint32_t block_num() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) { return ab.pending_block_header_state.block_num; }, + [](const assembled_block_if& ab) { return ab.bhs.block_num(); }}, + v); + } + + account_name producer() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) { return ab.pending_block_header_state.producer; }, + [](const assembled_block_if& ab) { return ab.active_producer_authority.producer_name; }}, + v); + } + + const block_header& header() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) -> const block_header& { return *ab.unsigned_block; }, + [](const assembled_block_if& ab) -> const block_header& { return ab.bhs.header; }}, + v); + } + + const producer_authority_schedule& active_producers() const { + return std::visit(overloaded{[](const assembled_block_legacy& ab) -> const producer_authority_schedule& { + return ab.pending_block_header_state.active_schedule; + }, + [](const assembled_block_if& ab) -> const producer_authority_schedule& { + return ab.bhs.active_schedule_auth(); + }}, + v); + } + + std::optional get_action_receipt_digests_savanna() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) -> std::optional { return ab.action_receipt_digests_savanna; }, + [](const assembled_block_if& ab) -> std::optional { return {}; }}, + v); + } + + const producer_authority_schedule* next_producers() const { + return std::visit(overloaded{[](const assembled_block_legacy& ab) -> const producer_authority_schedule* { + return ab.new_producer_authority_cache.has_value() + ? &ab.new_producer_authority_cache.value() + : nullptr; + }, + [](const assembled_block_if& ab) -> const producer_authority_schedule* { + return ab.bhs.proposer_policies.empty() + ? nullptr + : &ab.bhs.proposer_policies.begin()->second->proposer_schedule; + }}, + v); + } + + const producer_authority_schedule* pending_producers_legacy() const { + return std::visit( + overloaded{[](const assembled_block_legacy& ab) -> const producer_authority_schedule* { + return ab.new_producer_authority_cache.has_value() ? &ab.new_producer_authority_cache.value() + : nullptr; + }, + [](const assembled_block_if&) -> const producer_authority_schedule* { return nullptr; }}, + v); + } + + const block_signing_authority& pending_block_signing_authority() const { + return std::visit(overloaded{[](const assembled_block_legacy& ab) -> const block_signing_authority& { + return ab.pending_block_header_state.valid_block_signing_authority; + }, + [](const assembled_block_if& ab) -> const block_signing_authority& { + return ab.active_producer_authority.authority; + }}, + v); + } + + completed_block complete_block(const protocol_feature_set& pfs, validator_t validator, + const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority) { + return std::visit(overloaded{[&](assembled_block_legacy& ab) { + auto bsp = std::make_shared( + std::move(ab.pending_block_header_state), std::move(ab.unsigned_block), + std::move(ab.trx_metas), ab.action_receipt_digests_savanna, pfs, validator, signer); + return completed_block{block_handle{std::move(bsp)}}; + }, + [&](assembled_block_if& ab) { + auto bsp = std::make_shared(ab.bhs, std::move(ab.trx_metas), + std::move(ab.trx_receipts), ab.valid, ab.qc, signer, + valid_block_signing_authority, ab.action_mroot); + return completed_block{block_handle{std::move(bsp)}}; + }}, + v); + } }; -using block_stage_type = std::variant; +struct building_block { + // -------------------------------------------------------------------------------- + struct building_block_common { + using checksum_or_digests = std::variant; + + const vector new_protocol_feature_activations; + size_t num_new_protocol_features_that_have_activated = 0; + deque pending_trx_metas; + deque pending_trx_receipts; + checksum_or_digests trx_mroot_or_receipt_digests {digests_t{}}; + action_digests_t action_receipt_digests; + std::optional new_finalizer_policy; + + building_block_common(const vector& new_protocol_feature_activations, + action_digests_t::store_which_t store_which) : + new_protocol_feature_activations(new_protocol_feature_activations), + action_receipt_digests(store_which) + { + } + + bool is_protocol_feature_activated(const digest_type& digest, const flat_set& activated_features) const { + if (activated_features.find(digest) != activated_features.end()) + return true; + if (num_new_protocol_features_that_have_activated == 0) + return false; + auto end = new_protocol_feature_activations.begin() + num_new_protocol_features_that_have_activated; + return (std::find(new_protocol_feature_activations.begin(), end, digest) != end); + } -struct pending_state { - pending_state( maybe_session&& s, const block_header_state_legacy& prev, - block_timestamp_type when, - uint16_t num_prev_blocks_to_confirm, - const vector& new_protocol_feature_activations ) - :_db_session( std::move(s) ) - ,_block_stage( building_block( prev, when, num_prev_blocks_to_confirm, new_protocol_feature_activations ) ) + std::function make_block_restore_point() { + auto orig_trx_receipts_size = pending_trx_receipts.size(); + auto orig_trx_metas_size = pending_trx_metas.size(); + auto orig_trx_receipt_digests_size = std::holds_alternative(trx_mroot_or_receipt_digests) ? + std::get(trx_mroot_or_receipt_digests).size() : 0; + auto orig_action_receipt_digests_size = action_receipt_digests.size(); + return [this, + orig_trx_receipts_size, + orig_trx_metas_size, + orig_trx_receipt_digests_size, + orig_action_receipt_digests_size]() + { + pending_trx_receipts.resize(orig_trx_receipts_size); + pending_trx_metas.resize(orig_trx_metas_size); + if (std::holds_alternative(trx_mroot_or_receipt_digests)) + std::get(trx_mroot_or_receipt_digests).resize(orig_trx_receipt_digests_size); + action_receipt_digests.resize(orig_action_receipt_digests_size); + }; + } + }; + + // -------------------------------------------------------------------------------- + struct building_block_legacy : public building_block_common { + pending_block_header_state_legacy pending_block_header_state; + std::optional new_pending_producer_schedule; + + building_block_legacy( const block_header_state_legacy& prev, + block_timestamp_type when, + uint16_t num_prev_blocks_to_confirm, + const vector& new_protocol_feature_activations, + action_digests_t::store_which_t store_which) + : building_block_common(new_protocol_feature_activations, store_which), + pending_block_header_state(prev.next(when, num_prev_blocks_to_confirm)) + {} + + bool is_protocol_feature_activated(const digest_type& digest) const { + return building_block_common::is_protocol_feature_activated( + digest, pending_block_header_state.prev_activated_protocol_features->protocol_features); + } + + uint32_t get_block_num() const { return pending_block_header_state.block_num; } + }; + + // -------------------------------------------------------------------------------- + struct building_block_if : public building_block_common { + const block_state& parent; + const block_timestamp_type timestamp; // Comes from building_block_input::timestamp + const producer_authority active_producer_authority; // Comes from parent.get_scheduled_producer(timestamp) + const protocol_feature_activation_set_ptr prev_activated_protocol_features; // Cached: parent.activated_protocol_features() + const proposer_policy_ptr active_proposer_policy; // Cached: parent.get_next_active_proposer_policy(timestamp) + const uint32_t block_num; // Cached: parent.block_num() + 1 + + building_block_if(const block_state& parent, const building_block_input& input, action_digests_t::store_which_t store_which) + : building_block_common(input.new_protocol_feature_activations, store_which) + , parent (parent) + , timestamp(input.timestamp) + , active_producer_authority{input.producer, + [&]() -> block_signing_authority { + const auto& pas = parent.active_proposer_policy->proposer_schedule; + for (const auto& pa : pas.producers) + if (pa.producer_name == input.producer) + return pa.authority; + assert(0); // we should find the authority + return {}; + }()} + , prev_activated_protocol_features(parent.activated_protocol_features) + , active_proposer_policy(parent.active_proposer_policy) + , block_num(parent.block_num() + 1) {} + + bool is_protocol_feature_activated(const digest_type& digest) const { + return building_block_common::is_protocol_feature_activated(digest, prev_activated_protocol_features->protocol_features); + } + + uint32_t get_block_num() const { return block_num; } + + uint32_t get_next_proposer_schedule_version() const { + if (!parent.proposer_policies.empty()) { + block_timestamp_type active_time = detail::get_next_next_round_block_time(timestamp); + if (auto itr = parent.proposer_policies.find(active_time); itr != parent.proposer_policies.cend()) { + return itr->second->proposer_schedule.version; // will replace so return same version + } + return (--parent.proposer_policies.end())->second->proposer_schedule.version + 1; + } + assert(active_proposer_policy); + return active_proposer_policy->proposer_schedule.version + 1; + } + + }; + + std::variant v; + + // legacy constructor + building_block(const block_header_state_legacy& prev, block_timestamp_type when, uint16_t num_prev_blocks_to_confirm, + const vector& new_protocol_feature_activations) : + v(building_block_legacy(prev, when, num_prev_blocks_to_confirm, new_protocol_feature_activations, + action_digests_t::store_which_t::both)) // [todo] should be both only when transition starts {} - maybe_session _db_session; - block_stage_type _block_stage; - controller::block_status _block_status = controller::block_status::ephemeral; - std::optional _producer_block_id; - controller::block_report _block_report{}; + // if constructor + building_block(const block_state& prev, const building_block_input& input) : + v(building_block_if(prev, input, action_digests_t::store_which_t::savanna)) + {} - /** @pre _block_stage cannot hold completed_block alternative */ - const pending_block_header_state_legacy& get_pending_block_header_state_legacy()const { - if( std::holds_alternative(_block_stage) ) - return std::get(_block_stage)._pending_block_header_state_legacy; + bool is_legacy() const { return std::holds_alternative(v); } + + // apply legacy, building_block_legacy + template + R apply_l(F&& f) { + if constexpr (std::is_same_v) + std::visit(overloaded{[&](building_block_legacy& bb) { std::forward(f)(bb); }, + [&](building_block_if&) {}}, v); + else + return std::visit(overloaded{[&](building_block_legacy& bb) -> R { return std::forward(f)(bb); }, + [&](building_block_if&) -> R { return {}; }}, v); + } - return std::get(_block_stage)._pending_block_header_state_legacy; + void set_proposed_finalizer_policy(finalizer_policy&& fin_pol) + { + std::visit(overloaded{ [&](building_block_legacy& bb) { + fin_pol.generation = 1; // only allowed to be set once in legacy mode + bb.new_finalizer_policy = std::move(fin_pol); + }, + [&](building_block_if& bb) { + fin_pol.generation = bb.parent.active_finalizer_policy->generation + 1; + bb.new_finalizer_policy = std::move(fin_pol); + } }, + v); } deque extract_trx_metas() { - if( std::holds_alternative(_block_stage) ) - return std::move( std::get(_block_stage)._pending_trx_metas ); + return std::visit([](auto& bb) { return std::move(bb.pending_trx_metas); }, v); + } - if( std::holds_alternative(_block_stage) ) - return std::move( std::get(_block_stage)._trx_metas ); + bool is_protocol_feature_activated(const digest_type& digest) const { + return std::visit([&digest](const auto& bb) { return bb.is_protocol_feature_activated(digest); }, v); + } - return std::get(_block_stage)._block_state->extract_trxs_metas(); + std::function make_block_restore_point() { + return std::visit([](auto& bb) { return bb.make_block_restore_point(); }, v); } - bool is_protocol_feature_activated( const digest_type& feature_digest )const { - if( std::holds_alternative(_block_stage) ) { - auto& bb = std::get(_block_stage); - const auto& activated_features = bb._pending_block_header_state_legacy.prev_activated_protocol_features->protocol_features; + uint32_t block_num() const { + return std::visit([](const auto& bb) { return bb.get_block_num(); }, v); + } - if( activated_features.find( feature_digest ) != activated_features.end() ) return true; + block_timestamp_type timestamp() const { + return std::visit( + overloaded{[](const building_block_legacy& bb) { return bb.pending_block_header_state.timestamp; }, + [](const building_block_if& bb) { return bb.timestamp; }}, + v); + } - if( bb._num_new_protocol_features_that_have_activated == 0 ) return false; + account_name producer() const { + return std::visit( + overloaded{[](const building_block_legacy& bb) { return bb.pending_block_header_state.producer; }, + [](const building_block_if& bb) { return bb.active_producer_authority.producer_name; }}, + v); + } - auto end = bb._new_protocol_feature_activations.begin() + bb._num_new_protocol_features_that_have_activated; - return (std::find( bb._new_protocol_feature_activations.begin(), end, feature_digest ) != end); - } + const vector& new_protocol_feature_activations() { + return std::visit([](auto& bb) -> const vector& { return bb.new_protocol_feature_activations; }, v); + } - if( std::holds_alternative(_block_stage) ) { - // Calling is_protocol_feature_activated during the assembled_block stage is not efficient. - // We should avoid doing it. - // In fact for now it isn't even implemented. - EOS_THROW( misc_exception, - "checking if protocol feature is activated in the assembled_block stage is not yet supported" ); - // TODO: implement this - } + const block_signing_authority& pending_block_signing_authority() const { + return std::visit(overloaded{[](const building_block_legacy& bb) -> const block_signing_authority& { + return bb.pending_block_header_state.valid_block_signing_authority; + }, + [](const building_block_if& bb) -> const block_signing_authority& { + return bb.active_producer_authority.authority; + }}, + v); + } + + int64_t get_next_proposer_schedule_version() const { + return std::visit( + overloaded{[](const building_block_legacy&) -> int64_t { return -1; }, + [&](const building_block_if& bb) -> int64_t { return bb.get_next_proposer_schedule_version(); } + }, + v); + } + + size_t& num_new_protocol_features_activated() { + return std::visit([](auto& bb) -> size_t& { return bb.num_new_protocol_features_that_have_activated; }, v); + } + + deque& pending_trx_metas() { + return std::visit([](auto& bb) -> deque& { return bb.pending_trx_metas; }, v); + } + + deque& pending_trx_receipts() { + return std::visit([](auto& bb) -> deque& { return bb.pending_trx_receipts; }, v); + } + + building_block_common::checksum_or_digests& trx_mroot_or_receipt_digests() { + return std::visit( + [](auto& bb) -> building_block_common::checksum_or_digests& { return bb.trx_mroot_or_receipt_digests; }, v); + } + + action_digests_t& action_receipt_digests() { + return std::visit([](auto& bb) -> action_digests_t& { return bb.action_receipt_digests; }, v); + } + + const producer_authority_schedule& active_producers() const { + return std::visit(overloaded{[](const building_block_legacy& bb) -> const producer_authority_schedule& { + return bb.pending_block_header_state.active_schedule; + }, + [](const building_block_if& bb) -> const producer_authority_schedule& { + return bb.active_proposer_policy->proposer_schedule; + }}, + v); + } + + const producer_authority_schedule* next_producers() const { + return std::visit(overloaded{[](const building_block_legacy& bb) -> const producer_authority_schedule* { + if (bb.new_pending_producer_schedule) + return &bb.new_pending_producer_schedule.value(); + return &bb.pending_block_header_state.prev_pending_schedule.schedule; + }, + [](const building_block_if& bb) -> const producer_authority_schedule* { + if (!bb.parent.proposer_policies.empty()) + return &bb.parent.proposer_policies.begin()->second->proposer_schedule; + return nullptr; + }}, + v); + } + + const producer_authority_schedule* pending_producers_legacy() const { + return std::visit(overloaded{[](const building_block_legacy& bb) -> const producer_authority_schedule* { + if (bb.new_pending_producer_schedule) + return &bb.new_pending_producer_schedule.value(); + return &bb.pending_block_header_state.prev_pending_schedule.schedule; + }, + [](const building_block_if&) -> const producer_authority_schedule* { + return nullptr; + }}, + v); + } + + qc_data_t get_qc_data(fork_database& fork_db, const block_state& parent) { + // find most recent ancestor block that has a QC by traversing fork db + // branch from parent + + return fork_db.apply_s([&](const auto& forkdb) { + auto branch = forkdb.fetch_branch(parent.id()); + + for( auto it = branch.begin(); it != branch.end(); ++it ) { + if( auto qc = (*it)->get_best_qc(); qc ) { + EOS_ASSERT( qc->block_num <= block_header::num_from_id(parent.id()), block_validate_exception, + "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", + ("a", qc->block_num)("p", block_header::num_from_id(parent.id())) ); + auto qc_claim = qc->to_qc_claim(); + if( parent.is_needed(qc_claim) ) { + return qc_data_t{ *qc, qc_claim }; + } else { + // no new qc info, repeat existing + return qc_data_t{ {}, parent.core.latest_qc_claim() }; + } + } + } + + // This only happens when parent block is the IF genesis block or starting from snapshot. + // There is no ancestor block which has a QC. Construct a default QC claim. + return qc_data_t{ {}, parent.core.latest_qc_claim() }; + }); + } + + assembled_block assemble_block(boost::asio::io_context& ioc, + const protocol_feature_set& pfs, + fork_database& fork_db, + std::unique_ptr new_proposer_policy, + bool validating, + std::optional validating_qc_data, + const block_state_ptr& validating_bsp) { + auto& action_receipts = action_receipt_digests(); + return std::visit( + overloaded{ + [&](building_block_legacy& bb) -> assembled_block { + // compute the action_mroot and transaction_mroot + auto [transaction_mroot, action_mroot] = std::visit( + overloaded{[&](digests_t& trx_receipts) { // calculate the two merkle roots in separate threads + auto trx_merkle_fut = + post_async_task(ioc, [&]() { return calculate_merkle_legacy(std::move(trx_receipts)); }); + auto action_merkle_fut = + post_async_task(ioc, [&]() { return calculate_merkle_legacy(std::move(*action_receipts.digests_l)); }); + return std::make_pair(trx_merkle_fut.get(), action_merkle_fut.get()); + }, + [&](const checksum256_type& trx_checksum) { + return std::make_pair(trx_checksum, calculate_merkle_legacy(std::move(*action_receipts.digests_l))); + }}, + trx_mroot_or_receipt_digests()); + + if (validating_qc_data) { + bb.pending_block_header_state.qc_claim = validating_qc_data->qc_claim; + } + + // in dpos, we create a signed_block here. In IF mode, we do it later (when we are ready to sign it) + auto block_ptr = std::make_shared(bb.pending_block_header_state.make_block_header( + transaction_mroot, action_mroot, bb.new_pending_producer_schedule, std::move(bb.new_finalizer_policy), + vector(bb.new_protocol_feature_activations), pfs)); + + block_ptr->transactions = std::move(bb.pending_trx_receipts); + + return assembled_block{ + .v = assembled_block::assembled_block_legacy{block_ptr->calculate_id(), + std::move(bb.pending_block_header_state), + std::move(bb.pending_trx_metas), std::move(block_ptr), + std::move(bb.new_pending_producer_schedule), + std::move(bb.action_receipt_digests.digests_s)} + }; + }, + [&](building_block_if& bb) -> assembled_block { + // compute the action_mroot and transaction_mroot + auto [transaction_mroot, action_mroot] = std::visit( + overloaded{[&](digests_t& trx_receipts) { + // calculate_merkle takes 3.2ms for 50,000 digests (legacy version took 11.1ms) + return std::make_pair(calculate_merkle(trx_receipts), + calculate_merkle(*action_receipts.digests_s)); + }, + [&](const checksum256_type& trx_checksum) { + return std::make_pair(trx_checksum, + calculate_merkle(*action_receipts.digests_s)); + }}, + trx_mroot_or_receipt_digests()); + + qc_data_t qc_data; + digest_type finality_mroot_claim; + + if (validating) { + // we are simulating a block received from the network. Use the embedded qc from the block + assert(validating_qc_data); + qc_data = *validating_qc_data; + + assert(validating_bsp); + // Use the action_mroot from raceived block's header for + // finality_mroot_claim at the first stage such that the next + // block's header and block id can be built. The actual + // finality_mroot will be validated by apply_block at the + // second stage + finality_mroot_claim = validating_bsp->header.action_mroot; + } else { + qc_data = get_qc_data(fork_db, bb.parent);; + finality_mroot_claim = bb.parent.get_finality_mroot_claim(qc_data.qc_claim); + } + + building_block_input bb_input { + .parent_id = bb.parent.id(), + .parent_timestamp = bb.parent.timestamp(), + .timestamp = timestamp(), + .producer = producer(), + .new_protocol_feature_activations = new_protocol_feature_activations() + }; + + block_header_state_input bhs_input{ + bb_input, + transaction_mroot, + std::move(new_proposer_policy), + std::move(bb.new_finalizer_policy), + qc_data.qc_claim, + finality_mroot_claim + }; + + auto bhs = bb.parent.next(bhs_input); + + std::optional valid; // used for producing + + if (validating) { + // Create the valid structure for validating_bsp if it does not + // have one. + if (!validating_bsp->valid) { + validating_bsp->valid = bb.parent.new_valid(bhs, action_mroot, validating_bsp->strong_digest); + validating_bsp->action_mroot = action_mroot; // caching for constructing finality_data. Only needed when block is commited. + } + } else { + // Create the valid structure for producing + valid = bb.parent.new_valid(bhs, action_mroot, bhs.compute_finality_digest()); + } - const auto& activated_features = std::get(_block_stage)._block_state->activated_protocol_features->protocol_features; - return (activated_features.find( feature_digest ) != activated_features.end()); + assembled_block::assembled_block_if ab{ + bb.active_producer_authority, + bhs, + std::move(bb.pending_trx_metas), + std::move(bb.pending_trx_receipts), + valid, + qc_data.qc, + action_mroot // caching for constructing finality_data. + }; + + return assembled_block{.v = std::move(ab)}; + }}, + v); + } +}; + + +using block_stage_type = std::variant; + +struct pending_state { + maybe_session _db_session; + block_stage_type _block_stage; + controller::block_status _block_status = controller::block_status::ephemeral; + std::optional _producer_block_id; + controller::block_report _block_report{}; + + pending_state(maybe_session&& s, + const block_header_state_legacy& prev, + block_timestamp_type when, + uint16_t num_prev_blocks_to_confirm, + const vector& new_protocol_feature_activations) + :_db_session(std::move(s)) + ,_block_stage(building_block(prev, when, num_prev_blocks_to_confirm, new_protocol_feature_activations)) + {} + + pending_state(maybe_session&& s, + const block_state& prev, + const building_block_input& input) : + _db_session(std::move(s)), + _block_stage(building_block(prev, input)) + {} + + deque extract_trx_metas() { + return std::visit([](auto& stage) { return stage.extract_trx_metas(); }, _block_stage); + } + + bool is_protocol_feature_activated(const digest_type& digest) const { + return std::visit([&](const auto& stage) { return stage.is_protocol_feature_activated(digest); }, _block_stage); + } + + block_timestamp_type timestamp() const { + return std::visit([](const auto& stage) { return stage.timestamp(); }, _block_stage); + } + + uint32_t block_num() const { + return std::visit([](const auto& stage) { return stage.block_num(); }, _block_stage); + } + + account_name producer() const { + return std::visit([](const auto& stage) { return stage.producer(); }, _block_stage); } void push() { _db_session.push(); } + + bool is_legacy() const { return std::visit([](const auto& stage) { return stage.is_legacy(); }, _block_stage); } + + const block_signing_authority& pending_block_signing_authority() const { + return std::visit( + [](const auto& stage) -> const block_signing_authority& { return stage.pending_block_signing_authority(); }, + _block_stage); + } + + const producer_authority_schedule& active_producers() const { + return std::visit( + [](const auto& stage) -> const producer_authority_schedule& { return stage.active_producers(); }, + _block_stage); + } + + const producer_authority_schedule* pending_producers_legacy() const { + return std::visit( + [](const auto& stage) -> const producer_authority_schedule* { return stage.pending_producers_legacy(); }, + _block_stage); + } + + const producer_authority_schedule* next_producers()const { + return std::visit( + [](const auto& stage) -> const producer_authority_schedule* { return stage.next_producers(); }, + _block_stage); + } + + int64_t get_next_proposer_schedule_version() const { + return std::visit(overloaded{ + [](const building_block& stage) -> int64_t { return stage.get_next_proposer_schedule_version(); }, + [](const assembled_block&) -> int64_t { assert(false); return -1; }, + [](const completed_block&) -> int64_t { assert(false); return -1; } + }, + _block_stage); + } }; struct controller_impl { @@ -234,11 +903,13 @@ struct controller_impl { #endif controller& self; std::function shutdown; + std::function check_shutdown; chainbase::database db; block_log blog; std::optional pending; - block_state_legacy_ptr head; + block_handle chain_head; fork_database fork_db; + large_atomic if_irreversible_block_id; resource_limits_manager resource_limits; subjective_billing subjective_bill; authorization_manager authorization; @@ -256,6 +927,7 @@ struct controller_impl { named_thread_pool thread_pool; deep_mind_handler* deep_mind_logger = nullptr; bool okay_to_print_integrity_hash_on_stop = false; + my_finalizers_t my_finalizers; std::atomic writing_snapshot = false; thread_local static platform_timer timer; // a copy for main thread and each read-only thread @@ -269,24 +941,219 @@ struct controller_impl { map< account_name, map > apply_handlers; unordered_map< builtin_protocol_feature_t, std::function, enum_hash > protocol_feature_activation_handlers; + signal block_start; + signal accepted_block_header; + signal accepted_block; + signal irreversible_block; + signal)> applied_transaction; + signal voted_block; - void pop_block() { - auto prev = fork_db.get_block( head->header.previous ); + int64_t set_proposed_producers( vector producers ); + int64_t set_proposed_producers_legacy( vector producers ); + + protocol_feature_activation_set_ptr head_activated_protocol_features() const { + return apply(chain_head, [](const auto& head) { + return head->get_activated_protocol_features(); + }); + } + + const producer_authority_schedule& head_active_schedule_auth() const { + return apply(chain_head, [](const auto& head) -> const producer_authority_schedule& { + return head->active_schedule_auth(); + }); + } + + const producer_authority_schedule* head_pending_schedule_auth_legacy() const { + return apply(chain_head, + overloaded{[](const block_state_legacy_ptr& head) -> const producer_authority_schedule* { return head->pending_schedule_auth(); }, + [](const block_state_ptr&) -> const producer_authority_schedule* { return nullptr; } + }); + } + + const producer_authority_schedule* next_producers() { + return apply(chain_head, + overloaded{ + [](const block_state_legacy_ptr& head) -> const producer_authority_schedule* { + return head->pending_schedule_auth(); + }, + [](const block_state_ptr& head) -> const producer_authority_schedule* { + return head->proposer_policies.empty() + ? nullptr + : &head->proposer_policies.begin()->second->proposer_schedule; + } + }); + } + + void replace_producer_keys( const public_key_type& key ) { + ilog("Replace producer keys with ${k}", ("k", key)); + + apply(chain_head, + overloaded{ + [&](const block_state_legacy_ptr& head) { + auto version = head->pending_schedule.schedule.version; + head->pending_schedule = {}; + head->pending_schedule.schedule.version = version; + for (auto& prod: head->active_schedule.producers ) { + ilog("${n}", ("n", prod.producer_name)); + std::visit([&](auto &auth) { + auth.threshold = 1; + auth.keys = {key_weight{key, 1}}; + }, prod.authority); + } + }, + [](const block_state_ptr&) { + // TODO IF: add instant-finality implementation, will need to replace finalizers as well + } + }); + } + + // --------------- access fork_db head ---------------------------------------------------------------------- + bool fork_db_has_head() const { + return fork_db.apply([&](const auto& forkdb) { return !!forkdb.head(); }); + } + + template + typename ForkDB::bsp_t fork_db_head_or_pending(const ForkDB& forkdb) const { + if (irreversible_mode()) { + // When in IRREVERSIBLE mode fork_db blocks are marked valid when they become irreversible so that + // fork_db.head() returns irreversible block + // Use pending_head since this method should return the chain head and not last irreversible. + return forkdb.pending_head(); + } else { + return forkdb.head(); + } + } + + uint32_t fork_db_head_block_num() const { + return fork_db.apply( + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->block_num(); }); + } + + block_id_type fork_db_head_block_id() const { + return fork_db.apply( + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->id(); }); + } + + uint32_t fork_db_head_irreversible_blocknum() const { + return fork_db.apply( + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->irreversible_blocknum(); }); + } + + // --------------- access fork_db root ---------------------------------------------------------------------- + bool fork_db_has_root() const { + return fork_db.apply([&](const auto& forkdb) { return !!forkdb.has_root(); }); + } + + block_id_type fork_db_root_block_id() const { + return fork_db.apply([&](const auto& forkdb) { return forkdb.root()->id(); }); + } + + uint32_t fork_db_root_block_num() const { + return fork_db.apply([&](const auto& forkdb) { return forkdb.root()->block_num(); }); + } + + block_timestamp_type fork_db_root_timestamp() const { + return fork_db.apply([&](const auto& forkdb) { return forkdb.root()->timestamp(); }); + } + + // --------------- fork_db APIs ---------------------------------------------------------------------- + template + uint32_t pop_block(ForkDB& forkdb) { + typename ForkDB::bsp_t prev = forkdb.get_block( chain_head.previous() ); if( !prev ) { - EOS_ASSERT( fork_db.root()->id == head->header.previous, block_validate_exception, "attempt to pop beyond last irreversible block" ); - prev = fork_db.root(); + EOS_ASSERT( forkdb.root()->id() == chain_head.previous(), block_validate_exception, + "attempt to pop beyond last irreversible block" ); + prev = forkdb.root(); } - EOS_ASSERT( head->block, block_validate_exception, "attempting to pop a block that was sparsely loaded from a snapshot"); + EOS_ASSERT( chain_head.block(), block_validate_exception, + "attempting to pop a block that was sparsely loaded from a snapshot"); + chain_head = block_handle{prev}; - head = prev; + return prev->block_num(); + } - db.undo(); + bool fork_db_block_exists( const block_id_type& id ) const { + return fork_db.apply([&](const auto& forkdb) { + return forkdb.block_exists(id); + }); + } + + bool fork_db_validated_block_exists( const block_id_type& id ) const { + return fork_db.apply([&](const auto& forkdb) { + return forkdb.validated_block_exists(id); + }); + } + + signed_block_ptr fork_db_fetch_block_by_id( const block_id_type& id ) const { + return fork_db.apply([&](const auto& forkdb) { + auto bsp = forkdb.get_block(id); + return bsp ? bsp->block : nullptr; + }); + } + + signed_block_ptr fetch_block_on_head_branch_by_num(uint32_t block_num) const { + return fork_db.apply([&](const auto& forkdb) { + auto bsp = forkdb.search_on_head_branch(block_num); + if (bsp) return bsp->block; + return signed_block_ptr{}; + }); + } + + std::optional fetch_block_id_on_head_branch_by_num(uint32_t block_num) const { + return fork_db.apply>([&](const auto& forkdb) -> std::optional { + auto bsp = forkdb.search_on_head_branch(block_num, include_root_t::yes); + if (bsp) return bsp->id(); + return {}; + }); + } - protocol_features.popped_blocks_to( prev->block_num ); + // search on the branch of head + block_state_ptr fetch_bsp_on_head_branch_by_num(uint32_t block_num) const { + return fork_db.apply( + overloaded{ + [](const fork_database_legacy_t&) -> block_state_ptr { return nullptr; }, + [&](const fork_database_if_t& forkdb) -> block_state_ptr { + return forkdb.search_on_head_branch(block_num, include_root_t::yes); + } + } + ); } + // search on the branch of given id + block_state_ptr fetch_bsp_on_branch_by_num(const block_id_type& id, uint32_t block_num) const { + return fork_db.apply( + overloaded{ + [](const fork_database_legacy_t&) -> block_state_ptr { return nullptr; }, + [&](const fork_database_if_t& forkdb) -> block_state_ptr { + return forkdb.search_on_branch(id, block_num, include_root_t::yes); + } + } + ); + } + + block_state_ptr fetch_bsp(const block_id_type& id) const { + return fork_db.apply( + overloaded{ + [](const fork_database_legacy_t&) -> block_state_ptr { return nullptr; }, + [&](const fork_database_if_t& forkdb) -> block_state_ptr { + return forkdb.get_block(id, include_root_t::yes); + } + } + ); + } + + void pop_block() { + uint32_t prev_block_num = fork_db.apply([&](auto& forkdb) { + return pop_block(forkdb); + }); + db.undo(); + protocol_features.popped_blocks_to(prev_block_num); + } + + // ------------------------------------------- + template void on_activation(); @@ -313,7 +1180,7 @@ struct controller_impl { cfg.read_only ? database::read_only : database::read_write, cfg.state_size, false, cfg.db_map_mode ), blog( cfg.blocks_dir, cfg.blog ), - fork_db( cfg.blocks_dir / config::reversible_blocks_dir_name ), + fork_db(cfg.blocks_dir / config::reversible_blocks_dir_name), resource_limits( db, [&s](bool is_trx_transient) { return s.get_deep_mind_logger(is_trx_transient); }), authorization( s, db ), protocol_features( std::move(pfs), [&s](bool is_trx_transient) { return s.get_deep_mind_logger(is_trx_transient); } ), @@ -321,14 +1188,9 @@ struct controller_impl { chain_id( chain_id ), read_mode( cfg.read_mode ), thread_pool(), + my_finalizers(fc::time_point::now(), cfg.finalizers_dir / "safety.dat"), wasmif( conf.wasm_runtime, conf.eosvmoc_tierup, db, conf.state_dir, conf.eosvmoc_config, !conf.profile_accounts.empty() ) { - fork_db.open( [this]( block_timestamp_type timestamp, - const flat_set& cur_features, - const vector& new_features ) - { check_protocol_features( timestamp, cur_features, new_features ); } - ); - thread_pool.start( cfg.thread_pool_size, [this]( const fc::exception& e ) { elog( "Exception in chain thread pool, exiting: ${e}", ("e", e.to_detail_string()) ); if( shutdown ) shutdown(); @@ -347,8 +1209,9 @@ struct controller_impl { set_activation_handler(); set_activation_handler(); set_activation_handler(); + set_activation_handler(); - self.irreversible_block.connect([this](const block_signal_params& t) { + irreversible_block.connect([this](const block_signal_params& t) { const auto& [ block, id] = t; wasmif.current_lib(block->block_num()); }); @@ -365,15 +1228,17 @@ struct controller_impl { SET_APP_HANDLER( eosio, eosio, deleteauth ); SET_APP_HANDLER( eosio, eosio, linkauth ); SET_APP_HANDLER( eosio, eosio, unlinkauth ); -/* - SET_APP_HANDLER( eosio, eosio, postrecovery ); - SET_APP_HANDLER( eosio, eosio, passrecovery ); - SET_APP_HANDLER( eosio, eosio, vetorecovery ); -*/ SET_APP_HANDLER( eosio, eosio, canceldelay ); } + void open_fork_db() { + fork_db.open([this](block_timestamp_type timestamp, const flat_set& cur_features, + const vector& new_features) { + check_protocol_features(timestamp, cur_features, new_features); + }); + } + /** * Plugins / observers listening to signals emited might trigger * errors and throw exceptions. Unless those exceptions are caught it could impact consensus and/or @@ -412,82 +1277,206 @@ struct controller_impl { if (auto dm_logger = get_deep_mind_logger(false)) { if (trx && is_onblock(*t)) dm_logger->on_onblock(*trx); - dm_logger->on_applied_transaction(self.head_block_num() + 1, t); + dm_logger->on_applied_transaction(chain_head.block_num() + 1, t); + } + } + + template + void apply_irreversible_block(ForkDB& forkdb, const BSP& bsp) { + if (read_mode != db_read_mode::IRREVERSIBLE) + return; + controller::block_report br; + if constexpr (std::is_same_v>) { + // before transition to savanna + apply_block(br, bsp, controller::block_status::complete, trx_meta_cache_lookup{}); + } else { + assert(bsp->block); + if (bsp->block->is_proper_svnn_block()) { + apply_l(chain_head, [&](const auto&) { + // if chain_head is legacy, update to non-legacy chain_head, this is needed so that the correct block_state is created in apply_block + block_state_ptr prev = forkdb.get_block(bsp->previous(), include_root_t::yes); + assert(prev); + chain_head = block_handle{prev}; + }); + apply_block(br, bsp, controller::block_status::complete, trx_meta_cache_lookup{}); + } else { + // only called during transition when not a proper savanna block + fork_db.apply_l([&](const auto& forkdb_l) { + block_state_legacy_ptr legacy = forkdb_l.get_block(bsp->id()); + fork_db.switch_to(fork_database::in_use_t::legacy); // apply block uses to know what types to create + fc::scoped_exit> e([&]{fork_db.switch_to(fork_database::in_use_t::both);}); + apply_block(br, legacy, controller::block_status::complete, trx_meta_cache_lookup{}); + // irreversible apply was just done, calculate new_valid here instead of in transition_to_savanna() + assert(legacy->action_mroot_savanna); + block_state_ptr prev = forkdb.get_block(legacy->previous(), include_root_t::yes); + assert(prev); + transition_add_to_savanna_fork_db(forkdb, legacy, bsp, prev); + }); + } + } + } + + void transition_add_to_savanna_fork_db(fork_database_if_t& forkdb, + const block_state_legacy_ptr& legacy, const block_state_ptr& new_bsp, + const block_state_ptr& prev) { + // legacy_branch is from head, all will be validated unless irreversible_mode(), + // IRREVERSIBLE applies (validates) blocks when irreversible, new_valid will be done after apply in log_irreversible + assert(read_mode == db_read_mode::IRREVERSIBLE || legacy->action_mroot_savanna); + if (legacy->action_mroot_savanna) { + // Create the valid structure for producing + new_bsp->valid = prev->new_valid(*new_bsp, *legacy->action_mroot_savanna, new_bsp->strong_digest); + } + forkdb.add(new_bsp, legacy->is_valid() ? mark_valid_t::yes : mark_valid_t::no, ignore_duplicate_t::yes); + } + + void transition_to_savanna() { + assert(chain_head.header().contains_header_extension(instant_finality_extension::extension_id())); + // copy head branch branch from legacy forkdb legacy to savanna forkdb + fork_database_legacy_t::branch_t legacy_branch; + block_state_legacy_ptr legacy_root; + fork_db.apply_l([&](const auto& forkdb) { + legacy_root = forkdb.root(); + legacy_branch = forkdb.fetch_branch(fork_db_head_or_pending(forkdb)->id()); + }); + + assert(!!legacy_root); + assert(read_mode == db_read_mode::IRREVERSIBLE || !legacy_branch.empty()); + ilog("Transitioning to savanna, IF Genesis Block ${gb}, IF Critical Block ${cb}", ("gb", legacy_root->block_num())("cb", chain_head.block_num())); + auto new_root = block_state::create_if_genesis_block(*legacy_root); + fork_db.switch_from_legacy(new_root); + fork_db.apply_s([&](auto& forkdb) { + block_state_ptr prev = forkdb.root(); + assert(prev); + for (auto bitr = legacy_branch.rbegin(); bitr != legacy_branch.rend(); ++bitr) { + const bool skip_validate_signee = true; // validated already + auto new_bsp = std::make_shared( + *prev, + (*bitr)->block, + protocol_features.get_protocol_feature_set(), + validator_t{}, skip_validate_signee); + transition_add_to_savanna_fork_db(forkdb, *bitr, new_bsp, prev); + prev = new_bsp; + } + assert(read_mode == db_read_mode::IRREVERSIBLE || forkdb.head()->id() == legacy_branch.front()->id()); + if (read_mode != db_read_mode::IRREVERSIBLE) + chain_head = block_handle{forkdb.head()}; + ilog("Transition to instant finality happening after block ${b}, First IF Proper Block ${pb}", ("b", prev->block_num())("pb", prev->block_num()+1)); + }); + + { + // If Leap started at a block prior to the IF transition, it needs to provide a default safety + // information for those finalizers that don't already have one. This typically should be done when + // we create the non-legacy fork_db, as from this point we may need to cast votes to participate + // to the IF consensus. See https://github.com/AntelopeIO/leap/issues/2070#issuecomment-1941901836 + auto start_block = chain_head; // doesn't matter this is not updated for IRREVERSIBLE, can be in irreversible mode and be a finalizer + auto lib_block = chain_head; + my_finalizers.set_default_safety_information( + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {start_block.id(), start_block.block_time()}, + .lock = {lib_block.id(), lib_block.block_time()} }); } } void log_irreversible() { - EOS_ASSERT( fork_db.root(), fork_database_exception, "fork database not properly initialized" ); + EOS_ASSERT( fork_db_has_root(), fork_database_exception, "fork database not properly initialized" ); const std::optional log_head_id = blog.head_id(); const bool valid_log_head = !!log_head_id; const auto lib_num = valid_log_head ? block_header::num_from_id(*log_head_id) : (blog.first_block_num() - 1); - auto root_id = fork_db.root()->id; + auto root_id = fork_db_root_block_id(); if( valid_log_head ) { - EOS_ASSERT( root_id == log_head_id, fork_database_exception, "fork database root does not match block log head" ); + EOS_ASSERT( root_id == log_head_id, fork_database_exception, + "fork database root ${rid} does not match block log head ${hid}", ("rid", root_id)("hid", log_head_id) ); } else { - EOS_ASSERT( fork_db.root()->block_num == lib_num, fork_database_exception, + EOS_ASSERT( fork_db_root_block_num() == lib_num, fork_database_exception, "The first block ${lib_num} when starting with an empty block log should be the block after fork database root ${bn}.", - ("lib_num", lib_num)("bn", fork_db.root()->block_num) ); + ("lib_num", lib_num)("bn", fork_db_root_block_num()) ); } - const auto fork_head = fork_db_head(); + block_id_type irreversible_block_id = if_irreversible_block_id.load(); + uint32_t if_lib_num = block_header::num_from_id(irreversible_block_id); + const uint32_t new_lib_num = if_lib_num > 0 ? if_lib_num : fork_db_head_irreversible_blocknum(); - if( fork_head->dpos_irreversible_blocknum <= lib_num ) + if( new_lib_num <= lib_num ) return; - auto branch = fork_db.fetch_branch( fork_head->id, fork_head->dpos_irreversible_blocknum ); - try { - - std::vector>> v; - v.reserve( branch.size() ); - for( auto bitr = branch.rbegin(); bitr != branch.rend(); ++bitr ) { - v.emplace_back( post_async_task( thread_pool.get_executor(), [b=(*bitr)->block]() { return fc::raw::pack(*b); } ) ); - } - auto it = v.begin(); + bool savanna_transistion_required = false; + auto mark_branch_irreversible = [&, this](auto& forkdb) { + auto branch = (if_lib_num > 0) ? forkdb.fetch_branch( irreversible_block_id, new_lib_num) + : forkdb.fetch_branch( fork_db_head_or_pending(forkdb)->id(), new_lib_num ); + try { + auto should_process = [&](auto& bsp) { + // Only make irreversible blocks that have been validated. Blocks in the fork database may not be on our current best head + // and therefore have not been validated. + // An alternative more complex implementation would be to do a fork switch here and validate all blocks so they can be then made + // irreversible. Instead this moves irreversible as much as possible and allows the next maybe_switch_forks call to apply these + // non-validated blocks. After the maybe_switch_forks call (before next produced block or on next received block), irreversible + // can then move forward on the then validated blocks. + return read_mode == db_read_mode::IRREVERSIBLE || bsp->is_valid(); + }; - for( auto bitr = branch.rbegin(); bitr != branch.rend(); ++bitr ) { - if( read_mode == db_read_mode::IRREVERSIBLE ) { - controller::block_report br; - apply_block( br, *bitr, controller::block_status::complete, trx_meta_cache_lookup{} ); + std::vector>> v; + v.reserve( branch.size() ); + for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) { + v.emplace_back( post_async_task( thread_pool.get_executor(), [b=(*bitr)->block]() { return fc::raw::pack(*b); } ) ); } + auto it = v.begin(); - emit( self.irreversible_block, std::tie((*bitr)->block, (*bitr)->id) ); + for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) { + apply_irreversible_block(forkdb, *bitr); - // blog.append could fail due to failures like running out of space. - // Do it before commit so that in case it throws, DB can be rolled back. - blog.append( (*bitr)->block, (*bitr)->id, it->get() ); - ++it; + emit( irreversible_block, std::tie((*bitr)->block, (*bitr)->id()) ); - db.commit( (*bitr)->block_num ); - root_id = (*bitr)->id; + // blog.append could fail due to failures like running out of space. + // Do it before commit so that in case it throws, DB can be rolled back. + blog.append( (*bitr)->block, (*bitr)->id(), it->get() ); + ++it; + + db.commit( (*bitr)->block_num() ); + root_id = (*bitr)->id(); + + if constexpr (std::is_same_v>) { + if ((*bitr)->header.contains_header_extension(instant_finality_extension::extension_id())) { + savanna_transistion_required = true; + // Do not advance irreversible past IF Genesis Block + break; + } + } else if ((*bitr)->block->is_proper_svnn_block() && fork_db.version_in_use() == fork_database::in_use_t::both) { + fork_db.switch_to(fork_database::in_use_t::savanna); + break; + } + } + } catch( std::exception& ) { + if( root_id != forkdb.root()->id() ) { + forkdb.advance_root( root_id ); + } + throw; } - } catch( std::exception& ) { - if( root_id != fork_db.root()->id ) { - fork_db.advance_root( root_id ); + + //db.commit( new_lib ); // redundant + + if( root_id != forkdb.root()->id() ) { + branch.emplace_back(forkdb.root()); + forkdb.advance_root( root_id ); } - throw; - } - //db.commit( fork_head->dpos_irreversible_blocknum ); // redundant + // delete branch in thread pool + boost::asio::post( thread_pool.get_executor(), [branch{std::move(branch)}]() {} ); + }; - if( root_id != fork_db.root()->id ) { - branch.emplace_back(fork_db.root()); - fork_db.advance_root( root_id ); + fork_db.apply(mark_branch_irreversible); + if (savanna_transistion_required) { + transition_to_savanna(); } - - // delete branch in thread pool - boost::asio::post( thread_pool.get_executor(), [branch{std::move(branch)}]() {} ); } - /** - * Sets fork database head to the genesis state. - */ void initialize_blockchain_state(const genesis_state& genesis) { - wlog( "Initializing new blockchain with genesis state" ); + ilog( "Initializing new blockchain with genesis state" ); + + // genesis state starts in legacy mode producer_authority_schedule initial_schedule = { 0, { producer_authority{config::system_account_name, block_signing_authority_v0{ 1, {{genesis.initial_key, 1}} } } } }; legacy::producer_schedule_type initial_legacy_schedule{ 0, {{config::system_account_name, genesis.initial_key}} }; @@ -501,34 +1490,83 @@ struct controller_impl { genheader.id = genheader.header.calculate_id(); genheader.block_num = genheader.header.block_num(); - head = std::make_shared(); + auto head = std::make_shared(); static_cast(*head) = genheader; - head->activated_protocol_features = std::make_shared(); + head->activated_protocol_features = std::make_shared(); // no activated protocol features in genesis head->block = std::make_shared(genheader.header); - db.set_revision( head->block_num ); + chain_head = block_handle{head}; + + db.set_revision( chain_head.block_num() ); initialize_database(genesis); } - void replay(std::function check_shutdown) { + enum class startup_t { genesis, snapshot, existing_state }; + + std::exception_ptr replay_block_log() { auto blog_head = blog.head(); - if( !fork_db.root() ) { - fork_db.reset( *head ); - if (!blog_head) - return; + if (!blog_head) { + ilog( "no block log found" ); + return {}; } - replaying = true; - auto start_block_num = head->block_num + 1; + auto start_block_num = chain_head.block_num() + 1; auto start = fc::time_point::now(); std::exception_ptr except_ptr; - - if( blog_head && start_block_num <= blog_head->block_num() ) { - ilog( "existing block log, attempting to replay from ${s} to ${n} blocks", - ("s", start_block_num)("n", blog_head->block_num()) ); + if( start_block_num <= blog_head->block_num() ) { + ilog( "existing block log, attempting to replay from ${s} to ${n} blocks", ("s", start_block_num)("n", blog_head->block_num()) ); try { - while( auto next = blog.read_block_by_num( head->block_num + 1 ) ) { - replay_push_block( next, controller::block_status::irreversible ); + std::vector legacy_branch; // for blocks that will need to be converted to IF blocks + while( auto next = blog.read_block_by_num( chain_head.block_num() + 1 ) ) { + apply_l(chain_head, [&](const auto& head) { + if (next->is_proper_svnn_block()) { + const bool skip_validate_signee = true; // validated already or not in replay_push_block according to conf.force_all_checks; + assert(!legacy_branch.empty()); // should have started with a block_state chain_head or we transition during replay + // transition to savanna + block_state_ptr prev; + for (size_t i = 0; i < legacy_branch.size(); ++i) { + if (i == 0) { + prev = block_state::create_if_genesis_block(*legacy_branch[0]); + } else { + const auto& bspl = legacy_branch[i]; + auto new_bsp = std::make_shared( + *prev, + bspl->block, + protocol_features.get_protocol_feature_set(), + validator_t{}, skip_validate_signee); + // legacy_branch is from head, all should be validated + assert(bspl->action_mroot_savanna); + // Create the valid structure for producing + new_bsp->valid = prev->new_valid(*new_bsp, *bspl->action_mroot_savanna, new_bsp->strong_digest); + prev = new_bsp; + } + } + chain_head = block_handle{ prev }; // apply_l will not execute again after this + { + // If Leap started at a block prior to the IF transition, it needs to provide a default safety + // information for those finalizers that don't already have one. This typically should be done when + // we create the non-legacy fork_db, as from this point we may need to cast votes to participate + // to the IF consensus. See https://github.com/AntelopeIO/leap/issues/2070#issuecomment-1941901836 + auto start_block = chain_head; + auto lib_block = chain_head; + my_finalizers.set_default_safety_information( + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {start_block.id(), start_block.block_time()}, + .lock = {lib_block.id(), lib_block.block_time()} }); + } + } + }); + apply(chain_head, [&](const T&) { + replay_push_block( next, controller::block_status::irreversible ); + }); + apply_l(chain_head, [&](const auto& head) { // chain_head is updated via replay_push_block + assert(!next->is_proper_svnn_block()); + if (next->contains_header_extension(instant_finality_extension::extension_id())) { + assert(legacy_branch.empty() || head->block->previous == legacy_branch.back()->block->calculate_id()); + legacy_branch.push_back(head); + // note if is_proper_svnn_block is not reached then transistion will happen live + } + }); if( check_shutdown() ) break; if( next->block_num() % 500 == 0 ) { ilog( "${n} of ${head}", ("n", next->block_num())("head", blog_head->block_num()) ); @@ -537,57 +1575,128 @@ struct controller_impl { } catch( const database_guard_exception& e ) { except_ptr = std::current_exception(); } - ilog( "${n} irreversible blocks replayed", ("n", 1 + head->block_num - start_block_num) ); - - auto pending_head = fork_db.pending_head(); - if( pending_head ) { - ilog( "fork database head ${h}, root ${r}", ("h", pending_head->block_num)( "r", fork_db.root()->block_num ) ); - if( pending_head->block_num < head->block_num || head->block_num < fork_db.root()->block_num ) { - ilog( "resetting fork database with new last irreversible block as the new root: ${id}", ("id", head->id) ); - fork_db.reset( *head ); - } else if( head->block_num != fork_db.root()->block_num ) { - auto new_root = fork_db.search_on_branch( pending_head->id, head->block_num ); - EOS_ASSERT( new_root, fork_database_exception, - "unexpected error: could not find new LIB in fork database" ); - ilog( "advancing fork database root to new last irreversible block within existing fork database: ${id}", - ("id", new_root->id) ); - fork_db.mark_valid( new_root ); - fork_db.advance_root( new_root->id ); - } - } + auto end = fc::time_point::now(); + ilog( "${n} irreversible blocks replayed", ("n", 1 + chain_head.block_num() - start_block_num) ); + ilog( "replayed ${n} blocks in ${duration} seconds, ${mspb} ms/block", + ("n", chain_head.block_num() + 1 - start_block_num)("duration", (end-start).count()/1000000) + ("mspb", ((end-start).count()/1000.0)/(chain_head.block_num()-start_block_num)) ); // if the irreverible log is played without undo sessions enabled, we need to sync the // revision ordinal to the appropriate expected value here. - if( self.skip_db_sessions( controller::block_status::irreversible ) ) - db.set_revision( head->block_num ); + if( skip_db_sessions( controller::block_status::irreversible ) ) + db.set_revision( chain_head.block_num() ); } else { ilog( "no irreversible blocks need to be replayed" ); } - if (snapshot_head_block != 0 && !blog_head) { - // loading from snapshot without a block log so fork_db can't be considered valid - fork_db.reset( *head ); - } else if( !except_ptr && !check_shutdown() && fork_db.head() ) { - auto head_block_num = head->block_num; - auto branch = fork_db.fetch_branch( fork_db.head()->id ); - int rev = 0; - for( auto i = branch.rbegin(); i != branch.rend(); ++i ) { - if( check_shutdown() ) break; - if( (*i)->block_num <= head_block_num ) continue; - ++rev; - replay_push_block( (*i)->block, controller::block_status::validated ); + return except_ptr; + } + + void replay(startup_t startup) { + replaying = true; + + auto blog_head = blog.head(); + auto start_block_num = chain_head.block_num() + 1; + std::exception_ptr except_ptr; + + if (blog_head) { + except_ptr = replay_block_log(); + } else { + ilog( "no block log found" ); + } + + try { + if (startup != startup_t::existing_state) + open_fork_db(); + } catch (const fc::exception& e) { + elog( "Unable to open fork database, continuing without reversible blocks: ${e}", ("e", e)); + } + + auto fork_db_reset_root_to_chain_head = [&]() { + fork_db.apply([&](auto& forkdb) { + apply(chain_head, [&](const auto& head) { + if constexpr (std::is_same_v, std::decay_t>) + forkdb.reset_root(head); + }); + }); + }; + + auto switch_from_legacy_if_needed = [&]() { + if (fork_db.version_in_use() == fork_database::in_use_t::legacy) { + // switch to savanna if needed + apply_s(chain_head, [&](const auto& head) { + fork_db.switch_from_legacy(head); + }); } - ilog( "${n} reversible blocks replayed", ("n",rev) ); + }; + + if (startup == startup_t::genesis) { + switch_from_legacy_if_needed(); + auto do_startup = [&](auto& forkdb) { + if( forkdb.head() ) { + if( read_mode == db_read_mode::IRREVERSIBLE && forkdb.head()->id() != forkdb.root()->id() ) { + forkdb.rollback_head_to_root(); + } + wlog( "No existing chain state. Initializing fresh blockchain state." ); + } else { + wlog( "No existing chain state or fork database. Initializing fresh blockchain state and resetting fork database."); + } + + if( !forkdb.head() ) { + fork_db_reset_root_to_chain_head(); + } + }; + fork_db.apply(do_startup); + } + + if( !fork_db_has_root() ) { + switch_from_legacy_if_needed(); + fork_db_reset_root_to_chain_head(); } - if( !fork_db.head() ) { - fork_db.reset( *head ); - } + auto replay_fork_db = [&](auto& forkdb) { + using BSP = std::decay_t; + + auto pending_head = forkdb.pending_head(); + if( pending_head && blog_head && start_block_num <= blog_head->block_num() ) { + ilog( "fork database head ${h}, root ${r}", ("h", pending_head->block_num())( "r", forkdb.root()->block_num() ) ); + if( pending_head->block_num() < chain_head.block_num() || chain_head.block_num() < forkdb.root()->block_num() ) { + ilog( "resetting fork database with new last irreversible block as the new root: ${id}", ("id", chain_head.id()) ); + fork_db_reset_root_to_chain_head(); + } else if( chain_head.block_num() != forkdb.root()->block_num() ) { + auto new_root = forkdb.search_on_branch( pending_head->id(), chain_head.block_num() ); + EOS_ASSERT( new_root, fork_database_exception, + "unexpected error: could not find new LIB in fork database" ); + ilog( "advancing fork database root to new last irreversible block within existing fork database: ${id}", + ("id", new_root->id()) ); + forkdb.mark_valid( new_root ); + forkdb.advance_root( new_root->id() ); + } + } + + if (snapshot_head_block != 0 && !blog.head()) { + // loading from snapshot without a block log so fork_db can't be considered valid + fork_db_reset_root_to_chain_head(); + } else if( !except_ptr && !check_shutdown() && forkdb.head() ) { + auto head_block_num = chain_head.block_num(); + auto branch = fork_db.fetch_branch_from_head(); + int rev = 0; + for( auto i = branch.rbegin(); i != branch.rend(); ++i ) { + if( check_shutdown() ) break; + if( (*i)->block_num() <= head_block_num ) continue; + ++rev; + replay_push_block( *i, controller::block_status::validated ); + } + ilog( "${n} reversible blocks replayed", ("n",rev) ); + } + + if( !forkdb.head() ) { + fork_db_reset_root_to_chain_head(); + } + + }; + fork_db.apply(replay_fork_db); - auto end = fc::time_point::now(); - ilog( "replayed ${n} blocks in ${duration} seconds, ${mspb} ms/block", - ("n", head->block_num + 1 - start_block_num)("duration", (end-start).count()/1000000) - ("mspb", ((end-start).count()/1000.0)/(head->block_num-start_block_num)) ); replaying = false; if( except_ptr ) { @@ -597,25 +1706,37 @@ struct controller_impl { void startup(std::function shutdown, std::function check_shutdown, const snapshot_reader_ptr& snapshot) { EOS_ASSERT( snapshot, snapshot_exception, "No snapshot reader provided" ); - this->shutdown = shutdown; + this->shutdown = std::move(shutdown); + assert(this->shutdown); + this->check_shutdown = std::move(check_shutdown); + assert(this->check_shutdown); try { auto snapshot_load_start_time = fc::time_point::now(); snapshot->validate(); + block_state_pair block_states; if( auto blog_head = blog.head() ) { ilog( "Starting initialization from snapshot and block log ${b}-${e}, this may take a significant amount of time", ("b", blog.first_block_num())("e", blog_head->block_num()) ); - read_from_snapshot( snapshot, blog.first_block_num(), blog_head->block_num() ); + block_states = read_from_snapshot( snapshot, blog.first_block_num(), blog_head->block_num() ); } else { ilog( "Starting initialization from snapshot and no block log, this may take a significant amount of time" ); - read_from_snapshot( snapshot, 0, std::numeric_limits::max() ); - EOS_ASSERT( head->block_num > 0, snapshot_exception, + block_states = read_from_snapshot( snapshot, 0, std::numeric_limits::max() ); + EOS_ASSERT( chain_head.block_num() > 0, snapshot_exception, "Snapshot indicates controller head at block number 0, but that is not allowed. " "Snapshot is invalid." ); - blog.reset( chain_id, head->block_num + 1 ); + blog.reset( chain_id, chain_head.block_num() + 1 ); } - ilog( "Snapshot loaded, lib: ${lib}", ("lib", head->block_num) ); - - init(std::move(check_shutdown)); + ilog( "Snapshot loaded, lib: ${lib}", ("lib", chain_head.block_num()) ); + + init(startup_t::snapshot); + apply_l(chain_head, [&](auto& head) { + if (block_states.second && head->header.contains_header_extension(instant_finality_extension::extension_id())) { + // snapshot generated in transition to savanna + if (fork_db.version_in_use() == fork_database::in_use_t::legacy) { + fork_db.switch_from_legacy(block_states.second); + } + } + }); auto snapshot_load_time = (fc::time_point::now() - snapshot_load_start_time).to_seconds(); ilog( "Finished initialization from snapshot (snapshot load time was ${t}s)", ("t", snapshot_load_time) ); } catch (boost::interprocess::bad_alloc& e) { @@ -633,36 +1754,35 @@ struct controller_impl { ); this->shutdown = std::move(shutdown); - if( fork_db.head() ) { - if( read_mode == db_read_mode::IRREVERSIBLE && fork_db.head()->id != fork_db.root()->id ) { - fork_db.rollback_head_to_root(); - } - wlog( "No existing chain state. Initializing fresh blockchain state." ); - } else { - wlog( "No existing chain state or fork database. Initializing fresh blockchain state and resetting fork database."); - } - initialize_blockchain_state(genesis); // sets head to genesis state + assert(this->shutdown); + this->check_shutdown = std::move(check_shutdown); + assert(this->check_shutdown); - if( !fork_db.head() ) { - fork_db.reset( *head ); - } + initialize_blockchain_state(genesis); // sets chain_head to genesis state if( blog.head() ) { - EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, - "block log does not start with genesis block" - ); + EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, "block log does not start with genesis block" ); } else { - blog.reset( genesis, head->block ); + blog.reset( genesis, chain_head.block() ); } - init(std::move(check_shutdown)); + + init(startup_t::genesis); } void startup(std::function shutdown, std::function check_shutdown) { - EOS_ASSERT( db.revision() >= 1, database_exception, "This version of controller::startup does not work with a fresh state database." ); - EOS_ASSERT( fork_db.head(), fork_database_exception, "No existing fork database despite existing chain state. Replay required." ); + EOS_ASSERT( db.revision() >= 1, database_exception, + "This version of controller::startup does not work with a fresh state database." ); + + open_fork_db(); + + EOS_ASSERT( fork_db_has_head(), fork_database_exception, + "No existing fork database despite existing chain state. Replay required." ); this->shutdown = std::move(shutdown); - uint32_t lib_num = fork_db.root()->block_num; + assert(this->shutdown); + this->check_shutdown = std::move(check_shutdown); + assert(this->check_shutdown); + uint32_t lib_num = fork_db_root_block_num(); auto first_block_num = blog.first_block_num(); if( auto blog_head = blog.head() ) { EOS_ASSERT( first_block_num <= lib_num && lib_num <= blog_head->block_num(), @@ -679,12 +1799,16 @@ struct controller_impl { } } - if( read_mode == db_read_mode::IRREVERSIBLE && fork_db.head()->id != fork_db.root()->id ) { - fork_db.rollback_head_to_root(); - } - head = fork_db.head(); + auto do_startup = [&](auto& forkdb) { + if( read_mode == db_read_mode::IRREVERSIBLE && forkdb.head()->id() != forkdb.root()->id() ) { + forkdb.rollback_head_to_root(); + } + chain_head = block_handle{forkdb.head()}; + }; - init(std::move(check_shutdown)); + fork_db.apply(do_startup); + + init(startup_t::existing_state); } @@ -701,7 +1825,7 @@ struct controller_impl { return header_itr; } - void init(std::function check_shutdown) { + void init(startup_t startup) { auto header_itr = validate_db_version( db ); { @@ -720,16 +1844,16 @@ struct controller_impl { } // At this point head != nullptr - EOS_ASSERT( db.revision() >= head->block_num, fork_database_exception, + EOS_ASSERT( db.revision() >= chain_head.block_num(), fork_database_exception, "fork database head (${head}) is inconsistent with state (${db})", - ("db",db.revision())("head",head->block_num) ); + ("db", db.revision())("head", chain_head.block_num()) ); - if( db.revision() > head->block_num ) { + if( db.revision() > chain_head.block_num() ) { wlog( "database revision (${db}) is greater than head block number (${head}), " "attempting to undo pending changes", - ("db",db.revision())("head",head->block_num) ); + ("db", db.revision())("head", chain_head.block_num()) ); } - while( db.revision() > head->block_num ) { + while( db.revision() > chain_head.block_num() ) { db.undo(); } @@ -737,40 +1861,76 @@ struct controller_impl { // At startup, no transaction specific logging is possible if (auto dm_logger = get_deep_mind_logger(false)) { - dm_logger->on_startup(db, head->block_num); + dm_logger->on_startup(db, chain_head.block_num()); } if( conf.integrity_hash_on_start ) ilog( "chain database started with hash: ${hash}", ("hash", calculate_integrity_hash()) ); okay_to_print_integrity_hash_on_stop = true; - replay( check_shutdown ); // replay any irreversible and reversible blocks ahead of current head + replay( startup ); // replay any irreversible and reversible blocks ahead of current head if( check_shutdown() ) return; // At this point head != nullptr && fork_db.head() != nullptr && fork_db.root() != nullptr. - // Furthermore, fork_db.root()->block_num <= lib_num. + // Furthermore, fork_db.root()->block_num() <= lib_num. // Also, even though blog.head() may still be nullptr, blog.first_block_num() is guaranteed to be lib_num + 1. - if( read_mode != db_read_mode::IRREVERSIBLE - && fork_db.pending_head()->id != fork_db.head()->id - && fork_db.head()->id == fork_db.root()->id - ) { - wlog( "read_mode has changed from irreversible: applying best branch from fork database" ); + auto finish_init = [&](auto& forkdb) { + if( read_mode != db_read_mode::IRREVERSIBLE ) { + auto pending_head = forkdb.pending_head(); + auto head = forkdb.head(); + if ( head && pending_head && pending_head->id() != head->id() && head->id() == forkdb.root()->id() ) { + ilog( "read_mode has changed from irreversible: applying best branch from fork database" ); + + for( ; pending_head->id() != forkdb.head()->id(); pending_head = forkdb.pending_head() ) { + ilog( "applying branch from fork database ending with block: ${id}", ("id", pending_head->id()) ); + controller::block_report br; + maybe_switch_forks( br, pending_head, controller::block_status::complete, {}, trx_meta_cache_lookup{} ); + } + } + } + }; - for( auto pending_head = fork_db.pending_head(); - pending_head->id != fork_db.head()->id; - pending_head = fork_db.pending_head() - ) { - wlog( "applying branch from fork database ending with block: ${id}", ("id", pending_head->id) ); - controller::block_report br; - maybe_switch_forks( br, pending_head, controller::block_status::complete, forked_branch_callback{}, trx_meta_cache_lookup{} ); + fork_db.apply(finish_init); + + // At Leap startup, we want to provide to our local finalizers the correct safety information + // to use if they don't already have one. + // If we start at a block prior to the IF transition, that information will be provided when + // we create the new `fork_db_if`. + // If we start at a block during or after the IF transition, we need to provide this information + // at startup. + // --------------------------------------------------------------------------------------------- + if (auto in_use = fork_db.version_in_use(); in_use == fork_database::in_use_t::both || in_use == fork_database::in_use_t::savanna) { + // we are already past the IF transition point where we create the updated fork_db. + // so we can't rely on the finalizer safety information update happening during the transition. + // see https://github.com/AntelopeIO/leap/issues/2070#issuecomment-1941901836 + // ------------------------------------------------------------------------------------------- + if (in_use == fork_database::in_use_t::both) { + // fork_db_legacy is present as well, which means that we have not completed the transition + auto set_finalizer_defaults = [&](auto& forkdb) -> void { + auto lib = forkdb.root(); + my_finalizers.set_default_safety_information( + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {}, + .lock = {lib->id(), lib->timestamp()} }); + }; + fork_db.apply_s(set_finalizer_defaults); + } else { + // we are past the IF transition. + auto set_finalizer_defaults = [&](auto& forkdb) -> void { + auto lib = forkdb.root(); + my_finalizers.set_default_safety_information( + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {}, + .lock = {lib->id(), lib->timestamp()} }); + }; + fork_db.apply_s(set_finalizer_defaults); } } } ~controller_impl() { - thread_pool.stop(); pending.reset(); //only log this not just if configured to, but also if initialization made it to the point we'd log the startup too if(okay_to_print_integrity_hash_on_stop && conf.integrity_hash_on_stop) @@ -788,11 +1948,6 @@ struct controller_impl { void clear_all_undo() { // Rewind the database to the last irreversible block db.undo_all(); - /* - FC_ASSERT(db.revision() == self.head_block_num(), - "Chainbase revision does not match head block num", - ("rev", db.revision())("head_block", self.head_block_num())); - */ } void add_contract_tables_to_snapshot( const snapshot_writer_ptr& snapshot ) const { @@ -850,6 +2005,22 @@ struct controller_impl { }); } + block_state_pair get_block_state_to_snapshot() const + { + return apply(chain_head, overloaded{ + [&](const block_state_legacy_ptr& head) -> block_state_pair { + if (fork_db.version_in_use() == fork_database::in_use_t::both) { + return fork_db.apply_s([&](const auto& forkdb) -> block_state_pair { + return { head, forkdb.head() }; + }); + } + return block_state_pair{ head, {} }; + }, + [](const block_state_ptr& head) { + return block_state_pair{ {}, head }; + }}); + } + void add_to_snapshot( const snapshot_writer_ptr& snapshot ) { // clear in case the previous call to clear did not finish in time of deadline clear_expired_input_transactions( fc::time_point::maximum() ); @@ -858,10 +2029,14 @@ struct controller_impl { section.add_row(chain_snapshot_header(), db); }); - snapshot->write_section("eosio::chain::block_state", [this]( auto §ion ){ - section.template add_row(*head, db); - }); + apply(chain_head, [&](const auto& head) { + snapshot_detail::snapshot_block_state_data_v7 block_state_data(get_block_state_to_snapshot()); + snapshot->write_section("eosio::chain::block_state", [&]( auto& section ) { + section.add_row(block_state_data, db); + }); + }); + controller_index_set::walk_indices([this, &snapshot]( auto utils ){ using value_t = typename decltype(utils)::index_t::value_type; @@ -901,57 +2076,88 @@ struct controller_impl { return genesis; } - void read_from_snapshot( const snapshot_reader_ptr& snapshot, uint32_t blog_start, uint32_t blog_end ) { + block_state_pair read_from_snapshot( const snapshot_reader_ptr& snapshot, uint32_t blog_start, uint32_t blog_end ) { chain_snapshot_header header; snapshot->read_section([this, &header]( auto §ion ){ section.read_row(header, db); header.validate(); }); - { /// load and upgrade the block header state - block_header_state_legacy head_header_state; - using v2 = legacy::snapshot_block_header_state_v2; + using namespace snapshot_detail; + using v7 = snapshot_block_state_data_v7; + + block_state_pair result; + if (header.version >= v7::minimum_version) { + // loading a snapshot saved by Leap 6.0 and above. + // ----------------------------------------------- + if (std::clamp(header.version, v7::minimum_version, v7::maximum_version) == header.version ) { + snapshot->read_section("eosio::chain::block_state", [this, &result]( auto §ion ){ + v7 block_state_data; + section.read_row(block_state_data, db); + assert(block_state_data.bs_l || block_state_data.bs); + if (block_state_data.bs_l) { + auto legacy_ptr = std::make_shared(std::move(*block_state_data.bs_l)); + chain_head = block_handle{legacy_ptr}; + result.first = std::move(legacy_ptr); + } else { + auto bs_ptr = std::make_shared(std::move(*block_state_data.bs)); + chain_head = block_handle{bs_ptr}; + result.second = std::move(bs_ptr); + } + }); + } else { + EOS_THROW(snapshot_exception, "Unsupported block_state version"); + } + } else { + // loading a snapshot saved by Leap up to version 5. + // ------------------------------------------------- + auto head_header_state = std::make_shared(); + using v2 = snapshot_block_header_state_legacy_v2; + using v3 = snapshot_block_header_state_legacy_v3; if (std::clamp(header.version, v2::minimum_version, v2::maximum_version) == header.version ) { snapshot->read_section("eosio::chain::block_state", [this, &head_header_state]( auto §ion ) { - legacy::snapshot_block_header_state_v2 legacy_header_state; + v2 legacy_header_state; section.read_row(legacy_header_state, db); - head_header_state = block_header_state_legacy(std::move(legacy_header_state)); + static_cast(*head_header_state) = block_header_state_legacy(std::move(legacy_header_state)); }); - } else { + } else if (std::clamp(header.version, v3::minimum_version, v3::maximum_version) == header.version ) { snapshot->read_section("eosio::chain::block_state", [this,&head_header_state]( auto §ion ){ - section.read_row(head_header_state, db); + v3 legacy_header_state; + section.read_row(legacy_header_state, db); + static_cast(*head_header_state) = block_header_state_legacy(std::move(legacy_header_state)); }); + } else { + EOS_THROW(snapshot_exception, "Unsupported block_header_state version"); } - - snapshot_head_block = head_header_state.block_num; - EOS_ASSERT( blog_start <= (snapshot_head_block + 1) && snapshot_head_block <= blog_end, - block_log_exception, - "Block log is provided with snapshot but does not contain the head block from the snapshot nor a block right after it", - ("snapshot_head_block", snapshot_head_block) - ("block_log_first_num", blog_start) - ("block_log_last_num", blog_end) - ); - - head = std::make_shared(); - static_cast(*head) = head_header_state; + chain_head = block_handle{head_header_state}; + result.first = head_header_state; } + snapshot_head_block = chain_head.block_num(); + EOS_ASSERT( blog_start <= (snapshot_head_block + 1) && snapshot_head_block <= blog_end, + block_log_exception, + "Block log is provided with snapshot but does not contain the head block from the snapshot nor a block right after it", + ("snapshot_head_block", snapshot_head_block) + ("block_log_first_num", blog_start) + ("block_log_last_num", blog_end) + ); + controller_index_set::walk_indices([this, &snapshot, &header]( auto utils ){ using value_t = typename decltype(utils)::index_t::value_type; // skip the table_id_object as its inlined with contract tables section - if (std::is_same::value) { + if (std::is_same_v) { return; } // skip the database_header as it is only relevant to in-memory database - if (std::is_same::value) { + if (std::is_same_v) { return; } // special case for in-place upgrade of global_property_object - if (std::is_same::value) { + if (std::is_same_v) { using v2 = legacy::snapshot_global_property_object_v2; using v3 = legacy::snapshot_global_property_object_v3; using v4 = legacy::snapshot_global_property_object_v4; @@ -1014,7 +2220,7 @@ struct controller_impl { authorization.read_from_snapshot(snapshot); resource_limits.read_from_snapshot(snapshot); - db.set_revision( head->block_num ); + db.set_revision( chain_head.block_num() ); db.create([](const auto& header){ // nothing to do }); @@ -1024,6 +2230,20 @@ struct controller_impl { "chain ID in snapshot (${snapshot_chain_id}) does not match the chain ID that controller was constructed with (${controller_chain_id})", ("snapshot_chain_id", gpo.chain_id)("controller_chain_id", chain_id) ); + + return result; + } + + 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() { @@ -1084,7 +2304,7 @@ struct controller_impl { const auto& tapos_block_summary = db.get(1); db.modify( tapos_block_summary, [&]( auto& bs ) { - bs.block_id = head->id; + bs.block_id = chain_head.id(); }); genesis.initial_configuration.validate(); @@ -1140,26 +2360,7 @@ struct controller_impl { } auto& bb = std::get(pending->_block_stage); - auto orig_trx_receipts_size = bb._pending_trx_receipts.size(); - auto orig_trx_metas_size = bb._pending_trx_metas.size(); - auto orig_trx_receipt_digests_size = std::holds_alternative(bb._trx_mroot_or_receipt_digests) ? - std::get(bb._trx_mroot_or_receipt_digests).size() : 0; - auto orig_action_receipt_digests_size = bb._action_receipt_digests.size(); - std::function callback = [this, - orig_trx_receipts_size, - orig_trx_metas_size, - orig_trx_receipt_digests_size, - orig_action_receipt_digests_size]() - { - auto& bb = std::get(pending->_block_stage); - bb._pending_trx_receipts.resize(orig_trx_receipts_size); - bb._pending_trx_metas.resize(orig_trx_metas_size); - if( std::holds_alternative(bb._trx_mroot_or_receipt_digests) ) - std::get(bb._trx_mroot_or_receipt_digests).resize(orig_trx_receipt_digests_size); - bb._action_receipt_digests.resize(orig_action_receipt_digests_size); - }; - - return fc::make_scoped_exit( std::move(callback) ); + return fc::make_scoped_exit(bb.make_block_restore_point()); } transaction_trace_ptr apply_onerror( const generated_transaction& gtrx, @@ -1176,18 +2377,20 @@ struct controller_impl { // Deliver onerror action containing the failed deferred transaction directly back to the sender. etrx.actions.emplace_back( vector{{gtrx.sender, config::active_name}}, onerror( gtrx.sender_id, gtrx.packed_trx.data(), gtrx.packed_trx.size() ) ); - if( self.is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) { + if( is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) { etrx.expiration = time_point_sec(); etrx.ref_block_num = 0; etrx.ref_block_prefix = 0; } else { - etrx.expiration = time_point_sec{self.pending_block_time() + fc::microseconds(999'999)}; // Round up to nearest second to avoid appearing expired - etrx.set_reference_block( self.head_block_id() ); + etrx.expiration = time_point_sec{pending_block_time() + fc::microseconds(999'999)}; // Round up to nearest second to avoid appearing expired + etrx.set_reference_block( chain_head.id() ); } + auto& bb = std::get(pending->_block_stage); + transaction_checktime_timer trx_timer(timer); const packed_transaction trx( std::move( etrx ) ); - transaction_context trx_context( self, trx, trx.id(), std::move(trx_timer), start ); + transaction_context trx_context( self, trx, trx.id(), std::move(trx_timer), bb.action_receipt_digests().store_which(), start ); if (auto dm_logger = get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_onerror(etrx); @@ -1198,6 +2401,7 @@ struct controller_impl { trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time; trx_context.billed_cpu_time_us = billed_cpu_time_us; trx_context.enforce_whiteblacklist = enforce_whiteblacklist; + transaction_trace_ptr trace = trx_context.trace; auto handle_exception = [&](const auto& e) @@ -1217,8 +2421,8 @@ struct controller_impl { auto restore = make_block_restore_point(); trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::soft_fail, trx_context.billed_cpu_time_us, trace->net_usage ); - fc::move_append( std::get(pending->_block_stage)._action_receipt_digests, - std::move(trx_context.executed_action_receipt_digests) ); + + bb.action_receipt_digests().append(std::move(trx_context.executed_action_receipts)); trx_context.squash(); restore.cancel(); @@ -1294,11 +2498,11 @@ struct controller_impl { { try { auto start = fc::time_point::now(); - const bool validating = !self.is_speculative_block(); + const bool validating = !is_speculative_block(); EOS_ASSERT( !validating || explicit_billed_cpu_time, transaction_exception, "validating requires explicit billing" ); maybe_session undo_session; - if ( !self.skip_db_sessions() ) + if ( !skip_db_sessions() ) undo_session = maybe_session(db); auto gtrx = generated_transaction(gto); @@ -1314,9 +2518,9 @@ struct controller_impl { fc::datastream ds( gtrx.packed_trx.data(), gtrx.packed_trx.size() ); // check delay_until only before disable_deferred_trxs_stage_1 is activated. - if( !self.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) ) { - EOS_ASSERT( gtrx.delay_until <= self.pending_block_time(), transaction_exception, "this transaction isn't ready", - ("gtrx.delay_until",gtrx.delay_until)("pbt",self.pending_block_time()) ); + if( !is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) ) { + EOS_ASSERT( gtrx.delay_until <= pending_block_time(), transaction_exception, "this transaction isn't ready", + ("gtrx.delay_until",gtrx.delay_until)("pbt",pending_block_time()) ); } signed_transaction dtrx; @@ -1330,12 +2534,12 @@ struct controller_impl { // can only be retired as expired, and it can be retired as expired // regardless of whether its delay_util or expiration times have been reached. transaction_trace_ptr trace; - if( self.is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) || gtrx.expiration < self.pending_block_time() ) { + if( is_builtin_activated( builtin_protocol_feature_t::disable_deferred_trxs_stage_1 ) || gtrx.expiration < pending_block_time() ) { trace = std::make_shared(); trace->id = gtrx.trx_id; - trace->block_num = self.head_block_num() + 1; - trace->block_time = self.pending_block_time(); - trace->producer_block_id = self.pending_producer_block_id(); + trace->block_num = chain_head.block_num() + 1; + trace->block_time = pending_block_time(); + trace->producer_block_id = pending_producer_block_id(); trace->scheduled = true; trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction trace->account_ram_delta = account_delta( gtrx.payer, trx_removal_ram_delta ); @@ -1344,7 +2548,7 @@ struct controller_impl { pending->_block_report.total_elapsed_time += trace->elapsed; pending->_block_report.total_time += trace->elapsed; dmlog_applied_transaction(trace); - emit( self.applied_transaction, std::tie(trace, trx->packed_trx()) ); + emit( applied_transaction, std::tie(trace, trx->packed_trx()) ); undo_session.squash(); return trace; } @@ -1355,9 +2559,10 @@ struct controller_impl { in_trx_requiring_checks = true; uint32_t cpu_time_to_bill_us = billed_cpu_time_us; + auto& bb = std::get(pending->_block_stage); transaction_checktime_timer trx_timer( timer ); - transaction_context trx_context( self, *trx->packed_trx(), gtrx.trx_id, std::move(trx_timer) ); + transaction_context trx_context( self, *trx->packed_trx(), gtrx.trx_id, std::move(trx_timer), bb.action_receipt_digests().store_which() ); trx_context.leeway = fc::microseconds(0); // avoid stealing cpu resource trx_context.block_deadline = block_deadline; trx_context.max_transaction_time_subjective = max_transaction_time; @@ -1383,7 +2588,7 @@ struct controller_impl { try { trx_context.init_for_deferred_trx( gtrx.published ); - if( trx_context.enforce_whiteblacklist && self.is_speculative_block() ) { + if( trx_context.enforce_whiteblacklist && is_speculative_block() ) { flat_set actors; for( const auto& act : trx->packed_trx()->get_transaction().actions ) { for( const auto& auth : act.authorization ) { @@ -1403,13 +2608,12 @@ struct controller_impl { trx_context.billed_cpu_time_us, trace->net_usage ); - fc::move_append( std::get(pending->_block_stage)._action_receipt_digests, - std::move(trx_context.executed_action_receipt_digests) ); + bb.action_receipt_digests().append(std::move(trx_context.executed_action_receipts)); trace->account_ram_delta = account_delta( gtrx.payer, trx_removal_ram_delta ); dmlog_applied_transaction(trace); - emit( self.applied_transaction, std::tie(trace, trx->packed_trx()) ); + emit( applied_transaction, std::tie(trace, trx->packed_trx()) ); trx_context.squash(); undo_session.squash(); @@ -1453,7 +2657,7 @@ struct controller_impl { trace->account_ram_delta = account_delta( gtrx.payer, trx_removal_ram_delta ); trace->elapsed = fc::time_point::now() - start; dmlog_applied_transaction(trace); - emit( self.applied_transaction, std::tie(trace, trx->packed_trx()) ); + emit( applied_transaction, std::tie(trace, trx->packed_trx()) ); undo_session.squash(); pending->_block_report.total_net_usage += trace->net_usage; if( trace->receipt ) pending->_block_report.total_cpu_usage_us += trace->receipt->cpu_usage_us; @@ -1478,8 +2682,7 @@ struct controller_impl { // hard failure logic if( !validating ) { - auto& rl = self.get_mutable_resource_limits_manager(); - rl.update_account_usage( trx_context.bill_to_accounts, block_timestamp_type(self.pending_block_time()).slot ); + resource_limits.update_account_usage( trx_context.bill_to_accounts, block_timestamp_type(pending_block_time()).slot ); int64_t account_cpu_limit = 0; std::tie( std::ignore, account_cpu_limit, std::ignore, std::ignore ) = trx_context.max_bandwidth_billed_accounts_can_pay( true ); @@ -1492,18 +2695,18 @@ struct controller_impl { } resource_limits.add_transaction_usage( trx_context.bill_to_accounts, cpu_time_to_bill_us, 0, - block_timestamp_type(self.pending_block_time()).slot ); // Should never fail + block_timestamp_type(pending_block_time()).slot ); // Should never fail trace->receipt = push_receipt(gtrx.trx_id, transaction_receipt::hard_fail, cpu_time_to_bill_us, 0); trace->account_ram_delta = account_delta( gtrx.payer, trx_removal_ram_delta ); dmlog_applied_transaction(trace); - emit( self.applied_transaction, std::tie(trace, trx->packed_trx()) ); + emit( applied_transaction, std::tie(trace, trx->packed_trx()) ); undo_session.squash(); } else { dmlog_applied_transaction(trace); - emit( self.applied_transaction, std::tie(trace, trx->packed_trx()) ); + emit( applied_transaction, std::tie(trace, trx->packed_trx()) ); } pending->_block_report.total_net_usage += trace->net_usage; @@ -1523,15 +2726,16 @@ struct controller_impl { uint64_t cpu_usage_us, uint64_t net_usage ) { uint64_t net_usage_words = net_usage / 8; EOS_ASSERT( net_usage_words*8 == net_usage, transaction_exception, "net_usage is not divisible by 8" ); - auto& receipts = std::get(pending->_block_stage)._pending_trx_receipts; + auto& bb = std::get(pending->_block_stage); + auto& receipts = bb.pending_trx_receipts(); receipts.emplace_back( trx ); transaction_receipt& r = receipts.back(); r.cpu_usage_us = cpu_usage_us; r.net_usage_words = net_usage_words; r.status = status; - auto& bb = std::get(pending->_block_stage); - if( std::holds_alternative(bb._trx_mroot_or_receipt_digests) ) - std::get(bb._trx_mroot_or_receipt_digests).emplace_back( r.digest() ); + auto& mroot_or_digests = bb.trx_mroot_or_receipt_digests(); + if( std::holds_alternative(mroot_or_digests) ) + std::get(mroot_or_digests).emplace_back( r.digest() ); return r; } @@ -1552,7 +2756,7 @@ struct controller_impl { transaction_trace_ptr trace; try { auto start = fc::time_point::now(); - const bool check_auth = !self.skip_auth_check() && !trx->implicit() && !trx->is_read_only(); + const bool check_auth = !skip_auth_check() && !trx->implicit() && !trx->is_read_only(); const fc::microseconds sig_cpu_usage = trx->signature_cpu_usage(); if( !explicit_billed_cpu_time ) { @@ -1565,10 +2769,13 @@ struct controller_impl { } } + auto& bb = std::get(pending->_block_stage); + const signed_transaction& trn = trx->packed_trx()->get_signed_transaction(); transaction_checktime_timer trx_timer(timer); - transaction_context trx_context(self, *trx->packed_trx(), trx->id(), std::move(trx_timer), start, trx->get_trx_type()); - if ((bool)subjective_cpu_leeway && self.is_speculative_block()) { + transaction_context trx_context(self, *trx->packed_trx(), trx->id(), std::move(trx_timer), + bb.action_receipt_digests().store_which(), start, trx->get_trx_type()); + if ((bool)subjective_cpu_leeway && is_speculative_block()) { trx_context.leeway = *subjective_cpu_leeway; } trx_context.block_deadline = block_deadline; @@ -1619,7 +2826,7 @@ struct controller_impl { ? transaction_receipt::executed : transaction_receipt::delayed; trace->receipt = push_receipt(*trx->packed_trx(), s, trx_context.billed_cpu_time_us, trace->net_usage); - std::get(pending->_block_stage)._pending_trx_metas.emplace_back(trx); + bb.pending_trx_metas().emplace_back(trx); } else { transaction_receipt_header r; r.status = transaction_receipt::executed; @@ -1629,17 +2836,17 @@ struct controller_impl { } if ( !trx->is_read_only() ) { - fc::move_append( std::get(pending->_block_stage)._action_receipt_digests, - std::move(trx_context.executed_action_receipt_digests) ); - if ( !trx->is_dry_run() ) { - // call the accept signal but only once for this transaction - if (!trx->accepted) { - trx->accepted = true; - } - - dmlog_applied_transaction(trace, &trn); - emit(self.applied_transaction, std::tie(trace, trx->packed_trx())); - } + bb.action_receipt_digests().append(std::move(trx_context.executed_action_receipts)); + + if ( !trx->is_dry_run() ) { + // call the accept signal but only once for this transaction + if (!trx->accepted) { + trx->accepted = true; + } + + dmlog_applied_transaction(trace, &trn); + emit(applied_transaction, std::tie(trace, trx->packed_trx())); + } } if ( trx->is_transient() ) { @@ -1682,7 +2889,7 @@ struct controller_impl { if (!trx->is_transient()) { dmlog_applied_transaction(trace); - emit(self.applied_transaction, std::tie(trace, trx->packed_trx())); + emit(applied_transaction, std::tie(trace, trx->packed_trx())); pending->_block_report.total_net_usage += trace->net_usage; if( trace->receipt ) pending->_block_report.total_cpu_usage_us += trace->receipt->cpu_usage_us; @@ -1703,38 +2910,44 @@ struct controller_impl { { EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); - emit( self.block_start, head->block_num + 1 ); + emit( block_start, chain_head.block_num() + 1 ); // at block level, no transaction specific logging is possible if (auto dm_logger = get_deep_mind_logger(false)) { // The head block represents the block just before this one that is about to start, so add 1 to get this block num - dm_logger->on_start_block(head->block_num + 1); + dm_logger->on_start_block(chain_head.block_num() + 1); } - auto guard_pending = fc::make_scoped_exit([this, head_block_num=head->block_num](){ + auto guard_pending = fc::make_scoped_exit([this, head_block_num=chain_head.block_num()]() { protocol_features.popped_blocks_to( head_block_num ); pending.reset(); }); - if (!self.skip_db_sessions(s)) { - EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", - ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); - - pending.emplace( maybe_session(db), *head, when, confirm_block_count, new_protocol_feature_activations ); - } else { - pending.emplace( maybe_session(), *head, when, confirm_block_count, new_protocol_feature_activations ); - } + EOS_ASSERT( skip_db_sessions(s) || db.revision() == chain_head.block_num(), database_exception, + "db revision is not on par with head block", + ("db.revision()", db.revision())("controller_head_block", chain_head.block_num())("fork_db_head_block", fork_db_head_block_num()) ); + + apply(chain_head, overloaded{ + [&](const block_state_legacy_ptr& head) { + maybe_session session = skip_db_sessions(s) ? maybe_session() : maybe_session(db); + pending.emplace(std::move(session), *head, when, confirm_block_count, new_protocol_feature_activations); + }, + [&](const block_state_ptr& head) { + maybe_session session = skip_db_sessions(s) ? maybe_session() : maybe_session(db); + building_block_input bbi{head->id(), head->timestamp(), when, head->get_scheduled_producer(when).producer_name, + new_protocol_feature_activations}; + pending.emplace(std::move(session), *head, bbi); + } + }); pending->_block_status = s; pending->_producer_block_id = producer_block_id; auto& bb = std::get(pending->_block_stage); - const auto& pbhs = bb._pending_block_header_state_legacy; // block status is either ephemeral or incomplete. Modify state of speculative block only if we are building a // speculative incomplete block (otherwise we need clean state for head mode, ephemeral block) - if ( pending->_block_status != controller::block_status::ephemeral ) - { + if ( pending->_block_status != controller::block_status::ephemeral ) { const auto& pso = db.get(); auto num_preactivated_protocol_features = pso.preactivated_protocol_features.size(); @@ -1775,9 +2988,9 @@ struct controller_impl { trigger_activation_handler( *f.builtin_feature ); } - protocol_features.activate_feature( feature_digest, pbhs.block_num ); + protocol_features.activate_feature( feature_digest, bb.block_num() ); - ++bb._num_new_protocol_features_that_have_activated; + ++bb.num_new_protocol_features_activated(); } if( num_preactivated_features_that_have_activated == num_preactivated_protocol_features ) { @@ -1794,35 +3007,41 @@ struct controller_impl { ps.preactivated_protocol_features.clear(); for (const auto& digest : new_protocol_feature_activations) - ps.activated_protocol_features.emplace_back(digest, pbhs.block_num); + ps.activated_protocol_features.emplace_back(digest, bb.block_num()); }); } - const auto& gpo = self.get_global_properties(); + const auto& gpo = db.get(); + + // instant finality uses alternative method for changing producer schedule + bb.apply_l([&](building_block::building_block_legacy& bb_legacy) { + pending_block_header_state_legacy& pbhs = bb_legacy.pending_block_header_state; + + if( gpo.proposed_schedule_block_num && // if there is a proposed schedule that was proposed in a block ... + ( *gpo.proposed_schedule_block_num <= pbhs.dpos_irreversible_blocknum ) && // ... that has now become irreversible ... + pbhs.prev_pending_schedule.schedule.producers.size() == 0 // ... and there was room for a new pending schedule prior to any possible promotion + ) + { + // Promote proposed schedule to pending schedule. + if( !replaying ) { + ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", + ("proposed_num", *gpo.proposed_schedule_block_num)("n", pbhs.block_num) + ("lib", pbhs.dpos_irreversible_blocknum) + ("schedule", bb_legacy.new_pending_producer_schedule ) ); + } - if( gpo.proposed_schedule_block_num && // if there is a proposed schedule that was proposed in a block ... - ( *gpo.proposed_schedule_block_num <= pbhs.dpos_irreversible_blocknum ) && // ... that has now become irreversible ... - pbhs.prev_pending_schedule.schedule.producers.size() == 0 // ... and there was room for a new pending schedule prior to any possible promotion - ) - { - // Promote proposed schedule to pending schedule. - if( !replaying ) { - ilog( "promoting proposed schedule (set in block ${proposed_num}) to pending; current block: ${n} lib: ${lib} schedule: ${schedule} ", - ("proposed_num", *gpo.proposed_schedule_block_num)("n", pbhs.block_num) - ("lib", pbhs.dpos_irreversible_blocknum) - ("schedule", producer_authority_schedule::from_shared(gpo.proposed_schedule) ) ); - } + EOS_ASSERT( gpo.proposed_schedule.version == pbhs.active_schedule_version + 1, + producer_schedule_exception, "wrong producer schedule version specified" ); - EOS_ASSERT( gpo.proposed_schedule.version == pbhs.active_schedule_version + 1, - producer_schedule_exception, "wrong producer schedule version specified" ); + bb_legacy.new_pending_producer_schedule = producer_authority_schedule::from_shared(gpo.proposed_schedule); - std::get(pending->_block_stage)._new_pending_producer_schedule = producer_authority_schedule::from_shared(gpo.proposed_schedule); - db.modify( gpo, [&]( auto& gp ) { - gp.proposed_schedule_block_num = std::optional(); - gp.proposed_schedule.version=0; - gp.proposed_schedule.producers.clear(); - }); - } + db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = std::optional(); + gp.proposed_schedule.version=0; + gp.proposed_schedule.producers.clear(); + }); + } + }); try { transaction_metadata_ptr onbtrx = @@ -1835,7 +3054,7 @@ struct controller_impl { auto trace = push_transaction( onbtrx, fc::time_point::maximum(), fc::microseconds::maximum(), gpo.configuration.min_transaction_cpu_usage, true, 0 ); if( trace->except ) { - wlog("onblock ${block_num} is REJECTING: ${entire_trace}",("block_num", head->block_num + 1)("entire_trace", trace)); + wlog("onblock ${block_num} is REJECTING: ${entire_trace}",("block_num", chain_head.block_num() + 1)("entire_trace", trace)); } } catch( const std::bad_alloc& e ) { elog( "on block transaction failed due to a std::bad_alloc" ); @@ -1860,83 +3079,66 @@ struct controller_impl { guard_pending.cancel(); } /// start_block - void finalize_block() + void assemble_block(bool validating, std::optional validating_qc_data, const block_state_ptr& validating_bsp) { EOS_ASSERT( pending, block_validate_exception, "it is not valid to finalize when there is no pending block"); - EOS_ASSERT( std::holds_alternative(pending->_block_stage), block_validate_exception, "already called finalize_block"); + EOS_ASSERT( std::holds_alternative(pending->_block_stage), block_validate_exception, "already called finish_block"); try { + auto& bb = std::get(pending->_block_stage); - auto& pbhs = pending->get_pending_block_header_state_legacy(); + // Update resource limits: + resource_limits.process_account_limit_updates(); + const auto& chain_config = db.get().configuration; + resource_limits.set_block_parameters( + { EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct), + chain_config.max_block_cpu_usage, + config::block_cpu_usage_average_window_ms / config::block_interval_ms, + config::maximum_elastic_resource_multiplier, {99, 100}, {1000, 999}}, + { EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), + chain_config.max_block_net_usage, + config::block_size_average_window_ms / config::block_interval_ms, + config::maximum_elastic_resource_multiplier, {99, 100}, {1000, 999}} + ); + resource_limits.process_block_usage(bb.block_num()); + + // Any proposer policy? + std::unique_ptr new_proposer_policy; + auto process_new_proposer_policy = [&](auto&) -> void { + const auto& gpo = db.get(); + if (gpo.proposed_schedule_block_num) { + new_proposer_policy = std::make_unique(); + new_proposer_policy->active_time = detail::get_next_next_round_block_time(bb.timestamp()); + new_proposer_policy->proposer_schedule = producer_authority_schedule::from_shared(gpo.proposed_schedule); + ilog("Scheduling proposer schedule change at ${t}: ${s}", ("t", new_proposer_policy->active_time)("s", new_proposer_policy->proposer_schedule)); + + db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = std::optional(); + gp.proposed_schedule.version = 0; + gp.proposed_schedule.producers.clear(); + }); + } + }; + apply_s(chain_head, process_new_proposer_policy); - auto& bb = std::get(pending->_block_stage); + auto assembled_block = + bb.assemble_block(thread_pool.get_executor(), protocol_features.get_protocol_feature_set(), fork_db, std::move(new_proposer_policy), + validating, std::move(validating_qc_data), validating_bsp); - auto action_merkle_fut = post_async_task( thread_pool.get_executor(), - [ids{std::move( bb._action_receipt_digests )}]() mutable { - return merkle( std::move( ids ) ); - } ); - const bool calc_trx_merkle = !std::holds_alternative(bb._trx_mroot_or_receipt_digests); - std::future trx_merkle_fut; - if( calc_trx_merkle ) { - trx_merkle_fut = post_async_task( thread_pool.get_executor(), - [ids{std::move( std::get(bb._trx_mroot_or_receipt_digests) )}]() mutable { - return merkle( std::move( ids ) ); - } ); - } - - // Update resource limits: - resource_limits.process_account_limit_updates(); - const auto& chain_config = self.get_global_properties().configuration; - uint64_t CPU_TARGET = EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct); - resource_limits.set_block_parameters( - { CPU_TARGET, chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, config::maximum_elastic_resource_multiplier, {99, 100}, {1000, 999}}, - {EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, config::maximum_elastic_resource_multiplier, {99, 100}, {1000, 999}} - ); - resource_limits.process_block_usage(pbhs.block_num); - - // Create (unsigned) block: - auto block_ptr = std::make_shared( pbhs.make_block_header( - calc_trx_merkle ? trx_merkle_fut.get() : std::get(bb._trx_mroot_or_receipt_digests), - action_merkle_fut.get(), - bb._new_pending_producer_schedule, - std::move( bb._new_protocol_feature_activations ), - protocol_features.get_protocol_feature_set() - ) ); - - block_ptr->transactions = std::move( bb._pending_trx_receipts ); - - auto id = block_ptr->calculate_id(); - - // Update TaPoS table: - create_block_summary( id ); - - /* - ilog( "finalized block ${n} (${id}) at ${t} by ${p} (${signing_key}); schedule_version: ${v} lib: ${lib} #dtrxs: ${ndtrxs} ${np}", - ("n",pbhs.block_num) - ("id",id) - ("t",pbhs.timestamp) - ("p",pbhs.producer) - ("signing_key", pbhs.block_signing_key) - ("v",pbhs.active_schedule_version) - ("lib",pbhs.dpos_irreversible_blocknum) - ("ndtrxs",db.get_index().size()) - ("np",block_ptr->new_producers) - ); - */ + // Update TaPoS table: + create_block_summary( assembled_block.id() ); - pending->_block_stage = assembled_block{ - id, - std::move( bb._pending_block_header_state_legacy ), - std::move( bb._pending_trx_metas ), - std::move( block_ptr ), - std::move( bb._new_pending_producer_schedule ) - }; - } FC_CAPTURE_AND_RETHROW() } /// finalize_block + pending->_block_stage = std::move(assembled_block); + } + FC_CAPTURE_AND_RETHROW() + } /** * @post regardless of the success of commit block there is no active pending block */ - void commit_block( controller::block_status s ) { + void commit_block( controller::block_report& br, controller::block_status s ) { + fc::time_point start = fc::time_point::now(); + auto reset_pending_on_exit = fc::make_scoped_exit([this]{ pending.reset(); }); @@ -1945,28 +3147,74 @@ struct controller_impl { EOS_ASSERT( std::holds_alternative(pending->_block_stage), block_validate_exception, "cannot call commit_block until pending block is completed" ); - const auto& bsp = std::get(pending->_block_stage)._block_state; + auto& cb = std::get(pending->_block_stage); - if( s == controller::block_status::incomplete ) { - fork_db.add( bsp ); - fork_db.mark_valid( bsp ); - emit( self.accepted_block_header, std::tie(bsp->block, bsp->id) ); - EOS_ASSERT( bsp == fork_db.head(), fork_database_exception, "committed block did not become the new head in fork database"); - } else if (s != controller::block_status::irreversible) { - fork_db.mark_valid( bsp ); + if (s != controller::block_status::irreversible) { + auto add_completed_block = [&](auto& forkdb) { + assert(std::holds_alternative>(cb.bsp.internal())); + const auto& bsp = std::get>(cb.bsp.internal()); + if( s == controller::block_status::incomplete ) { + forkdb.add( bsp, mark_valid_t::yes, ignore_duplicate_t::no ); + emit( accepted_block_header, std::tie(bsp->block, bsp->id()) ); + } else { + assert(s != controller::block_status::irreversible); + forkdb.mark_valid( bsp ); + } + }; + fork_db.apply(add_completed_block); } - head = bsp; - // at block level, no transaction specific logging is possible - if (auto* dm_logger = get_deep_mind_logger(false)) { - dm_logger->on_accepted_block(bsp); - } + chain_head = block_handle{cb.bsp}; + emit( accepted_block, std::tie(chain_head.block(), chain_head.id()) ); - emit( self.accepted_block, std::tie(bsp->block, bsp->id) ); + apply(chain_head, [&](const auto& head) { +#warning todo: support deep_mind_logger even when in IF mode + if constexpr (std::is_same_v>) { + // at block level, no transaction specific logging is possible + if (auto* dm_logger = get_deep_mind_logger(false)) { + dm_logger->on_accepted_block(head); + } + } + }); if( s == controller::block_status::incomplete ) { + fork_db.apply_s([&](auto& forkdb) { + assert(std::holds_alternative>(cb.bsp.internal())); + const auto& bsp = std::get>(cb.bsp.internal()); + + uint16_t if_ext_id = instant_finality_extension::extension_id(); + assert(bsp->header_exts.count(if_ext_id) > 0); // in all instant_finality block headers + const auto& if_ext = std::get(bsp->header_exts.lower_bound(if_ext_id)->second); + if (if_ext.qc_claim.is_strong_qc) { + // claim has already been verified + auto claimed = forkdb.search_on_branch(bsp->id(), if_ext.qc_claim.block_num); + if (claimed) { + auto& final_on_strong_qc_block_ref = claimed->core.get_block_reference(claimed->core.final_on_strong_qc_block_num); + set_if_irreversible_block_id(final_on_strong_qc_block_ref.block_id); + } + } + }); + log_irreversible(); } + + if ( s == controller::block_status::incomplete || s == controller::block_status::complete || s == controller::block_status::validated ) { + apply_s(chain_head, [&](const auto& head) { create_and_send_vote_msg(head); }); + } + + + if (s == controller::block_status::incomplete) { + const auto& id = chain_head.id(); + const auto& new_b = chain_head.block(); + br.total_time += fc::time_point::now() - start; + + ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} " + "[trxs: ${count}, lib: ${lib}, confirmed: ${confs}, net: ${net}, cpu: ${cpu}, elapsed: ${et}, time: ${tt}]", + ("p", new_b->producer)("id", id.str().substr(8, 16))("n", new_b->block_num())("t", new_b->timestamp) + ("count", new_b->transactions.size())("lib", fork_db_root_block_num())("net", br.total_net_usage) + ("cpu", br.total_cpu_usage_us)("et", br.total_elapsed_time)("tt", br.total_time)("confs", new_b->confirmed)); + } + } catch (...) { // dont bother resetting pending, instead abort the block reset_pending_on_exit.cancel(); @@ -1978,6 +3226,26 @@ struct controller_impl { pending->push(); } + void set_proposed_finalizers(finalizer_policy&& fin_pol) { + assert(pending); // has to exist and be building_block since called from host function + auto& bb = std::get(pending->_block_stage); + bb.set_proposed_finalizer_policy(std::move(fin_pol)); + + bb.apply_l([&](building_block::building_block_legacy& bl) { + // Savanna uses new algorithm for proposer schedule change, prevent any in-flight legacy proposer schedule changes + const auto& gpo = db.get(); + if (gpo.proposed_schedule_block_num) { + db.modify(gpo, [&](auto& gp) { + gp.proposed_schedule_block_num = std::optional(); + gp.proposed_schedule.version = 0; + gp.proposed_schedule.producers.clear(); + }); + } + bl.new_pending_producer_schedule = {}; + bl.pending_block_header_state.prev_pending_schedule.schedule.producers.clear(); + }); + } + /** * This method is called from other threads. The controller_impl should outlive those threads. * However, to avoid race conditions, it means that the behavior of this function should not change @@ -2042,9 +3310,9 @@ struct controller_impl { void report_block_header_diff( const block_header& b, const block_header& ab ) { #define EOS_REPORT(DESC,A,B) \ - if( A != B ) { \ - elog("${desc}: ${bv} != ${abv}", ("desc", DESC)("bv", A)("abv", B)); \ - } + if( A != B ) { \ + elog("${desc}: ${bv} != ${abv}", ("desc", DESC)("bv", A)("abv", B)); \ + } EOS_REPORT( "timestamp", b.timestamp, ab.timestamp ) EOS_REPORT( "producer", b.producer, ab.producer ) @@ -2056,141 +3324,417 @@ struct controller_impl { EOS_REPORT( "new_producers", b.new_producers, ab.new_producers ) EOS_REPORT( "header_extensions", b.header_extensions, ab.header_extensions ) + if (b.header_extensions != ab.header_extensions) { + flat_multimap bheader_exts = b.validate_and_extract_header_extensions(); + if (bheader_exts.count(instant_finality_extension::extension_id())) { + const auto& if_extension = + std::get(bheader_exts.lower_bound(instant_finality_extension::extension_id())->second); + elog("b if: ${i}", ("i", if_extension)); + } + flat_multimap abheader_exts = ab.validate_and_extract_header_extensions(); + if (abheader_exts.count(instant_finality_extension::extension_id())) { + const auto& if_extension = + std::get(abheader_exts.lower_bound(instant_finality_extension::extension_id())->second); + elog("ab if: ${i}", ("i", if_extension)); + } + } + #undef EOS_REPORT } + static std::optional extract_qc_data(const signed_block_ptr& b) { + std::optional qc_data; + auto hexts = b->validate_and_extract_header_extensions(); + if (auto if_entry = hexts.lower_bound(instant_finality_extension::extension_id()); if_entry != hexts.end()) { + auto& if_ext = std::get(if_entry->second); + + // get the matching qc extension if present + auto exts = b->validate_and_extract_extensions(); + if (auto entry = exts.lower_bound(quorum_certificate_extension::extension_id()); entry != exts.end()) { + auto& qc_ext = std::get(entry->second); + return qc_data_t{ std::move(qc_ext.qc), if_ext.qc_claim }; + } + return qc_data_t{ {}, if_ext.qc_claim }; + } + return {}; + } + - void apply_block( controller::block_report& br, const block_state_legacy_ptr& bsp, controller::block_status s, - const trx_meta_cache_lookup& trx_lookup ) - { try { + template + void log_applied(controller::block_report& br, const BSP& bsp) const { + if (replaying) // fork_db_root_block_num not available during replay + return; + fc::time_point now = fc::time_point::now(); + if (now - bsp->timestamp() < fc::minutes(5) || (bsp->block_num() % 1000 == 0)) { + ilog("Received block ${id}... #${n} @ ${t} signed by ${p} " // "Received" instead of "Applied" so it matches existing log output + "[trxs: ${count}, lib: ${lib}, net: ${net}, cpu: ${cpu}, elapsed: ${elapsed}, time: ${time}, latency: ${latency} ms]", + ("p", bsp->producer())("id", bsp->id().str().substr(8, 16))("n", bsp->block_num())("t", bsp->timestamp()) + ("count", bsp->block->transactions.size())("lib", fork_db_root_block_num()) + ("net", br.total_net_usage)("cpu", br.total_cpu_usage_us) + ("elapsed", br.total_elapsed_time)("time", br.total_time)("latency", (now - bsp->timestamp()).count() / 1000)); + const auto& hb_id = chain_head.id(); + const auto& hb = chain_head.block(); + if (read_mode != db_read_mode::IRREVERSIBLE && hb && hb_id != bsp->id() && hb != nullptr) { // not applied to head + ilog("Block not applied to head ${id}... #${n} @ ${t} signed by ${p} " + "[trxs: ${count}, lib: ${lib}, net: ${net}, cpu: ${cpu}, elapsed: ${elapsed}, time: ${time}, latency: ${latency} ms]", + ("p", hb->producer)("id", hb_id.str().substr(8, 16))("n", hb->block_num())("t", hb->timestamp) + ("count", hb->transactions.size())("lib", fork_db_root_block_num()) + ("net", br.total_net_usage)("cpu", br.total_cpu_usage_us)("elapsed", br.total_elapsed_time)("time", br.total_time) + ("latency", (now - hb->timestamp).count() / 1000)); + } + } + } + + template + void apply_block( controller::block_report& br, const BSP& bsp, controller::block_status s, + const trx_meta_cache_lookup& trx_lookup ) { try { - auto start = fc::time_point::now(); - const signed_block_ptr& b = bsp->block; - const auto& new_protocol_feature_activations = bsp->get_new_protocol_feature_activations(); - - auto producer_block_id = bsp->id; - start_block( b->timestamp, b->confirmed, new_protocol_feature_activations, s, producer_block_id, fc::time_point::maximum() ); - - // validated in create_block_state_future() - std::get(pending->_block_stage)._trx_mroot_or_receipt_digests = b->transaction_mroot; - - const bool existing_trxs_metas = !bsp->trxs_metas().empty(); - const bool pub_keys_recovered = bsp->is_pub_keys_recovered(); - const bool skip_auth_checks = self.skip_auth_check(); - std::vector> trx_metas; - bool use_bsp_cached = false; - if( pub_keys_recovered || (skip_auth_checks && existing_trxs_metas) ) { - use_bsp_cached = true; - } else { - trx_metas.reserve( b->transactions.size() ); - for( const auto& receipt : b->transactions ) { - if( std::holds_alternative(receipt.trx)) { - const auto& pt = std::get(receipt.trx); - transaction_metadata_ptr trx_meta_ptr = trx_lookup ? trx_lookup( pt.id() ) : transaction_metadata_ptr{}; - if( trx_meta_ptr && *trx_meta_ptr->packed_trx() != pt ) trx_meta_ptr = nullptr; - if( trx_meta_ptr && ( skip_auth_checks || !trx_meta_ptr->recovered_keys().empty() ) ) { - trx_metas.emplace_back( std::move( trx_meta_ptr ), recover_keys_future{} ); - } else if( skip_auth_checks ) { - packed_transaction_ptr ptrx( b, &pt ); // alias signed_block_ptr - trx_metas.emplace_back( + try { + auto start = fc::time_point::now(); + + const bool already_valid = bsp->is_valid(); + // When bsp was created in create_block_state_i, bsp was considered for voting. At that time, bsp->final_on_strong_qc_block_ref may + // not have been validated and we could not vote. At this point bsp->final_on_strong_qc_block_ref has been validated and we can vote. + // Only need to consider voting if not already validated, if already validated then we have already voted. + if (!already_valid) + consider_voting(bsp); + + const signed_block_ptr& b = bsp->block; + const auto& new_protocol_feature_activations = bsp->get_new_protocol_feature_activations(); + const auto& producer_block_id = bsp->id(); + + start_block( b->timestamp, b->confirmed, new_protocol_feature_activations, s, producer_block_id, fc::time_point::maximum() ); + + // validated in create_block_handle() + std::get(pending->_block_stage).trx_mroot_or_receipt_digests() = b->transaction_mroot; + + const bool existing_trxs_metas = !bsp->trxs_metas().empty(); + const bool pub_keys_recovered = bsp->is_pub_keys_recovered(); + const bool skip_auth_checks = skip_auth_check(); + std::vector> trx_metas; + bool use_bsp_cached = false; + if( pub_keys_recovered || (skip_auth_checks && existing_trxs_metas) ) { + use_bsp_cached = true; + } else { + trx_metas.reserve( b->transactions.size() ); + for( const auto& receipt : b->transactions ) { + if( std::holds_alternative(receipt.trx)) { + const auto& pt = std::get(receipt.trx); + transaction_metadata_ptr trx_meta_ptr = trx_lookup ? trx_lookup( pt.id() ) : transaction_metadata_ptr{}; + if( trx_meta_ptr && *trx_meta_ptr->packed_trx() != pt ) trx_meta_ptr = nullptr; + if( trx_meta_ptr && ( skip_auth_checks || !trx_meta_ptr->recovered_keys().empty() ) ) { + trx_metas.emplace_back( std::move( trx_meta_ptr ), recover_keys_future{} ); + } else if( skip_auth_checks ) { + packed_transaction_ptr ptrx( b, &pt ); // alias signed_block_ptr + trx_metas.emplace_back( transaction_metadata::create_no_recover_keys( std::move(ptrx), transaction_metadata::trx_type::input ), recover_keys_future{} ); - } else { - packed_transaction_ptr ptrx( b, &pt ); // alias signed_block_ptr - auto fut = transaction_metadata::start_recover_keys( - std::move( ptrx ), thread_pool.get_executor(), chain_id, fc::microseconds::maximum(), transaction_metadata::trx_type::input ); - trx_metas.emplace_back( transaction_metadata_ptr{}, std::move( fut ) ); + } else { + packed_transaction_ptr ptrx( b, &pt ); // alias signed_block_ptr + auto fut = transaction_metadata::start_recover_keys( + std::move( ptrx ), thread_pool.get_executor(), chain_id, fc::microseconds::maximum(), + transaction_metadata::trx_type::input ); + trx_metas.emplace_back( transaction_metadata_ptr{}, std::move( fut ) ); + } } } } - } - transaction_trace_ptr trace; - - size_t packed_idx = 0; - const auto& trx_receipts = std::get(pending->_block_stage)._pending_trx_receipts; - for( const auto& receipt : b->transactions ) { - auto num_pending_receipts = trx_receipts.size(); - if( std::holds_alternative(receipt.trx) ) { - const auto& trx_meta = ( use_bsp_cached ? bsp->trxs_metas().at( packed_idx ) - : ( !!std::get<0>( trx_metas.at( packed_idx ) ) ? - std::get<0>( trx_metas.at( packed_idx ) ) - : std::get<1>( trx_metas.at( packed_idx ) ).get() ) ); - trace = push_transaction( trx_meta, fc::time_point::maximum(), fc::microseconds::maximum(), receipt.cpu_usage_us, true, 0 ); - ++packed_idx; - } else if( std::holds_alternative(receipt.trx) ) { - trace = push_scheduled_transaction( std::get(receipt.trx), fc::time_point::maximum(), fc::microseconds::maximum(), receipt.cpu_usage_us, true ); + transaction_trace_ptr trace; + + size_t packed_idx = 0; + const auto& trx_receipts = std::get(pending->_block_stage).pending_trx_receipts(); + for( const auto& receipt : b->transactions ) { + auto num_pending_receipts = trx_receipts.size(); + if( std::holds_alternative(receipt.trx) ) { + const auto& trx_meta = (use_bsp_cached ? bsp->trxs_metas().at(packed_idx) + : (!!std::get<0>(trx_metas.at(packed_idx)) + ? std::get<0>(trx_metas.at(packed_idx)) + : std::get<1>(trx_metas.at(packed_idx)).get())); + trace = push_transaction(trx_meta, fc::time_point::maximum(), fc::microseconds::maximum(), + receipt.cpu_usage_us, true, 0); + ++packed_idx; + } else if( std::holds_alternative(receipt.trx) ) { + trace = push_scheduled_transaction(std::get(receipt.trx), fc::time_point::maximum(), + fc::microseconds::maximum(), receipt.cpu_usage_us, true); + } else { + EOS_ASSERT( false, block_validate_exception, "encountered unexpected receipt type" ); + } + + bool transaction_failed = trace && trace->except; + bool transaction_can_fail = receipt.status == transaction_receipt_header::hard_fail && + std::holds_alternative(receipt.trx); + + if( transaction_failed && !transaction_can_fail) { + edump((*trace)); + throw *trace->except; + } + + EOS_ASSERT(trx_receipts.size() > 0, block_validate_exception, + "expected a receipt, block_num ${bn}, block_id ${id}, receipt ${e}", + ("bn", b->block_num())("id", producer_block_id)("e", receipt)); + EOS_ASSERT(trx_receipts.size() == num_pending_receipts + 1, block_validate_exception, + "expected receipt was not added, block_num ${bn}, block_id ${id}, receipt ${e}", + ("bn", b->block_num())("id", producer_block_id)("e", receipt)); + const transaction_receipt_header& r = trx_receipts.back(); + EOS_ASSERT(r == static_cast(receipt), block_validate_exception, + "receipt does not match, ${lhs} != ${rhs}", + ("lhs", r)("rhs", static_cast(receipt))); + } + + if constexpr (std::is_same_v) { + // assemble_block will mutate bsp by setting the valid structure + assemble_block(true, extract_qc_data(b), bsp); + + // verify received finality digest in action_mroot is the same as the actual one + + // For proper IF blocks that do not have an associated Finality Tree defined, + // its finality_mroot is empty + digest_type actual_finality_mroot; + + if (!bsp->core.is_genesis_block_num(bsp->core.final_on_strong_qc_block_num)) { + actual_finality_mroot = bsp->get_validation_mroot(bsp->core.final_on_strong_qc_block_num); + } + + EOS_ASSERT(bsp->finality_mroot() == actual_finality_mroot, + block_validate_exception, + "finality_mroot does not match, received finality_mroot: ${r} != actual_finality_mroot: ${a}", + ("r", bsp->finality_mroot())("a", actual_finality_mroot)); } else { - EOS_ASSERT( false, block_validate_exception, "encountered unexpected receipt type" ); + assemble_block(true, {}, nullptr); + auto& ab = std::get(pending->_block_stage); + ab.apply_legacy([&](assembled_block::assembled_block_legacy& abl) { + assert(abl.action_receipt_digests_savanna); + const auto& digests = *abl.action_receipt_digests_savanna; + bsp->action_mroot_savanna = calculate_merkle(digests); + }); } + auto& ab = std::get(pending->_block_stage); + + if( producer_block_id != ab.id() ) { + elog( "Validation block id does not match producer block id" ); - bool transaction_failed = trace && trace->except; - bool transaction_can_fail = receipt.status == transaction_receipt_header::hard_fail && std::holds_alternative(receipt.trx); - if( transaction_failed && !transaction_can_fail) { - edump((*trace)); - throw *trace->except; + report_block_header_diff(*b, ab.header()); + + // this implicitly asserts that all header fields (less the signature) are identical + EOS_ASSERT(producer_block_id == ab.id(), block_validate_exception, "Block ID does not match, ${producer_block_id} != ${validator_block_id}", + ("producer_block_id", producer_block_id)("validator_block_id", ab.id())); } - EOS_ASSERT( trx_receipts.size() > 0, - block_validate_exception, "expected a receipt, block_num ${bn}, block_id ${id}, receipt ${e}", - ("bn", b->block_num())("id", producer_block_id)("e", receipt) - ); - EOS_ASSERT( trx_receipts.size() == num_pending_receipts + 1, - block_validate_exception, "expected receipt was not added, block_num ${bn}, block_id ${id}, receipt ${e}", - ("bn", b->block_num())("id", producer_block_id)("e", receipt) - ); - const transaction_receipt_header& r = trx_receipts.back(); - EOS_ASSERT( r == static_cast(receipt), - block_validate_exception, "receipt does not match, ${lhs} != ${rhs}", - ("lhs", r)("rhs", static_cast(receipt)) ); - } + if( !use_bsp_cached ) { + bsp->set_trxs_metas( ab.extract_trx_metas(), !skip_auth_checks ); + } + // create completed_block with the existing block_state as we just verified it is the same as assembled_block + pending->_block_stage = completed_block{ block_handle{bsp} }; - finalize_block(); + br = pending->_block_report; // copy before commit block destroys pending + br.total_time += fc::time_point::now() - start; + commit_block(br, s); - auto& ab = std::get(pending->_block_stage); + if (!already_valid) + log_applied(br, bsp); - if( producer_block_id != ab._id ) { - elog( "Validation block id does not match producer block id" ); - report_block_header_diff( *b, *ab._unsigned_block ); - // this implicitly asserts that all header fields (less the signature) are identical - EOS_ASSERT( producer_block_id == ab._id, block_validate_exception, "Block ID does not match", - ("producer_block_id", producer_block_id)("validator_block_id", ab._id) ); + } catch ( const std::bad_alloc& ) { + throw; + } catch ( const boost::interprocess::bad_alloc& ) { + throw; + } catch ( const fc::exception& e ) { + edump((e.to_detail_string())); + abort_block(); + throw; + } catch ( const std::exception& e ) { + edump((e.what())); + abort_block(); + throw; } + } FC_CAPTURE_AND_RETHROW(); + } /// apply_block + + + // called from net threads and controller's thread pool + vote_status process_vote_message( const vote_message& vote ) { + // only aggregate votes on proper if blocks + auto aggregate_vote = [&vote](auto& forkdb) -> vote_status { + auto bsp = forkdb.get_block(vote.block_id); + if (bsp && bsp->block->is_proper_svnn_block()) { + return bsp->aggregate_vote(vote); + } + return vote_status::unknown_block; + }; + auto aggregate_vote_legacy = [](auto&) -> vote_status { + return vote_status::unknown_block; + }; + return fork_db.apply(aggregate_vote_legacy, aggregate_vote); + } + + bool node_has_voted_if_finalizer(const block_id_type& id) const { + if (my_finalizers.empty()) + return true; - if( !use_bsp_cached ) { - bsp->set_trxs_metas( std::move( ab._trx_metas ), !skip_auth_checks ); + std::optional voted = fork_db.apply_s>([&](auto& forkdb) -> std::optional { + auto bsp = forkdb.get_block(id); + if (bsp) { + return my_finalizers.all_of_public_keys([&bsp](const auto& k) { + return bsp->has_voted(k); + }); } - // create completed_block with the existing block_state as we just verified it is the same as assembled_block - pending->_block_stage = completed_block{ bsp }; + return false; + }); + // empty optional means legacy forkdb + return !voted || *voted; + } - br = pending->_block_report; // copy before commit block destroys pending - commit_block(s); - br.total_time = fc::time_point::now() - start; + // thread safe + void create_and_send_vote_msg(const block_state_ptr& bsp) { + if (!bsp->block->is_proper_svnn_block()) return; - } catch ( const std::bad_alloc& ) { - throw; - } catch ( const boost::interprocess::bad_alloc& ) { - throw; - } catch ( const fc::exception& e ) { - edump((e.to_detail_string())); - abort_block(); - throw; - } catch ( const std::exception& e ) { - edump((e.what())); - abort_block(); - throw; + + // Each finalizer configured on the node which is present in the active finalizer policy may create and sign a vote. + my_finalizers.maybe_vote( + *bsp->active_finalizer_policy, bsp, bsp->strong_digest, [&](const vote_message& vote) { + // net plugin subscribed to this signal. it will broadcast the vote message on receiving the signal + emit(voted_block, vote); + + // also aggregate our own vote into the pending_qc for this block. + boost::asio::post(thread_pool.get_executor(), + [control = this, vote]() { control->process_vote_message(vote); }); + }); + } + + // Verify QC claim made by instant_finality_extension in header extension + // and quorum_certificate_extension in block extension are valid. + // Called from net-threads. It is thread safe as signed_block is never modified + // after creation. + // ----------------------------------------------------------------------------- + void verify_qc_claim( const block_id_type& id, const signed_block_ptr& b, const block_header_state& prev ) { + auto qc_ext_id = quorum_certificate_extension::extension_id(); + auto if_ext_id = instant_finality_extension::extension_id(); + + // extract current block extension and previous header extension + auto block_exts = b->validate_and_extract_extensions(); + std::optional prev_header_ext = prev.header.extract_header_extension(if_ext_id); + std::optional header_ext = b->extract_header_extension(if_ext_id); + + bool qc_extension_present = block_exts.count(qc_ext_id) != 0; + uint32_t block_num = b->block_num(); + + if( !header_ext ) { + // If there is no header extension, ensure the block does not have a QC and also the previous + // block doesn't have a header extension either. Then return early. + // ------------------------------------------------------------------------------------------ + EOS_ASSERT( !qc_extension_present, + invalid_qc_claim, + "Block #${b} includes a QC block extension, but doesn't have a finality header extension", + ("b", block_num) ); + + EOS_ASSERT( !prev_header_ext, + invalid_qc_claim, + "Block #${b} doesn't have a finality header extension even though its predecessor does.", + ("b", block_num) ); + return; + } + + assert(header_ext); + const auto& if_ext = std::get(*header_ext); + const auto new_qc_claim = if_ext.qc_claim; + + // If there is a header extension, but the previous block does not have a header extension, + // ensure the block does not have a QC and the QC claim of the current block has a block_num + // of the current block’s number and that it is a claim of a weak QC. Then return early. + // ------------------------------------------------------------------------------------------------- + if (!prev_header_ext) { + EOS_ASSERT( !qc_extension_present && new_qc_claim.block_num == block_num && new_qc_claim.is_strong_qc == false, + invalid_qc_claim, + "Block #${b}, which is the finality transition block, doesn't have the expected extensions", + ("b", block_num) ); + return; + } + + // at this point both current block and its parent have IF extensions, and we are past the + // IF transition block + // ---------------------------------------------------------------------------------------- + assert(header_ext && prev_header_ext); + + const auto& prev_if_ext = std::get(*prev_header_ext); + const auto prev_qc_claim = prev_if_ext.qc_claim; + + // validate QC claim against previous block QC info + + // new claimed QC block number cannot be smaller than previous block's + EOS_ASSERT( new_qc_claim.block_num >= prev_qc_claim.block_num, + invalid_qc_claim, + "Block #${b} claims a block_num (${n1}) less than the previous block's (${n2})", + ("n1", new_qc_claim.block_num)("n2", prev_qc_claim.block_num)("b", block_num) ); + + if( new_qc_claim.block_num == prev_qc_claim.block_num ) { + if( new_qc_claim.is_strong_qc == prev_qc_claim.is_strong_qc ) { + // QC block extension is redundant + EOS_ASSERT( !qc_extension_present, + invalid_qc_claim, + "Block #${b} should not provide a QC block extension since its QC claim is the same as the previous block's", + ("b", block_num) ); + + // if previous block's header extension has the same claim, just return + // (previous block already validated the claim) + return; + } + + // new claimed QC must be stronger than previous if the claimed block number is the same + EOS_ASSERT( new_qc_claim.is_strong_qc, + invalid_qc_claim, + "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", + ("s1", new_qc_claim.is_strong_qc)("s2", prev_qc_claim.is_strong_qc)("b", block_num) ); } - } FC_CAPTURE_AND_RETHROW() } /// apply_block + // At this point, we are making a new claim in this block, so it better include a QC to justify this claim. + EOS_ASSERT( qc_extension_present, + invalid_qc_claim, + "Block #${b} is making a new finality claim, but doesn't include a qc to justify this claim", ("b", block_num) ); + + const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); + const auto& qc_proof = qc_ext.qc; + + // Check QC information in header extension and block extension match + EOS_ASSERT( qc_proof.block_num == new_qc_claim.block_num, + invalid_qc_claim, + "Block #${b}: Mismatch between qc.block_num (${n1}) in block extension and block_num (${n2}) in header extension", + ("n1", qc_proof.block_num)("n2", new_qc_claim.block_num)("b", block_num) ); + + // Verify claimed strictness is the same as in proof + EOS_ASSERT( qc_proof.qc.is_strong() == new_qc_claim.is_strong_qc, + invalid_qc_claim, + "QC is_strong (${s1}) in block extension does not match is_strong_qc (${s2}) in header extension. Block number: ${b}", + ("s1", qc_proof.qc.is_strong())("s2", new_qc_claim.is_strong_qc)("b", block_num) ); + + // find the claimed block's block state on branch of id + auto bsp = fetch_bsp_on_branch_by_num( prev.id(), new_qc_claim.block_num ); + EOS_ASSERT( bsp, + invalid_qc_claim, + "Block state was not found in forkdb for block_num ${q}. Block number: ${b}", + ("q", new_qc_claim.block_num)("b", block_num) ); + + // verify the QC proof against the claimed block + bsp->verify_qc(qc_proof.qc); + } // thread safe, expected to be called from thread other than the main thread - block_state_legacy_ptr create_block_state_i( const block_id_type& id, const signed_block_ptr& b, const block_header_state_legacy& prev ) { - auto trx_mroot = calculate_trx_merkle( b->transactions ); - EOS_ASSERT( b->transaction_mroot == trx_mroot, block_validate_exception, + template + block_handle create_block_state_i( ForkDB& forkdb, const block_id_type& id, const signed_block_ptr& b, const BS& prev ) { + constexpr bool savanna_mode = std::is_same_v, block_state>; + if constexpr (savanna_mode) { + // Verify claim made by instant_finality_extension in block header extension and + // quorum_certificate_extension in block extension are valid. + // This is the only place the evaluation is done. + verify_qc_claim(id, b, prev); + } + + auto trx_mroot = calculate_trx_merkle( b->transactions, savanna_mode ); + EOS_ASSERT( b->transaction_mroot == trx_mroot, + block_validate_exception, "invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) ); const bool skip_validate_signee = false; - auto bsp = std::make_shared( + auto bsp = std::make_shared( prev, b, protocol_features.get_protocol_feature_set(), @@ -2201,47 +3745,158 @@ struct controller_impl { skip_validate_signee ); - EOS_ASSERT( id == bsp->id, block_validate_exception, - "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id) ); - return bsp; + EOS_ASSERT( id == bsp->id(), block_validate_exception, + "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) ); + + if constexpr (savanna_mode) { + integrate_received_qc_to_block(bsp); // Save the received QC as soon as possible, no matter whether the block itself is valid or not + consider_voting(bsp); + } + + if (conf.terminate_at_block == 0 || bsp->block_num() <= conf.terminate_at_block) { + forkdb.add(bsp, mark_valid_t::no, ignore_duplicate_t::yes); + } + + return block_handle{bsp}; } - std::future create_block_state_future( const block_id_type& id, const signed_block_ptr& b ) { + std::future create_block_handle_future( const block_id_type& id, const signed_block_ptr& b ) { EOS_ASSERT( b, block_validate_exception, "null block" ); - return post_async_task( thread_pool.get_executor(), [b, id, control=this]() { - // no reason for a block_state if fork_db already knows about block - auto existing = control->fork_db.get_block( id ); - EOS_ASSERT( !existing, fork_database_exception, "we already know about this block: ${id}", ("id", id) ); + auto f = [&](auto& forkdb) -> std::future { + return post_async_task( thread_pool.get_executor(), [b, id, &forkdb, control=this]() { + auto prev = forkdb.get_block( b->previous, include_root_t::yes ); + EOS_ASSERT( prev, unlinkable_block_exception, + "unlinkable block ${id} previous ${p}", ("id", id)("p", b->previous) ); + + return control->create_block_state_i( forkdb, id, b, *prev ); + } ); + }; - auto prev = control->fork_db.get_block_header( b->previous ); - EOS_ASSERT( prev, unlinkable_block_exception, - "unlinkable block ${id}", ("id", id)("previous", b->previous) ); + // always return a valid future + auto unlinkable = [&](const auto&) -> std::future { + std::packaged_task task( [b, id]() -> block_handle { + EOS_ASSERT( false, unlinkable_block_exception, + "unlinkable block ${id} previous ${p} not in fork db", ("id", id)("p", b->previous) ); + } ); + task(); + return task.get_future(); + }; - return control->create_block_state_i( id, b, *prev ); - } ); + if (!b->is_proper_svnn_block()) { + return fork_db.apply>(f, unlinkable); + } + return fork_db.apply>(unlinkable, f); } // thread safe, expected to be called from thread other than the main thread - block_state_legacy_ptr create_block_state( const block_id_type& id, const signed_block_ptr& b ) { + std::optional create_block_handle( const block_id_type& id, const signed_block_ptr& b ) { EOS_ASSERT( b, block_validate_exception, "null block" ); + + auto f = [&](auto& forkdb) -> std::optional { + // previous not found could mean that previous block not applied yet + auto prev = forkdb.get_block( b->previous, include_root_t::yes ); + if( !prev ) return {}; - // no reason for a block_state if fork_db already knows about block - auto existing = fork_db.get_block( id ); - EOS_ASSERT( !existing, fork_database_exception, "we already know about this block: ${id}", ("id", id) ); + return create_block_state_i( forkdb, id, b, *prev ); + }; + + auto unlinkable = [&](const auto&) -> std::optional { + return {}; + }; + + if (!b->is_proper_svnn_block()) { + return fork_db.apply>(f, unlinkable); + } + return fork_db.apply>(unlinkable, f); + } + + // thread safe + void integrate_received_qc_to_block(const block_state_ptr& bsp_in) { + // extract QC from block extension + const auto& block_exts = bsp_in->block->validate_and_extract_extensions(); + auto qc_ext_id = quorum_certificate_extension::extension_id(); + + if( block_exts.count(qc_ext_id) == 0 ) { + return; + } + const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); + const auto& received_qc = qc_ext.qc.qc; + + const auto claimed = fetch_bsp_on_branch_by_num( bsp_in->previous(), qc_ext.qc.block_num ); + if( !claimed ) { + dlog("qc not found in forkdb, qc: ${qc} for block ${bn} ${id}, previous ${p}", + ("qc", qc_ext.qc.to_qc_claim())("bn", bsp_in->block_num())("id", bsp_in->id())("p", bsp_in->previous())); + return; + } + + // Don't save the QC from block extension if the claimed block has a better or same valid_qc. + // claimed->valid_qc_is_strong() acquires a mutex. + if (received_qc.is_weak() || claimed->valid_qc_is_strong()) { + dlog("qc not better, claimed->valid: ${qbn} ${qid}, strong=${s}, received: ${rqc}, for block ${bn} ${id}", + ("qbn", claimed->block_num())("qid", claimed->id())("s", !received_qc.is_weak()) // use is_weak() to avoid mutex on valid_qc_is_strong() + ("rqc", qc_ext.qc.to_qc_claim())("bn", bsp_in->block_num())("id", bsp_in->id())); + return; + } - // previous not found could mean that previous block not applied yet - auto prev = fork_db.get_block_header( b->previous ); - if( !prev ) return {}; + // Save the QC. + dlog("setting valid qc: ${rqc} into claimed block ${bn} ${id}", ("rqc", qc_ext.qc.to_qc_claim())("bn", claimed->block_num())("id", claimed->id())); + claimed->set_valid_qc(received_qc); + + // advance LIB if QC is strong + if( received_qc.is_strong() ) { + // We evaluate a block extension qc and advance lib if strong. + // This is done before evaluating the block. It is possible the block + // will not be valid or forked out. This is safe because the block is + // just acting as a carrier of this info. It doesn't matter if the block + // is actually valid as it simply is used as a network message for this data. + const auto& final_on_strong_qc_block_ref = claimed->core.get_block_reference(claimed->core.final_on_strong_qc_block_num); + set_if_irreversible_block_id(final_on_strong_qc_block_ref.block_id); + } + } + + void consider_voting(const block_state_legacy_ptr&) {} + + // thread safe + void consider_voting(const block_state_ptr& bsp) { + // 1. Get the `core.final_on_strong_qc_block_num` for the block you are considering to vote on and use that to find the actual block ID + // of the ancestor block that has that block number. + // 2. If that block ID is for a non validated block, then do not vote for that block. + // 3. Otherwise, consider voting for that block according to the decide_vote rules. + + if (bsp->core.final_on_strong_qc_block_num > 0) { + const auto& final_on_strong_qc_block_ref = bsp->core.get_block_reference(bsp->core.final_on_strong_qc_block_num); + if (fork_db_validated_block_exists(final_on_strong_qc_block_ref.block_id)) { + create_and_send_vote_msg(bsp); + } + } + } - return create_block_state_i( id, b, *prev ); + template + void accept_block(const BSP& bsp) { + assert(bsp && bsp->block); + + // consider voting again as final_on_strong_qc_block may have been validated since the bsp was created in create_block_state_i + consider_voting(bsp); + + auto do_accept_block = [&](auto& forkdb) { + if constexpr (std::is_same_v>) + forkdb.add( bsp, mark_valid_t::no, ignore_duplicate_t::yes ); + + emit( accepted_block_header, std::tie(bsp->block, bsp->id()) ); + }; + + fork_db.apply(do_accept_block); } + template void push_block( controller::block_report& br, - const block_state_legacy_ptr& bsp, - const forked_branch_callback& forked_branch_cb, + const BSP& bsp, + const forked_callback_t& forked_branch_cb, const trx_meta_cache_lookup& trx_lookup ) { + assert(bsp && bsp->block); + controller::block_status s = controller::block_status::complete; EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block"); @@ -2249,34 +3904,41 @@ struct controller_impl { trusted_producer_light_validation = old_value; }); try { - EOS_ASSERT( bsp, block_validate_exception, "null block" ); const auto& b = bsp->block; - if( conf.terminate_at_block > 0 && conf.terminate_at_block <= self.head_block_num()) { + if( conf.terminate_at_block > 0 && conf.terminate_at_block <= chain_head.block_num()) { ilog("Reached configured maximum block ${num}; terminating", ("num", conf.terminate_at_block) ); shutdown(); return; } + + auto do_push = [&](auto& forkdb) { + if constexpr (std::is_same_v>) { + forkdb.add( bsp, mark_valid_t::no, ignore_duplicate_t::yes ); + } + + if (is_trusted_producer(b->producer)) { + trusted_producer_light_validation = true; + }; - fork_db.add( bsp ); + emit( accepted_block_header, std::tie(bsp->block, bsp->id()) ); - if (self.is_trusted_producer(b->producer)) { - trusted_producer_light_validation = true; + if( read_mode != db_read_mode::IRREVERSIBLE ) { + if constexpr (std::is_same_v>) + maybe_switch_forks( br, forkdb.pending_head(), s, forked_branch_cb, trx_lookup ); + } else { + log_irreversible(); + } }; - emit( self.accepted_block_header, std::tie(bsp->block, bsp->id) ); - - if( read_mode != db_read_mode::IRREVERSIBLE ) { - maybe_switch_forks( br, fork_db.pending_head(), s, forked_branch_cb, trx_lookup ); - } else { - log_irreversible(); - } + fork_db.apply(do_push); } FC_LOG_AND_RETHROW( ) } + template void replay_push_block( const signed_block_ptr& b, controller::block_status s ) { - self.validate_db_available_size(); + validate_db_available_size(); EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block"); @@ -2285,144 +3947,192 @@ struct controller_impl { EOS_ASSERT( (s == controller::block_status::irreversible || s == controller::block_status::validated), block_validate_exception, "invalid block status for replay" ); - if( conf.terminate_at_block > 0 && conf.terminate_at_block <= self.head_block_num() ) { + if( conf.terminate_at_block > 0 && conf.terminate_at_block <= chain_head.block_num() ) { ilog("Reached configured maximum block ${num}; terminating", ("num", conf.terminate_at_block) ); shutdown(); return; } const bool skip_validate_signee = !conf.force_all_checks; + validator_t validator = [this](block_timestamp_type timestamp, const flat_set& cur_features, + const vector& new_features) { + check_protocol_features(timestamp, cur_features, new_features); + }; - auto bsp = std::make_shared( - *head, - b, - protocol_features.get_protocol_feature_set(), - [this]( block_timestamp_type timestamp, - const flat_set& cur_features, - const vector& new_features ) - { check_protocol_features( timestamp, cur_features, new_features ); }, - skip_validate_signee - ); + auto do_push = [&](const auto& head) { + if constexpr (std::is_same_v>) { + BSP bsp = std::make_shared(*head, b, protocol_features.get_protocol_feature_set(),validator, skip_validate_signee); - if( s != controller::block_status::irreversible ) { - fork_db.add( bsp, true ); - } + if (s != controller::block_status::irreversible) { + fork_db.apply([&](auto& forkdb) { + if constexpr (std::is_same_v, std::decay_t>) + forkdb.add(bsp, mark_valid_t::no, ignore_duplicate_t::yes); + }); + } - emit( self.accepted_block_header, std::tie(bsp->block, bsp->id) ); + emit(accepted_block_header, std::tie(bsp->block, bsp->id())); - controller::block_report br; - if( s == controller::block_status::irreversible ) { - apply_block( br, bsp, s, trx_meta_cache_lookup{} ); + controller::block_report br; + if (s == controller::block_status::irreversible) { + apply_block(br, bsp, s, trx_meta_cache_lookup{}); - // On replay, log_irreversible is not called and so no irreversible_block signal is emitted. - // So emit it explicitly here. - emit( self.irreversible_block, std::tie(bsp->block, bsp->id) ); + // On replay, log_irreversible is not called and so no irreversible_block signal is emitted. + // So emit it explicitly here. + emit(irreversible_block, std::tie(bsp->block, bsp->id())); - if (!self.skip_db_sessions(s)) { - db.commit(bsp->block_num); + if (!skip_db_sessions(s)) { + db.commit(bsp->block_num()); + } + } else { + EOS_ASSERT(read_mode != db_read_mode::IRREVERSIBLE, block_validate_exception, + "invariant failure: cannot replay reversible blocks while in irreversible mode"); + maybe_switch_forks(br, bsp, s, {}, trx_meta_cache_lookup{}); + } } - - } else { - EOS_ASSERT( read_mode != db_read_mode::IRREVERSIBLE, block_validate_exception, - "invariant failure: cannot replay reversible blocks while in irreversible mode" ); - maybe_switch_forks( br, bsp, s, forked_branch_callback{}, trx_meta_cache_lookup{} ); - } + }; + apply(chain_head, do_push); } FC_LOG_AND_RETHROW( ) } - void maybe_switch_forks( controller::block_report& br, const block_state_legacy_ptr& new_head, controller::block_status s, - const forked_branch_callback& forked_branch_cb, const trx_meta_cache_lookup& trx_lookup ) - { - bool head_changed = true; - if( new_head->header.previous == head->id ) { - try { - apply_block( br, new_head, s, trx_lookup ); - } catch ( const std::exception& e ) { - fork_db.remove( new_head->id ); - throw; - } - } else if( new_head->id != head->id ) { - auto old_head = head; - ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})", - ("current_head_id", head->id)("current_head_num", head->block_num)("new_head_id", new_head->id)("new_head_num", new_head->block_num) ); - - // not possible to log transaction specific infor when switching forks - if (auto dm_logger = get_deep_mind_logger(false)) { - dm_logger->on_switch_forks(head->id, new_head->id); - } - - auto branches = fork_db.fetch_branch_from( new_head->id, head->id ); - - if( branches.second.size() > 0 ) { - for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { - pop_block(); + void maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { + auto maybe_switch = [&](auto& forkdb) { + if (read_mode != db_read_mode::IRREVERSIBLE) { + auto pending_head = forkdb.pending_head(); + if (chain_head.id() != pending_head->id() && pending_head->id() != forkdb.head()->id()) { + dlog("switching forks on controller->maybe_switch_forks call"); + controller::block_report br; + maybe_switch_forks(br, pending_head, + pending_head->is_valid() ? controller::block_status::validated + : controller::block_status::complete, + cb, trx_lookup); } - EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception, - "loss of sync between fork_db and chainbase during fork switch" ); // _should_ never fail - - if( forked_branch_cb ) forked_branch_cb( branches.second ); } + }; + + fork_db.apply(maybe_switch); + } - for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { - auto except = std::exception_ptr{}; + template + void maybe_switch_forks( controller::block_report& br, const BSP& new_head, controller::block_status s, + const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) + { + auto do_maybe_switch_forks = [&](auto& forkdb) { + if( new_head->header.previous == chain_head.id() ) { try { - br = controller::block_report{}; - apply_block( br, *ritr, (*ritr)->is_valid() ? controller::block_status::validated - : controller::block_status::complete, trx_lookup ); - } catch ( const std::bad_alloc& ) { - throw; - } catch ( const boost::interprocess::bad_alloc& ) { - throw; - } catch (const fc::exception& e) { - elog("exception thrown while switching forks ${e}", ("e", e.to_detail_string())); - except = std::current_exception(); - } catch (const std::exception& e) { - elog("exception thrown while switching forks ${e}", ("e", e.what())); - except = std::current_exception(); + apply_block( br, new_head, s, trx_lookup ); + } catch ( const std::exception& e ) { + forkdb.remove( new_head->id() ); + throw; } + } else if( new_head->id() != chain_head.id() ) { + auto branches = forkdb.fetch_branch_from( new_head->id(), chain_head.id() ); + + bool switch_fork = !branches.second.empty(); + if( switch_fork ) { + auto head_fork_comp_str = apply(chain_head, [](auto& head) -> std::string { return log_fork_comparison(*head); }); + ilog("switching forks from ${chid} (block number ${chn}) ${c} to ${nhid} (block number ${nhn}) ${n}", + ("chid", chain_head.id())("chn}", chain_head.block_num())("nhid", new_head->id())("nhn", new_head->block_num()) + ("c", head_fork_comp_str)("n", log_fork_comparison(*new_head))); + + // not possible to log transaction specific info when switching forks + if (auto dm_logger = get_deep_mind_logger(false)) { + dm_logger->on_switch_forks(chain_head.id(), new_head->id()); + } - if( except ) { - // ritr currently points to the block that threw - // Remove the block that threw and all forks built off it. - fork_db.remove( (*ritr)->id ); - - // pop all blocks from the bad fork, discarding their transactions - // ritr base is a forward itr to the last block successfully applied - auto applied_itr = ritr.base(); - for( auto itr = applied_itr; itr != branches.first.end(); ++itr ) { + for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { pop_block(); } - EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception, - "loss of sync between fork_db and chainbase during fork switch reversal" ); // _should_ never fail + EOS_ASSERT( chain_head.id() == branches.second.back()->header.previous, fork_database_exception, + "loss of sync between fork_db and chainbase during fork switch" ); // _should_ never fail + + if( forked_cb ) { + // forked_branch is in reverse order, maintain execution order + for( auto ritr = branches.second.rbegin(), rend = branches.second.rend(); ritr != rend; ++ritr ) { + const auto& bsptr = *ritr; + for( auto itr = bsptr->trxs_metas().begin(), end = bsptr->trxs_metas().end(); itr != end; ++itr ) { + forked_cb(*itr); + } + } + } + } else if (!branches.first.empty()) { + ilog("applying ${n} fork db blocks from ${cbn}:${cbid} to ${nbn}:${nbid}", + ("n", branches.first.size())("cbid", (*branches.first.rbegin())->id())("cbn", (*branches.first.rbegin())->block_num()) + ("nbid", new_head->id())("nbn", new_head->block_num())); + } + + for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { + auto except = std::exception_ptr{}; + try { + const auto& bsp = *ritr; - // re-apply good blocks - for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) { br = controller::block_report{}; - apply_block( br, *ritr, controller::block_status::validated /* we previously validated these blocks*/, trx_lookup ); - } - std::rethrow_exception(except); - } // end if exception - } /// end for each block in branch - - if (fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::info)) { - auto get_ids = [&](auto& container)->std::string { - std::string ids; - for(auto ritr = container.rbegin(), e = container.rend(); ritr != e; ++ritr) { - ids += std::to_string((*ritr)->block_num) + ":" + (*ritr)->id.str() + ","; + apply_block( br, bsp, bsp->is_valid() ? controller::block_status::validated + : controller::block_status::complete, trx_lookup ); + + if( conf.terminate_at_block > 0 && conf.terminate_at_block <= chain_head.block_num()) { + ilog("Reached configured maximum block ${num}; terminating", ("num", conf.terminate_at_block) ); + shutdown(); + break; + } + if (!switch_fork && check_shutdown()) { + shutdown(); + break; + } + } catch ( const std::bad_alloc& ) { + throw; + } catch ( const boost::interprocess::bad_alloc& ) { + throw; + } catch (const fc::exception& e) { + elog("exception thrown while switching forks ${e}", ("e", e.to_detail_string())); + except = std::current_exception(); + } catch (const std::exception& e) { + elog("exception thrown while switching forks ${e}", ("e", e.what())); + except = std::current_exception(); } - if (!ids.empty()) ids.resize(ids.size()-1); - return ids; - }; - ilog("successfully switched fork to new head ${new_head_id}, removed {${rm_ids}}, applied {${new_ids}}", - ("new_head_id", new_head->id)("rm_ids", get_ids(branches.second))("new_ids", get_ids(branches.first))); + + if( except ) { + // ritr currently points to the block that threw + // Remove the block that threw and all forks built off it. + forkdb.remove( (*ritr)->id() ); + + // pop all blocks from the bad fork, discarding their transactions + // ritr base is a forward itr to the last block successfully applied + auto applied_itr = ritr.base(); + for( auto itr = applied_itr; itr != branches.first.end(); ++itr ) { + pop_block(); + } + EOS_ASSERT( chain_head.id() == branches.second.back()->header.previous, fork_database_exception, + "loss of sync between fork_db and chainbase during fork switch reversal" ); // _should_ never fail + + // re-apply good blocks + for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) { + br = controller::block_report{}; + apply_block( br, *ritr, controller::block_status::validated /* we previously validated these blocks*/, trx_lookup ); + } + std::rethrow_exception(except); + } // end if exception + } /// end for each block in branch + + if (switch_fork && fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::info)) { + auto get_ids = [&](auto& container)->std::string { + std::string ids; + for(auto ritr = container.rbegin(), e = container.rend(); ritr != e; ++ritr) { + ids += std::to_string((*ritr)->block_num()) + ":" + (*ritr)->id().str() + ","; + } + if (!ids.empty()) ids.resize(ids.size()-1); + return ids; + }; + ilog("successfully switched fork to new head ${new_head_id}, removed {${rm_ids}}, applied {${new_ids}}", + ("new_head_id", new_head->id())("rm_ids", get_ids(branches.second))("new_ids", get_ids(branches.first))); + } } - } else { - head_changed = false; - } - if( head_changed ) + // irreversible can change even if block not applied to head, integrated qc can move LIB log_irreversible(); + }; + + fork_db.apply(do_maybe_switch_forks); } /// push_block @@ -2431,53 +4141,67 @@ struct controller_impl { if( pending ) { applied_trxs = pending->extract_trx_metas(); pending.reset(); - protocol_features.popped_blocks_to( head->block_num ); + protocol_features.popped_blocks_to( chain_head.block_num() ); } return applied_trxs; } - static checksum256_type calculate_trx_merkle( const deque& trxs ) { + // @param if_active true if instant finality is active + static checksum256_type calc_merkle( deque&& digests, bool if_active ) { + if (if_active) { + return calculate_merkle( digests ); + } else { + return calculate_merkle_legacy( std::move(digests) ); + } + } + + static checksum256_type calculate_trx_merkle( const deque& trxs, bool if_active ) { deque trx_digests; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - return merkle( std::move(trx_digests) ); + return calc_merkle(std::move(trx_digests), if_active); } void update_producers_authority() { - const auto& producers = pending->get_pending_block_header_state_legacy().active_schedule.producers; - - auto update_permission = [&]( auto& permission, auto threshold ) { - auto auth = authority( threshold, {}, {}); - for( auto& p : producers ) { - auth.accounts.push_back({{p.producer_name, config::active_name}, 1}); - } + // this is not called when hotstuff is activated + auto& bb = std::get(pending->_block_stage); + bb.apply_l([this](building_block::building_block_legacy& legacy_header) { + pending_block_header_state_legacy& pbhs = legacy_header.pending_block_header_state; + const auto& producers = pbhs.active_schedule.producers; + + auto update_permission = [&](auto& permission, auto threshold) { + auto auth = authority(threshold, {}, {}); + for (auto& p : producers) { + auth.accounts.push_back({ + {p.producer_name, config::active_name}, + 1 + }); + } - if( permission.auth != auth ) { - db.modify(permission, [&]( auto& po ) { - po.auth = auth; - }); - } - }; + if (permission.auth != auth) { + db.modify(permission, [&](auto& po) { po.auth = auth; }); + } + }; - uint32_t num_producers = producers.size(); - auto calculate_threshold = [=]( uint32_t numerator, uint32_t denominator ) { - return ( (num_producers * numerator) / denominator ) + 1; - }; + uint32_t num_producers = producers.size(); + auto calculate_threshold = [=](uint32_t numerator, uint32_t denominator) { + return ((num_producers * numerator) / denominator) + 1; + }; - update_permission( authorization.get_permission({config::producers_account_name, - config::active_name}), - calculate_threshold( 2, 3 ) /* more than two-thirds */ ); + update_permission(authorization.get_permission({config::producers_account_name, config::active_name}), + calculate_threshold(2, 3) /* more than two-thirds */); - update_permission( authorization.get_permission({config::producers_account_name, - config::majority_producers_permission_name}), - calculate_threshold( 1, 2 ) /* more than one-half */ ); + update_permission( + authorization.get_permission({config::producers_account_name, config::majority_producers_permission_name}), + calculate_threshold(1, 2) /* more than one-half */); - update_permission( authorization.get_permission({config::producers_account_name, - config::minority_producers_permission_name}), - calculate_threshold( 1, 3 ) /* more than one-third */ ); + update_permission( + authorization.get_permission({config::producers_account_name, config::minority_producers_permission_name}), + calculate_threshold(1, 3) /* more than one-third */); - //TODO: Add tests + // TODO: Add tests + }); } void create_block_summary(const block_id_type& id) { @@ -2493,7 +4217,7 @@ struct controller_impl { //Look for expired transactions in the deduplication list, and remove them. auto& transaction_idx = db.get_mutable_index(); const auto& dedupe_index = transaction_idx.indices().get(); - auto now = self.is_building_block() ? self.pending_block_time() : self.head_block_time(); + auto now = is_building_block() ? pending_block_time() : chain_head.block_time().to_time_point(); const auto total = dedupe_index.size(); uint32_t num_removed = 0; while( (!dedupe_index.empty()) && ( now > dedupe_index.begin()->expiration.to_time_point() ) ) { @@ -2636,22 +4360,6 @@ struct controller_impl { } } - /* - bool should_check_tapos()const { return true; } - - void validate_tapos( const transaction& trx )const { - if( !should_check_tapos() ) return; - - const auto& tapos_block_summary = db.get((uint16_t)trx.ref_block_num); - - //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration - EOS_ASSERT(trx.verify_reference_block(tapos_block_summary.block_id), invalid_ref_block_exception, - "Transaction's reference block did not match. Is this transaction from a different fork?", - ("tapos_summary", tapos_block_summary)); - } - */ - - /** * At the start of each block we notify the system contract with a transaction that passes in * the block header of the prior block (which is currently our head block) @@ -2662,17 +4370,17 @@ struct controller_impl { on_block_act.account = config::system_account_name; on_block_act.name = "onblock"_n; on_block_act.authorization = vector{{config::system_account_name, config::active_name}}; - on_block_act.data = fc::raw::pack(self.head_block_header()); + on_block_act.data = fc::raw::pack(chain_head.header()); signed_transaction trx; trx.actions.emplace_back(std::move(on_block_act)); - if( self.is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) { + if( is_builtin_activated( builtin_protocol_feature_t::no_duplicate_deferred_id ) ) { trx.expiration = time_point_sec(); trx.ref_block_num = 0; trx.ref_block_prefix = 0; } else { - trx.expiration = time_point_sec{self.pending_block_time() + fc::microseconds(999'999)}; // Round up to nearest second to avoid appearing expired - trx.set_reference_block( self.head_block_id() ); + trx.expiration = time_point_sec{pending_block_time() + fc::microseconds(999'999)}; // Round up to nearest second to avoid appearing expired + trx.set_reference_block( chain_head.id() ); } return trx; @@ -2683,8 +4391,94 @@ struct controller_impl { return is_trx_transient ? nullptr : deep_mind_logger; } + void set_if_irreversible_block_id(const block_id_type& id) { + const block_num_type id_num = block_header::num_from_id(id); + auto accessor = if_irreversible_block_id.make_accessor(); + const block_num_type current_num = block_header::num_from_id(accessor.value()); + if( id_num > current_num ) { + dlog("set irreversible block ${bn}: ${id}, old ${obn}: ${oid}", ("bn", id_num)("id", id)("obn", current_num)("oid", accessor.value())); + accessor.value() = id; + } + } + + // This is only used during Savanna transition, which is a one-time occurrence, + // and it is only used by SHiP.. + // It is OK to calculate from Savanna Genesis block for each Transition block. + std::optional get_transition_block_finality_data(const block_state_legacy_ptr& head) const { + fork_database_legacy_t::branch_t legacy_branch; + block_state_legacy_ptr legacy_root; + fork_db.apply_l([&](const auto& forkdb) { + legacy_root = forkdb.root(); + legacy_branch = forkdb.fetch_branch(head->id()); + }); + + block_state_ptr prev; + auto bitr = legacy_branch.rbegin(); + + // get_transition_block_finality_data is called by SHiP as a result + // of receiving accepted_block signal. That is before + // the call to log_irreversible where root() is updated. + // Search both root and legacy_branch for the first block having + // instant_finality_extension -- the Savanna Genesis Block. + // Then start from the Savanna Genesis Block to create corresponding + // Savanna blocks. + // genesis_block already contains all information for finality_data. + if (legacy_root->header.contains_header_extension(instant_finality_extension::extension_id())) { + prev = block_state::create_if_genesis_block(*legacy_root); + } else { + for (; bitr != legacy_branch.rend(); ++bitr) { + if ((*bitr)->header.contains_header_extension(instant_finality_extension::extension_id())) { + prev = block_state::create_if_genesis_block(*(*bitr)); + ++bitr; + break; + } + } + } + + assert(prev); + const bool skip_validate_signee = true; // validated already + + for (; bitr != legacy_branch.rend(); ++bitr) { + auto new_bsp = std::make_shared( + *prev, + (*bitr)->block, + protocol_features.get_protocol_feature_set(), + validator_t{}, skip_validate_signee); + + // We only need action_mroot of the last block for finality_data + if ((bitr + 1) == legacy_branch.rend()) { + assert((*bitr)->action_mroot_savanna); + new_bsp->action_mroot = *((*bitr)->action_mroot_savanna); + } + + prev = new_bsp; + } + + assert(prev); + return prev->get_finality_data(); + } + + std::optional head_finality_data() const { + return apply>(chain_head, overloaded{ + [&](const block_state_legacy_ptr& head) -> std::optional { + // When in Legacy, if it is during transition to Savana, we need to + // build finality_data for the corresponding Savanna block + if (head->header.contains_header_extension(instant_finality_extension::extension_id())) { + // during transition + return get_transition_block_finality_data(head); + } else { + // pre transition + return {}; + } + }, + [](const block_state_ptr& head) { + // Returns finality_data from chain_head because we are in Savanna + return head->get_finality_data(); + }}); + } + uint32_t earliest_available_block_num() const { - return (blog.first_block_num() != 0) ? blog.first_block_num() : fork_db.root()->block_num; + return (blog.first_block_num() != 0) ? blog.first_block_num() : fork_db_root_block_num(); } void set_to_write_window() { @@ -2721,7 +4515,113 @@ struct controller_impl { wasmif.code_block_num_last_used(code_hash, vm_type, vm_version, block_num); } - block_state_legacy_ptr fork_db_head() const; + void set_node_finalizer_keys(const bls_pub_priv_key_map_t& finalizer_keys) { + my_finalizers.set_keys(finalizer_keys); + } + + bool irreversible_mode() const { return read_mode == db_read_mode::IRREVERSIBLE; } + + bool light_validation_allowed() const { + if (!pending || in_trx_requiring_checks) { + return false; + } + + const auto pb_status = pending->_block_status; + + // in a pending irreversible or previously validated block and we have forcing all checks + const bool consider_skipping_on_replay = + (pb_status == controller::block_status::irreversible || pb_status == controller::block_status::validated) && !conf.force_all_checks; + + // OR in a signed block and in light validation mode + const bool consider_skipping_on_validate = pb_status == controller::block_status::complete && + (conf.block_validation_mode == validation_mode::LIGHT || trusted_producer_light_validation); + + return consider_skipping_on_replay || consider_skipping_on_validate; + } + + + bool skip_auth_check() const { + return light_validation_allowed(); + } + + bool skip_trx_checks() const { + return light_validation_allowed(); + } + + bool skip_db_sessions( controller::block_status bs ) const { + bool consider_skipping = bs == controller::block_status::irreversible; + return consider_skipping + && !conf.disable_replay_opts + && !in_trx_requiring_checks; + } + + bool skip_db_sessions() const { + if (pending) { + return skip_db_sessions(pending->_block_status); + } else { + return false; + } + } + + bool is_trusted_producer( const account_name& producer) const { + return conf.block_validation_mode == validation_mode::LIGHT || conf.trusted_producers.count(producer); + } + + bool is_builtin_activated( builtin_protocol_feature_t f )const { + uint32_t current_block_num = chain_head.block_num(); + + if( pending ) { + ++current_block_num; + } + + return protocol_features.is_builtin_activated( f, current_block_num ); + } + + block_timestamp_type pending_block_timestamp()const { + EOS_ASSERT( pending, block_validate_exception, "no pending block" ); + + return pending->timestamp(); + } + + time_point pending_block_time()const { + return pending_block_timestamp(); + } + + bool is_building_block()const { + return pending.has_value() && !std::holds_alternative(pending->_block_stage); + } + + bool is_speculative_block()const { + if( !pending ) return false; + + return (pending->_block_status == controller::block_status::incomplete || pending->_block_status == controller::block_status::ephemeral ); + } + + std::optional pending_producer_block_id()const { + EOS_ASSERT( pending, block_validate_exception, "no pending block" ); + return pending->_producer_block_id; + } + + void validate_db_available_size() const { + const auto free = db.get_free_memory(); + const auto guard = conf.state_guard_size; + EOS_ASSERT(free >= guard, database_guard_exception, "database free: ${f}, guard size: ${g}", ("f", free)("g",guard)); + } + + const producer_authority_schedule& active_producers()const { + if( !(pending) ) + return head_active_schedule_auth(); + + return pending->active_producers(); + } + + const producer_authority_schedule* pending_producers_legacy()const { + if( !(pending) ) + return head_pending_schedule_auth_legacy(); + + return pending->pending_producers_legacy(); + } + }; /// controller_impl thread_local platform_timer controller_impl::timer; @@ -2773,12 +4673,12 @@ controller::controller( const config& cfg, protocol_feature_set&& pfs, const cha controller::~controller() { my->abort_block(); - /* Shouldn't be needed anymore. - //close fork_db here, because it can generate "irreversible" signal to this controller, - //in case if read-mode == IRREVERSIBLE, we will apply latest irreversible block - //for that we need 'my' to be valid pointer pointing to valid controller_impl. - my->fork_db.close(); - */ + // controller_impl (my) holds a reference to controller (controller_impl.self). + // The self is passed to transaction_context which passes it on to apply_context. + // Currently nothing posted to the thread_pool accesses the `self` reference, but to make + // sure it is safe in case something is added to the thread pool that does access self, + // stop the thread pool before the unique_ptr (my) destructor runs. + my->thread_pool.stop(); } void controller::add_indices() { @@ -2801,8 +4701,6 @@ const chainbase::database& controller::db()const { return my->db; } chainbase::database& controller::mutable_db()const { return my->db; } -const fork_database& controller::fork_db()const { return my->fork_db; } - void controller::preactivate_feature( const digest_type& feature_digest, bool is_trx_transient ) { const auto& pfs = my->protocol_features.get_protocol_feature_set(); auto cur_time = pending_block_time(); @@ -2926,8 +4824,8 @@ vector controller::get_preactivated_protocol_features()const { } void controller::validate_protocol_features( const vector& features_to_activate )const { - my->check_protocol_features( my->head->header.timestamp, - my->head->activated_protocol_features->protocol_features, + my->check_protocol_features( my->chain_head.block_time(), + my->head_activated_protocol_features()->protocol_features, features_to_activate ); } @@ -2949,37 +4847,33 @@ void controller::start_block( block_timestamp_type when, bs, std::optional(), deadline ); } -block_state_legacy_ptr controller::finalize_block( block_report& br, const signer_callback_type& signer_callback ) { +void controller::assemble_and_complete_block( block_report& br, const signer_callback_type& signer_callback ) { validate_db_available_size(); - my->finalize_block(); + my->assemble_block(false, {}, nullptr); auto& ab = std::get(my->pending->_block_stage); - - auto bsp = std::make_shared( - std::move( ab._pending_block_header_state_legacy ), - std::move( ab._unsigned_block ), - std::move( ab._trx_metas ), - my->protocol_features.get_protocol_feature_set(), - []( block_timestamp_type timestamp, - const flat_set& cur_features, - const vector& new_features ) - {}, - signer_callback - ); - - my->pending->_block_stage = completed_block{ bsp }; + const auto& valid_block_signing_authority = my->head_active_schedule_auth().get_scheduled_producer(ab.timestamp()).authority; + my->pending->_block_stage = ab.complete_block( + my->protocol_features.get_protocol_feature_set(), + [](block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features) {}, + signer_callback, + valid_block_signing_authority); br = my->pending->_block_report; +} - return bsp; +void controller::commit_block(block_report& br) { + validate_db_available_size(); + my->commit_block(br, block_status::incomplete); } -void controller::commit_block() { +void controller::maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { validate_db_available_size(); - my->commit_block(block_status::incomplete); + my->maybe_switch_forks(cb, trx_lookup); } + deque controller::abort_block() { return my->abort_block(); } @@ -2988,21 +4882,25 @@ boost::asio::io_context& controller::get_thread_pool() { return my->thread_pool.get_executor(); } -std::future controller::create_block_state_future( const block_id_type& id, const signed_block_ptr& b ) { - return my->create_block_state_future( id, b ); +std::future controller::create_block_handle_future( const block_id_type& id, const signed_block_ptr& b ) { + return my->create_block_handle_future( id, b ); } -block_state_legacy_ptr controller::create_block_state( const block_id_type& id, const signed_block_ptr& b ) const { - return my->create_block_state( id, b ); +std::optional controller::create_block_handle( const block_id_type& id, const signed_block_ptr& b ) const { + return my->create_block_handle( id, b ); } -void controller::push_block( controller::block_report& br, - const block_state_legacy_ptr& bsp, - const forked_branch_callback& forked_branch_cb, +void controller::push_block( block_report& br, + const block_handle& bh, + const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) { validate_db_available_size(); - my->push_block( br, bsp, forked_branch_cb, trx_lookup ); + apply(bh, [&](const auto& bsp) { my->push_block( br, bsp, forked_cb, trx_lookup); }); +} + +void controller::accept_block(const block_handle& bh) { + apply(bh, [&](const auto& bsp) { my->accept_block(bsp); }); } transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, @@ -3071,101 +4969,96 @@ void controller::set_disable_replay_opts( bool v ) { } uint32_t controller::head_block_num()const { - return my->head->block_num; + return my->chain_head.block_num(); +} +block_timestamp_type controller::head_block_timestamp()const { + return my->chain_head.block_time(); } time_point controller::head_block_time()const { - return my->head->header.timestamp; + return my->chain_head.block_time(); } block_id_type controller::head_block_id()const { - return my->head->id; + return my->chain_head.id(); } + account_name controller::head_block_producer()const { - return my->head->header.producer; + return my->chain_head.producer(); } + const block_header& controller::head_block_header()const { - return my->head->header; + return my->chain_head.header(); } -block_state_legacy_ptr controller::head_block_state()const { - return my->head; + +block_state_legacy_ptr controller::head_block_state_legacy()const { + // returns null after instant finality activated + return apply_l(my->chain_head, [](const auto& head) { + return head; + }); } -block_state_legacy_ptr controller_impl::fork_db_head() const { - if( read_mode == db_read_mode::IRREVERSIBLE ) { - // When in IRREVERSIBLE mode fork_db blocks are marked valid when they become irreversible so that - // fork_db.head() returns irreversible block - // Use pending_head since this method should return the chain head and not last irreversible. - return fork_db.pending_head(); - } else { - return fork_db.head(); - } +const signed_block_ptr& controller::head_block()const { + return my->chain_head.block(); +} + +std::optional controller::head_finality_data() const { + return my->head_finality_data(); } uint32_t controller::fork_db_head_block_num()const { - return my->fork_db_head()->block_num; + return my->fork_db_head_block_num(); } block_id_type controller::fork_db_head_block_id()const { - return my->fork_db_head()->id; + return my->fork_db_head_block_id(); } block_timestamp_type controller::pending_block_timestamp()const { - EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); - - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->header.timestamp; - - return my->pending->get_pending_block_header_state_legacy().timestamp; + return my->pending_block_timestamp(); } time_point controller::pending_block_time()const { - return pending_block_timestamp(); + return my->pending_block_time(); } uint32_t controller::pending_block_num()const { EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); - - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->header.block_num(); - - return my->pending->get_pending_block_header_state_legacy().block_num; + return my->pending->block_num(); } account_name controller::pending_block_producer()const { EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); - - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->header.producer; - - return my->pending->get_pending_block_header_state_legacy().producer; + return my->pending->producer(); } -const block_signing_authority& controller::pending_block_signing_authority()const { +const block_signing_authority& controller::pending_block_signing_authority() const { EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); + return my->pending->pending_block_signing_authority(); +} - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->valid_block_signing_authority; +std::optional controller::pending_producer_block_id()const { + return my->pending_producer_block_id(); +} - return my->pending->get_pending_block_header_state_legacy().valid_block_signing_authority; +void controller::set_if_irreversible_block_id(const block_id_type& id) { + my->set_if_irreversible_block_id(id); } -std::optional controller::pending_producer_block_id()const { - EOS_ASSERT( my->pending, block_validate_exception, "no pending block" ); - return my->pending->_producer_block_id; +uint32_t controller::if_irreversible_block_num() const { + return block_header::num_from_id(my->if_irreversible_block_id.load()); } uint32_t controller::last_irreversible_block_num() const { - return my->fork_db.root()->block_num; + return my->fork_db_root_block_num(); } block_id_type controller::last_irreversible_block_id() const { - return my->fork_db.root()->id; + return my->fork_db_root_block_id(); } time_point controller::last_irreversible_block_time() const { - return my->fork_db.root()->header.timestamp.to_time_point(); + return my->fork_db_root_timestamp().to_time_point(); } - const dynamic_global_property_object& controller::get_dynamic_global_properties()const { return my->db.get(); } @@ -3174,47 +5067,51 @@ const global_property_object& controller::get_global_properties()const { } signed_block_ptr controller::fetch_block_by_id( const block_id_type& id )const { - auto state = my->fork_db.get_block(id); - if( state && state->block ) return state->block; + auto sb_ptr = my->fork_db_fetch_block_by_id(id); + if( sb_ptr ) return sb_ptr; auto bptr = my->blog.read_block_by_num( block_header::num_from_id(id) ); if( bptr && bptr->calculate_id() == id ) return bptr; return signed_block_ptr(); } +bool controller::block_exists(const block_id_type& id) const { + bool exists = my->fork_db_block_exists(id); + if( exists ) return true; + std::optional sbh = my->blog.read_block_header_by_num( block_header::num_from_id(id) ); + return sbh && sbh->calculate_id() == id; +} + +bool controller::validated_block_exists(const block_id_type& id) const { + bool exists = my->fork_db_validated_block_exists(id); + if( exists ) return true; + std::optional sbh = my->blog.read_block_header_by_num( block_header::num_from_id(id) ); + return sbh && sbh->calculate_id() == id; +} + std::optional controller::fetch_block_header_by_id( const block_id_type& id )const { - auto state = my->fork_db.get_block(id); - if( state && state->block ) return state->header; + auto sb_ptr = my->fork_db_fetch_block_by_id(id); + if( sb_ptr ) return *static_cast(sb_ptr.get()); auto result = my->blog.read_block_header_by_num( block_header::num_from_id(id) ); if( result && result->calculate_id() == id ) return result; return {}; } signed_block_ptr controller::fetch_block_by_number( uint32_t block_num )const { try { - auto blk_state = fetch_block_state_by_number( block_num ); - if( blk_state ) { - return blk_state->block; - } + auto b = my->fetch_block_on_head_branch_by_num( block_num ); + if (b) + return b; return my->blog.read_block_by_num(block_num); } FC_CAPTURE_AND_RETHROW( (block_num) ) } std::optional controller::fetch_block_header_by_number( uint32_t block_num )const { try { - auto blk_state = fetch_block_state_by_number( block_num ); - if( blk_state ) { - return blk_state->header; - } + auto b = my->fetch_block_on_head_branch_by_num(block_num); + if (b) + return *b; return my->blog.read_block_header_by_num(block_num); } FC_CAPTURE_AND_RETHROW( (block_num) ) } -block_state_legacy_ptr controller::fetch_block_state_by_id( block_id_type id )const { - auto state = my->fork_db.get_block(id); - return state; -} - -block_state_legacy_ptr controller::fetch_block_state_by_number( uint32_t block_num )const { try { - return my->fork_db.search_on_branch( fork_db_head_block_id(), block_num ); -} FC_CAPTURE_AND_RETHROW( (block_num) ) } block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try { const auto& blog_head = my->blog.head(); @@ -3222,8 +5119,8 @@ block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try bool find_in_blog = (blog_head && block_num <= blog_head->block_num()); if( !find_in_blog ) { - auto bsp = fetch_block_state_by_number( block_num ); - if( bsp ) return bsp->id; + std::optional id = my->fetch_block_id_on_head_branch_by_num(block_num); + if (id) return *id; } auto id = my->blog.read_block_id_by_num(block_num); @@ -3234,6 +5131,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() } @@ -3252,8 +5153,44 @@ bool controller::is_writing_snapshot() const { } int64_t controller::set_proposed_producers( vector producers ) { - const auto& gpo = get_global_properties(); - auto cur_block_num = head_block_num() + 1; + assert(my->pending); + if (my->pending->is_legacy()) { + return my->set_proposed_producers_legacy(std::move(producers)); + } else { + return my->set_proposed_producers(std::move(producers)); + } +} + +int64_t controller_impl::set_proposed_producers( vector producers ) { + // Savanna sets the global_property_object.proposed_schedule similar to legacy, but it is only set during the building of the block. + // global_property_object is used instead of building_block so that if the transaction fails it is rolledback. + + if (producers.empty()) + return -1; // INSTANT_FINALITY depends on DISALLOW_EMPTY_PRODUCER_SCHEDULE + + assert(pending); + const auto& gpo = db.get(); + auto cur_block_num = chain_head.block_num() + 1; + + producer_authority_schedule sch; + + sch.version = pending->get_next_proposer_schedule_version(); + sch.producers = std::move(producers); + + ilog( "proposed producer schedule with version ${v}", ("v", sch.version) ); + + // overwrite any existing proposed_schedule set earlier in this block + db.modify( gpo, [&]( auto& gp ) { + gp.proposed_schedule_block_num = cur_block_num; + gp.proposed_schedule = sch; + }); + + return sch.version; +} + +int64_t controller_impl::set_proposed_producers_legacy( vector producers ) { + const auto& gpo = db.get(); + auto cur_block_num = chain_head.block_num() + 1; if( producers.size() == 0 && is_builtin_activated( builtin_protocol_feature_t::disallow_empty_producer_schedule ) ) { return -1; @@ -3273,68 +5210,71 @@ int64_t controller::set_proposed_producers( vector producers decltype(sch.producers.cend()) end; decltype(end) begin; - const auto& pending_sch = pending_producers(); + const auto* pending_sch = pending_producers_legacy(); + assert(pending_sch); // can't be null during dpos - if( pending_sch.producers.size() == 0 ) { + if( pending_sch->producers.size() == 0 ) { const auto& active_sch = active_producers(); begin = active_sch.producers.begin(); end = active_sch.producers.end(); sch.version = active_sch.version + 1; } else { - begin = pending_sch.producers.begin(); - end = pending_sch.producers.end(); - sch.version = pending_sch.version + 1; + begin = pending_sch->producers.begin(); + end = pending_sch->producers.end(); + sch.version = pending_sch->version + 1; } if( std::equal( producers.begin(), producers.end(), begin, end ) ) return -1; // the producer schedule would not change + // ignore proposed producers during transition + assert(pending); + auto& bb = std::get(pending->_block_stage); + bool transition_block = bb.apply_l([](building_block::building_block_legacy& bl) { + return bl.pending_block_header_state.is_if_transition_block() || bl.new_finalizer_policy; + }); + if (transition_block) + return -1; + sch.producers = std::move(producers); int64_t version = sch.version; ilog( "proposed producer schedule with version ${v}", ("v", version) ); - my->db.modify( gpo, [&]( auto& gp ) { + db.modify( gpo, [&]( auto& gp ) { gp.proposed_schedule_block_num = cur_block_num; gp.proposed_schedule = sch; }); return version; } -const producer_authority_schedule& controller::active_producers()const { - if( !(my->pending) ) - return my->head->active_schedule; - - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->active_schedule; - - return my->pending->get_pending_block_header_state_legacy().active_schedule; +void controller::set_proposed_finalizers( finalizer_policy&& fin_pol ) { + my->set_proposed_finalizers(std::move(fin_pol)); } -const producer_authority_schedule& controller::pending_producers()const { - if( !(my->pending) ) - return my->head->pending_schedule.schedule; - - if( std::holds_alternative(my->pending->_block_stage) ) - return std::get(my->pending->_block_stage)._block_state->pending_schedule.schedule; +// called from net threads +vote_status controller::process_vote_message( const vote_message& vote ) { + return my->process_vote_message( vote ); +}; - if( std::holds_alternative(my->pending->_block_stage) ) { - const auto& new_prods_cache = std::get(my->pending->_block_stage)._new_producer_authority_cache; - if( new_prods_cache ) { - return *new_prods_cache; - } - } +bool controller::node_has_voted_if_finalizer(const block_id_type& id) const { + return my->node_has_voted_if_finalizer(id); +} - const auto& bb = std::get(my->pending->_block_stage); +const producer_authority_schedule& controller::active_producers()const { + return my->active_producers(); +} - if( bb._new_pending_producer_schedule ) - return *bb._new_pending_producer_schedule; +const producer_authority_schedule& controller::head_active_producers()const { + return my->head_active_schedule_auth(); +} - return bb._pending_block_header_state_legacy.prev_pending_schedule.schedule; +const producer_authority_schedule* controller::pending_producers_legacy()const { + return my->pending_producers_legacy(); } -std::optional controller::proposed_producers()const { +std::optional controller::proposed_producers_legacy()const { const auto& gpo = get_global_properties(); if( !gpo.proposed_schedule_block_num ) return std::optional(); @@ -3342,50 +5282,35 @@ std::optional controller::proposed_producers()const return producer_authority_schedule::from_shared(gpo.proposed_schedule); } -bool controller::light_validation_allowed() const { - if (!my->pending || my->in_trx_requiring_checks) { - return false; - } - - const auto pb_status = my->pending->_block_status; - - // in a pending irreversible or previously validated block and we have forcing all checks - const bool consider_skipping_on_replay = - (pb_status == block_status::irreversible || pb_status == block_status::validated) && !my->conf.force_all_checks; - - // OR in a signed block and in light validation mode - const bool consider_skipping_on_validate = (pb_status == block_status::complete && - (my->conf.block_validation_mode == validation_mode::LIGHT || my->trusted_producer_light_validation)); +const producer_authority_schedule* controller::next_producers()const { + if( !(my->pending) ) + return my->next_producers(); - return consider_skipping_on_replay || consider_skipping_on_validate; + return my->pending->next_producers(); } +bool controller::light_validation_allowed() const { + return my->light_validation_allowed(); +} bool controller::skip_auth_check() const { - return light_validation_allowed(); + return my->skip_auth_check(); } bool controller::skip_trx_checks() const { - return light_validation_allowed(); + return my->skip_trx_checks(); } bool controller::skip_db_sessions( block_status bs ) const { - bool consider_skipping = bs == block_status::irreversible; - return consider_skipping - && !my->conf.disable_replay_opts - && !my->in_trx_requiring_checks; + return my->skip_db_sessions( bs ); } bool controller::skip_db_sessions() const { - if (my->pending) { - return skip_db_sessions(my->pending->_block_status); - } else { - return false; - } + return my->skip_db_sessions(); } bool controller::is_trusted_producer( const account_name& producer) const { - return get_validation_mode() == chain::validation_mode::LIGHT || my->conf.trusted_producers.count(producer); + return my->is_trusted_producer( producer ); } bool controller::contracts_console()const { @@ -3452,13 +5377,11 @@ void controller::check_key_list( const public_key_type& key )const { } bool controller::is_building_block()const { - return my->pending.has_value(); + return my->is_building_block(); } bool controller::is_speculative_block()const { - if( !my->pending ) return false; - - return (my->pending->_block_status == block_status::incomplete || my->pending->_block_status == block_status::ephemeral ); + return my->is_speculative_block(); } bool controller::is_ram_billing_in_notify_allowed()const { @@ -3495,34 +5418,19 @@ void controller::validate_tapos( const transaction& trx )const { try { } FC_CAPTURE_AND_RETHROW() } void controller::validate_db_available_size() const { - const auto free = db().get_free_memory(); - const auto guard = my->conf.state_guard_size; - EOS_ASSERT(free >= guard, database_guard_exception, "database free: ${f}, guard size: ${g}", ("f", free)("g",guard)); - - // give a change to chainbase to write some pages to disk if memory becomes scarce. - if (is_write_window()) { - if (auto flushed_pages = mutable_db().check_memory_and_flush_if_needed()) { - ilog("CHAINBASE: flushed ${p} pages to disk to decrease memory pressure", ("p", flushed_pages)); - } - } + return my->validate_db_available_size(); } bool controller::is_protocol_feature_activated( const digest_type& feature_digest )const { if( my->pending ) return my->pending->is_protocol_feature_activated( feature_digest ); - const auto& activated_features = my->head->activated_protocol_features->protocol_features; + const auto& activated_features = my->head_activated_protocol_features()->protocol_features; return (activated_features.find( feature_digest ) != activated_features.end()); } bool controller::is_builtin_activated( builtin_protocol_feature_t f )const { - uint32_t current_block_num = head_block_num(); - - if( my->pending ) { - ++current_block_num; - } - - return my->protocol_features.is_builtin_activated( f, current_block_num ); + return my->is_builtin_activated( f ); } bool controller::is_known_unexpired_transaction( const transaction_id_type& id) const { @@ -3625,6 +5533,13 @@ std::optional controller::convert_exception_to_error_code( const fc::e return e_ptr->error_code; } +signal& controller::block_start() { return my->block_start; } +signal& controller::accepted_block_header() { return my->accepted_block_header; } +signal& controller::accepted_block() { return my->accepted_block; } +signal& controller::irreversible_block() { return my->irreversible_block; } +signal)>& controller::applied_transaction() { return my->applied_transaction; } +signal& controller::voted_block() { return my->voted_block; } + chain_id_type controller::extract_chain_id(snapshot_reader& snapshot) { chain_snapshot_header header; snapshot.read_section([&header]( auto §ion ){ @@ -3685,21 +5600,14 @@ std::optional controller::extract_chain_id_from_db( const path& s void controller::replace_producer_keys( const public_key_type& key ) { ilog("Replace producer keys with ${k}", ("k", key)); + // can be done even after instant-finality, will be no-op then mutable_db().modify( db().get(), [&]( auto& gp ) { gp.proposed_schedule_block_num = {}; gp.proposed_schedule.version = 0; gp.proposed_schedule.producers.clear(); }); - auto version = my->head->pending_schedule.schedule.version; - my->head->pending_schedule = {}; - my->head->pending_schedule.schedule.version = version; - for (auto& prod: my->head->active_schedule.producers ) { - ilog("${n}", ("n", prod.producer_name)); - std::visit([&](auto &auth) { - auth.threshold = 1; - auth.keys = {key_weight{key, 1}}; - }, prod.authority); - } + + my->replace_producer_keys(key); } void controller::replace_account_keys( name account, name permission, const public_key_type& key ) { @@ -3750,6 +5658,10 @@ void controller::code_block_num_last_used(const digest_type& code_hash, uint8_t return my->code_block_num_last_used(code_hash, vm_type, vm_version, block_num); } +void controller::set_node_finalizer_keys(const bls_pub_priv_key_map_t& finalizer_keys) { + my->set_node_finalizer_keys(finalizer_keys); +} + /// Protocol feature activation handlers: template<> @@ -3878,6 +5790,13 @@ void controller_impl::on_activation +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "set_finalizers" ); + } ); +} + /// End of protocol feature activation handlers -} } /// eosio::chain +} /// eosio::chain diff --git a/libraries/chain/deep_mind.cpp b/libraries/chain/deep_mind.cpp index 7d740235cd..3714adf6ce 100644 --- a/libraries/chain/deep_mind.cpp +++ b/libraries/chain/deep_mind.cpp @@ -77,7 +77,7 @@ namespace eosio::chain { auto packed_blk = fc::raw::pack(*bsp); fc_dlog(_logger, "ACCEPTED_BLOCK ${num} ${blk}", - ("num", bsp->block_num) + ("num", bsp->block_num()) ("blk", fc::to_hex(packed_blk)) ); } diff --git a/libraries/chain/finality_core.cpp b/libraries/chain/finality_core.cpp new file mode 100644 index 0000000000..360473e335 --- /dev/null +++ b/libraries/chain/finality_core.cpp @@ -0,0 +1,364 @@ +#include +#include + +namespace eosio::chain { + +/** + * @pre block_id is not null + * @returns the extracted block_num from block_id + */ +block_num_type block_ref::block_num() const { + return block_header::num_from_id(block_id); +} + +/** + * @pre none + * + * @post returned core has current_block_num() == block_num + * @post returned core has latest_qc_claim() == {.block_num=block_num, .is_strong_qc=false} + * @post returned core has final_on_strong_qc_block_num == block_num + * @post returned core has last_final_block_num() == block_num + */ +finality_core finality_core::create_core_for_genesis_block(block_num_type block_num) +{ + return finality_core { + .links = { + qc_link{ + .source_block_num = block_num, + .target_block_num = block_num, + .is_link_strong = false, + }, + }, + .refs = {}, + .final_on_strong_qc_block_num = block_num, + }; + + // Invariants 1 to 7 can be easily verified to be satisfied for the returned core. + // (And so, remaining invariants are also automatically satisfied.) +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns block number of the core + */ +block_num_type finality_core::current_block_num() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return links.back().source_block_num; +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns last final block_num in respect to the core + */ +block_num_type finality_core::last_final_block_num() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return links.front().target_block_num; +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns latest qc_claim made by the core + */ +qc_claim_t finality_core::latest_qc_claim() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return qc_claim_t{.block_num = links.back().target_block_num, .is_strong_qc = links.back().is_link_strong}; +} + +/** + * @pre all finality_core invariants + * @post same + * @returns timestamp of latest qc_claim made by the core + */ +block_time_type finality_core::latest_qc_block_timestamp() const { + return get_block_reference(links.back().target_block_num).timestamp; +} + +/** + * @pre all finality_core invariants + * @post same + * @returns boolean indicating whether `id` is an ancestor of this block + */ +bool finality_core::extends(const block_id_type& id) const { + uint32_t block_num = block_header::num_from_id(id); + if (block_num >= last_final_block_num() && block_num < current_block_num()) { + const block_ref& ref = get_block_reference(block_num); + return ref.block_id == id; + } + return false; +} + +/** + * @pre last_final_block_num() <= candidate_block_num <= current_block_block_num() + * @post same + * @returns boolean indicating whether `candidate_block_num` is the genesis block number or not + */ +bool finality_core::is_genesis_block_num(block_num_type candidate_block_num) const { + assert(last_final_block_num() <= candidate_block_num && + candidate_block_num <= current_block_num()); + + return (links.front().source_block_num == links.front().target_block_num && + links.front().source_block_num == candidate_block_num); +} + +/** + * @pre last_final_block_num() <= block_num < current_block_num() + * + * @post returned block_ref has block_num() == block_num + */ +const block_ref& finality_core::get_block_reference(block_num_type block_num) const +{ + assert(last_final_block_num() <= block_num); // Satisfied by precondition. + assert(block_num < current_block_num()); // Satisfied by precondition. + + // If refs.empty() == true, then by invariant 3, current_block_num() == last_final_block_num(), + // and therefore it is impossible to satisfy the precondition. So going forward, it is safe to assume refs.empty() == false. + + const size_t ref_index = block_num - last_final_block_num(); + + // By the precondition, 0 <= ref_index < (current_block_num() - last_final_block_num()). + // Then, by invariant 8, 0 <= ref_index < refs.size(). + + assert(ref_index < refs.size()); // Satisfied by justification above. + + return refs[ref_index]; + // By invariants 4 and 6, tail[ref_index].block_num() == block_num, which satisfies the post-condition. +} + +/** + * @pre links.front().source_block_num <= block_num <= current_block_num() + * + * @post returned qc_link has source_block_num == block_num + */ +const qc_link& finality_core::get_qc_link_from(block_num_type block_num) const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + assert(links.front().source_block_num <= block_num); // Satisfied by precondition. + assert(block_num <= current_block_num()); // Satisfied by precondition. + + const size_t link_index = block_num - links.front().source_block_num; + + // By the precondition, 0 <= link_index <= (current_block_num() - links.front().source_block_num). + // Then, by invariant 9, 0 <= link_index <= links.size() - 1 + + assert(link_index < links.size()); // Satisfied by justification above. + + return links[link_index]; + // By invariants 7, links[link_index].source_block_num == block_num, which satisfies the post-condition. +} + +/** + * @pre c.latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= c.current_block_num() + * + * @post std::get<0>(returned_value) <= std::get<1>(returned_value) <= std::get<2>(returned_value) <= most_recent_ancestor_with_qc.block_num + * @post c.last_final_block_num() <= std::get<0>(returned_value) + * @post c.links.front().source_block_num <= std::get<1>(returned_value) + * @post c.final_on_strong_qc_block_num <= std::get<2>(returned_value) + */ +std::tuple get_new_block_numbers(const finality_core& c, const qc_claim_t& most_recent_ancestor_with_qc) +{ + assert(most_recent_ancestor_with_qc.block_num <= c.current_block_num()); // Satisfied by the precondition. + + // Invariant 2 of core guarantees that: + // c.last_final_block_num() <= c.links.front().source_block_num <= c.final_on_strong_qc_block_num <= c.latest_qc_claim().block_num + + assert(c.links.front().source_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied by invariant 2 of core and the precondition. + + // No changes on new claim of weak QC. + if (!most_recent_ancestor_with_qc.is_strong_qc) { + return {c.last_final_block_num(), c.links.front().source_block_num, c.final_on_strong_qc_block_num}; + } + + const auto& link1 = c.get_qc_link_from(most_recent_ancestor_with_qc.block_num); + + // By the post-condition of get_qc_link_from, link1.source_block_num == most_recent_ancestor_with_qc.block_num. + // By the invariant on qc_link, link1.target_block_num <= link1.source_block_num. + // Therefore, link1.target_block_num <= most_recent_ancestor_with_qc.block_num. + // And also by the precondition, link1.target_block_num <= c.current_block_num(). + + // If c.refs.empty() == true, then by invariant 3 of core, link1 == c.links.front() == c.links.back() and so + // link1.target_block_num == c.current_block_num(). + + // Otherwise, if c.refs.empty() == false, consider two cases. + // Case 1: link1 != c.links.back() + // In this case, link1.target_block_num <= link1.source_block_num < c.links.back().source_block_num. + // The strict inequality is justified by invariant 7 of core. + // Therefore, link1.target_block_num < c.current_block_num(). + // Case 2: link1 == c.links.back() + // In this case, link1.target_block_num < link1.source_block_num == c.links.back().source_block_num. + // The strict inequality is justified because target_block_num and source_block_num of a qc_link can only be equal for a + // genesis block. And a link mapping genesis block number to genesis block number can only possibly exist for c.links.front(). + // Therefore, link1.target_block_num < c.current_block_num(). + + // There must exist some link, call it link0, within c.links where + // link0.target_block_num == c.final_on_strong_qc_block_num and link0.source_block_num <= c.latest_qc_claim().block_num. + // By the precondition, link0.source_block_num <= most_recent_ancestor_with_qc.block_num. + // If c.links.size() > 1, then by invariant 7 of core, link0.target_block_num <= link1.target_block_num. + // Otherwise if c.links.size() == 1, then link0 == link1 and so link0.target_block_num == link1.target_block_num. + // Therefore, c.final_on_strong_qc_block_num <= link1.target_block_num. + + assert(c.final_on_strong_qc_block_num <= link1.target_block_num); // Satisfied by justification above. + + // Finality does not advance if a better 3-chain is not found. + if (!link1.is_link_strong || (link1.target_block_num < c.links.front().source_block_num)) { + return {c.last_final_block_num(), c.links.front().source_block_num, link1.target_block_num}; + } + + const auto& link2 = c.get_qc_link_from(link1.target_block_num); + + // By the post-condition of get_qc_link_from, link2.source_block_num == link1.target_block_num. + // By the invariant on qc_link, link2.target_block_num <= link2.source_block_num. + // Therefore, link2.target_block_num <= link1.target_block_num. + + // Wherever link2 is found within c.links, it must be the case that c.links.front().target_block_num <= link2.target_block_num. + // This is obvious if c.links.size() == 1 (even though the code would even not get to this point if c.links.size() == 1), and + // for the case where c.links.size() > 1, it is justified by invariant 7 of core. + // Therefore, c.last_final_block_num() <= link2.target_block_num. + + return {link2.target_block_num, link2.source_block_num, link1.target_block_num}; +} + +core_metadata finality_core::next_metadata(const qc_claim_t& most_recent_ancestor_with_qc) const +{ + assert(most_recent_ancestor_with_qc.block_num <= current_block_num()); // Satisfied by precondition 1. + assert(latest_qc_claim() <= most_recent_ancestor_with_qc); // Satisfied by precondition 2. + + const auto [new_last_final_block_num, new_links_front_source_block_num, new_final_on_strong_qc_block_num] = + get_new_block_numbers(*this, most_recent_ancestor_with_qc); + + (void)new_links_front_source_block_num; + + return core_metadata { + .last_final_block_num = new_last_final_block_num, + .final_on_strong_qc_block_num = new_final_on_strong_qc_block_num, + .latest_qc_claim_block_num = most_recent_ancestor_with_qc.block_num, + }; + // Post-conditions satisfied by post-conditions of get_new_block_numbers. +} + +/** + * @pre current_block.block_num() == this->current_block_num() + * @pre If this->refs.empty() == false, then current_block is the block after the one referenced by this->refs.back() + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc + * + * @post returned core has current_block_num() == this->current_block_num() + 1 + * @post returned core has latest_qc_claim() == most_recent_ancestor_with_qc + * @post returned core has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core has last_final_block_num() >= this->last_final_block_num() + */ +finality_core finality_core::next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const +{ + assert(current_block.block_num() == current_block_num()); // Satisfied by precondition 1. + + assert(refs.empty() || (refs.back().block_num() + 1 == current_block.block_num())); // Satisfied by precondition 2. + assert(refs.empty() || (refs.back().timestamp < current_block.timestamp)); // Satisfied by precondition 2. + + assert(most_recent_ancestor_with_qc.block_num <= current_block_num()); // Satisfied by precondition 3. + + assert(latest_qc_claim() <= most_recent_ancestor_with_qc); // Satisfied by precondition 4. + + finality_core next_core; + + const auto [new_last_final_block_num, new_links_front_source_block_num, new_final_on_strong_qc_block_num] = + get_new_block_numbers(*this, most_recent_ancestor_with_qc); + + assert(new_last_final_block_num <= new_links_front_source_block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + assert(new_links_front_source_block_num <= new_final_on_strong_qc_block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + assert(new_final_on_strong_qc_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + + assert(last_final_block_num() <= new_last_final_block_num); // Satisfied by post-condition 2 of get_new_block_numbers. + assert(links.front().source_block_num <= new_links_front_source_block_num); // Satisfied by post-condition 3 of get_new_block_numbers. + assert(final_on_strong_qc_block_num <= new_final_on_strong_qc_block_num); // Satisfied by post-condition 4 of get_new_block_numbers. + + next_core.final_on_strong_qc_block_num = new_final_on_strong_qc_block_num; + // Post-condition 3 is satisfied, assuming next_core will be returned without further modifications to next_core.final_on_strong_qc_block_num. + + // Post-condition 4 and invariant 2 will be satisfied when next_core.last_final_block_num() is updated to become new_last_final_block_num. + + // Setup next_core.links by garbage collecting unnecessary links and then adding the new QC link. + { + const size_t links_index = new_links_front_source_block_num - links.front().source_block_num; + + assert(links_index < links.size()); // Satisfied by justification in this->get_qc_link_from(new_links_front_source_block_num). + + // Garbage collect unnecessary links + next_core.links = { links.cbegin() + links_index, links.cend() }; + + assert(next_core.last_final_block_num() == new_last_final_block_num); // Satisfied by choice of links_index. + + // Also, by choice of links_index, at this point, next_core.links.back() == this->links.back(). + assert(next_core.links.back().source_block_num == current_block_num()); // Satisfied because last item in links has not yet changed. + assert(next_core.links.back().target_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied because of above and precondition 3. + + // Add new link + next_core.links.emplace_back( + qc_link{ + .source_block_num = current_block_num() + 1, + .target_block_num = most_recent_ancestor_with_qc.block_num, // Guaranteed to be less than current_block_num() + 1. + .is_link_strong = most_recent_ancestor_with_qc.is_strong_qc, + }); + + // Post-conditions 1, 2, and 4 are satisfied, assuming next_core will be returned without further modifications to next_core.links. + + // Invariants 1, 2, and 7 are satisfied for next_core. + } + + // Setup next_core.refs by garbage collecting unnecessary block references in the refs and then adding the new block reference. + { + const size_t refs_index = new_last_final_block_num - last_final_block_num(); + + // Using the justifications in new_block_nums, 0 <= ref_index <= (current_block_num() - last_final_block_num). + // If refs.empty() == true, then by invariant 3, current_block_num() == last_final_block_num, and therefore ref_index == 0. + // Otherwise if refs.empty() == false, the justification in new_block_nums provides the stronger inequality + // 0 <= ref_index < (current_block_num() - last_final_block_num), which, using invariant 8, can be simplified to + // 0 <= ref_index < refs.size(). + + assert(!refs.empty() || (refs_index == 0)); // Satisfied by justification above. + assert(refs.empty() || (refs_index < refs.size())); // Satisfied by justification above. + + // Garbage collect unnecessary block references + next_core.refs = {refs.cbegin() + refs_index, refs.cend()}; + assert(refs.empty() || (next_core.refs.front().block_num() == new_last_final_block_num)); // Satisfied by choice of refs_index. + + // Add new block reference + next_core.refs.emplace_back(current_block); + + // Invariant 3 is trivially satisfied for next_core because next_core.refs.empty() == false. + + // Invariant 5 is clearly satisfied for next_core because next_core.refs.back().block_num() == this->current_block_num() + // and next_core.links.back().source_block_num == this->current_block_num() + 1. + + // Invariant 6 is also clearly satisfied for next_core because invariant 6 is satisfied for *this and the only + // additional requirements needed are the ones provided by precondition 2. + + // If this->refs.empty() == true, then new_last_final_block_num == this->last_final_block_num() == this->current_block_num(), + // and next_core.refs.size() == 1 and next_core.refs.front() == current_block. + // And so, next_core.refs.front().block_num() == new_last_final_block_num. + // If this->refs.empty() == false, then adding the current_block to the end does not change the fact that + // next_core.refs.front().block_num() is still equal to new_last_final_block_num. + + assert(next_core.refs.front().block_num() == new_last_final_block_num); // Satisfied by justification above. + + // Because it was also already shown earlier that links.front().target_block_num == new_last_final_block_num, + // then the justification above satisfies the remaining equalities needed to satisfy invariant 4 for next_core. + + // So, invariants 3 to 6 are now satisfied for next_core in addition to the invariants 1, 2, and 7 that were shown to be satisfied + // earlier (and still remain satisfied since next_core.links and next_core.final_on_strong_qc_block_num have not changed). + } + + return next_core; + // Invariants 1 to 7 were verified to be satisfied for the current value of next_core at various points above. + // (And so, the remaining invariants for next_core are also automatically satisfied.) +} + +} /// eosio::chain diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 16d15a62c4..6ec9d9034d 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -1,217 +1,219 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include -#include +#include -namespace eosio { namespace chain { +namespace eosio::chain { using boost::multi_index_container; using namespace boost::multi_index; - const uint32_t fork_database::magic_number = 0x30510FDB; - - const uint32_t fork_database::min_supported_version = 1; - const uint32_t fork_database::max_supported_version = 1; - - // work around block_state_legacy::is_valid being private - inline bool block_state_is_valid( const block_state_legacy& bs ) { - return bs.is_valid(); - } - /** * History: * Version 1: initial version of the new refactored fork database portable format + * Version 2: Savanna version, store either `block_state`, `block_state_legacy` or both versions, + * root is full `block_state`, not just the header. */ + struct block_state_accessor { + static bool is_valid(const block_state& bs) { return bs.is_valid(); } + static void set_valid(block_state& bs, bool v) { bs.validated.store(v); } + }; + + struct block_state_legacy_accessor { + static bool is_valid(const block_state_legacy& bs) { return bs.is_valid(); } + static void set_valid(block_state_legacy& bs, bool v) { bs.validated = v; } + }; + + std::string log_fork_comparison(const block_state& bs) { + std::string r; + r += "[ valid: " + std::to_string(block_state_accessor::is_valid(bs)) + ", "; + r += "last_final_block_num: " + std::to_string(bs.last_final_block_num()) + ", "; + r += "last_qc_block_num: " + std::to_string(bs.last_qc_block_num()) + ", "; + r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string() + " ]"; + return r; + } + + std::string log_fork_comparison(const block_state_legacy& bs) { + std::string r; + r += "[ valid: " + std::to_string(block_state_legacy_accessor::is_valid(bs)) + ", "; + r += "irreversible_blocknum: " + std::to_string(bs.irreversible_blocknum()) + ", "; + r += "block_num: " + std::to_string(bs.block_num()) + ", "; + r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string() + " ]"; + return r; + } + struct by_block_id; - struct by_lib_block_num; + struct by_best_branch; struct by_prev; - typedef multi_index_container< - block_state_legacy_ptr, - indexed_by< - hashed_unique< tag, member, std::hash>, - ordered_non_unique< tag, const_mem_fun >, - ordered_unique< tag, - composite_key< block_state_legacy, - global_fun, - member, - member, - member - >, - composite_key_compare< - std::greater, - std::greater, - std::greater, - sha256_less - > - > - > - > fork_multi_index_type; - bool first_preferred( const block_header_state_legacy& lhs, const block_header_state_legacy& rhs ) { - return std::tie( lhs.dpos_irreversible_blocknum, lhs.block_num ) - > std::tie( rhs.dpos_irreversible_blocknum, rhs.block_num ); + // match comparison of by_best_branch + bool first_preferred( const block_state& lhs, const block_state& rhs ) { + return std::make_tuple(lhs.last_final_block_num(), lhs.last_qc_block_num(), lhs.timestamp()) > + std::make_tuple(rhs.last_final_block_num(), rhs.last_qc_block_num(), rhs.timestamp()); + } + bool first_preferred( const block_state_legacy& lhs, const block_state_legacy& rhs ) { + return std::make_tuple(lhs.irreversible_blocknum(), lhs.block_num()) > + std::make_tuple(rhs.irreversible_blocknum(), rhs.block_num()); } + template // either [block_state_legacy_ptr, block_state_ptr], same with block_header_state_ptr struct fork_database_impl { - explicit fork_database_impl( const std::filesystem::path& data_dir ) - :datadir(data_dir) - {} + using bsp_t = BSP; + using bs_t = bsp_t::element_type; + using bs_accessor_t = bs_t::fork_db_block_state_accessor_t; + using bhsp_t = bs_t::bhsp_t; + using bhs_t = bhsp_t::element_type; + + using fork_db_t = fork_database_t; + using branch_t = fork_db_t::branch_t; + using full_branch_t = fork_db_t::full_branch_t; + using branch_pair_t = fork_db_t::branch_pair_t; + + using by_best_branch_legacy_t = + ordered_unique, + composite_key, + const_mem_fun, + const_mem_fun, + const_mem_fun + >, + composite_key_compare, std::greater, std::greater, sha256_less + >>; + + using by_best_branch_if_t = + ordered_unique, + composite_key, + const_mem_fun, + const_mem_fun, + const_mem_fun, + const_mem_fun + >, + composite_key_compare, std::greater, std::greater, + std::greater, sha256_less> + >; + + using by_best_branch_t = std::conditional_t, + by_best_branch_if_t, + by_best_branch_legacy_t>; + + using fork_multi_index_type = multi_index_container< + bsp_t, + indexed_by< + hashed_unique, BOOST_MULTI_INDEX_CONST_MEM_FUN(bs_t, const block_id_type&, id), std::hash>, + ordered_non_unique, const_mem_fun>, + by_best_branch_t + > + >; - std::shared_mutex mtx; + std::mutex mtx; + bsp_t root; + bsp_t head; fork_multi_index_type index; - block_state_legacy_ptr root; // Only uses the block_header_state_legacy portion - block_state_legacy_ptr head; - std::filesystem::path datadir; - - void open_impl( const std::function&, - const vector& )>& validator ); - void close_impl(); - - - block_header_state_legacy_ptr get_block_header_impl( const block_id_type& id )const; - block_state_legacy_ptr get_block_impl( const block_id_type& id )const; - void reset_impl( const block_header_state_legacy& root_bhs ); - void rollback_head_to_root_impl(); - void advance_root_impl( const block_id_type& id ); - void remove_impl( const block_id_type& id ); - branch_type fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num )const; - block_state_legacy_ptr search_on_branch_impl( const block_id_type& h, uint32_t block_num )const; - pair fetch_branch_from_impl( const block_id_type& first, - const block_id_type& second )const; - void mark_valid_impl( const block_state_legacy_ptr& h ); - - void add_impl( const block_state_legacy_ptr& n, - bool ignore_duplicate, bool validate, - const std::function&, - const vector& )>& validator ); - }; + explicit fork_database_impl() = default; + + void open_impl( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ); + void close_impl( std::ofstream& out ); + void add_impl( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator ); + bool is_valid() const; + + bsp_t get_block_impl( const block_id_type& id, include_root_t include_root = include_root_t::no ) const; + bool block_exists_impl( const block_id_type& id ) const; + bool validated_block_exists_impl( const block_id_type& id ) const; + void reset_root_impl( const bsp_t& root_bs ); + void rollback_head_to_root_impl(); + void advance_root_impl( const block_id_type& id ); + void remove_impl( const block_id_type& id ); + branch_t fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; + block_branch_t fetch_block_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; + full_branch_t fetch_full_branch_impl(const block_id_type& h) const; + bsp_t search_on_branch_impl( const block_id_type& h, uint32_t block_num, include_root_t include_root ) const; + bsp_t search_on_head_branch_impl( uint32_t block_num, include_root_t include_root ) const; + void mark_valid_impl( const bsp_t& h ); + branch_pair_t fetch_branch_from_impl( const block_id_type& first, const block_id_type& second ) const; + + }; - fork_database::fork_database( const std::filesystem::path& data_dir ) - :my( new fork_database_impl( data_dir ) ) + template + fork_database_t::fork_database_t() + :my( new fork_database_impl() ) {} + template + fork_database_t::~fork_database_t() = default; // close is performed in fork_database::~fork_database() - void fork_database::open( const std::function&, - const vector& )>& validator ) - { + template + void fork_database_t::open( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ) { std::lock_guard g( my->mtx ); - my->open_impl( validator ); + my->open_impl( fork_db_file, ds, validator ); } - void fork_database_impl::open_impl( const std::function&, - const vector& )>& validator ) - { - if (!std::filesystem::is_directory(datadir)) - std::filesystem::create_directories(datadir); - - auto fork_db_dat = datadir / config::forkdb_filename; - if( std::filesystem::exists( fork_db_dat ) ) { - try { - string content; - fc::read_file_contents( fork_db_dat, content ); - - fc::datastream ds( content.data(), content.size() ); - - // validate totem - uint32_t totem = 0; - fc::raw::unpack( ds, totem ); - EOS_ASSERT( totem == fork_database::magic_number, fork_database_exception, - "Fork database file '${filename}' has unexpected magic number: ${actual_totem}. Expected ${expected_totem}", - ("filename", fork_db_dat) - ("actual_totem", totem) - ("expected_totem", fork_database::magic_number) - ); - - // validate version - uint32_t version = 0; - fc::raw::unpack( ds, version ); - EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version, - fork_database_exception, - "Unsupported version of fork database file '${filename}'. " - "Fork database version is ${version} while code supports version(s) [${min},${max}]", - ("filename", fork_db_dat) - ("version", version) - ("min", fork_database::min_supported_version) - ("max", fork_database::max_supported_version) - ); - - block_header_state_legacy bhs; - fc::raw::unpack( ds, bhs ); - reset_impl( bhs ); - - unsigned_int size; fc::raw::unpack( ds, size ); - for( uint32_t i = 0, n = size.value; i < n; ++i ) { - block_state_legacy s; - fc::raw::unpack( ds, s ); - // do not populate transaction_metadatas, they will be created as needed in apply_block with appropriate key recovery - s.header_exts = s.block->validate_and_extract_header_extensions(); - add_impl( std::make_shared( std::move( s ) ), false, true, validator ); - } - block_id_type head_id; - fc::raw::unpack( ds, head_id ); - - if( root->id == head_id ) { - head = root; - } else { - head = get_block_impl( head_id ); - EOS_ASSERT( head, fork_database_exception, - "could not find head while reconstructing fork database from file; '${filename}' is likely corrupted", - ("filename", fork_db_dat) ); - } + template + void fork_database_impl::open_impl( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ) { + bsp_t _root = std::make_shared(); + fc::raw::unpack( ds, *_root ); + reset_root_impl( _root ); + + unsigned_int size; fc::raw::unpack( ds, size ); + for( uint32_t i = 0, n = size.value; i < n; ++i ) { + bs_t s; + fc::raw::unpack( ds, s ); + // do not populate transaction_metadatas, they will be created as needed in apply_block with appropriate key recovery + s.header_exts = s.block->validate_and_extract_header_extensions(); + add_impl( std::make_shared( std::move( s ) ), mark_valid_t::no, ignore_duplicate_t::no, true, validator ); + } + block_id_type head_id; + fc::raw::unpack( ds, head_id ); - auto candidate = index.get().begin(); - if( candidate == index.get().end() || !(*candidate)->is_valid() ) { - EOS_ASSERT( head->id == root->id, fork_database_exception, - "head not set to root despite no better option available; '${filename}' is likely corrupted", - ("filename", fork_db_dat) ); - } else { - EOS_ASSERT( !first_preferred( **candidate, *head ), fork_database_exception, - "head not set to best available option available; '${filename}' is likely corrupted", - ("filename", fork_db_dat) ); - } - } FC_CAPTURE_AND_RETHROW( (fork_db_dat) ) + if( root->id() == head_id ) { + head = root; + } else { + head = get_block_impl( head_id ); + if (!head) + std::filesystem::remove( fork_db_file ); + EOS_ASSERT( head, fork_database_exception, + "could not find head while reconstructing fork database from file; " + "'${filename}' is likely corrupted and has been removed", + ("filename", fork_db_file) ); + } - std::filesystem::remove( fork_db_dat ); + auto candidate = index.template get().begin(); + if( candidate == index.template get().end() || !bs_accessor_t::is_valid(**candidate) ) { + EOS_ASSERT( head->id() == root->id(), fork_database_exception, + "head not set to root despite no better option available; '${filename}' is likely corrupted", + ("filename", fork_db_file) ); + } else { + EOS_ASSERT( !first_preferred( **candidate, *head ), fork_database_exception, + "head not set to best available option available; '${filename}' is likely corrupted", + ("filename", fork_db_file) ); } } - void fork_database::close() { + template + void fork_database_t::close(std::ofstream& out) { std::lock_guard g( my->mtx ); - my->close_impl(); + my->close_impl(out); } - void fork_database_impl::close_impl() { - auto fork_db_dat = datadir / config::forkdb_filename; + template + void fork_database_impl::close_impl(std::ofstream& out) { + assert(!!head && !!root); // if head or root are null, we don't save and shouldn't get here - if( !root ) { - if( index.size() > 0 ) { - elog( "fork_database is in a bad state when closing; not writing out '${filename}'", - ("filename", fork_db_dat) ); - } - return; - } + fc::raw::pack( out, *root ); - std::ofstream out( fork_db_dat.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); - fc::raw::pack( out, fork_database::magic_number ); - fc::raw::pack( out, fork_database::max_supported_version ); // write out current version which is always max_supported_version - fc::raw::pack( out, *static_cast(&*root) ); uint32_t num_blocks_in_fork_db = index.size(); fc::raw::pack( out, unsigned_int{num_blocks_in_fork_db} ); - const auto& indx = index.get(); + const auto& indx = index.template get(); auto unvalidated_itr = indx.rbegin(); auto unvalidated_end = boost::make_reverse_iterator( indx.lower_bound( false ) ); @@ -246,70 +248,67 @@ namespace eosio { namespace chain { fc::raw::pack( out, *(*itr) ); } - if( head ) { - fc::raw::pack( out, head->id ); - } else { - elog( "head not set in fork database; '${filename}' will be corrupted", - ("filename", fork_db_dat) ); - } + fc::raw::pack( out, head->id() ); index.clear(); } - fork_database::~fork_database() { - my->close_impl(); - } - - void fork_database::reset( const block_header_state_legacy& root_bhs ) { + template + void fork_database_t::reset_root( const bsp_t& root_bsp ) { std::lock_guard g( my->mtx ); - my->reset_impl(root_bhs); + my->reset_root_impl(root_bsp); } - void fork_database_impl::reset_impl( const block_header_state_legacy& root_bhs ) { + template + void fork_database_impl::reset_root_impl( const bsp_t& root_bsp ) { index.clear(); - root = std::make_shared(); - static_cast(*root) = root_bhs; - root->validated = true; + root = root_bsp; + bs_accessor_t::set_valid(*root, true); head = root; } - void fork_database::rollback_head_to_root() { + template + void fork_database_t::rollback_head_to_root() { std::lock_guard g( my->mtx ); my->rollback_head_to_root_impl(); } - void fork_database_impl::rollback_head_to_root_impl() { - auto& by_id_idx = index.get(); + template + void fork_database_impl::rollback_head_to_root_impl() { + auto& by_id_idx = index.template get(); auto itr = by_id_idx.begin(); while (itr != by_id_idx.end()) { - by_id_idx.modify( itr, [&]( block_state_legacy_ptr& bsp ) { - bsp->validated = false; + by_id_idx.modify( itr, []( auto& i ) { + bs_accessor_t::set_valid(*i, false); } ); ++itr; } head = root; } - void fork_database::advance_root( const block_id_type& id ) { + template + void fork_database_t::advance_root( const block_id_type& id ) { std::lock_guard g( my->mtx ); my->advance_root_impl( id ); } - void fork_database_impl::advance_root_impl( const block_id_type& id ) { + template + void fork_database_impl::advance_root_impl( const block_id_type& id ) { EOS_ASSERT( root, fork_database_exception, "root not yet set" ); auto new_root = get_block_impl( id ); EOS_ASSERT( new_root, fork_database_exception, "cannot advance root to a block that does not exist in the fork database" ); - EOS_ASSERT( new_root->is_valid(), fork_database_exception, + EOS_ASSERT( bs_accessor_t::is_valid(*new_root), fork_database_exception, "cannot advance root to a block that has not yet been validated" ); deque blocks_to_remove; for( auto b = new_root; b; ) { - blocks_to_remove.emplace_back( b->header.previous ); + blocks_to_remove.emplace_back( b->previous() ); b = get_block_impl( blocks_to_remove.back() ); - EOS_ASSERT( b || blocks_to_remove.back() == root->id, fork_database_exception, "invariant violation: orphaned branch was present in forked database" ); + EOS_ASSERT( b || blocks_to_remove.back() == root->id(), fork_database_exception, + "invariant violation: orphaned branch was present in forked database" ); } // The new root block should be erased from the fork database index individually rather than with the remove method, @@ -328,63 +327,48 @@ namespace eosio { namespace chain { root = new_root; } - block_header_state_legacy_ptr fork_database::get_block_header( const block_id_type& id )const { - std::shared_lock g( my->mtx ); - return my->get_block_header_impl( id ); - } - - block_header_state_legacy_ptr fork_database_impl::get_block_header_impl( const block_id_type& id )const { - if( root->id == id ) { - return root; - } - - auto itr = index.find( id ); - if( itr != index.end() ) - return *itr; - - return block_header_state_legacy_ptr(); - } - - void fork_database_impl::add_impl( const block_state_legacy_ptr& n, - bool ignore_duplicate, bool validate, - const std::function&, - const vector& )>& validator ) - { + template + void fork_database_impl::add_impl(const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, + bool validate, validator_t& validator) { EOS_ASSERT( root, fork_database_exception, "root not yet set" ); EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" ); - auto prev_bh = get_block_header_impl( n->header.previous ); - + auto prev_bh = get_block_impl( n->previous(), include_root_t::yes ); EOS_ASSERT( prev_bh, unlinkable_block_exception, - "unlinkable block", ("id", n->id)("previous", n->header.previous) ); + "forkdb unlinkable block ${id} previous ${p}", ("id", n->id())("p", n->previous()) ); - if( validate ) { + if (validate) { try { const auto& exts = n->header_exts; - if( exts.count(protocol_feature_activation::extension_id()) > 0 ) { - const auto& new_protocol_features = std::get(exts.lower_bound(protocol_feature_activation::extension_id())->second).protocol_features; - validator( n->header.timestamp, prev_bh->activated_protocol_features->protocol_features, new_protocol_features ); + if (exts.count(protocol_feature_activation::extension_id()) > 0) { + const auto& pfa = exts.lower_bound(protocol_feature_activation::extension_id())->second; + const auto& new_protocol_features = std::get(pfa).protocol_features; + validator(n->timestamp(), prev_bh->get_activated_protocol_features()->protocol_features, new_protocol_features); } - } EOS_RETHROW_EXCEPTIONS( fork_database_exception, "serialized fork database is incompatible with configured protocol features" ) + } + EOS_RETHROW_EXCEPTIONS( fork_database_exception, "serialized fork database is incompatible with configured protocol features" ) } + if (mark_valid == mark_valid_t::yes) + bs_accessor_t::set_valid(*n, true); + auto inserted = index.insert(n); if( !inserted.second ) { - if( ignore_duplicate ) return; - EOS_THROW( fork_database_exception, "duplicate block added", ("id", n->id) ); + if( ignore_duplicate == ignore_duplicate_t::yes ) return; + EOS_THROW( fork_database_exception, "duplicate block added", ("id", n->id()) ); } - auto candidate = index.get().begin(); - if( (*candidate)->is_valid() ) { + auto candidate = index.template get().begin(); + if( bs_accessor_t::is_valid(**candidate) ) { head = *candidate; } } - void fork_database::add( const block_state_legacy_ptr& n, bool ignore_duplicate ) { + template + void fork_database_t::add( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ) { std::lock_guard g( my->mtx ); - my->add_impl( n, ignore_duplicate, false, + my->add_impl( n, mark_valid, ignore_duplicate, false, []( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -392,22 +376,41 @@ namespace eosio { namespace chain { ); } - block_state_legacy_ptr fork_database::root()const { - std::shared_lock g( my->mtx ); + template + bool fork_database_t::is_valid() const { + std::lock_guard g( my->mtx ); + return my->is_valid(); + } + + template + bool fork_database_impl::is_valid() const { + return !!root && !!head && (root->id() == head->id() || get_block_impl(head->id())); + } + + template + bool fork_database_t::has_root() const { + return !!my->root; + } + + template + BSP fork_database_t::root() const { + std::lock_guard g( my->mtx ); return my->root; } - block_state_legacy_ptr fork_database::head()const { - std::shared_lock g( my->mtx ); + template + BSP fork_database_t::head() const { + std::lock_guard g( my->mtx ); return my->head; } - block_state_legacy_ptr fork_database::pending_head()const { - std::shared_lock g( my->mtx ); - const auto& indx = my->index.get(); + template + BSP fork_database_t::pending_head() const { + std::lock_guard g( my->mtx ); + const auto& indx = my->index.template get(); auto itr = indx.lower_bound( false ); - if( itr != indx.end() && !(*itr)->is_valid() ) { + if( itr != indx.end() && !fork_database_impl::bs_accessor_t::is_valid(**itr) ) { if( first_preferred( **itr, *my->head ) ) return *itr; } @@ -415,85 +418,148 @@ namespace eosio { namespace chain { return my->head; } - branch_type fork_database::fetch_branch( const block_id_type& h, uint32_t trim_after_block_num )const { - std::shared_lock g( my->mtx ); - return my->fetch_branch_impl( h, trim_after_block_num ); + template + fork_database_t::branch_t + fork_database_t::fetch_branch(const block_id_type& h, uint32_t trim_after_block_num) const { + std::lock_guard g(my->mtx); + return my->fetch_branch_impl(h, trim_after_block_num); + } + + template + fork_database_t::branch_t + fork_database_impl::fetch_branch_impl(const block_id_type& h, uint32_t trim_after_block_num) const { + branch_t result; + result.reserve(index.size()); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + if ((*i)->block_num() <= trim_after_block_num) + result.push_back(*i); + } + + return result; + } + + template + block_branch_t + fork_database_t::fetch_block_branch(const block_id_type& h, uint32_t trim_after_block_num) const { + std::lock_guard g(my->mtx); + return my->fetch_block_branch_impl(h, trim_after_block_num); } - branch_type fork_database_impl::fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num )const { - branch_type result; - for( auto s = get_block_impl(h); s; s = get_block_impl( s->header.previous ) ) { - if( s->block_num <= trim_after_block_num ) - result.push_back( s ); + template + block_branch_t + fork_database_impl::fetch_block_branch_impl(const block_id_type& h, uint32_t trim_after_block_num) const { + block_branch_t result; + result.reserve(index.size()); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + if ((*i)->block_num() <= trim_after_block_num) + result.push_back((*i)->block); } return result; } - block_state_legacy_ptr fork_database::search_on_branch( const block_id_type& h, uint32_t block_num )const { - std::shared_lock g( my->mtx ); - return my->search_on_branch_impl( h, block_num ); + template + fork_database_t::full_branch_t + fork_database_t::fetch_full_branch(const block_id_type& h) const { + std::lock_guard g(my->mtx); + return my->fetch_full_branch_impl(h); + } + + template + fork_database_t::full_branch_t + fork_database_impl::fetch_full_branch_impl(const block_id_type& h) const { + full_branch_t result; + result.reserve(index.size()); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + result.push_back(*i); + } + result.push_back(root); + return result; + } + + template + BSP fork_database_t::search_on_branch( const block_id_type& h, uint32_t block_num, include_root_t include_root /* = include_root_t::no */ ) const { + std::lock_guard g( my->mtx ); + return my->search_on_branch_impl( h, block_num, include_root ); } - block_state_legacy_ptr fork_database_impl::search_on_branch_impl( const block_id_type& h, uint32_t block_num )const { - for( auto s = get_block_impl(h); s; s = get_block_impl( s->header.previous ) ) { - if( s->block_num == block_num ) - return s; + template + BSP fork_database_impl::search_on_branch_impl( const block_id_type& h, uint32_t block_num, include_root_t include_root ) const { + if( include_root == include_root_t::yes && root->id() == h ) { + return root; + } + + for( auto i = index.find(h); i != index.end(); i = index.find( (*i)->previous() ) ) { + if ((*i)->block_num() == block_num) + return *i; } return {}; } + template + BSP fork_database_t::search_on_head_branch( uint32_t block_num, include_root_t include_root /* = include_root_t::no */ ) const { + std::lock_guard g(my->mtx); + return my->search_on_head_branch_impl(block_num, include_root); + } + + template + BSP fork_database_impl::search_on_head_branch_impl( uint32_t block_num, include_root_t include_root ) const { + return search_on_branch_impl(head->id(), block_num, include_root); + } + /** * Given two head blocks, return two branches of the fork graph that * end with a common ancestor (same prior block) */ - pair< branch_type, branch_type > fork_database::fetch_branch_from( const block_id_type& first, - const block_id_type& second )const { - std::shared_lock g( my->mtx ); - return my->fetch_branch_from_impl( first, second ); + template + fork_database_t::branch_pair_t + fork_database_t::fetch_branch_from(const block_id_type& first, const block_id_type& second) const { + std::lock_guard g(my->mtx); + return my->fetch_branch_from_impl(first, second); } - pair< branch_type, branch_type > fork_database_impl::fetch_branch_from_impl( const block_id_type& first, - const block_id_type& second )const { - pair result; - auto first_branch = (first == root->id) ? root : get_block_impl(first); - auto second_branch = (second == root->id) ? root : get_block_impl(second); + template + fork_database_t::branch_pair_t + fork_database_impl::fetch_branch_from_impl(const block_id_type& first, const block_id_type& second) const { + branch_pair_t result; + auto first_branch = (first == root->id()) ? root : get_block_impl(first); + auto second_branch = (second == root->id()) ? root : get_block_impl(second); EOS_ASSERT(first_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", first)); EOS_ASSERT(second_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", second)); - while( first_branch->block_num > second_branch->block_num ) + while( first_branch->block_num() > second_branch->block_num() ) { result.first.push_back(first_branch); - const auto& prev = first_branch->header.previous; - first_branch = (prev == root->id) ? root : get_block_impl( prev ); + const auto& prev = first_branch->previous(); + first_branch = (prev == root->id()) ? root : get_block_impl( prev ); EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", prev) ); } - while( second_branch->block_num > first_branch->block_num ) + while( second_branch->block_num() > first_branch->block_num() ) { result.second.push_back( second_branch ); - const auto& prev = second_branch->header.previous; - second_branch = (prev == root->id) ? root : get_block_impl( prev ); + const auto& prev = second_branch->previous(); + second_branch = (prev == root->id()) ? root : get_block_impl( prev ); EOS_ASSERT( second_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", prev) ); } - if (first_branch->id == second_branch->id) return result; + if (first_branch->id() == second_branch->id()) return result; - while( first_branch->header.previous != second_branch->header.previous ) + while( first_branch->previous() != second_branch->previous() ) { result.first.push_back(first_branch); result.second.push_back(second_branch); - const auto &first_prev = first_branch->header.previous; + const auto &first_prev = first_branch->previous(); first_branch = get_block_impl( first_prev ); - const auto &second_prev = second_branch->header.previous; + const auto &second_prev = second_branch->previous(); second_branch = get_block_impl( second_prev ); EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", @@ -514,23 +580,25 @@ namespace eosio { namespace chain { } /// fetch_branch_from_impl /// remove all of the invalid forks built off of this id including this id - void fork_database::remove( const block_id_type& id ) { + template + void fork_database_t::remove( const block_id_type& id ) { std::lock_guard g( my->mtx ); return my->remove_impl( id ); } - void fork_database_impl::remove_impl( const block_id_type& id ) { + template + void fork_database_impl::remove_impl( const block_id_type& id ) { deque remove_queue{id}; - const auto& previdx = index.get(); - const auto& head_id = head->id; + const auto& previdx = index.template get(); + const auto& head_id = head->id(); for( uint32_t i = 0; i < remove_queue.size(); ++i ) { EOS_ASSERT( remove_queue[i] != head_id, fork_database_exception, "removing the block and its descendants would remove the current head block" ); auto previtr = previdx.lower_bound( remove_queue[i] ); - while( previtr != previdx.end() && (*previtr)->header.previous == remove_queue[i] ) { - remove_queue.emplace_back( (*previtr)->id ); + while( previtr != previdx.end() && (*previtr)->previous() == remove_queue[i] ) { + remove_queue.emplace_back( (*previtr)->id() ); ++previtr; } } @@ -540,41 +608,221 @@ namespace eosio { namespace chain { } } - void fork_database::mark_valid( const block_state_legacy_ptr& h ) { + template + void fork_database_t::mark_valid( const bsp_t& h ) { std::lock_guard g( my->mtx ); my->mark_valid_impl( h ); } - void fork_database_impl::mark_valid_impl( const block_state_legacy_ptr& h ) { - if( h->validated ) return; + template + void fork_database_impl::mark_valid_impl( const bsp_t& h ) { + if( bs_accessor_t::is_valid(*h) ) return; - auto& by_id_idx = index.get(); + auto& by_id_idx = index.template get(); - auto itr = by_id_idx.find( h->id ); + auto itr = by_id_idx.find( h->id() ); EOS_ASSERT( itr != by_id_idx.end(), fork_database_exception, "block state not in fork database; cannot mark as valid", - ("id", h->id) ); + ("id", h->id()) ); - by_id_idx.modify( itr, []( block_state_legacy_ptr& bsp ) { - bsp->validated = true; + by_id_idx.modify( itr, []( auto& i ) { + bs_accessor_t::set_valid(*i, true); } ); - auto candidate = index.get().begin(); + auto candidate = index.template get().begin(); if( first_preferred( **candidate, *head ) ) { head = *candidate; } } - block_state_legacy_ptr fork_database::get_block(const block_id_type& id)const { - std::shared_lock g( my->mtx ); - return my->get_block_impl(id); + template + BSP fork_database_t::get_block(const block_id_type& id, + include_root_t include_root /* = include_root_t::no */) const { + std::lock_guard g( my->mtx ); + return my->get_block_impl(id, include_root); } - block_state_legacy_ptr fork_database_impl::get_block_impl(const block_id_type& id)const { + template + BSP fork_database_impl::get_block_impl(const block_id_type& id, + include_root_t include_root /* = include_root_t::no */) const { + if( include_root == include_root_t::yes && root->id() == id ) { + return root; + } auto itr = index.find( id ); if( itr != index.end() ) return *itr; - return block_state_legacy_ptr(); + return {}; + } + + template + bool fork_database_t::block_exists(const block_id_type& id) const { + std::lock_guard g( my->mtx ); + return my->block_exists_impl(id); + } + + template + bool fork_database_impl::block_exists_impl(const block_id_type& id) const { + return index.find( id ) != index.end(); + } + + template + bool fork_database_t::validated_block_exists(const block_id_type& id) const { + std::lock_guard g( my->mtx ); + return my->validated_block_exists_impl(id); + } + + template + bool fork_database_impl::validated_block_exists_impl(const block_id_type& id) const { + auto itr = index.find( id ); + return itr != index.end() && bs_accessor_t::is_valid(*(*itr)); + } + +// ------------------ fork_database ------------------------- + + fork_database::fork_database(const std::filesystem::path& data_dir) + : data_dir(data_dir) + { + } + + fork_database::~fork_database() { + close(); + } + + void fork_database::close() { + auto fork_db_file {data_dir / config::forkdb_filename}; + bool legacy_valid = fork_db_l.is_valid(); + bool savanna_valid = fork_db_s.is_valid(); + + auto in_use_value = in_use.load(); + // check that fork_dbs are in a consistent state + if (!legacy_valid && !savanna_valid) { + wlog( "fork_database is in a bad state when closing; not writing out '${filename}', legacy_valid=${l}, savanna_valid=${s}", + ("filename", fork_db_file)("l", legacy_valid)("s", savanna_valid) ); + return; + } else if (legacy_valid && savanna_valid && in_use_value == in_use_t::savanna) { + legacy_valid = false; // don't write legacy if not needed, we delay 'clear' of legacy until close + } + assert( (legacy_valid && (in_use_value == in_use_t::legacy)) || + (savanna_valid && (in_use_value == in_use_t::savanna)) || + (legacy_valid && savanna_valid && (in_use_value == in_use_t::both)) ); + + std::ofstream out( fork_db_file.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); + + fc::raw::pack( out, magic_number ); + fc::raw::pack( out, max_supported_version ); // write out current version which is always max_supported_version + // version == 1 -> legacy + // version == 2 -> savanna (two possible fork_db, one containing `block_state_legacy`, + // one containing `block_state`) + + fc::raw::pack(out, static_cast(in_use_value)); + + fc::raw::pack(out, legacy_valid); + if (legacy_valid) + fork_db_l.close(out); + + fc::raw::pack(out, savanna_valid); + if (savanna_valid) + fork_db_s.close(out); + } + + void fork_database::open( validator_t& validator ) { + if (!std::filesystem::is_directory(data_dir)) + std::filesystem::create_directories(data_dir); + + assert(!fork_db_l.is_valid() && !fork_db_s.is_valid()); + + auto fork_db_file = data_dir / config::forkdb_filename; + if( std::filesystem::exists( fork_db_file ) ) { + try { + fc::cfile f; + f.set_file_path(fork_db_file); + f.open("rb"); + + fc::cfile_datastream ds(f); + + // determine file type, validate totem + uint32_t totem = 0; + fc::raw::unpack( ds, totem ); + EOS_ASSERT( totem == magic_number, fork_database_exception, + "Fork database file '${filename}' has unexpected magic number: ${actual_totem}. Expected ${t}", + ("filename", fork_db_file)("actual_totem", totem)("t", magic_number)); + + uint32_t version = 0; + fc::raw::unpack( ds, version ); + EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version, + fork_database_exception, + "Unsupported version of fork database file '${filename}'. " + "Fork database version is ${version} while code supports version(s) [${min},${max}]", + ("filename", fork_db_file)("version", version)("min", min_supported_version)("max", max_supported_version)); + + switch(version) { + case 1: + { + // ---------- pre-Savanna format. Just a single fork_database_l ---------------- + in_use = in_use_t::legacy; + fork_db_l.open(fork_db_file, ds, validator); + break; + } + + case 2: + { + // ---------- Savanna format ---------------------------- + uint32_t in_use_raw; + fc::raw::unpack( ds, in_use_raw ); + in_use = static_cast(in_use_raw); + + bool legacy_valid { false }; + fc::raw::unpack( ds, legacy_valid ); + if (legacy_valid) { + fork_db_l.open(fork_db_file, ds, validator); + } + + bool savanna_valid { false }; + fc::raw::unpack( ds, savanna_valid ); + if (savanna_valid) { + fork_db_s.open(fork_db_file, ds, validator); + } + break; + } + + default: + assert(0); + break; + } + } FC_CAPTURE_AND_RETHROW( (fork_db_file) ); + std::filesystem::remove( fork_db_file ); + } + } + + // only called from the main thread + void fork_database::switch_from_legacy(const block_state_ptr& root) { + // no need to close fork_db because we don't want to write anything out, file is removed on open + // threads may be accessing (or locked on mutex about to access legacy forkdb) so don't delete it until program exit + if (in_use == in_use_t::legacy) { + fork_db_s.reset_root(root); + if (fork_db_l.has_root()) { + in_use = in_use_t::both; + } else { + in_use = in_use_t::savanna; + } + } else if (in_use == in_use_t::both) { + assert(fork_db_s.root()->id() == root->id()); // should always set the same root + } else { + assert(false); + } } -} } /// eosio::chain + block_branch_t fork_database::fetch_branch_from_head() const { + return apply([&](auto& forkdb) { + return forkdb.fetch_block_branch(forkdb.head()->id()); + }); + } + + // do class instantiations + template class fork_database_t; + template class fork_database_t; + + template struct fork_database_impl; + template struct fork_database_impl; + +} /// eosio::chain diff --git a/libraries/chain/hotstuff/block_construction_data_flow.md b/libraries/chain/hotstuff/block_construction_data_flow.md new file mode 100644 index 0000000000..9169b50fc5 --- /dev/null +++ b/libraries/chain/hotstuff/block_construction_data_flow.md @@ -0,0 +1,233 @@ +Below, `parent` refers to the `block_state` of the parent block from which a new block is being constructed. + +## dpos data + +currently in controller.cpp, we have the `building_block` whose members are: + +```c++ +struct building_block { + pending_block_header_state _pending_block_header_state; // IF: Remove from building_block. See below for replacements. + std::optional _new_pending_producer_schedule; // IF: Replaced by new_proposal_policy. + vector _new_protocol_feature_activations; // IF: Comes from building_block_input::new_protocol_feature_activations + size_t _num_new_protocol_features_that_have_activated = 0; // Stays only in building_block + deque _pending_trx_metas; // Moved from building_block to assembled_block + deque _pending_trx_receipts; // Moved from building_block to the transactions in the constructed block + std::variant _trx_mroot_or_receipt_digests; // IF: Extract computed trx mroot to assembled_block_input::transaction_mroot + digests_t _action_receipt_digests; // IF: Extract computed action mroot to assembled_block_input::action_mroot +}; +``` + +the `assembled_block`: + + +```c++ +struct assembled_block { + block_id_type _id; // Cache of _unsigned_block->calculate_id(). + pending_block_header_state _pending_block_header_state; // IF: Remove from assembled_block. See below for replacements. + deque _trx_metas; // Comes from building_block::_pending_trx_metas + // Carried over to put into block_state (optimization for fork reorgs) + signed_block_ptr _unsigned_block; // IF: keep same member + + // if the _unsigned_block pre-dates block-signing authorities this may be present. + std::optional _new_producer_authority_cache; // IF: Remove from assembled_block + // pending_producers() not needed in IF. proposed_proposers() sufficient. +}; +``` + +and the `pending_block_header_state`: + +```c++ + +struct block_header_state_legacy_common { + uint32_t block_num = 0; // IF: block_header::num_from_id(parent_id) + 1 + uint32_t dpos_proposed_irreversible_blocknum = 0; // Unneeded for IF + uint32_t dpos_irreversible_blocknum = 0; // Unneeded during the building block stage for IF + producer_authority_schedule active_schedule; // IF: Replaced by active_proposer_policy stored in building_block. + incremental_merkle blockroot_merkle; // Unneeded during the building block stage for IF + flat_map producer_to_last_produced; // Unneeded for IF + flat_map producer_to_last_implied_irb; // Unneeded for IF + block_signing_authority valid_block_signing_authority; // IF: Get from within active_proposer_policy for building_block.producer. + vector confirm_count; // Unneeded for IF +}; + +struct pending_block_header_state : public detail::block_header_state_legacy_common { + protocol_feature_activation_set_ptr prev_activated_protocol_features; // IF: building_block.prev_activated_protocol_features + detail::schedule_info prev_pending_schedule; // Unneeded for IF + bool was_pending_promoted = false; // Unneeded for IF + block_id_type previous; // Not needed but present anyway at building_block.parent_id + account_name producer; // IF: building_block.producer + block_timestamp_type timestamp; // IF: building_block.timestamp + uint32_t active_schedule_version = 0; // Unneeded for IF + uint16_t confirmed = 1; // Unneeded for IF +}; +``` + +and all this lives in `pending_state` which I believe can stay unchanged. + +## IF data + +The new storage for IF is: + +```c++ +struct block_header_state_core { + uint32_t last_final_block_num = 0; // last irreversible (final) block. + std::optional final_on_strong_qc_block_num; // will become final if this header achives a strong QC. + uint32_t last_qc_block_num; // + uint32_t finalizer_policy_generation; + + block_header_state_core next(uint32_t last_qc_block_num, bool is_last_qc_strong) const; +}; + +struct quorum_certificate { + uint32_t block_num; + valid_quorum_certificate qc; +}; + +struct block_header_state { + block_header header; + protocol_feature_activation_set_ptr activated_protocol_features; + block_header_state_core core; + incremental_merkle_tree proposal_mtree; + incremental_merkle_tree finality_mtree; + finalizer_policy_ptr active_finalizer_policy; // finalizer set + threshold + generation, supports `digest()` + proposer_policy_ptr active_proposer_policy; // producer authority schedule, supports `digest()` + + flat_map proposer_policies; + flat_map finalizer_policies; + + digest_type compute_finalizer_digest() const; + + proposer_policy_ptr get_next_active_proposer_policy(block_timestamp_type next_timestamp) const { + // Find latest proposer policy within proposer_policies that has an active_time <= next_timestamp. + // If found, return the proposer policy that was found. + // Otherwise, return active_proposer_policy. + } + + block_timestamp_type timestamp() const { return header.timestamp; } + account_name producer() const { return header.producer; } + block_id_type previous() const { return header.previous; } + uint32_t block_num() const { return block_header::num_from_id(previous()) + 1; } + + // block descending from this need the provided qc in the block extension + bool is_needed(const quorum_certificate& qc) const { + return qc.block_num > core.last_qc_block_num; + } + + block_header_state next(const block_header_state_input& data) const; +}; + +struct block_state { + const block_header_state bhs; + const signed_block_ptr block; + + const block_id_type id; // cache of bhs.header.calculate_id() (indexed on this field) + const digest_type finalizer_digest; // cache of bhs.compute_finalizer_digest() + + std::optional pending_qc; + std::optional valid_qc; + + std::optional get_best_qc() const { + // If pending_qc does not have a valid QC, return valid_qc. + // Otherwise, extract the valid QC from *pending_qc. + // Compare that to valid_qc to determine which is better: Strong beats Weak. Break tie with highest accumulated weight. + // Return the better one. + } + + uint64_t block_num() const { return block_header::num_from_id(id); } +}; + +``` + +In addition, in IF `pending_state._block_stage` will still contain the three stages: `building_block`, `assembled_block`, and `completed_block`. + +1. `building_block`: + +```c++ +struct building_block { + const block_header_state& parent; // Needed for block_header_state::next() + const block_id_type parent_id; // Comes from building_block_input::parent_id + const block_timestamp_type timestamp; // Comes from building_block_input::timestamp + const account_name producer; // Comes from building_block_input::producer + const vector new_protocol_feature_activations; // Comes from building_block_input::new_protocol_feature_activations + const protocol_feature_activation_set_ptr prev_activated_protocol_features; // Cached: parent.bhs.activated_protocol_features + const proposer_policy_ptr active_proposer_policy; // Cached: parent.bhs.get_next_active_proposer_policy(timestamp) + + // Members below start from initial state and are mutated as the block is built. + size_t num_new_protocol_features_that_have_activated = 0; + std::optional new_proposer_policy; + std::optional new_finalizer_policy; + deque pending_trx_metas; + deque pending_trx_receipts; + std::variant trx_mroot_or_receipt_digests; + digests_t action_receipt_digests; +}; +``` + +``` +struct building_block { + pending_block_header_state _pending_block_header_state; // IF: Remove from building_block. See below for replacements. + std::optional _new_pending_producer_schedule; // IF: Replaced by new_proposal_policy. + vector _new_protocol_feature_activations; // IF: Comes from building_block_input::new_protocol_feature_activations + size_t _num_new_protocol_features_that_have_activated = 0; // Stays only in building_block + deque _pending_trx_metas; // Moved from building_block to assembled_block + deque _pending_trx_receipts; // Moved from building_block to the transactions in the constructed block + std::variant _trx_mroot_or_receipt_digests; // IF: Extract computed trx mroot to assembled_block_input::transaction_mroot + digests_t _action_receipt_digests; // IF: Extract computed action mroot to assembled_block_input::action_mroot +}; +``` + +which is constructed from: + +```c++ +struct building_block_input { + block_id_type parent_id; + block_timestamp_type timestamp; + account_name producer; + vector new_protocol_feature_activations; +}; +``` + +When done with building the block, from `building_block` we can extract: + +```c++ +struct qc_info_t { + uint32_t last_qc_block_num; // The block height of the most recent ancestor block that has a QC justification + bool is_last_qc_strong; // Whether the QC for the block referenced by last_qc_block_height is strong or weak. +}; + +struct block_header_state_input : public building_block_input { + digest_type transaction_mroot; // Comes from std::get(building_block::trx_mroot_or_receipt_digests) + digest_type action_mroot; // Compute root from building_block::action_receipt_digests + std::optional new_proposer_policy; // Comes from building_block::new_proposer_policy + std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy + std::optional qc_info; + // ... ? +}; +``` + +which is the input needed to `block_header_state::next` to compute the new block header state. + +2. `assembled_block`: + + +```c++ +struct assembled_block { + block_header_state new_block_header_state; + deque trx_metas; // Comes from building_block::pending_trx_metas + // Carried over to put into block_state (optimization for fork reorgs) + deque trx_receipts; // Comes from building_block::pending_trx_receipts + std::optional qc; // QC to add as block extension to new block +}; +``` + +which is constructed from `building_block` and `parent.bhs`. + +3. `completed_block`: + +```c++ +struct completed_block { + block_state_ptr block_state; +}; +``` + +which is constructed from `assembled_block` and a block header signature provider. \ No newline at end of file diff --git a/libraries/chain/hotstuff/finalizer.cpp b/libraries/chain/hotstuff/finalizer.cpp new file mode 100644 index 0000000000..af0d3028c2 --- /dev/null +++ b/libraries/chain/hotstuff/finalizer.cpp @@ -0,0 +1,253 @@ +#include +#include +#include + +namespace eosio::chain { + +// ---------------------------------------------------------------------------------------- +finalizer::vote_result finalizer::decide_vote(const block_state_ptr& bsp) { + vote_result res; + + res.monotony_check = fsi.last_vote.empty() || bsp->timestamp() > fsi.last_vote.timestamp; + // fsi.last_vote.empty() means we have never voted on a proposal, so the protocol feature + // just activated and we can proceed + + if (!res.monotony_check) { + if (fsi.last_vote.empty()) { + dlog("monotony check failed, block ${bn} ${p}, cannot vote, fsi.last_vote empty", ("bn", bsp->block_num())("p", bsp->id())); + } else { + if (fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::debug)) { + if (bsp->id() != fsi.last_vote.block_id) { // we may have already voted when we received the block + dlog("monotony check failed, block ${bn} ${p}, cannot vote, ${t} <= ${lt}, fsi.last_vote ${lbn} ${lid}", + ("bn", bsp->block_num())("p", bsp->id())("t", bsp->timestamp())("lt", fsi.last_vote.timestamp)("lbn", fsi.last_vote.block_num())("lid", fsi.last_vote.block_id)); + } + } + } + return res; + } + + if (!fsi.lock.empty()) { + // Liveness check : check if the height of this proposal's justification is higher + // than the height of the proposal I'm locked on. + // This allows restoration of liveness if a replica is locked on a stale proposal + // ------------------------------------------------------------------------------- + res.liveness_check = bsp->core.latest_qc_block_timestamp() > fsi.lock.timestamp; + + if (!res.liveness_check) { + dlog("liveness check failed, block ${bn} ${id}: ${c} <= ${l}, fsi.lock ${lbn} ${lid}, latest_qc_claim: ${qc}", + ("bn", bsp->block_num())("id", bsp->id())("c", bsp->core.latest_qc_block_timestamp())("l", fsi.lock.timestamp) + ("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)("qc", bsp->core.latest_qc_claim())); + // Safety check : check if this proposal extends the proposal we're locked on + res.safety_check = bsp->core.extends(fsi.lock.block_id); + if (!res.safety_check) { + dlog("safety check failed, block ${bn} ${id} did not extend fsi.lock ${lbn} ${lid}", + ("bn", bsp->block_num())("id", bsp->id())("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)); + } + } + } else { + // Safety and Liveness both fail if `fsi.lock` is empty. It should not happen. + // `fsi.lock` is initially set to `lib` when switching to IF or starting from a snapshot. + // ------------------------------------------------------------------------------------- + wlog("liveness check & safety check failed, block ${bn} ${id}, fsi.lock is empty", ("bn", bsp->block_num())("id", bsp->id())); + res.liveness_check = false; + res.safety_check = false; + } + + bool can_vote = res.liveness_check || res.safety_check; + + // Figure out if we can vote and wether our vote will be strong or weak + // If we vote, update `fsi.last_vote` and also `fsi.lock` if we have a newer commit qc + // ----------------------------------------------------------------------------------- + if (can_vote) { + auto [p_start, p_end] = std::make_pair(bsp->core.latest_qc_block_timestamp(), bsp->timestamp()); + + bool time_range_disjoint = fsi.last_vote_range_start >= p_end || fsi.last_vote.timestamp <= p_start; + bool voting_strong = time_range_disjoint; + if (!voting_strong && !fsi.last_vote.empty()) { + // we can vote strong if the proposal is a descendant of (i.e. extends) our last vote id + voting_strong = bsp->core.extends(fsi.last_vote.block_id); + } + + fsi.last_vote = { bsp->id(), bsp->timestamp() }; + fsi.last_vote_range_start = p_start; + + auto& final_on_strong_qc_block_ref = bsp->core.get_block_reference(bsp->core.final_on_strong_qc_block_num); + if (voting_strong && final_on_strong_qc_block_ref.timestamp > fsi.lock.timestamp) { + fsi.lock = { final_on_strong_qc_block_ref.block_id, final_on_strong_qc_block_ref.timestamp }; + } + + res.decision = voting_strong ? vote_decision::strong_vote : vote_decision::weak_vote; + } + + dlog("block=${bn} ${id}, liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}, voting=${v}, locked=${lbn} ${lid}", + ("bn", bsp->block_num())("id", bsp->id())("l",res.liveness_check)("s",res.safety_check)("m",res.monotony_check) + ("can_vote",can_vote)("v", res.decision)("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)); + return res; +} + +// ---------------------------------------------------------------------------------------- +std::optional finalizer::maybe_vote(const bls_public_key& pub_key, + const block_state_ptr& bsp, + const digest_type& digest) { + finalizer::vote_decision decision = decide_vote(bsp).decision; + if (decision == vote_decision::strong_vote || decision == vote_decision::weak_vote) { + bls_signature sig; + if (decision == vote_decision::weak_vote) { + // if voting weak, the digest to sign should be a hash of the concatenation of the finalizer_digest + // and the string "WEAK" + sig = priv_key.sign(create_weak_digest(digest)); + } else { + sig = priv_key.sign({(uint8_t*)digest.data(), (uint8_t*)digest.data() + digest.data_size()}); + } + return std::optional{vote_message{ bsp->id(), decision == vote_decision::strong_vote, pub_key, sig }}; + } + return {}; +} + +// ---------------------------------------------------------------------------------------- +void my_finalizers_t::save_finalizer_safety_info() const { + + if (!persist_file.is_open()) { + EOS_ASSERT(!persist_file_path.empty(), finalizer_safety_exception, + "path for storing finalizer safety information file not specified"); + if (!std::filesystem::exists(persist_file_path.parent_path())) + std::filesystem::create_directories(persist_file_path.parent_path()); + persist_file.set_file_path(persist_file_path); + persist_file.open(fc::cfile::truncate_rw_mode); + } + try { + persist_file.seek(0); + fc::raw::pack(persist_file, fsi_t::magic); + fc::raw::pack(persist_file, (uint64_t)(finalizers.size() + inactive_safety_info.size())); + for (const auto& [pub_key, f] : finalizers) { + fc::raw::pack(persist_file, pub_key); + fc::raw::pack(persist_file, f.fsi); + } + if (!inactive_safety_info_written) { + // save also the fsi that was originally present in the file, but which applied to + // finalizers not configured anymore. + for (const auto& [pub_key, fsi] : inactive_safety_info) { + fc::raw::pack(persist_file, pub_key); + fc::raw::pack(persist_file, fsi); + } + inactive_safety_info_written = true; + } + persist_file.flush(); + } catch (const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } catch (const std::exception& e) { + edump((e.what())); + throw; + } +} + +// ---------------------------------------------------------------------------------------- +my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() { + fsi_map res; + + EOS_ASSERT(!persist_file_path.empty(), finalizer_safety_exception, + "path for storing finalizer safety persistence file not specified"); + EOS_ASSERT(!persist_file.is_open(), finalizer_safety_exception, + "Trying to read an already open finalizer safety persistence file: ${p}", + ("p", persist_file_path)); + + if (!std::filesystem::exists(persist_file_path)) { + elog( "unable to open finalizer safety persistence file ${p}, file doesn't exist", + ("p", persist_file_path)); + return res; + } + + persist_file.set_file_path(persist_file_path); + + try { + // if we can't open the finalizer safety file, we return an empty map. + persist_file.open(fc::cfile::update_rw_mode); + } catch(std::exception& e) { + elog( "unable to open finalizer safety persistence file ${p}, using defaults. Exception: ${e}", + ("p", persist_file_path)("e", e.what())); + return res; + } catch(...) { + elog( "unable to open finalizer safety persistence file ${p}, using defaults", ("p", persist_file_path)); + return res; + } + try { + persist_file.seek(0); + uint64_t magic = 0; + fc::raw::unpack(persist_file, magic); + EOS_ASSERT(magic == fsi_t::magic, finalizer_safety_exception, + "bad magic number in finalizer safety persistence file: ${p}", ("p", persist_file_path)); + uint64_t num_finalizers {0}; + fc::raw::unpack(persist_file, num_finalizers); + for (size_t i=0; i& finalizer_keys) { + assert(finalizers.empty()); // set_keys should be called only once at startup + if (finalizer_keys.empty()) + return; + + fsi_map safety_info = load_finalizer_safety_info(); + for (const auto& [pub_key_str, priv_key_str] : finalizer_keys) { + auto public_key {bls_public_key{pub_key_str}}; + auto it = safety_info.find(public_key); + const auto& fsi = it != safety_info.end() ? it->second : default_fsi; + finalizers[public_key] = finalizer{bls_private_key{priv_key_str}, fsi}; + } + + // Now that we have updated the finalizer_safety_info of our local finalizers, + // remove these from the in-memory map. Whenever we save the finalizer_safety_info, we will + // write the info for the local finalizers, and the first time we'll write the information for + // currently inactive finalizers (which might be configured again in the future). + // + // So for every vote but the first, we'll only have to write the safety_info for the configured + // finalizers. + // -------------------------------------------------------------------------------------------- + for (const auto& [pub_key_str, priv_key_str] : finalizer_keys) + safety_info.erase(bls_public_key{pub_key_str}); + + // now only inactive finalizers remain in safety_info => move it to inactive_safety_info + inactive_safety_info = std::move(safety_info); +} + + +// -------------------------------------------------------------------------------------------- +// Can be called either: +// - when transitioning to IF (before any votes are to be sent) +// - at leap startup, if we start at a block which is either within or past the IF transition. +// In either case, we are never updating existing finalizer safety information. This is only +// to ensure that the safety information will have defaults that ensure safety as much as +// possible, and allow for liveness which will allow the finalizers to eventually vote. +// -------------------------------------------------------------------------------------------- +void my_finalizers_t::set_default_safety_information(const fsi_t& fsi) { + for (auto& [pub_key, f] : finalizers) { + // update only finalizers which are uninitialized + if (!f.fsi.last_vote.empty() || !f.fsi.lock.empty()) + continue; + + f.fsi = fsi; + } + + // save it in case set_keys called afterwards. + default_fsi = fsi; +} + +} // namespace eosio::chain diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp new file mode 100644 index 0000000000..ca39f7d93f --- /dev/null +++ b/libraries/chain/hotstuff/hotstuff.cpp @@ -0,0 +1,226 @@ +#include +#include + +namespace eosio::chain { + +inline std::string bitset_to_string(const hs_bitset& bs) { + std::string r; + boost::to_string(bs, r); + return r; +} + +inline hs_bitset vector_to_bitset(const std::vector& v) { + return {v.cbegin(), v.cend()}; +} + +inline std::vector bitset_to_vector(const hs_bitset& bs) { + std::vector r; + r.resize(bs.num_blocks()); + boost::to_block_range(bs, r.begin()); + return r; +} + +bool pending_quorum_certificate::has_voted(size_t index) const { + return _strong_votes.has_voted(index) || _weak_votes.has_voted(index); +} + +bool pending_quorum_certificate::has_voted_no_lock(bool strong, size_t index) const { + if (strong) { + return _strong_votes.has_voted(index); + } + return _weak_votes.has_voted(index); +} + +void pending_quorum_certificate::votes_t::reflector_init() { + _processed = std::vector>(_bitset.size()); + for (size_t i = 0; i < _bitset.size(); ++i) { + if (_bitset[i]) { + _processed[i].store(true, std::memory_order_relaxed); + } + } +} + +bool pending_quorum_certificate::votes_t::has_voted(size_t index) const { + assert(index < _processed.size()); + return _processed[index].load(std::memory_order_relaxed); +} + + +vote_status pending_quorum_certificate::votes_t::add_vote(size_t index, const bls_signature& sig) { + if (_bitset[index]) { // check here as could have come in while unlocked + return vote_status::duplicate; // shouldn't be already present + } + _processed[index].store(true, std::memory_order_relaxed); + _bitset.set(index); + _sig.aggregate(sig); // works even if _sig is default initialized (fp2::zero()) + return vote_status::success; +} + +pending_quorum_certificate::pending_quorum_certificate() + : _mtx(std::make_unique()) { +} + +pending_quorum_certificate::pending_quorum_certificate(size_t num_finalizers, uint64_t quorum, uint64_t max_weak_sum_before_weak_final) + : _mtx(std::make_unique()) + , _quorum(quorum) + , _max_weak_sum_before_weak_final(max_weak_sum_before_weak_final) + , _weak_votes(num_finalizers) + , _strong_votes(num_finalizers) { +} + +bool pending_quorum_certificate::is_quorum_met() const { + std::lock_guard g(*_mtx); + return is_quorum_met_no_lock(); +} + +// called by add_vote, already protected by mutex +vote_status pending_quorum_certificate::add_strong_vote(size_t index, const bls_signature& sig, uint64_t weight) { + if (auto s = _strong_votes.add_vote(index, sig); s != vote_status::success) { + return s; + } + _strong_sum += weight; + + switch (_state) { + case state_t::unrestricted: + case state_t::restricted: + if (_strong_sum >= _quorum) { + assert(_state != state_t::restricted); + _state = state_t::strong; + } else if (_weak_sum + _strong_sum >= _quorum) + _state = (_state == state_t::restricted) ? state_t::weak_final : state_t::weak_achieved; + break; + + case state_t::weak_achieved: + if (_strong_sum >= _quorum) + _state = state_t::strong; + break; + + case state_t::weak_final: + case state_t::strong: + // getting another strong vote...nothing to do + break; + } + return vote_status::success; +} + +// called by add_vote, already protected by mutex +vote_status pending_quorum_certificate::add_weak_vote(size_t index, const bls_signature& sig, uint64_t weight) { + if (auto s = _weak_votes.add_vote(index, sig); s != vote_status::success) + return s; + _weak_sum += weight; + + switch (_state) { + case state_t::unrestricted: + case state_t::restricted: + if (_weak_sum + _strong_sum >= _quorum) + _state = state_t::weak_achieved; + + if (_weak_sum > _max_weak_sum_before_weak_final) { + if (_state == state_t::weak_achieved) + _state = state_t::weak_final; + else if (_state == state_t::unrestricted) + _state = state_t::restricted; + } + break; + + case state_t::weak_achieved: + if (_weak_sum >= _max_weak_sum_before_weak_final) + _state = state_t::weak_final; + break; + + case state_t::weak_final: + case state_t::strong: + // getting another weak vote... nothing to do + break; + } + return vote_status::success; +} + +// thread safe +vote_status pending_quorum_certificate::add_vote(block_num_type block_num, bool strong, std::span proposal_digest, size_t index, + const bls_public_key& pubkey, const bls_signature& sig, uint64_t weight) { + vote_status s = vote_status::success; + + if (has_voted_no_lock(strong, index)) { + dlog("block_num: ${bn}, vote strong: ${sv}, duplicate", ("bn", block_num)("sv", strong)); + return vote_status::duplicate; + } + + if (!fc::crypto::blslib::verify(pubkey, proposal_digest, sig)) { + wlog( "signature from finalizer ${i} cannot be verified", ("i", index) ); + return vote_status::invalid_signature; + } + + std::unique_lock g(*_mtx); + state_t pre_state = _state; + s = strong ? add_strong_vote(index, sig, weight) + : add_weak_vote(index, sig, weight); + state_t post_state = _state; + g.unlock(); + + dlog("block_num: ${bn}, vote strong: ${sv}, status: ${s}, pre-state: ${pre}, post-state: ${state}, quorum_met: ${q}", + ("bn", block_num)("sv", strong)("s", s)("pre", pre_state)("state", post_state)("q", is_quorum_met(post_state))); + return s; +} + +// called by get_best_qc which acquires a mutex +valid_quorum_certificate pending_quorum_certificate::to_valid_quorum_certificate() const { + valid_quorum_certificate valid_qc; + + if( _state == state_t::strong ) { + valid_qc._strong_votes = _strong_votes._bitset; + valid_qc._sig = _strong_votes._sig; + } else if (is_quorum_met_no_lock()) { + valid_qc._strong_votes = _strong_votes._bitset; + valid_qc._weak_votes = _weak_votes._bitset; + valid_qc._sig = _strong_votes._sig; + valid_qc._sig.aggregate(_weak_votes._sig); + } else + assert(0); // this should be called only when we have a valid qc. + + return valid_qc; +} + +std::optional pending_quorum_certificate::get_best_qc(block_num_type block_num) const { + std::lock_guard g(*_mtx); + // if pending_qc does not have a valid QC, consider valid_qc only + if( !is_quorum_met_no_lock() ) { + if( _valid_qc ) { + return std::optional{quorum_certificate{ block_num, *_valid_qc }}; + } else { + return std::nullopt; + } + } + + // extract valid QC from pending_qc + valid_quorum_certificate valid_qc_from_pending = to_valid_quorum_certificate(); + + // if valid_qc does not have value, consider valid_qc_from_pending only + if( !_valid_qc ) { + return std::optional{quorum_certificate{ block_num, valid_qc_from_pending }}; + } + + // Both valid_qc and valid_qc_from_pending have value. Compare them and select a better one. + // Strong beats weak. Tie break by valid_qc. + const auto& best_qc = + _valid_qc->is_strong() == valid_qc_from_pending.is_strong() ? + *_valid_qc : // tie broken by valid_qc + _valid_qc->is_strong() ? *_valid_qc : valid_qc_from_pending; // strong beats weak + return std::optional{quorum_certificate{ block_num, best_qc }}; +} + +void pending_quorum_certificate::set_valid_qc(const valid_quorum_certificate& qc) { + std::lock_guard g(*_mtx); + _valid_qc = qc; +} + +bool pending_quorum_certificate::valid_qc_is_strong() const { + std::lock_guard g(*_mtx); + return _valid_qc && _valid_qc->is_strong(); +} + +bool pending_quorum_certificate::is_quorum_met_no_lock() const { + return is_quorum_met(_state); +} + +} // namespace eosio::chain diff --git a/libraries/chain/hotstuff/hs_pseudo b/libraries/chain/hotstuff/hs_pseudo new file mode 100644 index 0000000000..d39d4dfcd9 --- /dev/null +++ b/libraries/chain/hotstuff/hs_pseudo @@ -0,0 +1,432 @@ +//notes : under this pseudo code, the hotstuff information is mapped to Antelope concepts : +b_leaf (becomes) -> block_header_state.id //block_state pointer to head + (`head->bhs.id`) + +b_lock (becomes) -> finalizer_safety_information.locked_block_ref + (`block_id_type` of the proposal we voted on and are locked to) + +b_exec (becomes) -> block proposal refered to by block_header_state_core.last_final_block_height //head->last_final_block_height + (`head->bhs.core.last_final_block_height`) + +v_height (becomes) -> finalizer_safety_information.last_vote_block_ref + (`block_id_type` of the last proposal we voted on) + +high_qc (becomes) -> block proposal refered to by block_header_state_core.last_qc_block_height + (fork_db.get_block_by_height(head->bhs.id, head->bhs.core.last_qc_block_height).get_best_qc()) + maybe add new index in fork_db? + +proposal_store is now fork_db + + + +//structures + +struct finalizer_authority { + bls_public_key key; + weight uint32_t; +} + +struct finalizer_policy { + finalizer_authority[] finalizers; + uint32_t weight_quorum_threshold; +} + +struct finalizer_safety_information{ + uint32_t last_vote_range_lower_bound; + uint32_t last_vote_range_upper_bound; + sha256 last_vote_block_ref; //v_height under hotstuff + sha256 locked_block_ref; //b_lock under hotstuff + bool is_last_vote_strong; + bool recovery_mode; //todo : discuss +} + +struct fork_db { + block_handle get_block_by_id(block_id_type id){ [...] //get block by id} + block_handle get_block_by_finalizer_digest(sha256 digest){ [...] //get block by finalizer digest} + block_handle get_block_by_height(block_id_type branch, uint32_t last_qc_block_height){ [...] //on a given branch, get block by height} + block_handle get_head_block(){ [...] //get the head block on the branch I'm looking to extend } +} + +struct block_header_state_core { + uint32_t last_final_block_height; //b_exec under hotstuff + std::optional final_on_strong_qc_block_height; + std::optional last_qc_block_height; //high_qc under hotstuff + + block_header_state_core next(uint32_t last_qc_block_height, bool is_last_qc_strong){ + // no state change if last_qc_block_height is the same + if( last_qc_block_height == this->last_qc_block_height ) { + return {*this}; + } + EOS_ASSERT( last_qc_block_height > this->last_qc_block_height, block_validate_exception, + "new last_qc_block_height must be greater than old last_qc_block_height" ); + auto old_last_qc_block_height = this->last_qc_block_height; + auto old_final_on_strong_qc_block_height = this->final_on_strong_qc_block_height; + block_header_state_core result{*this}; + if( is_last_qc_strong ) { + // last QC is strong. We can progress forward. + // block with old final_on_strong_qc_block_height becomes irreversible + if( old_final_on_strong_qc_block_height.has_value() ) { + //old commit / fork_db.log_irreversible() + result.last_final_block_height = *old_final_on_strong_qc_block_height; + } + // next block which can become irreversible is the block with + // old last_qc_block_height + if( old_last_qc_block_height.has_value() ) { + result.final_on_strong_qc_block_height = *old_last_qc_block_height; + } + } else { + // new final_on_strong_qc_block_height should not be present + result.final_on_strong_qc_block_height.reset(); + // new last_final_block_height should be the same as the old last_final_block_height + } + // new last_qc_block_height is always the input last_qc_block_height. + result.last_qc_block_height = last_qc_block_height; + return result; + } +} + +struct building_block_input { + block_id_type previous; + block_timestamp_type timestamp; + account_name producer; + vector new_protocol_feature_activations; +}; + +// this struct can be extracted from a building block +struct assembled_block_input : public building_block_input { + digest_type transaction_mroot; + digest_type action_mroot; + std::optional new_proposer_policy; + std::optional new_finalizer_policy; + std::optional qc; // assert(qc.block_height <= num_from_id(previous)); +}; + +struct block_header_state { + + //existing block_header_state members + + sha256 id; //b_leaf under hotstuff + + [...] //other existing block_header_state members + + protocol_feature_activation_set_ptr activated_protocol_features; + + //new additions + + block_header_state_core core; + incremental_block_mtree proposal_mtree; + incremental_block_mtree finality_mtree; + + finalizer_policy_ptr finalizer_policy; // finalizer set + threshold + generation, supports `digest()` + proposer_policy_ptr proposer_policy; // producer authority schedule, supports `digest()` + + flat_map proposer_policies; + flat_map finalizer_policies; + + + block_header_state next(const assembled_block_input& data) const { + } + + sha256 compute_finalizer_digest() const { + } +} + +//shared pointer to a block_state +struct block_handle { + block_state_ptr _handle; +} + +struct block_state { + sha256 finalizer_digest; + block_header_state_ptr bhs; + finalizer_policy_ptr active_fp; + std::optional pending_qc; + std::optional valid_qc; + + block_id_type id() const { return bhs->id;} + uint64_t get_height() const { return block_header::num_from_id(bhs->id);} + quorum_certificate get_best_qc() { [...] //return the best QC available } + +} + +//this structure holds the required information and methods for the Hotstuff algorithm. It is derived from a block and block_header content, notably extensions +struct hs_proposal { + //may not exist in final implementation, subject to change + block_id_type block_id; //computed, to be replaced with proposal_digest eventually + uint32_t get_height(); //from block_id + block_timestamp_type timestamp; //from block header + //qc specific information + uint32_t last_qc_block_height; //from block header extension + bool is_last_qc_strong; //from block header extension + valid_quorum_certificate qc; //from block extension +}; + +struct valid_quorum_certificate { + hs_bitset strong_bitset; + optional weak_bitset; //omitted if strong qc + bls_signature signature; //set to strong_signature if strong qc, set to strong_signature + weak_signature if weak qc + + //constructor used for strong qc + valid_quorum_certificate(hs_bitset b, bls_signature s) : + strong_bitset(b), + signature(s) {} + + //constructor used for weak qc + valid_quorum_certificate(hs_bitset sb, hs_bitset wb, bls_signature s) : + strong_bitset(sb), + weak_bitset(wb), + signature(s) {} + + bool is_strong() {if (weak_bitset.has_value()) return false; else return true; } +} + +struct pending_quorum_certificate { + hs_bitset strong_bitset; + bls_signature strong_signature; + hs_bitset weak_bitset; + bls_signature weak_signature; + + bool strong_quorum_met() [...] //abstracted, returns true if a strong quorum is met, false otherwise + bool weak_quorum_met()[...] //abstracted, returns true if a weak quorum is met, false otherwise +} + +struct quorum_certificate { + uint32_t block_height; + valid_quorum_certificate qc; +} + +struct hs_vote_message { + block_id_type block_id; //temporary, probably not needed later + sha256 proposal_digest; //proposal digest + bls_public_key finalizer_key; + bls_signature sig; + bool weak; //indicate if vote is weak, strong otherwise +}; + + +//added as a block_header extension before signing +struct hotstuff_header_extension { + uint32_t last_qc_block_height; + bool is_last_qc_strong; + + std::optional new_finalizer_policy; + std::optional new_proposer_policy; +} + +//added as a block extension before broadcast +struct hotstuff_block_extension { + valid_quorum_certificate qc; +} + +struct signed_block { + [...] //existing signed_block members +} + +//helper functions + +//not currently used +sha256 get_proposal_digest(block_header_state bhs, signed_block p, bool weak){ + //provide a proposal digest with sufficient commitments for a light client to construct proofs of finality and inclusion + //todo : determine require commitments and complete digest function + //note : interface is probably too wide, but serves to illustrate that the proposal digest is generated from elements from the state and elements from the signed block + //temporary implementation (insufficient for IBC but sufficient for internal Hotstuff) + sha256 digest = p.block_id; + if (weak) digest = hash(digest, "_WEAK"); //if weak is set to true, concatenate desambiguator + return digest; +} + +// +hotstuff_header_extension construct_hotstuff_header_extension(quorum_certificate qc, std::optional new_finalizer_policy, std::optional new_proposer_policy){ + return {qc.block_height, qc.is_strong(), new_finalizer_policy, new_proposer_policy}; + +} + +hotstuff_block_extension construct_hotstuff_block_extension(quorum_certificate qc){ + return {qc.qc}; +} + +//get finalizer info from storage, loaded on start, held in cache afterwards +void get_finalizer_info(bls_public_key key){ + [...] //abstracted, must get or create the finalizer safety info state for the given finalizer key +} + +//write the finalizer info to disk to prevent accidental double-signing in case of crash + recovery +void save_finalizer_info(bls_public_key key, finalizer_safety_information fsi){ + [...] //abstracted, must save the finalizer info associated to the key, and throw an exception / prevent additional signing if the write operation fails (?) +} + +bool extends(hs_proposal descendant, hs_proposal ancestor){ + [...] //abstracted, returns true if ancestor is a parent of descendant, false otherwise +} + +void update_pending_qc(hs_vote_message v, block_handle& bc){ + if (bc.valid_qc.has_value()) return; //can only update a pending qc + pending_quorum_certificate pqc = bc.pending_qc.value(); + + //update the current pending_quorum_certificate with new vote information + [...] //abstracted + +} + +hs_proposal extract_proposal(signed_block sb, block_handle& bc){ + hs_proposal p; + [...] //abstracted, see hs_proposal for how to retrieve the values + return p; +} + +enum VoteDecision { + StrongVote, + WeakVote, + NoVote +} + +VoteDecision decide_vote(finalizer_safety_information& fsi, block_handle p){ + + bool monotony_check = false; + bool safety_check = false; + bool liveness_check = false; + + b_phases = get_qc_chain(p); + b2 = b_phases[2] //first phase, prepare + b1 = b_phases[1] //second phase, precommit + b = b_phases[0] //third phase, commit + + if (fsi.last_vote_block_ref != sha256.empty()){ + if (p.timestamp > fork_db.get_block_by_id(fsi.last_vote_block_ref).timestamp){ + monotony_check = true; + } + } + else monotony_check = true; //if I have never voted on a proposal, means the protocol feature just activated and we can proceed + + if (fsi.locked_block_ref != sha256.empty()){ + //Safety check : check if this proposal extends the proposal we're locked on + if (extends(p, fork_db.get_block_by_id(fsi.locked_block_ref)) safety_check = true; + //Liveness check : check if the height of this proposal's justification is higher than the height of the proposal I'm locked on. This allows restoration of liveness if a replica is locked on a stale proposal + if (fork_db.get_block_by_height(p.id(), p.last_qc_block_height).timestamp > fork_db.get_block_by_id(fsi.locked_block_ref).timestamp)) liveness_check = true; + } + else { + //if we're not locked on anything, means the protocol feature just activated and we can proceed + liveness_check = true; + safety_check = true; + } + + if (monotony_check && (liveness_check || safety_check)){ + + uint32_t requested_vote_range_lower_bound = fork_db.get_block_by_height(p.block_id, p.last_qc_block_height).timestamp; + uint32_t requested_vote_range_upper_bound = p.timestamp; + + bool time_range_interference = fsi.last_vote_range_lower_bound < requested_vote_range_upper_bound && requested_vote_range_lower_bound < fsi.last_vote_range_upper_bound; + + //my last vote was on (t9, t10_1], I'm asked to vote on t10 : t9 < t10 && t9 < t10_1; //time_range_interference == true, correct + //my last vote was on (t9, t10_1], I'm asked to vote on t11 : t9 < t11 && t10 < t10_1; //time_range_interference == false, correct + //my last vote was on (t7, t9], I'm asked to vote on t10 : t7 < t10 && t9 < t9; //time_range_interference == false, correct + + bool enough_for_strong_vote = false; + + if (!time_range_interference || extends(p, fork_db.get_block_by_id(fsi.last_vote_block_ref)) enough_for_strong_vote = true; + + //fsi.is_last_vote_strong = enough_for_strong_vote; + fsi.last_vote_block_ref = p.block_id; //v_height + + if (b1.timestamp > fork_db.get_block_by_id(fsi.locked_block_ref).timestamp) fsi.locked_block_ref = b1.block_id; //commit phase on b1 + + fsi.last_vote_range_lower_bound = requested_vote_range_lower_bound; + fsi.last_vote_range_upper_bound = requested_vote_range_upper_bound; + + if (enough_for_strong_vote) return VoteDecision::StrongVote; + else return VoteDecision::WeakVote; + + } + else return VoteDecision::NoVote; +} + +//handlers + +void on_signed_block_received(signed_block sb){ + [...] //verify if block can be linked to our fork database, throw exception if unable to or if duplicate + block_handle previous = fork_db.get_block_by_id(sb.previous); + hs_proposal p = extract_proposal(sb, previous); + on_proposal_received(p, previous); +} + +void on_proposal_received(signed_block_ptr new_block, block_handle& parent){ + + //relevant to all nodes + if (new_block.last_qc_block_height > parent.bhs.last_qc_block_height) { + block_handle found = fork_db.get_block_by_height(new_block.block_id, new_block.last_qc_block_height); + //verify qc is present and if the qc is valid with respect to the found block, throw exception otherwise + + found->valid_qc = new_block.block_extension.qc; + } + + [...] //abstracted, relay proposal to other nodes + + assembled_block_input data = [...] //construct from new_block; + + block_header_state new_block_header_state = parent.bhs.next(data); //f1 & f2 + + block_handle new_block_handle = add_to_fork_db(parent, new_block_header_state); + + bls_public_key[] my_finalizers = [...] //abstracted, must return the public keys of my finalizers that are also active in the current finalizer policy + //only relevant if I have at least one finalizer + if (my_finalizers.size()>0) { + for (auto f : my_finalizers){ + finalizer_safety_information& fsi = get_finalizer_info(f); + vote_decision vd = decide_vote(fsi, new_block_handle); //changes fsi unless NoVote + if (vd == VoteDecision::StrongVote || vd == VoteDecision::WeakVote){ + save_finalizer_info(f, fsi); //save finalizer info to prevent double-voting + hs_vote_message msg = [...] //create + broadcast vote message + } + } + } +} + +//when a node receives a vote on a proposal +void on_vote_received(hs_vote_message v){ + + //[...] check for duplicate or invalid vote, return in either case + + block_handle& bc = fork_db.get_block_by_id(v.block_id); + + [...] //abstracted, relay vote to other nodes + + am_i_leader = [...] //abstracted, must return true if I am the leader, false otherwise + + if(!am_i_leader) return; + + //only leader need to take further action on votes + update_pending_qc(v, bc); //update qc for this proposal + +} + +hs_proposal[] get_qc_chain(hs_proposal p){ + b[]; + b[2] = fork_db.get_block_by_height(p.block_id, p.last_qc_block_height); //first phase, prepare + b[1] = fork_db.get_block_by_height(p.block_id, b[2].last_qc_block_height); //second phase, precommit + b[0] = fork_db.get_block_by_height(p.block_id, b[1].last_qc_block_height); //third phase, commit + return b; +} + +//main algorithm entry point. This replaces on_beat() / create_proposal(), and it is now unified with existing systems +{ + block_handle head = fork_db.get_head_block(); + + [...] //if a new finalizer or proposer policy is needed, add it as new_finalizer_policy, new_proposer_policy + + [...] //abstracted, create block header + + + auto found = fork_db.get_block_with_latest_qc(head); + if (head.bhs.is_needed(found.get_best_qc()) { + //insert block extension if a new qc was created + block_extensions.push(construct_hotstuff_block_extension(found.get_best_qc())); + } + header_extensions.push(construct_hotstuff_header_extension(found.get_best_qc(), new_finalizer_policy, new_proposer_policy)); + [...] //abstracted, complete block + + + [...] //abstracted, sign block header + [...] //broadcast signed_block. The signed_block is processed by the on_signed_block_received handler by other nodes on the network +} + + diff --git a/libraries/chain/hotstuff/instant_finality_extension.cpp b/libraries/chain/hotstuff/instant_finality_extension.cpp new file mode 100644 index 0000000000..e14f48bbe8 --- /dev/null +++ b/libraries/chain/hotstuff/instant_finality_extension.cpp @@ -0,0 +1,13 @@ +#include +#include + +namespace eosio::chain { + +#warning this file can be removed if no actual validation comes up after all design is complete + void instant_finality_extension::reflector_init() { + static_assert( fc::raw::has_feature_reflector_init_on_unpacked_reflected_types, + "instant_finality_extension expects FC to support reflector_init" ); + static_assert( extension_id() == 2, "instant_finality_extension extension id must be 2" ); + } + +} // eosio::chain diff --git a/libraries/chain/hotstuff/test/CMakeLists.txt b/libraries/chain/hotstuff/test/CMakeLists.txt new file mode 100644 index 0000000000..4642bc850b --- /dev/null +++ b/libraries/chain/hotstuff/test/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable( test_hotstuff finality_misc_tests.cpp ) +target_link_libraries( test_hotstuff eosio_chain fc Boost::unit_test_framework) + +add_test(NAME test_hotstuff COMMAND test_hotstuff WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST test_hotstuff PROPERTY LABELS nonparallelizable_tests) + diff --git a/libraries/chain/hotstuff/test/finality_misc_tests.cpp b/libraries/chain/hotstuff/test/finality_misc_tests.cpp new file mode 100644 index 0000000000..ba58a06ed5 --- /dev/null +++ b/libraries/chain/hotstuff/test/finality_misc_tests.cpp @@ -0,0 +1,238 @@ +#define BOOST_TEST_MODULE hotstuff + +#include +#include +#include + +#include +#include +#include + +#include + +// ----------------------------------------------------------------------------- +// Allow boost to print `pending_quorum_certificate::state_t` +// ----------------------------------------------------------------------------- +namespace std { + using state_t = eosio::chain::pending_quorum_certificate::state_t; + std::ostream& operator<<(std::ostream& os, state_t s) + { + switch(s) { + case state_t::unrestricted: os << "unrestricted"; break; + case state_t::restricted: os << "restricted"; break; + case state_t::weak_achieved: os << "weak_achieved"; break; + case state_t::weak_final: os << "weak_final"; break; + case state_t::strong: os << "strong"; break; + } + return os; + } +} + +BOOST_AUTO_TEST_CASE(qc_state_transitions) try { + using namespace eosio::chain; + using namespace fc::crypto::blslib; + using state_t = pending_quorum_certificate::state_t; + + digest_type d(fc::sha256("0000000000000000000000000000001")); + std::vector digest(d.data(), d.data() + d.data_size()); + + std::vector sk { + bls_private_key("PVT_BLS_0d8dsux83r42Qg8CHgAqIuSsn9AV-QdCzx3tPj0K8yOJA_qb"), + bls_private_key("PVT_BLS_Wfs3KzfTI2P5F85PnoHXLnmYgSbp-XpebIdS6BUCHXOKmKXK"), + bls_private_key("PVT_BLS_74crPc__6BlpoQGvWjkHmUdzcDKh8QaiN_GtU4SD0QAi4BHY"), + bls_private_key("PVT_BLS_foNjZTu0k6qM5ftIrqC5G_sim1Rg7wq3cRUaJGvNtm2rM89K"), + bls_private_key("PVT_BLS_FWK1sk_DJnoxNvUNhwvJAYJFcQAFtt_mCtdQCUPQ4jN1K7eT"), + bls_private_key("PVT_BLS_tNAkC5MnI-fjHWSX7la1CPC2GIYgzW5TBfuKFPagmwVVsOeW") + }; + + std::vector pubkey; + pubkey.reserve(sk.size()); + for (const auto& k : sk) + pubkey.push_back(k.get_public_key()); + + auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index, uint64_t weight) { + return qc.add_vote(0, false, digest, index, pubkey[index], sk[index].sign(digest), weight); + }; + + auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index, uint64_t weight) { + return qc.add_vote(0, true, digest, index, pubkey[index], sk[index].sign(digest), weight); + }; + + constexpr uint64_t weight = 1; + + { + constexpr uint64_t quorum = 1; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(2, quorum, max_weak_sum_before_weak_final); // 2 finalizers + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + + // add one weak vote + // ----------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add duplicate weak vote + // ----------------------- + auto ok = weak_vote(qc, digest, 0, weight); + BOOST_CHECK(ok != vote_status::success); // vote was a duplicate + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add another weak vote + // --------------------- + weak_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_final); + } + + { + constexpr uint64_t quorum = 1; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(2, quorum, max_weak_sum_before_weak_final); // 2 finalizers + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + } + + { + constexpr uint64_t quorum = 1; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(2, quorum, max_weak_sum_before_weak_final); // 2 finalizers, weight_sum_minus_quorum = 1 + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + } + + { + constexpr uint64_t quorum = 2; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(3, quorum, max_weak_sum_before_weak_final); // 3 finalizers + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(std::move(qc)); + + // add a weak vote + // --------------- + weak_vote(qc2, digest, 2, weight); + BOOST_CHECK_EQUAL(qc2.state(), state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + + { + constexpr uint64_t quorum = 2; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(3, quorum, max_weak_sum_before_weak_final); // 3 finalizers, quorum = 2 + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(std::move(qc)); + + // add a strong vote + // ----------------- + strong_vote(qc2, digest, 2, weight); + BOOST_CHECK_EQUAL(qc2.state(), state_t::strong); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + + { + constexpr uint64_t quorum = 2; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(3, quorum, max_weak_sum_before_weak_final); // 3 finalizers, quorum = 2 + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a weak vote + // --------------- + weak_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_final); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(std::move(qc)); + + // add a weak vote + // --------------- + weak_vote(qc2, digest, 2, weight); + BOOST_CHECK_EQUAL(qc2.state(), state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + + { + constexpr uint64_t quorum = 2; + constexpr uint64_t max_weak_sum_before_weak_final = 1; + pending_quorum_certificate qc(3, quorum, max_weak_sum_before_weak_final); // 3 finalizers, quorum = 2 + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a weak vote + // --------------- + weak_vote(qc, digest, 1, weight); + BOOST_CHECK_EQUAL(qc.state(), state_t::weak_final); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(std::move(qc)); + + // add a strong vote + // ----------------- + strong_vote(qc2, digest, 2, weight); + BOOST_CHECK_EQUAL(qc2.state(), state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + +} FC_LOG_AND_RETHROW(); diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 3cad62bcf5..d11e014138 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -636,6 +637,8 @@ namespace impl { out(name, std::move(mvo)); } + static void add_block_header_instant_finality_extension( mutable_variant_object& mvo, const header_extension_multimap& header_exts ); + /** * overload of to_variant_object for signed_block * @@ -676,6 +679,7 @@ namespace impl { std::get(header_exts.lower_bound(producer_schedule_change_extension::extension_id())->second); mvo("new_producer_schedule", new_producer_schedule); } + add_block_header_instant_finality_extension(mvo, header_exts); mvo("producer_signature", block.producer_signature); add(mvo, "transactions", block.transactions, resolver, ctx); @@ -687,6 +691,12 @@ namespace impl { std::get(block_exts.lower_bound(additional_block_signatures_extension::extension_id())->second); mvo("additional_signatures", additional_signatures); } + auto qc_extension_count = block_exts.count(quorum_certificate_extension::extension_id()); + if ( qc_extension_count > 0) { + const auto& qc_extension = + std::get(block_exts.lower_bound(quorum_certificate_extension::extension_id())->second); + mvo("qc_extension", qc_extension); + } out(name, std::move(mvo)); } diff --git a/libraries/chain/include/eosio/chain/action_receipt.hpp b/libraries/chain/include/eosio/chain/action_receipt.hpp index 4afe35371d..b921b115be 100644 --- a/libraries/chain/include/eosio/chain/action_receipt.hpp +++ b/libraries/chain/include/eosio/chain/action_receipt.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include -namespace eosio { namespace chain { +namespace eosio::chain { /** * For each action dispatched this receipt is generated @@ -15,20 +16,8 @@ namespace eosio { namespace chain { flat_map auth_sequence; fc::unsigned_int code_sequence = 0; ///< total number of setcodes fc::unsigned_int abi_sequence = 0; ///< total number of setabis - - digest_type digest()const { - digest_type::encoder e; - fc::raw::pack(e, receiver); - fc::raw::pack(e, act_digest); - fc::raw::pack(e, global_sequence); - fc::raw::pack(e, recv_sequence); - fc::raw::pack(e, auth_sequence); - fc::raw::pack(e, code_sequence); - fc::raw::pack(e, abi_sequence); - return e.result(); - } }; -} } /// namespace eosio::chain +} /// namespace eosio::chain FC_REFLECT( eosio::chain::action_receipt, (receiver)(act_digest)(global_sequence)(recv_sequence)(auth_sequence)(code_sequence)(abi_sequence) ) diff --git a/libraries/chain/include/eosio/chain/block.hpp b/libraries/chain/include/eosio/chain/block.hpp index ab06da5d42..ab88ee0868 100644 --- a/libraries/chain/include/eosio/chain/block.hpp +++ b/libraries/chain/include/eosio/chain/block.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace eosio { namespace chain { @@ -55,19 +56,18 @@ namespace eosio { namespace chain { static constexpr uint16_t extension_id() { return 2; } static constexpr bool enforce_unique() { return true; } - additional_block_signatures_extension() = default; + void reflector_init(); - additional_block_signatures_extension( const vector& signatures ) - :signatures( signatures ) - {} + vector signatures; + }; - additional_block_signatures_extension( vector&& signatures ) - :signatures( std::move(signatures) ) - {} + struct quorum_certificate_extension : fc::reflect_init { + static constexpr uint16_t extension_id() { return 3; } + static constexpr bool enforce_unique() { return true; } void reflector_init(); - vector signatures; + quorum_certificate qc; }; namespace detail { @@ -79,7 +79,7 @@ namespace eosio { namespace chain { } using block_extension_types = detail::block_extension_types< - additional_block_signatures_extension + additional_block_signatures_extension, quorum_certificate_extension >; using block_extension = block_extension_types::block_extension_t; @@ -119,4 +119,5 @@ FC_REFLECT_ENUM( eosio::chain::transaction_receipt::status_enum, FC_REFLECT(eosio::chain::transaction_receipt_header, (status)(cpu_usage_us)(net_usage_words) ) FC_REFLECT_DERIVED(eosio::chain::transaction_receipt, (eosio::chain::transaction_receipt_header), (trx) ) FC_REFLECT(eosio::chain::additional_block_signatures_extension, (signatures)); +FC_REFLECT(eosio::chain::quorum_certificate_extension, (qc)); FC_REFLECT_DERIVED(eosio::chain::signed_block, (eosio::chain::signed_block_header), (transactions)(block_extensions) ) diff --git a/libraries/chain/include/eosio/chain/block_handle.hpp b/libraries/chain/include/eosio/chain/block_handle.hpp new file mode 100644 index 0000000000..9590c2da46 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_handle.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + +// Created via controller::create_block_handle(const block_id_type& id, const signed_block_ptr& b) +// Valid to request id and signed_block_ptr it was created from. +struct block_handle { +private: + std::variant _bsp; + +public: + block_handle() = default; + explicit block_handle(block_state_legacy_ptr bsp) : _bsp(std::move(bsp)) {} + explicit block_handle(block_state_ptr bsp) : _bsp(std::move(bsp)) {} + + // Avoid using internal block_state/block_state_legacy as those types are internal to controller. + const auto& internal() const { return _bsp; } + + uint32_t block_num() const { return std::visit([](const auto& bsp) { return bsp->block_num(); }, _bsp); } + block_timestamp_type block_time() const { return std::visit([](const auto& bsp) { return bsp->timestamp(); }, _bsp); }; + const block_id_type& id() const { return std::visit([](const auto& bsp) -> const block_id_type& { return bsp->id(); }, _bsp); } + const block_id_type& previous() const { return std::visit([](const auto& bsp) -> const block_id_type& { return bsp->previous(); }, _bsp); } + const signed_block_ptr& block() const { return std::visit([](const auto& bsp) -> const signed_block_ptr& { return bsp->block; }, _bsp); } + const block_header& header() const { return std::visit([](const auto& bsp) -> const block_header& { return bsp->header; }, _bsp); }; + account_name producer() const { return std::visit([](const auto& bsp) { return bsp->producer(); }, _bsp); } +}; + +} // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index f245841777..514879ccd0 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -2,10 +2,12 @@ #include #include #include +#include +#include #include -namespace eosio { namespace chain { +namespace eosio::chain { namespace detail { template @@ -17,10 +19,14 @@ namespace eosio { namespace chain { using block_header_extension_types = detail::block_header_extension_types< protocol_feature_activation, - producer_schedule_change_extension + producer_schedule_change_extension, + instant_finality_extension >; using block_header_extension = block_header_extension_types::block_header_extension_t; + using header_extension_multimap = flat_multimap; + + using validator_t = const std::function&, const vector&)>; struct block_header { @@ -28,6 +34,7 @@ namespace eosio { namespace chain { account_name producer; /** + * Legacy block confirmation: * By signing this block this producer is confirming blocks [block_num() - confirmed, blocknum()) * as being the best blocks for that range and that he has not signed any other * statements that would contradict. @@ -35,13 +42,23 @@ namespace eosio { namespace chain { * No producer should sign a block with overlapping ranges or it is proof of byzantine * behavior. When producing a block a producer is always confirming at least the block he * is building off of. A producer cannot confirm "this" block, only prior blocks. + * + * Instant-finality: + * Once instant-finality is enabled a producer can no longer confirm blocks, only propose them; + * confirmed is 0 after instant-finality is enabled. */ uint16_t confirmed = 1; block_id_type previous; checksum256_type transaction_mroot; /// mroot of cycles_summary - checksum256_type action_mroot; /// mroot of all delivered action receipts + + // In Legacy, action_mroot is the mroot of all delivered action receipts. + // In Savanna, action_mroot is the root of the Finality Tree + // associated with the block, i.e. the root of + // validation_tree(core.final_on_strong_qc_block_num). + checksum256_type action_mroot; + /** * LEGACY SUPPORT - After enabling the wtmsig-blocks extension this field is deprecated and must be empty @@ -59,15 +76,29 @@ namespace eosio { namespace chain { new_producers_type new_producers; extensions_type header_extensions; - - block_header() = default; - digest_type digest()const; block_id_type calculate_id() const; uint32_t block_num() const { return num_from_id(previous) + 1; } static uint32_t num_from_id(const block_id_type& id); - - flat_multimap validate_and_extract_header_extensions()const; + uint32_t protocol_version() const { return 0; } + + // A flag to indicate whether a block is a Proper Savanna Block + static constexpr uint32_t proper_svnn_schedule_version = (1LL << 31); + + // Returns true if the block is a Proper Savanna Block. + // We don't check whether finality extension exists here for performance reason. + // When block header is validated in block_header_state's next(), + // it is already validate if schedule_version == proper_svnn_schedule_version, + // finality extension must exist. + bool is_proper_svnn_block() const { return ( schedule_version == proper_svnn_schedule_version ); } + + header_extension_multimap validate_and_extract_header_extensions()const; + std::optional extract_header_extension(uint16_t extension_id)const; + template Ext extract_header_extension()const { + assert(contains_header_extension(Ext::extension_id())); + return std::get(*extract_header_extension(Ext::extension_id())); + } + bool contains_header_extension(uint16_t extension_id)const; }; @@ -76,7 +107,7 @@ namespace eosio { namespace chain { signature_type producer_signature; }; -} } /// namespace eosio::chain +} /// namespace eosio::chain FC_REFLECT(eosio::chain::block_header, (timestamp)(producer)(confirmed)(previous) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp new file mode 100644 index 0000000000..02291c3547 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio::chain { + +namespace snapshot_detail { + struct snapshot_block_state_v7; +} + +namespace detail { struct schedule_info; }; + +// Light header protocol version, separate from protocol feature version +constexpr uint32_t light_header_protocol_version_major = 1; +constexpr uint32_t light_header_protocol_version_minor = 0; + +struct building_block_input { + block_id_type parent_id; + block_timestamp_type parent_timestamp; + block_timestamp_type timestamp; + account_name producer; + vector new_protocol_feature_activations; +}; + +// this struct can be extracted from a building block +struct block_header_state_input : public building_block_input { + digest_type transaction_mroot; // Comes from std::get(building_block::trx_mroot_or_receipt_digests) + std::shared_ptr new_proposer_policy; // Comes from building_block::new_proposer_policy + std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy + qc_claim_t most_recent_ancestor_with_qc; // Comes from traversing branch from parent and calling get_best_qc() + digest_type finality_mroot_claim; +}; + +struct block_header_state { + // ------ data members ------------------------------------------------------------ + block_id_type block_id; + block_header header; + protocol_feature_activation_set_ptr activated_protocol_features; + + finality_core core; // thread safe, not modified after creation + + finalizer_policy_ptr active_finalizer_policy; // finalizer set + threshold + generation, supports `digest()` + proposer_policy_ptr active_proposer_policy; // producer authority schedule, supports `digest()` + + // block time when proposer_policy will become active + flat_map proposer_policies; + flat_map finalizer_policies; + + + // ------ data members caching information available elsewhere ---------------------- + header_extension_multimap header_exts; // redundant with the data stored in header + + + // ------ functions ----------------------------------------------------------------- + const block_id_type& id() const { return block_id; } + const digest_type finality_mroot() const { return header.is_proper_svnn_block() ? header.action_mroot : digest_type{}; } + block_timestamp_type timestamp() const { return header.timestamp; } + account_name producer() const { return header.producer; } + const block_id_type& previous() const { return header.previous; } + uint32_t block_num() const { return block_header::num_from_id(previous()) + 1; } + block_timestamp_type last_qc_block_timestamp() const { + auto last_qc_block_num = core.latest_qc_claim().block_num; + return core.get_block_reference(last_qc_block_num).timestamp; + } + const producer_authority_schedule& active_schedule_auth() const { return active_proposer_policy->proposer_schedule; } + const protocol_feature_activation_set_ptr& get_activated_protocol_features() const { return activated_protocol_features; } + + block_header_state next(block_header_state_input& data) const; + block_header_state next(const signed_block_header& h, validator_t& validator) const; + + digest_type compute_base_digest() const; + digest_type compute_finality_digest() const; + + // Returns true if the block is a Proper Savanna Block + bool is_proper_svnn_block() const; + + // block descending from this need the provided qc in the block extension + bool is_needed(const qc_claim_t& qc_claim) const { + return qc_claim > core.latest_qc_claim(); + } + + const vector& get_new_protocol_feature_activations() const; + const producer_authority& get_scheduled_producer(block_timestamp_type t) const; +}; + +using block_header_state_ptr = std::shared_ptr; + +} + +FC_REFLECT( eosio::chain::block_header_state, (block_id)(header) + (activated_protocol_features)(core)(active_finalizer_policy) + (active_proposer_policy)(proposer_policies)(finalizer_policies)(header_exts)) diff --git a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp index 7e4a5e8abc..a96b73d414 100644 --- a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp @@ -1,46 +1,27 @@ #pragma once #include -#include +#include #include #include #include -namespace eosio { namespace chain { - -namespace legacy { - - /** - * a fc::raw::unpack compatible version of the old block_state structure stored in - * version 2 snapshots - */ - struct snapshot_block_header_state_v2 { - static constexpr uint32_t minimum_version = 0; - static constexpr uint32_t maximum_version = 2; - static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_block_header_state_v2 is no longer needed"); - - struct schedule_info { - uint32_t schedule_lib_num = 0; /// last irr block num - digest_type schedule_hash; - producer_schedule_type schedule; - }; - - /// from block_header_state_legacy_common - uint32_t block_num = 0; - uint32_t dpos_proposed_irreversible_blocknum = 0; - uint32_t dpos_irreversible_blocknum = 0; - producer_schedule_type active_schedule; - incremental_merkle blockroot_merkle; - flat_map producer_to_last_produced; - flat_map producer_to_last_implied_irb; - public_key_type block_signing_key; - vector confirm_count; - - // from block_header_state_legacy - block_id_type id; - signed_block_header header; - schedule_info pending_schedule; - protocol_feature_activation_set_ptr activated_protocol_features; +namespace eosio::chain { + +namespace snapshot_detail { + struct snapshot_block_header_state_legacy_v2; + struct snapshot_block_header_state_legacy_v3; +} + +namespace detail { + struct schedule_info { + // schedule_lib_num is compared with dpos lib, but the value is actually current block at time of pending + // After hotstuff is activated, schedule_lib_num is compared to next().next() round for determination of + // changing from pending to active. + uint32_t schedule_lib_num = 0; /// block_num of pending + digest_type schedule_hash; + producer_authority_schedule schedule; }; + } using signer_callback_type = std::function(const digest_type&)>; @@ -53,22 +34,12 @@ namespace detail { uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; producer_authority_schedule active_schedule; - incremental_merkle blockroot_merkle; + incremental_merkle_tree_legacy blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; block_signing_authority valid_block_signing_authority; vector confirm_count; }; - - struct schedule_info { - uint32_t schedule_lib_num = 0; /// last irr block num - digest_type schedule_hash; - producer_authority_schedule schedule; - }; - - bool is_builtin_activated( const protocol_feature_activation_set_ptr& pfa, - const protocol_feature_set& pfs, - builtin_protocol_feature_t feature_codename ); } struct pending_block_header_state_legacy : public detail::block_header_state_legacy_common { @@ -80,37 +51,63 @@ struct pending_block_header_state_legacy : public detail::block_header_state_leg block_timestamp_type timestamp; uint32_t active_schedule_version = 0; uint16_t confirmed = 1; + std::optional qc_claim; // transition to savanna has begun + + bool is_if_transition_block() const { return !!qc_claim; } signed_block_header make_block_header( const checksum256_type& transaction_mroot, const checksum256_type& action_mroot, const std::optional& new_producers, + std::optional&& new_finalizer_policy, vector&& new_protocol_feature_activations, const protocol_feature_set& pfs)const; block_header_state_legacy finish_next( const signed_block_header& h, vector&& additional_signatures, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, bool skip_validate_signee = false )&&; block_header_state_legacy finish_next( signed_block_header& h, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, const signer_callback_type& signer )&&; protected: block_header_state_legacy _finish_next( const signed_block_header& h, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator )&&; + validator_t& validator )&&; }; /** + * @struct block_header_state + * + * Algorithm for producer schedule change (pre-savanna) + * privileged contract -> set_proposed_producers(producers) -> + * global_property_object.proposed_schedule_block_num = current_block_num + * global_property_object.proposed_schedule = producers + * + * start_block -> (global_property_object.proposed_schedule_block_num == dpos_lib) + * building_block._new_pending_producer_schedule = producers + * + * finish_block -> + * block_header.extensions.wtmsig_block_signatures = producers + * block_header.new_producers = producers + * + * create_block_state -> + * block_state.schedule_lib_num = current_block_num (note this should be named schedule_block_num) + * block_state.pending_schedule.schedule = producers + * + * start_block -> + * block_state.prev_pending_schedule = pending_schedule (producers) + * if (pending_schedule.schedule_lib_num == dpos_lib) + * block_state.active_schedule = pending_schedule + * block_state.was_pending_promoted = true + * block_state.pending_schedule.clear() // doesn't get copied from previous + * else + * block_state.pending_schedule = prev_pending_schedule + * + * * @struct block_header_state_legacy * @brief defines the minimum state necessary to validate transaction headers */ @@ -123,7 +120,7 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm /// this data is redundant with the data stored in header, but it acts as a cache that avoids /// duplication of work - flat_multimap header_exts; + header_extension_multimap header_exts; block_header_state_legacy() = default; @@ -131,23 +128,22 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm :detail::block_header_state_legacy_common( std::move(base) ) {} - explicit block_header_state_legacy( legacy::snapshot_block_header_state_v2&& snapshot ); + explicit block_header_state_legacy( snapshot_detail::snapshot_block_header_state_legacy_v2&& snapshot ); + explicit block_header_state_legacy( snapshot_detail::snapshot_block_header_state_legacy_v3&& snapshot ); pending_block_header_state_legacy next( block_timestamp_type when, uint16_t num_prev_blocks_to_confirm )const; block_header_state_legacy next( const signed_block_header& h, vector&& additional_signatures, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + validator_t& validator, bool skip_validate_signee = false )const; - bool has_pending_producers()const { return pending_schedule.schedule.producers.size(); } uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; - producer_authority get_scheduled_producer( block_timestamp_type t )const; - const block_id_type& prev()const { return header.previous; } + const protocol_feature_activation_set_ptr& get_activated_protocol_features() const { return activated_protocol_features; } + const producer_authority& get_scheduled_producer( block_timestamp_type t )const; + const block_id_type& previous()const { return header.previous; } digest_type sig_digest()const; void sign( const signer_callback_type& signer ); void verify_signee()const; @@ -157,7 +153,7 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm using block_header_state_legacy_ptr = std::shared_ptr; -} } /// namespace eosio::chain +} /// namespace eosio::chain FC_REFLECT( eosio::chain::detail::block_header_state_legacy_common, (block_num) @@ -186,25 +182,3 @@ FC_REFLECT_DERIVED( eosio::chain::block_header_state_legacy, (eosio::chain::det ) -FC_REFLECT( eosio::chain::legacy::snapshot_block_header_state_v2::schedule_info, - ( schedule_lib_num ) - ( schedule_hash ) - ( schedule ) -) - - -FC_REFLECT( eosio::chain::legacy::snapshot_block_header_state_v2, - ( block_num ) - ( dpos_proposed_irreversible_blocknum ) - ( dpos_irreversible_blocknum ) - ( active_schedule ) - ( blockroot_merkle ) - ( producer_to_last_produced ) - ( producer_to_last_implied_irb ) - ( block_signing_key ) - ( confirm_count ) - ( id ) - ( header ) - ( pending_schedule ) - ( activated_protocol_features ) -) diff --git a/libraries/chain/include/eosio/chain/block_header_state_utils.hpp b/libraries/chain/include/eosio/chain/block_header_state_utils.hpp new file mode 100644 index 0000000000..1f4deeef65 --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_header_state_utils.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "eosio/chain/protocol_feature_manager.hpp" +#include +#include + + +namespace eosio::chain::detail { + + inline bool is_builtin_activated(const protocol_feature_activation_set_ptr& pfa, + const protocol_feature_set& pfs, + builtin_protocol_feature_t feature_codename) { + auto digest = pfs.get_builtin_digest(feature_codename); + const auto& protocol_features = pfa->protocol_features; + return digest && protocol_features.find(*digest) != protocol_features.end(); + } + + inline block_timestamp_type get_next_next_round_block_time( block_timestamp_type t) { + auto index = t.slot % config::producer_repetitions; // current index in current round + // (increment to the end of this round ) + next round + return block_timestamp_type{t.slot + (config::producer_repetitions - index) + config::producer_repetitions}; + } + + inline const producer_authority& get_scheduled_producer(const vector& producers, block_timestamp_type t) { + auto index = t.slot % (producers.size() * config::producer_repetitions); + index /= config::producer_repetitions; + return producers[index]; + } + + constexpr auto additional_sigs_eid = additional_block_signatures_extension::extension_id(); + + /** + * Given a complete signed block, extract the validated additional signatures if present; + * + * @param b complete signed block + * @return the list of additional signatures + */ + inline vector extract_additional_signatures(const signed_block_ptr& b) { + auto exts = b->validate_and_extract_extensions(); + + if (exts.count(additional_sigs_eid) > 0) { + auto& additional_sigs = std::get(exts.lower_bound(additional_sigs_eid)->second); + return std::move(additional_sigs.signatures); + } + + return {}; + } + + /** + * Reference cannot outlive header_exts. Assumes header_exts is not mutated after instantiation. + */ + inline const vector& get_new_protocol_feature_activations(const header_extension_multimap& header_exts) { + static const vector no_activations{}; + + if( header_exts.count(protocol_feature_activation::extension_id()) == 0 ) + return no_activations; + + return std::get(header_exts.lower_bound(protocol_feature_activation::extension_id())->second).protocol_features; + } + +} /// namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp new file mode 100644 index 0000000000..a212f1655f --- /dev/null +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace eosio::chain { + +using signer_callback_type = std::function(const digest_type&)>; + +constexpr std::array weak_bls_sig_postfix = { 'W', 'E', 'A', 'K' }; +using weak_digest_t = std::array; + +inline weak_digest_t create_weak_digest(const digest_type& digest) { + weak_digest_t res; + std::memcpy(res.begin(), digest.data(), digest.data_size()); + std::memcpy(res.begin() + digest.data_size(), weak_bls_sig_postfix.data(), weak_bls_sig_postfix.size()); + return res; +} + +struct block_state_legacy; +struct block_state_accessor; + +/* + * Important concepts: + * 1. A Finality Merkle Tree is a Merkle tree over a sequence of Finality Leaf Nodes, + * one for each block starting from the IF Genesis Block and ending at some + * specified descendant block. + * 2. The Validation Tree associated with a target block is the Finality Merkle + * Tree over Finality Leaf Nodes starting with the one for the IF Genesis Block + * and ending with the one for the target Block. + * 3. The Finality Tree associated with a target block is the Validation Tree of the + * block referenced by the target block's final_on_strong_qc_block_num. + * That is, validation_tree(core.final_on_strong_qc_block_num)) + * */ +struct valid_t { + struct finality_leaf_node_t { + uint32_t major_version{light_header_protocol_version_major}; + uint32_t minor_version{light_header_protocol_version_minor}; + block_num_type block_num{0}; // the block number + digest_type finality_digest; // finality digest for the block + digest_type action_mroot; // digest of the root of the action Merkle tree of the block + }; + + // The Finality Merkle Tree, containing leaf nodes from IF genesis block to current block + incremental_merkle_tree validation_tree; + + // The sequence of root digests of the validation trees associated + // with an unbroken sequence of blocks consisting of the blocks + // starting with the one that has a block number equal + // to core.last_final_block_num, and ending with the current block + std::vector validation_mroots; +}; + +// This is mostly used by SHiP to stream finality_data +struct finality_data_t { + uint32_t major_version{light_header_protocol_version_major}; + uint32_t minor_version{light_header_protocol_version_minor}; + uint32_t active_finalizer_policy_generation{0}; + digest_type action_mroot{}; + digest_type base_digest{}; +}; + +struct block_state : public block_header_state { // block_header_state provides parent link + // ------ data members ------------------------------------------------------------- + signed_block_ptr block; + digest_type strong_digest; // finalizer_digest (strong, cached so we can quickly validate votes) + weak_digest_t weak_digest; // finalizer_digest (weak, cached so we can quickly validate votes) + pending_quorum_certificate pending_qc; // where we accumulate votes we receive + std::optional valid; + + // ------ updated for votes, used for fork_db ordering ------------------------------ +private: + copyable_atomic validated{false}; // We have executed the block's trxs and verified that action merkle root (block id) matches. + + // ------ data members caching information available elsewhere ---------------------- + bool pub_keys_recovered = false; + deque cached_trxs; + digest_type action_mroot; // For finality_data sent to SHiP + std::optional base_digest; // For finality_data sent to SHiP, computed on demand in get_finality_data() + + // ------ private methods ----------------------------------------------------------- + bool is_valid() const { return validated.load(); } + bool is_pub_keys_recovered() const { return pub_keys_recovered; } + deque extract_trxs_metas(); + void set_trxs_metas(deque&& trxs_metas, bool keys_recovered); + const deque& trxs_metas() const { return cached_trxs; } + + friend struct block_state_accessor; + friend struct fc::reflector; + friend struct controller_impl; + friend struct completed_block; + friend struct building_block; +public: + // ------ functions ----------------------------------------------------------------- + const block_id_type& id() const { return block_header_state::id(); } + const block_id_type& previous() const { return block_header_state::previous(); } + uint32_t block_num() const { return block_header_state::block_num(); } + block_timestamp_type timestamp() const { return block_header_state::timestamp(); } + const extensions_type& header_extensions() const { return block_header_state::header.header_extensions; } + uint32_t irreversible_blocknum() const { return core.last_final_block_num(); } // backwards compatibility + uint32_t last_final_block_num() const { return core.last_final_block_num(); } + std::optional get_best_qc() const { return pending_qc.get_best_qc(block_num()); } // thread safe + bool valid_qc_is_strong() const { return pending_qc.valid_qc_is_strong(); } // thread safe + void set_valid_qc(const valid_quorum_certificate& qc) { pending_qc.set_valid_qc(qc); } + + protocol_feature_activation_set_ptr get_activated_protocol_features() const { return block_header_state::activated_protocol_features; } + uint32_t last_qc_block_num() const { return core.latest_qc_claim().block_num; } + uint32_t final_on_strong_qc_block_num() const { return core.final_on_strong_qc_block_num; } + + // build next valid structure from current one with input of next + valid_t new_valid(const block_header_state& bhs, const digest_type& action_mroot, const digest_type& strong_digest) const; + + // Returns the root digest of the finality tree associated with the target_block_num + // [core.last_final_block_num, block_num] + digest_type get_validation_mroot( block_num_type target_block_num ) const; + + // Returns finality_mroot_claim of the current block + digest_type get_finality_mroot_claim(const qc_claim_t& qc_claim) const; + + // Returns finality_data of the current block + finality_data_t get_finality_data(); + + // vote_status + vote_status aggregate_vote(const vote_message& vote); // aggregate vote into pending_qc + bool has_voted(const bls_public_key& key) const; + void verify_qc(const valid_quorum_certificate& qc) const; // verify given qc is valid with respect block_state + + using bhs_t = block_header_state; + using bhsp_t = block_header_state_ptr; + using fork_db_block_state_accessor_t = block_state_accessor; + + block_state() = default; + block_state(const block_state&) = delete; + block_state(block_state&&) = default; + + block_state(const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs, + const validator_t& validator, bool skip_validate_signee); + + block_state(const block_header_state& bhs, + deque&& trx_metas, + deque&& trx_receipts, + const std::optional& valid, + const std::optional& qc, + const signer_callback_type& signer, + const block_signing_authority& valid_block_signing_authority, + const digest_type& action_mroot); + + static std::shared_ptr create_if_genesis_block(const block_state_legacy& bsp); + + explicit block_state(snapshot_detail::snapshot_block_state_v7&& sbs); + + void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority); + void verify_signee(const std::vector& additional_signatures, const block_signing_authority& valid_block_signing_authority) const; +}; + +using block_state_ptr = std::shared_ptr; +using block_state_pair = std::pair, block_state_ptr>; + +} // namespace eosio::chain + +// not exporting pending_qc or valid_qc +FC_REFLECT( eosio::chain::valid_t::finality_leaf_node_t, (major_version)(minor_version)(block_num)(finality_digest)(action_mroot) ) +FC_REFLECT( eosio::chain::valid_t, (validation_tree)(validation_mroots)) +FC_REFLECT( eosio::chain::finality_data_t, (major_version)(minor_version)(active_finalizer_policy_generation)(action_mroot)(base_digest)) +FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(strong_digest)(weak_digest)(pending_qc)(valid)(validated) ) diff --git a/libraries/chain/include/eosio/chain/block_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_state_legacy.hpp index b56bb7f990..bdc0fb645d 100644 --- a/libraries/chain/include/eosio/chain/block_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_state_legacy.hpp @@ -5,45 +5,66 @@ #include #include -namespace eosio { namespace chain { +namespace eosio::chain { + + namespace snapshot_detail { + struct snapshot_block_state_legacy_v7; + } + + struct block_state_legacy_accessor; struct block_state_legacy : public block_header_state_legacy { + using bhs_t = block_header_state_legacy; + using bhsp_t = block_header_state_legacy_ptr; + block_state_legacy( const block_header_state_legacy& prev, signed_block_ptr b, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + const validator_t& validator, bool skip_validate_signee ); block_state_legacy( pending_block_header_state_legacy&& cur, signed_block_ptr&& b, // unsigned block deque&& trx_metas, + const std::optional& action_receipt_digests_savanna, const protocol_feature_set& pfs, - const std::function&, - const vector& )>& validator, + const validator_t& validator, const signer_callback_type& signer ); + explicit block_state_legacy(snapshot_detail::snapshot_block_state_legacy_v7&& sbs); + block_state_legacy() = default; - signed_block_ptr block; + signed_block_ptr block; + // internal use only, not thread safe + const block_id_type& id() const { return block_header_state_legacy::id; } + const block_id_type& previous() const { return block_header_state_legacy::previous(); } + uint32_t irreversible_blocknum() const { return dpos_irreversible_blocknum; } + uint32_t block_num() const { return block_header_state_legacy::block_num; } + block_timestamp_type timestamp() const { return header.timestamp; } + account_name producer() const { return header.producer; } + const extensions_type& header_extensions() const { return header.header_extensions; } + + const producer_authority_schedule& active_schedule_auth() const { return block_header_state_legacy_common::active_schedule; } + const producer_authority_schedule* pending_schedule_auth() const { return &block_header_state_legacy::pending_schedule.schedule; } + const deque& trxs_metas() const { return _cached_trxs; } + + + using fork_db_block_state_accessor_t = block_state_legacy_accessor; private: // internal use only, not thread safe + friend struct block_state_legacy_accessor; friend struct fc::reflector; - friend bool block_state_is_valid( const block_state_legacy& ); // work-around for multi-index access friend struct controller_impl; - friend class fork_database; - friend struct fork_database_impl; - friend class unapplied_transaction_queue; - friend struct pending_state; + friend struct completed_block; + friend struct block_state; - bool is_valid()const { return validated; } + bool is_valid() const { return validated; } bool is_pub_keys_recovered()const { return _pub_keys_recovered; } - + deque extract_trxs_metas() { _pub_keys_recovered = false; auto result = std::move( _cached_trxs ); @@ -54,7 +75,6 @@ namespace eosio { namespace chain { _pub_keys_recovered = keys_recovered; _cached_trxs = std::move( trxs_metas ); } - const deque& trxs_metas()const { return _cached_trxs; } bool validated = false; @@ -62,11 +82,14 @@ namespace eosio { namespace chain { /// this data is redundant with the data stored in block, but facilitates /// recapturing transactions when we pop a block deque _cached_trxs; + + // to be used during Legacy to Savanna transistion where action_mroot + // needs to be converted from Legacy merkle to Savanna merkle + std::optional action_mroot_savanna; }; using block_state_legacy_ptr = std::shared_ptr; - using branch_type = deque; -} } /// namespace eosio::chain +} /// namespace eosio::chain -FC_REFLECT_DERIVED( eosio::chain::block_state_legacy, (eosio::chain::block_header_state_legacy), (block)(validated) ) +FC_REFLECT_DERIVED( eosio::chain::block_state_legacy, (eosio::chain::block_header_state_legacy), (block)(validated)(action_mroot_savanna) ) diff --git a/libraries/chain/include/eosio/chain/block_timestamp.hpp b/libraries/chain/include/eosio/chain/block_timestamp.hpp index a20f609ddc..0c5305ebb7 100644 --- a/libraries/chain/include/eosio/chain/block_timestamp.hpp +++ b/libraries/chain/include/eosio/chain/block_timestamp.hpp @@ -17,7 +17,10 @@ namespace eosio { namespace chain { template class block_timestamp { public: - explicit block_timestamp( uint32_t s=0 ) :slot(s){} + + block_timestamp() : slot(0) {} + + explicit block_timestamp( uint32_t s ) :slot(s){} block_timestamp(const fc::time_point& t) { set_time_point(t); @@ -51,12 +54,11 @@ namespace eosio { namespace chain { set_time_point(t); } - bool operator > ( const block_timestamp& t )const { return slot > t.slot; } - bool operator >=( const block_timestamp& t )const { return slot >= t.slot; } - bool operator < ( const block_timestamp& t )const { return slot < t.slot; } - bool operator <=( const block_timestamp& t )const { return slot <= t.slot; } - bool operator ==( const block_timestamp& t )const { return slot == t.slot; } - bool operator !=( const block_timestamp& t )const { return slot != t.slot; } + // needed, otherwise deleted because of above version of operator=() + block_timestamp& operator=(const block_timestamp&) = default; + + auto operator<=>(const block_timestamp&) const = default; + uint32_t slot; private: @@ -76,6 +78,13 @@ namespace eosio { namespace chain { } } /// eosio::chain +namespace std { + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::block_timestamp_type& t) { + os << "tstamp(" << t.slot << ")"; + return os; + } +} + #include FC_REFLECT(eosio::chain::block_timestamp_type, (slot)) diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 6df92ea3f8..7507109885 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -2,7 +2,7 @@ #include -namespace eosio { namespace chain { +namespace eosio::chain { struct chain_snapshot_header { /** @@ -23,10 +23,12 @@ struct chain_snapshot_header { * 5: Updated for v3.0.0 eos features: * - chain_config update * 6: Updated for v3.1.0 release + * 7: Updated for V6.0 release (Savanna consensus support) + * - */ static constexpr uint32_t minimum_compatible_version = 2; - static constexpr uint32_t current_version = 6; + static constexpr uint32_t current_version = 7; uint32_t version = current_version; @@ -40,6 +42,6 @@ struct chain_snapshot_header { } }; -} } +} FC_REFLECT(eosio::chain::chain_snapshot_header,(version)) diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 48a1d4de94..9dd10a1b85 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -7,13 +7,15 @@ namespace eosio { namespace chain { namespace config { typedef __uint128_t uint128_t; -const static auto default_blocks_dir_name = "blocks"; -const static auto reversible_blocks_dir_name = "reversible"; +const static auto default_finalizers_dir_name = "finalizers"; +const static auto default_blocks_dir_name = "blocks"; +const static auto reversible_blocks_dir_name = "reversible"; -const static auto default_state_dir_name = "state"; -const static auto forkdb_filename = "fork_db.dat"; -const static auto default_state_size = 1*1024*1024*1024ll; -const static auto default_state_guard_size = 128*1024*1024ll; +const static auto default_state_dir_name = "state"; +const static auto forkdb_filename = "fork_db.dat"; +const static auto safetydb_filename = "safety_db.dat"; +const static auto default_state_size = 1*1024*1024*1024ll; +const static auto default_state_guard_size = 128*1024*1024ll; const static name system_account_name { "eosio"_n }; @@ -128,6 +130,11 @@ const static int max_producers = 125; const static size_t maximum_tracked_dpos_confirmations = 1024; ///< static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); +/** + * Maximum number of finalizers in the finalizer set + */ +const static size_t max_finalizers = 64*1024; +const static size_t max_finalizer_description_size = 256; /** * The number of blocks produced per round is based upon all producers having a chance diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 50417aa31f..d2ac81dc32 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -1,25 +1,36 @@ #pragma once #include +#include +#include #include #include #include -#include -#include - #include #include #include +#include + +#include + +#include + namespace chainbase { class database; } -namespace boost { namespace asio { +namespace boost::asio { class thread_pool; -}} +} + +namespace eosio::vm { class wasm_allocator; } -namespace eosio { namespace vm { class wasm_allocator; }} +namespace eosio::chain { -namespace eosio { namespace chain { + struct hs_message; + struct finalizer_state; + enum class hs_message_warning; + using bls_pub_priv_key_map_t = std::map; + struct finalizer_policy; class authorization_manager; @@ -40,14 +51,14 @@ namespace eosio { namespace chain { class subjective_billing; using resource_limits::resource_limits_manager; using apply_handler = std::function; - using forked_branch_callback = std::function; + + using forked_callback_t = std::function; + // lookup transaction_metadata via supplied function to avoid re-creation using trx_meta_cache_lookup = std::function; using block_signal_params = std::tuple; - class fork_database; - enum class db_read_mode { HEAD, IRREVERSIBLE, @@ -69,6 +80,7 @@ namespace eosio { namespace chain { flat_set contract_blacklist; flat_set< pair > action_blacklist; flat_set key_blacklist; + path finalizers_dir = chain::config::default_finalizers_dir_name; path blocks_dir = chain::config::default_blocks_dir_name; block_log_config blog; path state_dir = chain::config::default_state_dir_name; @@ -163,32 +175,35 @@ namespace eosio { namespace chain { fc::microseconds total_time{}; }; - block_state_legacy_ptr finalize_block( block_report& br, const signer_callback_type& signer_callback ); + void assemble_and_complete_block( block_report& br, const signer_callback_type& signer_callback ); void sign_block( const signer_callback_type& signer_callback ); - void commit_block(); + void commit_block(block_report& br); + void maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup); // thread-safe - std::future create_block_state_future( const block_id_type& id, const signed_block_ptr& b ); + std::future create_block_handle_future( const block_id_type& id, const signed_block_ptr& b ); // thread-safe - block_state_legacy_ptr create_block_state( const block_id_type& id, const signed_block_ptr& b ) const; + // returns empty optional if block b is not immediately ready to be processed + std::optional create_block_handle( const block_id_type& id, const signed_block_ptr& b ) const; /** * @param br returns statistics for block - * @param bsp block to push + * @param b block to push, created by create_block_handle * @param cb calls cb with forked applied transactions for each forked block * @param trx_lookup user provided lookup function for externally cached transaction_metadata */ void push_block( block_report& br, - const block_state_legacy_ptr& bsp, - const forked_branch_callback& cb, + const block_handle& b, + const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup ); + /// Accept block into fork_database + void accept_block(const block_handle& b); + boost::asio::io_context& get_thread_pool(); const chainbase::database& db()const; - const fork_database& fork_db()const; - const account_object& get_account( account_name n )const; const global_property_object& get_global_properties()const; const dynamic_global_property_object& get_dynamic_global_properties()const; @@ -218,10 +233,16 @@ namespace eosio { namespace chain { uint32_t head_block_num()const; time_point head_block_time()const; + block_timestamp_type head_block_timestamp()const; block_id_type head_block_id()const; account_name head_block_producer()const; const block_header& head_block_header()const; - block_state_legacy_ptr head_block_state()const; + const signed_block_ptr& head_block()const; + // returns nullptr after instant finality enabled + block_state_legacy_ptr head_block_state_legacy()const; + // returns finality_data associated with chain head for SHiP when in Savanna, + // std::nullopt in Legacy + std::optional head_finality_data() const; uint32_t fork_db_head_block_num()const; block_id_type fork_db_head_block_id()const; @@ -234,8 +255,17 @@ namespace eosio { namespace chain { uint32_t pending_block_num()const; const producer_authority_schedule& active_producers()const; - const producer_authority_schedule& pending_producers()const; - std::optional proposed_producers()const; + const producer_authority_schedule& head_active_producers()const; + // pending for pre-instant-finality, next proposed that will take affect, null if none are pending/proposed + const producer_authority_schedule* next_producers()const; + // post-instant-finality this always returns empty std::optional + std::optional proposed_producers_legacy()const; + // pre-instant-finality this always returns a valid producer_authority_schedule + // post-instant-finality this always returns nullptr + const producer_authority_schedule* pending_producers_legacy()const; + + void set_if_irreversible_block_id(const block_id_type& id); + uint32_t if_irreversible_block_num() const; uint32_t last_irreversible_block_num() const; block_id_type last_irreversible_block_id() const; @@ -246,15 +276,16 @@ namespace eosio { namespace chain { // thread-safe signed_block_ptr fetch_block_by_id( const block_id_type& id )const; // thread-safe + bool block_exists(const block_id_type& id) const; + bool validated_block_exists(const block_id_type& id) const; + // thread-safe std::optional fetch_block_header_by_number( uint32_t block_num )const; // thread-safe std::optional fetch_block_header_by_id( const block_id_type& id )const; - // return block_state_legacy from forkdb, thread-safe - block_state_legacy_ptr fetch_block_state_by_number( uint32_t block_num )const; - // return block_state_legacy from forkdb, thread-safe - block_state_legacy_ptr fetch_block_state_by_id( 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 ); @@ -292,6 +323,13 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); + // called by host function set_finalizers + void set_proposed_finalizers( finalizer_policy&& fin_pol ); + // called from net threads + vote_status process_vote_message( const vote_message& msg ); + // thread safe, for testing + bool node_has_voted_if_finalizer(const block_id_type& id) const; + bool light_validation_allowed() const; bool skip_auth_check()const; bool skip_trx_checks()const; @@ -330,11 +368,14 @@ namespace eosio { namespace chain { static std::optional convert_exception_to_error_code( const fc::exception& e ); - signal block_start; - signal accepted_block_header; - signal accepted_block; - signal irreversible_block; - signal)> applied_transaction; + signal& block_start(); + signal& accepted_block_header(); + signal& accepted_block(); + signal& irreversible_block(); + signal)>& applied_transaction(); + + // Unlike other signals, voted_block can be signaled from other threads than the main thread. + signal& voted_block(); const apply_handler* find_apply_handler( account_name contract, scope_name scope, action_name act )const; wasm_interface& get_wasm_interface(); @@ -356,6 +397,7 @@ namespace eosio { namespace chain { void set_to_read_window(); bool is_write_window() const; void code_block_num_last_used(const digest_type& code_hash, uint8_t vm_type, uint8_t vm_version, uint32_t block_num); + void set_node_finalizer_keys(const bls_pub_priv_key_map_t& finalizer_keys); private: friend class apply_context; @@ -364,7 +406,6 @@ namespace eosio { namespace chain { chainbase::database& mutable_db()const; std::unique_ptr my; - }; -} } /// eosio::chain +} /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 50c322ff00..2a663a68c9 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -255,7 +255,8 @@ namespace eosio { namespace chain { 3030012, "Invalid block extension" ) FC_DECLARE_DERIVED_EXCEPTION( ill_formed_additional_block_signatures_extension, block_validate_exception, 3030013, "Block includes an ill-formed additional block signature extension" ) - + FC_DECLARE_DERIVED_EXCEPTION( invalid_qc_claim, block_validate_exception, + 3030014, "Block includes an invalid QC claim" ) FC_DECLARE_DERIVED_EXCEPTION( transaction_exception, chain_exception, 3040000, "Transaction exception" ) @@ -667,4 +668,9 @@ namespace eosio { namespace chain { 3250002, "Protocol feature exception (invalid block)" ) FC_DECLARE_DERIVED_EXCEPTION( protocol_feature_iterator_exception, protocol_feature_exception, 3250003, "Protocol feature iterator exception" ) + + FC_DECLARE_DERIVED_EXCEPTION( finalizer_exception, chain_exception, + 3260000, "Finalizer exception" ) + FC_DECLARE_DERIVED_EXCEPTION( finalizer_safety_exception, finalizer_exception, + 3260001, "Finalizer safety file exception" ) } } // eosio::chain diff --git a/libraries/chain/include/eosio/chain/finality_core.hpp b/libraries/chain/include/eosio/chain/finality_core.hpp new file mode 100644 index 0000000000..20931afcdf --- /dev/null +++ b/libraries/chain/include/eosio/chain/finality_core.hpp @@ -0,0 +1,193 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + +using block_num_type = uint32_t; +using block_time_type = chain::block_timestamp_type; + +struct block_ref +{ + block_id_type block_id; + block_time_type timestamp; + + bool empty() const { return block_id.empty(); } + block_num_type block_num() const; // Extract from block_id. + + auto operator<=>(const block_ref&) const = default; + bool operator==(const block_ref& o) const = default; +}; + +struct qc_link +{ + block_num_type source_block_num {0}; + block_num_type target_block_num {0}; // Must be less than or equal to source_block_num (only equal for genesis block). + bool is_link_strong {false}; +}; + +struct qc_claim_t +{ + block_num_type block_num {0}; + bool is_strong_qc {false}; + + auto operator<=>(const qc_claim_t&) const = default; +}; + +struct core_metadata +{ + block_num_type last_final_block_num {0}; + block_num_type final_on_strong_qc_block_num {0}; + block_num_type latest_qc_claim_block_num {0}; +}; + +struct finality_core +{ + std::vector links; // Captures all relevant links sorted in order of ascending source_block_num. + std::vector refs; // Covers ancestor blocks with block numbers greater than or equal to last_final_block_num. + // Sorted in order of ascending block_num. + block_num_type final_on_strong_qc_block_num {0}; + + // Invariants: + // 1. links.empty() == false + // 2. last_final_block_num() <= links.front().source_block_num <= final_on_strong_qc_block_num <= latest_qc_claim().block_num + // 3. If refs.empty() == true, then (links.size() == 1) and + // (links.back().target_block_num == links.back().source_block_num == final_on_strong_qc_block_num == last_final_block_num()) + // 4. If refs.empty() == false, then refs.front().block_num() == links.front().target_block_num == last_final_block_num() + // 5. If refs.empty() == false, then refs.back().block_num() + 1 == links.back().source_block_num == current_block_num() + // 6. If refs.size() > 1, then: + // For i = 0 to refs.size() - 2: + // (refs[i].block_num() + 1 == refs[i+1].block_num()) and (refs[i].timestamp < refs[i+1].timestamp) + // 7. If links.size() > 1, then: + // For i = 0 to links.size() - 2: + // (links[i].source_block_num + 1 == links[i+1].source_block_num) and (links[i].target_block_num <= links[i+1].target_block_num) + // 8. current_block_num() - last_final_block_num() == refs.size() (always implied by invariants 3 to 6) + // 9. current_block_num() - links.front().source_block_num == links.size() - 1 (always implied by invariants 1 and 7) + + /** + * @pre none + * + * @post returned core has current_block_num() == block_num + * @post returned core has latest_qc_claim() == {.block_num=block_num, .is_strong_qc=false} + * @post returned core has final_on_strong_qc_block_num == block_num + * @post returned core has last_final_block_num() == block_num + */ + static finality_core create_core_for_genesis_block(block_num_type block_num); + + /** + * @pre this->links.empty() == false + * @post none + * @returns block number of the core + */ + block_num_type current_block_num() const; + + /** + * @pre this->links.empty() == false + * @post none + * @returns last final block_num in respect to the core + */ + block_num_type last_final_block_num() const; + + /** + * @pre this->links.empty() == false + * @post none + * @returns latest qc_claim made by the core + */ + qc_claim_t latest_qc_claim() const; + + /** + * @pre all finality_core invariants + * @post same + * @returns timestamp of latest qc_claim made by the core + */ + block_time_type latest_qc_block_timestamp() const; + + /** + * @pre all finality_core invariants + * @post same + * @returns boolean indicating whether `id` is an ancestor of this block + */ + bool extends(const block_id_type& id) const; + + /** + * @pre last_final_block_num() <= candidate_block_num <= current_block_block_num() + * @post same + * @returns boolean indicating whether `candidate_block_num` is the genesis block number or not + */ + bool is_genesis_block_num(block_num_type candidate_block_num) const; + + /** + * @pre last_final_block_num() <= block_num < current_block_num() + * + * @post returned block_ref has block_num() == block_num + */ + const block_ref& get_block_reference(block_num_type block_num) const; + + /** + * @pre links.front().source_block_num <= block_num <= current_block_num() + * + * @post returned qc_link has source_block_num == block_num + */ + const qc_link& get_qc_link_from(block_num_type block_num) const; + + /** + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc + * + * @post returned core_metadata has last_final_block_num <= final_on_strong_qc_block_num <= latest_qc_claim_block_num + * @post returned core_metadata has latest_qc_claim_block_num == most_recent_ancestor_with_qc.block_num + * @post returned core_metadata has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core_metadata has last_final_block_num >= this->last_final_block_num() + */ + core_metadata next_metadata(const qc_claim_t& most_recent_ancestor_with_qc) const; + + /** + * @pre current_block.block_num() == this->current_block_num() + * @pre If this->refs.empty() == false, then current_block is the block after the one referenced by this->refs.back() + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc (i.e. + * this->latest_qc_claim().block_num == most_recent_ancestor_with_qc.block_num && + * most_recent_ancestor_with_qc.is_strong_qc ). + * When block_num is the same, most_recent_ancestor_with_qc must be stronger than latest_qc_claim() + * + * @post returned core has current_block_num() == this->current_block_num() + 1 + * @post returned core has latest_qc_claim() == most_recent_ancestor_with_qc + * @post returned core has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core has last_final_block_num() >= this->last_final_block_num() + */ + finality_core next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const; +}; + +} /// eosio::chain + +// ----------------------------------------------------------------------------- +namespace std { + // define std ostream output so we can use BOOST_CHECK_EQUAL in tests + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::block_ref& br) { + os << "block_ref(" << br.block_id << ", " << br.timestamp << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::qc_link& l) { + os << "qc_link(" << l.source_block_num << ", " << l.target_block_num << ", " << l.is_link_strong << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::qc_claim_t& c) { + os << "qc_claim_t(" << c.block_num << ", " << c.is_strong_qc << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::core_metadata& cm) { + os << "core_metadata(" << cm.last_final_block_num << ", " << cm.final_on_strong_qc_block_num << + ", " << cm.latest_qc_claim_block_num << ")"; + return os; + } +} + +FC_REFLECT( eosio::chain::block_ref, (block_id)(timestamp) ) +FC_REFLECT( eosio::chain::qc_link, (source_block_num)(target_block_num)(is_link_strong) ) +FC_REFLECT( eosio::chain::qc_claim_t, (block_num)(is_strong_qc) ) +FC_REFLECT( eosio::chain::core_metadata, (last_final_block_num)(final_on_strong_qc_block_num)(latest_qc_claim_block_num)) +FC_REFLECT( eosio::chain::finality_core, (links)(refs)(final_on_strong_qc_block_num)) diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 2367631096..39d37cef00 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -1,15 +1,25 @@ #pragma once #include -#include +#include -namespace eosio { namespace chain { +namespace fc { class cfile_datastream; } // forward decl - using boost::signals2::signal; +namespace eosio::chain { + template struct fork_database_impl; + using block_branch_t = std::vector; + enum class mark_valid_t { no, yes }; + enum class ignore_duplicate_t { no, yes }; + enum class include_root_t { no, yes }; + + // Used for logging of comparison values used for best fork determination + std::string log_fork_comparison(const block_state& bs); + std::string log_fork_comparison(const block_state_legacy& bs); + /** - * @class fork_database + * @class fork_database_t * @brief manages light-weight state for all potential unconfirmed forks * * As new blocks are received, they are pushed into the fork database. The fork @@ -18,83 +28,242 @@ namespace eosio { namespace chain { * irreversible signal. * * An internal mutex is used to provide thread-safety. + * + * fork_database should be used instead of fork_database_t directly as it manages + * the different supported types. */ - class fork_database { - public: + template // either block_state_legacy_ptr or block_state_ptr + class fork_database_t { + public: + using bsp_t = BSP; + using bs_t = bsp_t::element_type; + using bhsp_t = bs_t::bhsp_t; + using bhs_t = bhsp_t::element_type; + using branch_t = std::vector; + using full_branch_t = std::vector; + using branch_pair_t = pair; - explicit fork_database( const std::filesystem::path& data_dir ); - ~fork_database(); + explicit fork_database_t(); + ~fork_database_t(); - void open( const std::function&, - const vector& )>& validator ); - void close(); + void open( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ); + void close( std::ofstream& out ); - block_header_state_legacy_ptr get_block_header( const block_id_type& id )const; - block_state_legacy_ptr get_block( const block_id_type& id )const; + bsp_t get_block( const block_id_type& id, include_root_t include_root = include_root_t::no ) const; + bool block_exists( const block_id_type& id ) const; + bool validated_block_exists( const block_id_type& id ) const; - /** - * Purges any existing blocks from the fork database and resets the root block_header_state to the provided value. - * The head will also be reset to point to the root. - */ - void reset( const block_header_state_legacy& root_bhs ); + /** + * Purges any existing blocks from the fork database and resets the root block_header_state to the provided value. + * The head will also be reset to point to the root. + */ + void reset_root( const bsp_t& root_bhs ); - /** - * Removes validated flag from all blocks in fork database and resets head to point to the root. - */ - void rollback_head_to_root(); + /** + * Removes validated flag from all blocks in fork database and resets head to point to the root. + */ + void rollback_head_to_root(); - /** - * Advance root block forward to some other block in the tree. - */ - void advance_root( const block_id_type& id ); + /** + * Advance root block forward to some other block in the tree. + */ + void advance_root( const block_id_type& id ); - /** - * Add block state to fork database. - * Must link to existing block in fork database or the root. - */ - void add( const block_state_legacy_ptr& next_block, bool ignore_duplicate = false ); + /** + * Add block state to fork database. + * Must link to existing block in fork database or the root. + * @param mark_valid if true also mark next_block valid + */ + void add( const bsp_t& next_block, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ); - void remove( const block_id_type& id ); + void remove( const block_id_type& id ); - block_state_legacy_ptr root()const; - block_state_legacy_ptr head()const; - block_state_legacy_ptr pending_head()const; + bool is_valid() const; // sanity checks on this fork_db - /** - * Returns the sequence of block states resulting from trimming the branch from the - * root block (exclusive) to the block with an id of `h` (inclusive) by removing any - * block states corresponding to block numbers greater than `trim_after_block_num`. - * - * The order of the sequence is in descending block number order. - * A block with an id of `h` must exist in the fork database otherwise this method will throw an exception. - */ - branch_type fetch_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() )const; + bool has_root() const; + bsp_t root() const; // undefined if !has_root() + bsp_t head() const; + bsp_t pending_head() const; + /** + * Returns the sequence of block states resulting from trimming the branch from the + * root block (exclusive) to the block with an id of `h` (inclusive) by removing any + * block states corresponding to block numbers greater than `trim_after_block_num`. + * + * The order of the sequence is in descending block number order. + * A block with an id of `h` must exist in the fork database otherwise this method will throw an exception. + */ + branch_t fetch_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() ) const; + block_branch_t fetch_block_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() ) const; - /** - * Returns the block state with a block number of `block_num` that is on the branch that - * contains a block with an id of`h`, or the empty shared pointer if no such block can be found. - */ - block_state_legacy_ptr search_on_branch( const block_id_type& h, uint32_t block_num )const; + /** + * Returns full branch of block_header_state pointers including the root. + * The order of the sequence is in descending block number order. + * A block with an id of `h` must exist in the fork database otherwise this method will throw an exception. + */ + full_branch_t fetch_full_branch( const block_id_type& h ) const; - /** - * Given two head blocks, return two branches of the fork graph that - * end with a common ancestor (same prior block) - */ - pair< branch_type, branch_type > fetch_branch_from( const block_id_type& first, - const block_id_type& second )const; + /** + * Returns the block state with a block number of `block_num` that is on the branch that + * contains a block with an id of`h`, or the empty shared pointer if no such block can be found. + */ + bsp_t search_on_branch( const block_id_type& h, uint32_t block_num, include_root_t include_root = include_root_t::no ) const; + /** + * search_on_branch( head()->id(), block_num) + */ + bsp_t search_on_head_branch( uint32_t block_num, include_root_t include_root = include_root_t::no ) const; - void mark_valid( const block_state_legacy_ptr& h ); + /** + * Given two head blocks, return two branches of the fork graph that + * end with a common ancestor (same prior block) + */ + branch_pair_t fetch_branch_from(const block_id_type& first, const block_id_type& second) const; - static const uint32_t magic_number; + void mark_valid( const bsp_t& h ); - static const uint32_t min_supported_version; - static const uint32_t max_supported_version; - - private: - unique_ptr my; + private: + unique_ptr> my; }; -} } /// eosio::chain + using fork_database_legacy_t = fork_database_t; + using fork_database_if_t = fork_database_t; + + /** + * Provides mechanism for opening the correct type + * as well as switching from legacy (old dpos) to instant-finality. + * + * All methods assert until open() is closed. + */ + class fork_database { + public: + enum class in_use_t : uint32_t { legacy, savanna, both }; + + private: + static constexpr uint32_t magic_number = 0x30510FDB; + + const std::filesystem::path data_dir; + std::atomic in_use = in_use_t::legacy; + fork_database_legacy_t fork_db_l; // legacy + fork_database_if_t fork_db_s; // savanna + + public: + explicit fork_database(const std::filesystem::path& data_dir); + ~fork_database(); // close on destruction + + // not thread safe, expected to be called from main thread before allowing concurrent access + void open( validator_t& validator ); + void close(); + + // switches to using both legacy and savanna during transition + void switch_from_legacy(const block_state_ptr& root); + void switch_to(in_use_t v) { in_use = v; } + + in_use_t version_in_use() const { return in_use.load(); } + + // see fork_database_t::fetch_branch(forkdb->head()->id()) + block_branch_t fetch_branch_from_head() const; + + template + R apply(const F& f) const { + if constexpr (std::is_same_v) { + if (in_use.load() == in_use_t::legacy) { + f(fork_db_l); + } else { + f(fork_db_s); + } + } else { + if (in_use.load() == in_use_t::legacy) { + return f(fork_db_l); + } else { + return f(fork_db_s); + } + } + } + + template + R apply(const F& f) { + if constexpr (std::is_same_v) { + if (in_use.load() == in_use_t::legacy) { + f(fork_db_l); + } else { + f(fork_db_s); + } + } else { + if (in_use.load() == in_use_t::legacy) { + return f(fork_db_l); + } else { + return f(fork_db_s); + } + } + } + + /// Apply for when only need lambda executed on savanna fork db + template + R apply_s(const F& f) { + if constexpr (std::is_same_v) { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::savanna || in_use_value == in_use_t::both) { + f(fork_db_s); + } + } else { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::savanna || in_use_value == in_use_t::both) { + return f(fork_db_s); + } + return {}; + } + } + + /// Apply for when only need lambda executed on savanna fork db + template + R apply_s(const F& f) const { + if constexpr (std::is_same_v) { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::savanna || in_use_value == in_use_t::both) { + f(fork_db_s); + } + } else { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::savanna || in_use_value == in_use_t::both) { + return f(fork_db_s); + } + return {}; + } + } + + /// Apply for when only need lambda executed on legacy fork db + template + R apply_l(const F& f) const { + if constexpr (std::is_same_v) { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::legacy || in_use_value == in_use_t::both) { + f(fork_db_l); + } + } else { + if (auto in_use_value = in_use.load(); in_use_value == in_use_t::legacy || in_use_value == in_use_t::both) { + return f(fork_db_l); + } + return {}; + } + } + + /// @param legacy_f the lambda to execute if in legacy mode + /// @param savanna_f the lambda to execute if in savanna instant-finality mode + template + R apply(const LegacyF& legacy_f, const SavannaF& savanna_f) { + if constexpr (std::is_same_v) { + if (in_use.load() == in_use_t::legacy) { + legacy_f(fork_db_l); + } else { + savanna_f(fork_db_s); + } + } else { + if (in_use.load() == in_use_t::legacy) { + return legacy_f(fork_db_l); + } else { + return savanna_f(fork_db_s); + } + } + } + + // Update max_supported_version if the persistent file format changes. + static constexpr uint32_t min_supported_version = 1; + static constexpr uint32_t max_supported_version = 2; + }; +} /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 24180fad6a..8709cf9667 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -9,12 +9,11 @@ #include #include #include -#include #include #include #include "multi_index_includes.hpp" -namespace eosio { namespace chain { +namespace eosio::chain { /** * a fc::raw::unpack compatible version of the old global_property_object structure stored in @@ -24,7 +23,8 @@ namespace eosio { namespace chain { struct snapshot_global_property_object_v2 { static constexpr uint32_t minimum_version = 0; static constexpr uint32_t maximum_version = 2; - static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_global_property_object_v2 is no longer needed"); + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, + "snapshot_global_property_object_v2 is no longer needed"); std::optional proposed_schedule_block_num; producer_schedule_type proposed_schedule; @@ -33,7 +33,8 @@ namespace eosio { namespace chain { struct snapshot_global_property_object_v3 { static constexpr uint32_t minimum_version = 3; static constexpr uint32_t maximum_version = 3; - static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_global_property_object_v3 is no longer needed"); + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, + "snapshot_global_property_object_v3 is no longer needed"); std::optional proposed_schedule_block_num; producer_authority_schedule proposed_schedule; @@ -43,7 +44,8 @@ namespace eosio { namespace chain { struct snapshot_global_property_object_v4 { static constexpr uint32_t minimum_version = 4; static constexpr uint32_t maximum_version = 4; - static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_global_property_object_v4 is no longer needed"); + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, + "snapshot_global_property_object_v4 is no longer needed"); std::optional proposed_schedule_block_num; producer_authority_schedule proposed_schedule; @@ -73,7 +75,8 @@ namespace eosio { namespace chain { kv_database_config kv_configuration; wasm_config wasm_configuration; - void initalize_from( const legacy::snapshot_global_property_object_v2& legacy, const chain_id_type& chain_id_val, const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { + void initalize_from( const legacy::snapshot_global_property_object_v2& legacy, const chain_id_type& chain_id_val, + const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { proposed_schedule_block_num = legacy.proposed_schedule_block_num; proposed_schedule = producer_authority_schedule(legacy.proposed_schedule); configuration = legacy.configuration; @@ -82,7 +85,8 @@ namespace eosio { namespace chain { wasm_configuration = wasm_config_val; } - void initalize_from( const legacy::snapshot_global_property_object_v3& legacy, const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { + void initalize_from( const legacy::snapshot_global_property_object_v3& legacy, + const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { proposed_schedule_block_num = legacy.proposed_schedule_block_num; proposed_schedule = legacy.proposed_schedule; configuration = legacy.configuration; @@ -127,7 +131,8 @@ namespace eosio { namespace chain { using snapshot_type = snapshot_global_property_object; static snapshot_global_property_object to_snapshot_row( const global_property_object& value, const chainbase::database& ) { - return {value.proposed_schedule_block_num, producer_authority_schedule::from_shared(value.proposed_schedule), value.configuration, value.chain_id, value.kv_configuration, value.wasm_configuration}; + return {value.proposed_schedule_block_num, producer_authority_schedule::from_shared(value.proposed_schedule), + value.configuration, value.chain_id, value.kv_configuration, value.wasm_configuration}; } static void from_snapshot_row( snapshot_global_property_object&& row, global_property_object& value, chainbase::database& ) { @@ -164,7 +169,7 @@ namespace eosio { namespace chain { > >; -}} +} CHAINBASE_SET_INDEX_TYPE(eosio::chain::global_property_object, eosio::chain::global_property_multi_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::dynamic_global_property_object, diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp new file mode 100644 index 0000000000..762524a46b --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +// ------------------------------------------------------------------------------------------- +// this file defines the classes: +// +// finalizer: +// --------- +// - holds the bls12 private key which allows the finalizer to sign proposals (the +// proposal is assumed to have been previously validated for correctness). These +// signatures will be aggregated by block proposers into quorum certificates, which +// are an essential part of the Savanna consensus algorithm. +// - every time a finalizer votes, it may update its own safety info in memory +// - finalizer safety info is appropriately initialized (iff not already present +// in the persistent file) at Leap startup. +// +// my_finalizers_t: +// --------------- +// - stores the set of finalizers currently active on this node. +// - manages a `finalizer safety` file (`safety.dat`) which tracks the active finalizers +// safety info (file is updated after each vote), and also the safety information for +// every finalizer which has been active on this node (using the same `finalizer-dir`) +// ------------------------------------------------------------------------------------------- + +namespace eosio::chain { + + // ---------------------------------------------------------------------------------------- + struct finalizer_safety_information { + block_timestamp_type last_vote_range_start; + block_ref last_vote; + block_ref lock; + + static constexpr uint64_t magic = 0x5AFE11115AFE1111ull; + + static finalizer_safety_information unset_fsi() { return {}; } + + auto operator==(const finalizer_safety_information& o) const { + return last_vote_range_start == o.last_vote_range_start && + last_vote == o.last_vote && + lock == o.lock; + } + }; + + // ---------------------------------------------------------------------------------------- + // Access is protected by my_finalizers_t mutex + struct finalizer { + enum class vote_decision { no_vote, strong_vote, weak_vote }; + struct vote_result { + vote_decision decision {vote_decision::no_vote}; + bool safety_check {false}; + bool liveness_check {false}; + bool monotony_check {false}; + }; + + bls_private_key priv_key; + finalizer_safety_information fsi; + + vote_result decide_vote(const block_state_ptr& bsp); + std::optional maybe_vote(const bls_public_key& pub_key, const block_state_ptr& bsp, + const digest_type& digest); + }; + + // ---------------------------------------------------------------------------------------- + struct my_finalizers_t { + using fsi_t = finalizer_safety_information; + using fsi_map = std::map; + + private: + const block_timestamp_type t_startup; // nodeos startup time, used for default safety_information + const std::filesystem::path persist_file_path; // where we save the safety data + mutable std::mutex mtx; + mutable fc::datastream persist_file; // we want to keep the file open for speed + std::map finalizers; // the active finalizers for this node, loaded at startup, not mutated afterwards + fsi_map inactive_safety_info; // loaded at startup, not mutated afterwards + fsi_t default_fsi = fsi_t::unset_fsi(); // default provided at leap startup + mutable bool inactive_safety_info_written{false}; + + public: + my_finalizers_t(block_timestamp_type startup_time, const std::filesystem::path& persist_file_path) + : t_startup(startup_time) + , persist_file_path(persist_file_path) + {} + + template // thread safe + void maybe_vote(const finalizer_policy& fin_pol, + const block_state_ptr& bsp, + const digest_type& digest, + F&& process_vote) { + + if (finalizers.empty()) + return; + + std::vector votes; + votes.reserve(finalizers.size()); + + // Possible improvement in the future, look at locking only individual finalizers and releasing the lock for writing the file. + // Would require making sure that only the latest is ever written to the file and that the file access was protected separately. + std::unique_lock g(mtx); + + // first accumulate all the votes + for (const auto& f : fin_pol.finalizers) { + if (auto it = finalizers.find(f.public_key); it != finalizers.end()) { + std::optional vote_msg = it->second.maybe_vote(it->first, bsp, digest); + if (vote_msg) + votes.push_back(std::move(*vote_msg)); + } + } + // then save the safety info and, if successful, gossip the votes + if (!votes.empty()) { + save_finalizer_safety_info(); + g.unlock(); + for (const auto& vote : votes) + std::forward(process_vote)(vote); + } + } + + size_t size() const { return finalizers.size(); } // doesn't change, thread safe + bool empty() const { return finalizers.empty(); } // doesn't change, thread safe + + template + bool all_of_public_keys(F&& f) const { // only access keys which do not change, thread safe + return std::ranges::all_of(std::views::keys(finalizers), std::forward(f)); + } + + void set_keys(const std::map& finalizer_keys); // only call on startup + void set_default_safety_information(const fsi_t& fsi); + + // following two member functions could be private, but are used in testing, not thread safe + void save_finalizer_safety_info() const; + fsi_map load_finalizer_safety_info(); + + // for testing purposes only, not thread safe + const fsi_t& get_fsi(const bls_public_key& k) { return finalizers[k].fsi; } + void set_fsi(const bls_public_key& k, const fsi_t& fsi) { finalizers[k].fsi = fsi; } + }; + +} + +namespace std { + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer_safety_information& fsi) { + os << "fsi(" << fsi.last_vote_range_start.slot << ", " << fsi.last_vote << ", " << fsi.lock << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer::vote_result& vr) { + os << "vote_result(\""; + using vote_decision = eosio::chain::finalizer::vote_decision; + switch(vr.decision) { + case vote_decision::strong_vote: os << "strong_vote"; break; + case vote_decision::weak_vote: os << "weak_vote"; break; + case vote_decision::no_vote: os << "no_vote"; break; + } + os << "\", monotony_check(" << vr.monotony_check << "), liveness_check(" << vr.liveness_check << + "), safety_check(" << vr.safety_check<< "))"; + return os; + } +} + +FC_REFLECT(eosio::chain::finalizer_safety_information, (last_vote_range_start)(last_vote)(lock)) +FC_REFLECT_ENUM(eosio::chain::finalizer::vote_decision, (strong_vote)(weak_vote)(no_vote)) diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer_authority.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer_authority.hpp new file mode 100644 index 0000000000..e4fecdef03 --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer_authority.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + + struct finalizer_authority { + + std::string description; + uint64_t weight = 0; // weight that this finalizer's vote has for meeting fthreshold + fc::crypto::blslib::bls_public_key public_key; + + auto operator<=>(const finalizer_authority&) const = default; + }; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::finalizer_authority, (description)(weight)(public_key) ) diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer_policy.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer_policy.hpp new file mode 100644 index 0000000000..60a26a755d --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer_policy.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + + struct finalizer_policy { + uint32_t generation = 0; ///< sequentially incrementing version number + uint64_t threshold = 0; ///< vote weight threshold to finalize blocks + std::vector finalizers; ///< Instant Finality voter set + + // max accumulated weak weight before becoming weak_final + uint64_t max_weak_sum_before_weak_final() const { + uint64_t sum = std::accumulate( finalizers.begin(), finalizers.end(), 0, + [](uint64_t acc, const finalizer_authority& f) { + return acc + f.weight; + } + ); + + return (sum - threshold); + } + }; + + using finalizer_policy_ptr = std::shared_ptr; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::finalizer_policy, (generation)(threshold)(finalizers) ) diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp new file mode 100644 index 0000000000..b54f8d7416 --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace eosio::chain { + + using bls_public_key = fc::crypto::blslib::bls_public_key; + using bls_signature = fc::crypto::blslib::bls_signature; + using bls_aggregate_signature = fc::crypto::blslib::bls_aggregate_signature; + using bls_private_key = fc::crypto::blslib::bls_private_key; + + using hs_bitset = boost::dynamic_bitset; + using bls_key_map_t = std::map; + + struct vote_message { + block_id_type block_id; + bool strong{false}; + bls_public_key finalizer_key; + bls_signature sig; + }; + + enum class vote_status { + success, + duplicate, + unknown_public_key, + invalid_signature, + unknown_block + }; + + using bls_public_key = fc::crypto::blslib::bls_public_key; + using bls_signature = fc::crypto::blslib::bls_signature; + using bls_private_key = fc::crypto::blslib::bls_private_key; + + // valid_quorum_certificate + struct valid_quorum_certificate { + bool is_weak() const { return !!_weak_votes; } + bool is_strong() const { return !_weak_votes; } + + std::optional _strong_votes; + std::optional _weak_votes; + bls_aggregate_signature _sig; + }; + + // quorum_certificate + struct quorum_certificate { + uint32_t block_num; + valid_quorum_certificate qc; + + qc_claim_t to_qc_claim() const { + return {.block_num = block_num, .is_strong_qc = qc.is_strong()}; + } + }; + + struct qc_data_t { + std::optional qc; // Comes either from traversing branch from parent and calling get_best_qc() + // or from an incoming block extension. + qc_claim_t qc_claim; // describes the above qc. In rare cases (bootstrap, starting from snapshot, + // disaster recovery), we may not have a qc so we use the `lib` block_num + // and specify `weak`. + }; + + // pending_quorum_certificate + class pending_quorum_certificate { + public: + enum class state_t { + unrestricted, // No quorum reached yet, still possible to achieve any state. + restricted, // Enough `weak` votes received to know it is impossible to reach the `strong` state. + weak_achieved, // Enough `weak` + `strong` votes for a valid `weak` QC, still possible to reach the `strong` state. + weak_final, // Enough `weak` + `strong` votes for a valid `weak` QC, `strong` not possible anymore. + strong // Enough `strong` votes to have a valid `strong` QC + }; + + struct votes_t : fc::reflect_init { + private: + friend struct fc::reflector; + friend struct fc::reflector_init_visitor; + friend struct fc::has_reflector_init; + friend class pending_quorum_certificate; + + hs_bitset _bitset; + bls_aggregate_signature _sig; + std::vector> _processed; // avoid locking mutex for _bitset duplicate check + + void reflector_init(); + public: + explicit votes_t(size_t num_finalizers) + : _bitset(num_finalizers) + , _processed(num_finalizers) {} + + // thread safe + bool has_voted(size_t index) const; + + vote_status add_vote(size_t index, const bls_signature& sig); + }; + + pending_quorum_certificate(); + + explicit pending_quorum_certificate(size_t num_finalizers, uint64_t quorum, uint64_t max_weak_sum_before_weak_final); + + // thread safe + bool is_quorum_met() const; + static bool is_quorum_met(state_t s) { + return s == state_t::strong || s == state_t::weak_achieved || s == state_t::weak_final; + } + + // thread safe + vote_status add_vote(block_num_type block_num, + bool strong, + std::span proposal_digest, + size_t index, + const bls_public_key& pubkey, + const bls_signature& sig, + uint64_t weight); + + // thread safe + bool has_voted(size_t index) const; + + state_t state() const { std::lock_guard g(*_mtx); return _state; }; + + std::optional get_best_qc(block_num_type block_num) const; + void set_valid_qc(const valid_quorum_certificate& qc); + bool valid_qc_is_strong() const; + private: + friend struct fc::reflector; + friend class qc_chain; + std::unique_ptr _mtx; + std::optional _valid_qc; // best qc received from the network inside block extension + uint64_t _quorum {0}; + uint64_t _max_weak_sum_before_weak_final {0}; // max weak sum before becoming weak_final + state_t _state { state_t::unrestricted }; + uint64_t _strong_sum {0}; // accumulated sum of strong votes so far + uint64_t _weak_sum {0}; // accumulated sum of weak votes so far + votes_t _weak_votes {0}; + votes_t _strong_votes {0}; + + // called by add_vote, already protected by mutex + vote_status add_strong_vote(size_t index, + const bls_signature& sig, + uint64_t weight); + + // called by add_vote, already protected by mutex + vote_status add_weak_vote(size_t index, + const bls_signature& sig, + uint64_t weight); + + bool is_quorum_met_no_lock() const; + bool has_voted_no_lock(bool strong, size_t index) const; + valid_quorum_certificate to_valid_quorum_certificate() const; + }; +} //eosio::chain + + +FC_REFLECT(eosio::chain::vote_message, (block_id)(strong)(finalizer_key)(sig)); +FC_REFLECT_ENUM(eosio::chain::vote_status, (success)(duplicate)(unknown_public_key)(invalid_signature)(unknown_block)) +FC_REFLECT(eosio::chain::valid_quorum_certificate, (_strong_votes)(_weak_votes)(_sig)); +FC_REFLECT(eosio::chain::pending_quorum_certificate, (_valid_qc)(_quorum)(_max_weak_sum_before_weak_final)(_state)(_strong_sum)(_weak_sum)(_weak_votes)(_strong_votes)); +FC_REFLECT_ENUM(eosio::chain::pending_quorum_certificate::state_t, (unrestricted)(restricted)(weak_achieved)(weak_final)(strong)); +FC_REFLECT(eosio::chain::pending_quorum_certificate::votes_t, (_bitset)(_sig)); +FC_REFLECT(eosio::chain::quorum_certificate, (block_num)(qc)); diff --git a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp new file mode 100644 index 0000000000..3cdcc7b2df --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +namespace eosio::chain { + +struct instant_finality_extension : fc::reflect_init { + static constexpr uint16_t extension_id() { return 2; } + static constexpr bool enforce_unique() { return true; } + + instant_finality_extension() = default; + instant_finality_extension(qc_claim_t qc_claim, + std::optional new_finalizer_policy, + std::shared_ptr new_proposer_policy) : + qc_claim(qc_claim), + new_finalizer_policy(std::move(new_finalizer_policy)), + new_proposer_policy(std::move(new_proposer_policy)) + {} + + void reflector_init(); + + qc_claim_t qc_claim; + std::optional new_finalizer_policy; + std::shared_ptr new_proposer_policy; +}; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::instant_finality_extension, (qc_claim)(new_finalizer_policy)(new_proposer_policy) ) diff --git a/libraries/chain/include/eosio/chain/hotstuff/proposer_policy.hpp b/libraries/chain/include/eosio/chain/hotstuff/proposer_policy.hpp new file mode 100644 index 0000000000..9c0a10dd5e --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/proposer_policy.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + +struct proposer_policy { + constexpr static uint8_t current_schema_version = 1; + uint8_t schema_version {current_schema_version}; + // Useful for light clients, not necessary for nodeos + block_timestamp_type active_time; // block when schedule will become active + producer_authority_schedule proposer_schedule; +}; + +using proposer_policy_ptr = std::shared_ptr; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::proposer_policy, (schema_version)(active_time)(proposer_schedule) ) diff --git a/libraries/chain/include/eosio/chain/hotstuff/state.hpp b/libraries/chain/include/eosio/chain/hotstuff/state.hpp new file mode 100644 index 0000000000..fc899cafaf --- /dev/null +++ b/libraries/chain/include/eosio/chain/hotstuff/state.hpp @@ -0,0 +1,59 @@ +#include + +namespace eosio::chain { + + using namespace eosio::chain; + + struct safety_state { + + void set_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key, const view_number v_height) { + _states[finalizer_key].first = v_height; + } + + void set_b_lock(const fc::crypto::blslib::bls_public_key& finalizer_key, const fc::sha256& b_lock) { + _states[finalizer_key].second = b_lock; + } + + std::pair get_safety_state(const fc::crypto::blslib::bls_public_key& finalizer_key) const { + auto s = _states.find(finalizer_key); + if (s != _states.end()) return s->second; + else return {}; + } + + view_number get_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key) const { + auto s = _states.find(finalizer_key); + if (s != _states.end()) return s->second.first; + else return {}; + }; + + fc::sha256 get_b_lock(const fc::crypto::blslib::bls_public_key& finalizer_key) const { + auto s_itr = _states.find(finalizer_key); + if (s_itr != _states.end()) return s_itr->second.second; + else return {}; + }; + + //todo : implement safety state default / sorting + + std::pair get_safety_state() const { + auto s = _states.begin(); + if (s != _states.end()) return s->second; + else return {}; + } + + view_number get_v_height() const { + auto s = _states.begin(); + if (s != _states.end()) return s->second.first; + else return {}; + }; + + fc::sha256 get_b_lock() const { + auto s_itr = _states.begin(); + if (s_itr != _states.end()) return s_itr->second.second; + else return {}; + }; + + std::map> _states; + }; +} + +FC_REFLECT(eosio::chain::safety_state, (_states)) diff --git a/libraries/chain/include/eosio/chain/incremental_merkle.hpp b/libraries/chain/include/eosio/chain/incremental_merkle.hpp index 68b4348e9d..3e9f9dbb01 100644 --- a/libraries/chain/include/eosio/chain/incremental_merkle.hpp +++ b/libraries/chain/include/eosio/chain/incremental_merkle.hpp @@ -6,204 +6,92 @@ namespace eosio::chain { -namespace detail { - -/** - * Given a number of nodes return the depth required to store them - * in a fully balanced binary tree. - * - * @param node_count - the number of nodes in the implied tree - * @return the max depth of the minimal tree that stores them - */ -constexpr uint64_t calculate_max_depth(uint64_t node_count) { - if (node_count == 0) - return 0; - // following is non-floating point equivalent to `std::ceil(std::log2(node_count)) + 1)` (and about 9x faster) - return std::bit_width(std::bit_ceil(node_count)); -} - -template -inline void move_nodes(ContainerA& to, const ContainerB& from) { - to.clear(); - to.insert(to.begin(), from.begin(), from.end()); -} - -template -inline void move_nodes(Container& to, Container&& from) { - to = std::forward(from); -} - - -} /// detail - /** * A balanced merkle tree built in such that the set of leaf nodes can be - * appended to without triggering the reconstruction of inner nodes that - * represent a complete subset of previous nodes. + * appended to without triggering the reconstruction of previously + * constructed nodes. * - * to achieve this new nodes can either imply an set of future nodes - * that achieve a balanced tree OR realize one of these future nodes. + * this is achieved by keeping all possible power of two size trees, so + * for example: + * - after appending 3 digests, we have one `tree of two` digests, and a + * single digest. the mask os 0b11. + * - when appending another digest, a new `tree of two` is constructed with + * the single digest, and these two `trees of two` are conbined in a `tree + * of four`. The original tree of two is unchanged. + * Only the tree of four is stored, the mask 0b100 indicating its rank (4) + * - when appending another digest, the `tree of four` is unchanged, we store + * the new single digest. The mask 0b101 indicates that the tow digest stored + * are the roots of one `tree of four` and one `tree of one` (single digest) * - * Once a sub-tree contains only realized nodes its sub-root will never - * change. This allows proofs based on this merkle to be very stable - * after some time has past only needing to update or add a single + * Once a sub-tree is constructed, its sub-root will never change. + * This allows proofs based on this merkle to be very stable + * after some time has passed, only needing to update or add a single * value to maintain validity. */ -template class Container = vector, typename ...Args> -class incremental_merkle_impl { - public: - incremental_merkle_impl() - :_node_count(0) - {} - - incremental_merkle_impl( const incremental_merkle_impl& ) = default; - incremental_merkle_impl( incremental_merkle_impl&& ) = default; - incremental_merkle_impl& operator= (const incremental_merkle_impl& ) = default; - incremental_merkle_impl& operator= ( incremental_merkle_impl&& ) = default; - - template, incremental_merkle_impl>::value, int> = 0> - incremental_merkle_impl( Allocator&& alloc ):_active_nodes(forward(alloc)){} - - /* - template class OtherContainer, typename ...OtherArgs> - incremental_merkle_impl( incremental_merkle_impl&& other ) - :_node_count(other._node_count) - ,_active_nodes(other._active_nodes.begin(), other.active_nodes.end()) - {} - - incremental_merkle_impl( incremental_merkle_impl&& other ) - :_node_count(other._node_count) - ,_active_nodes(std::forward(other._active_nodes)) - {} - */ - - /** - * Add a node to the incremental tree and recalculate the _active_nodes so they - * are prepared for the next append. - * - * The algorithm for this is to start at the new node and retreat through the tree - * for any node that is the concatenation of a fully-realized node and a partially - * realized node we must record the value of the fully-realized node in the new - * _active_nodes so that the next append can fetch it. Fully realized nodes and - * Fully implied nodes do not have an effect on the _active_nodes. - * - * For convention _AND_ to allow appends when the _node_count is a power-of-2, the - * current root of the incremental tree is always appended to the end of the new - * _active_nodes. - * - * In practice, this can be done iteratively by recording any "left" value that - * is to be combined with an implied node. - * - * If the appended node is a "left" node in its pair, it will immediately push itself - * into the new active nodes list. - * - * If the new node is a "right" node it will begin collapsing upward in the tree, - * reading and discarding the "left" node data from the old active nodes list, until - * it becomes a "left" node. It must then push the "top" of its current collapsed - * sub-tree into the new active nodes list. - * - * Once any value has been added to the new active nodes, all remaining "left" nodes - * should be present in the order they are needed in the previous active nodes as an - * artifact of the previous append. As they are read from the old active nodes, they - * will need to be copied in to the new active nodes list as they are still needed - * for future appends. - * - * As a result, if an append collapses the entire tree while always being the "right" - * node, the new list of active nodes will be empty and by definition the tree contains - * a power-of-2 number of nodes. - * - * Regardless of the contents of the new active nodes list, the top "collapsed" value - * is appended. If this tree is _not_ a power-of-2 number of nodes, this node will - * not be used in the next append but still serves as a conventional place to access - * the root of the current tree. If this _is_ a power-of-2 number of nodes, this node - * will be needed during then collapse phase of the next append so, it serves double - * duty as a legitimate active node and the conventional storage location of the root. - * - * - * @param digest - the node to add - * @return - the new root - */ - const DigestType& append(const DigestType& digest) { - bool partial = false; - auto max_depth = detail::calculate_max_depth(_node_count + 1); - auto current_depth = max_depth - 1; - auto index = _node_count; - auto top = digest; - auto active_iter = _active_nodes.begin(); - auto updated_active_nodes = vector(); - updated_active_nodes.reserve(max_depth); - - while (current_depth > 0) { - if (!(index & 0x1)) { - // we are collapsing from a "left" value and an implied "right" creating a partial node - - // we only need to append this node if it is fully-realized and by definition - // if we have encountered a partial node during collapse this cannot be - // fully-realized - if (!partial) { - updated_active_nodes.emplace_back(top); - } - - // calculate the partially realized node value by implying the "right" value is identical - // to the "left" value - top = DigestType::hash(make_canonical_pair(top, top)); - partial = true; - } else { - // we are collapsing from a "right" value and an fully-realized "left" - - // pull a "left" value from the previous active nodes - const auto& left_value = *active_iter; - ++active_iter; - - // if the "right" value is a partial node we will need to copy the "left" as future appends still need it - // otherwise, it can be dropped from the set of active nodes as we are collapsing a fully-realized node - if (partial) { - updated_active_nodes.emplace_back(left_value); - } - - // calculate the node - top = DigestType::hash(make_canonical_pair(left_value, top)); - } - - // move up a level in the tree - current_depth--; - index = index >> 1; - } - - // append the top of the collapsed tree (aka the root of the merkle) - updated_active_nodes.emplace_back(top); - - // store the new active_nodes - detail::move_nodes(_active_nodes, std::move(updated_active_nodes)); - - // update the node count - _node_count++; - - return _active_nodes.back(); - - } - - /**l - * return the current root of the incremental merkle - * - * @return - */ - DigestType get_root() const { - if (_node_count > 0) { - return _active_nodes.back(); +class incremental_merkle_tree { +public: + void append(const digest_type& digest) { + assert(trees.size() == static_cast(detail::popcount(mask))); + _append(digest, trees.end(), 0); + assert(trees.size() == static_cast(detail::popcount(mask))); + } + + digest_type get_root() const { + if (!mask) + return {}; + assert(!trees.empty()); + return _get_root(0); + } + + uint64_t num_digests_appended() const { + return mask; + } + +private: + friend struct fc::reflector; + using vec_it = std::vector::iterator; + + bool is_bit_set(size_t idx) const { return !!(mask & (1ull << idx)); } + void set_bit(size_t idx) { mask |= (1ull << idx); } + void clear_bit(size_t idx) { mask &= ~(1ull << idx); } + + digest_type _get_root(size_t idx) const { + if (idx + 1 == trees.size()) + return trees[idx]; + return detail::hash_combine(trees[idx], _get_root(idx + 1)); // log2 recursion OK + } + + // slot points to the current insertion point. *(slot-1) is the digest for the first bit set >= idx + void _append(const digest_type& digest, vec_it slot, size_t idx) { + if (is_bit_set(idx)) { + assert(!trees.empty()); + if (!is_bit_set(idx+1)) { + // next location is empty, replace its tree with new combination, same number of slots and one bits + *(slot-1) = detail::hash_combine(*(slot-1), digest); + clear_bit(idx); + set_bit(idx+1); } else { - return DigestType(); + assert(trees.size() >= 2); + clear_bit(idx); + clear_bit(idx+1); + digest_type d = detail::hash_combine(*(slot-2), detail::hash_combine(*(slot-1), digest)); + trees.erase(slot-2, slot); + _append(d, slot-2, idx+2); // log2 recursion OK, uses less than 5KB stack space for 4 billion digests + // appended (or 0.25% of default 2MB thread stack size on Ubuntu) } + } else { + trees.insert(slot, digest); + set_bit(idx); } + } -// private: - uint64_t _node_count; - Container _active_nodes; + uint64_t mask = 0; // bits set signify tree present in trees vector. + // least signif. bit set maps to smallest tree present. + std::vector trees; // digests representing power of 2 trees, smallest tree last + // to minimize digest copying when appending. + // invariant: `trees.size() == detail::popcount(mask)` }; -typedef incremental_merkle_impl incremental_merkle; -typedef incremental_merkle_impl shared_incremental_merkle; - } /// eosio::chain -FC_REFLECT( eosio::chain::incremental_merkle, (_active_nodes)(_node_count) ); +FC_REFLECT( eosio::chain::incremental_merkle_tree, (mask)(trees) ); diff --git a/libraries/chain/include/eosio/chain/incremental_merkle_legacy.hpp b/libraries/chain/include/eosio/chain/incremental_merkle_legacy.hpp new file mode 100644 index 0000000000..0607ee9e3b --- /dev/null +++ b/libraries/chain/include/eosio/chain/incremental_merkle_legacy.hpp @@ -0,0 +1,203 @@ +#pragma once +#include +#include +#include + +namespace eosio::chain { + +namespace detail { + +/** + * Given a number of nodes return the depth required to store them + * in a fully balanced binary tree. + * + * @param node_count - the number of nodes in the implied tree + * @return the max depth of the minimal tree that stores them + */ +constexpr uint64_t calculate_max_depth(uint64_t node_count) { + if (node_count == 0) + return 0; + // following is non-floating point equivalent to `std::ceil(std::log2(node_count)) + 1)` (and about 9x faster) + return std::bit_width(std::bit_ceil(node_count)); +} + +template +inline void move_nodes(ContainerA& to, const ContainerB& from) { + to.clear(); + to.insert(to.begin(), from.begin(), from.end()); +} + +template +inline void move_nodes(Container& to, Container&& from) { + to = std::forward(from); +} + + +} /// detail + +/** + * A balanced merkle tree built in such that the set of leaf nodes can be + * appended to without triggering the reconstruction of inner nodes that + * represent a complete subset of previous nodes. + * + * to achieve this new nodes can either imply an set of future nodes + * that achieve a balanced tree OR realize one of these future nodes. + * + * Once a sub-tree contains only realized nodes its sub-root will never + * change. This allows proofs based on this merkle to be very stable + * after some time has passed, only needing to update or add a single + * value to maintain validity. + */ +template class Container = vector, typename ...Args> +class incremental_merkle_impl { + public: + incremental_merkle_impl() = default; + incremental_merkle_impl( const incremental_merkle_impl& ) = default; + incremental_merkle_impl( incremental_merkle_impl&& ) = default; + incremental_merkle_impl& operator= (const incremental_merkle_impl& ) = default; + incremental_merkle_impl& operator= ( incremental_merkle_impl&& ) = default; + + template, incremental_merkle_impl>::value, int> = 0> + incremental_merkle_impl( Allocator&& alloc ):_active_nodes(forward(alloc)){} + + /* + template class OtherContainer, typename ...OtherArgs> + incremental_merkle_impl( incremental_merkle_impl&& other ) + :_node_count(other._node_count) + ,_active_nodes(other._active_nodes.begin(), other.active_nodes.end()) + {} + + incremental_merkle_impl( incremental_merkle_impl&& other ) + :_node_count(other._node_count) + ,_active_nodes(std::forward(other._active_nodes)) + {} + */ + + /** + * Add a node to the incremental tree and recalculate the _active_nodes so they + * are prepared for the next append. + * + * The algorithm for this is to start at the new node and retreat through the tree + * for any node that is the concatenation of a fully-realized node and a partially + * realized node we must record the value of the fully-realized node in the new + * _active_nodes so that the next append can fetch it. Fully realized nodes and + * Fully implied nodes do not have an effect on the _active_nodes. + * + * For convention _AND_ to allow appends when the _node_count is a power-of-2, the + * current root of the incremental tree is always appended to the end of the new + * _active_nodes. + * + * In practice, this can be done iteratively by recording any "left" value that + * is to be combined with an implied node. + * + * If the appended node is a "left" node in its pair, it will immediately push itself + * into the new active nodes list. + * + * If the new node is a "right" node it will begin collapsing upward in the tree, + * reading and discarding the "left" node data from the old active nodes list, until + * it becomes a "left" node. It must then push the "top" of its current collapsed + * sub-tree into the new active nodes list. + * + * Once any value has been added to the new active nodes, all remaining "left" nodes + * should be present in the order they are needed in the previous active nodes as an + * artifact of the previous append. As they are read from the old active nodes, they + * will need to be copied in to the new active nodes list as they are still needed + * for future appends. + * + * As a result, if an append collapses the entire tree while always being the "right" + * node, the new list of active nodes will be empty and by definition the tree contains + * a power-of-2 number of nodes. + * + * Regardless of the contents of the new active nodes list, the top "collapsed" value + * is appended. If this tree is _not_ a power-of-2 number of nodes, this node will + * not be used in the next append but still serves as a conventional place to access + * the root of the current tree. If this _is_ a power-of-2 number of nodes, this node + * will be needed during then collapse phase of the next append so, it serves double + * duty as a legitimate active node and the conventional storage location of the root. + * + * + * @param digest - the node to add + * @return - the new root + */ + const DigestType& append(const DigestType& digest) { + bool partial = false; + auto max_depth = detail::calculate_max_depth(_node_count + 1); + auto current_depth = max_depth - 1; + auto index = _node_count; + auto top = digest; + auto active_iter = _active_nodes.begin(); + auto updated_active_nodes = vector(); + updated_active_nodes.reserve(max_depth); + + while (current_depth > 0) { + if (!(index & 0x1)) { + // we are collapsing from a "left" value and an implied "right" creating a partial node + + // we only need to append this node if it is fully-realized and by definition + // if we have encountered a partial node during collapse this cannot be + // fully-realized + if (!partial) { + updated_active_nodes.emplace_back(top); + } + + // calculate the partially realized node value by implying the "right" value is identical + // to the "left" value + top = DigestType::hash(detail::make_legacy_digest_pair(top, top)); + partial = true; + } else { + // we are collapsing from a "right" value and an fully-realized "left" + + // pull a "left" value from the previous active nodes + const auto& left_value = *active_iter; + ++active_iter; + + // if the "right" value is a partial node we will need to copy the "left" as future appends still need it + // otherwise, it can be dropped from the set of active nodes as we are collapsing a fully-realized node + if (partial) { + updated_active_nodes.emplace_back(left_value); + } + + // calculate the node + top = DigestType::hash(detail::make_legacy_digest_pair(left_value, top)); + } + + // move up a level in the tree + --current_depth; + index = index >> 1; + } + + // append the top of the collapsed tree (aka the root of the merkle) + updated_active_nodes.emplace_back(top); + + // store the new active_nodes + detail::move_nodes(_active_nodes, std::move(updated_active_nodes)); + + // update the node count + ++_node_count; + + return _active_nodes.back(); + + } + + /** + * return the current root of the incremental merkle + */ + DigestType get_root() const { + if (_node_count > 0) { + return _active_nodes.back(); + } else { + return DigestType(); + } + } + + private: + friend struct fc::reflector; + uint64_t _node_count = 0; + Container _active_nodes; +}; + +typedef incremental_merkle_impl incremental_merkle_tree_legacy; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::incremental_merkle_tree_legacy, (_active_nodes)(_node_count) ); diff --git a/libraries/chain/include/eosio/chain/merkle.hpp b/libraries/chain/include/eosio/chain/merkle.hpp index b99d18c101..8bdaa2a308 100644 --- a/libraries/chain/include/eosio/chain/merkle.hpp +++ b/libraries/chain/include/eosio/chain/merkle.hpp @@ -1,22 +1,114 @@ #pragma once #include +#include +#include +#include +#include -namespace eosio { namespace chain { +namespace eosio::chain { - digest_type make_canonical_left(const digest_type& val); - digest_type make_canonical_right(const digest_type& val); +namespace detail { - bool is_canonical_left(const digest_type& val); - bool is_canonical_right(const digest_type& val); +#if __cplusplus >= 202002L + inline int popcount(uint64_t x) noexcept { return std::popcount(x); } + inline uint64_t bit_floor(uint64_t x) noexcept { return std::bit_floor(x); } +#else + inline int popcount(uint64_t x) noexcept { return __builtin_popcountll(x); } + inline uint64_t bit_floor(uint64_t x) noexcept { return x == 0 ? 0ull : 1ull << (64 - 1 - __builtin_clzll(x)); } +#endif +inline digest_type hash_combine(const digest_type& a, const digest_type& b) { + return digest_type::hash(std::make_pair(std::cref(a), std::cref(b))); +} - inline auto make_canonical_pair(const digest_type& l, const digest_type& r) { - return make_pair(make_canonical_left(l), make_canonical_right(r)); - }; +template +requires std::is_same_v::value_type>, digest_type> +inline digest_type calculate_merkle_pow2(const It& start, const It& end) { + assert(end >= start + 2); + auto size = static_cast(end - start); + assert(detail::bit_floor(size) == size); - /** - * Calculates the merkle root of a set of digests, if ids is odd it will duplicate the last id. - */ - digest_type merkle( deque ids ); + if (size == 2) + return hash_combine(start[0], start[1]); + else { + if (async && size >= 256) { + auto async_calculate_merkle_pow2 = [&start, &size](auto fut) { + size_t slice_size = size / fut.size(); -} } /// eosio::chain + for (size_t i=0; i, + start + slice_size * i, start + slice_size * (i+1)); + + std::array res; + + for (size_t i=0; i= 2048) { + // use 4 threads. Future array size dictates the number of threads (must be power of two) + return async_calculate_merkle_pow2(std::array, 4>()); + } + // use 2 threads. Future array size dictates the number of threads (must be power of two) + return async_calculate_merkle_pow2(std::array, 2>()); + } else { + auto mid = start + size / 2; + return hash_combine(calculate_merkle_pow2(start, mid), calculate_merkle_pow2(mid, end)); + } + } +} + +} // namespace detail + +// ************* public interface starts here ************************************************ + +// ------------------------------------------------------------------------ +// calculate_merkle: +// ----------------- +// takes two random access iterators delimiting a sequence of `digest_type`, +// returns the root digest for the provided sequence. +// +// does not overwrite passed sequence +// +// log2 recursion OK, uses less than 5KB stack space for 4 billion digests +// appended (or 0.25% of default 2MB thread stack size on Ubuntu). +// ------------------------------------------------------------------------ +template +#if __cplusplus >= 202002L +requires std::random_access_iterator && + std::is_same_v::value_type>, digest_type> +#endif +inline digest_type calculate_merkle(const It& start, const It& end) { + assert(end >= start); + auto size = static_cast(end - start); + if (size <= 1) + return (size == 0) ? digest_type{} : *start; + + auto midpoint = detail::bit_floor(size); + if (size == midpoint) + return detail::calculate_merkle_pow2(start, end); + + auto mid = start + midpoint; + return detail::hash_combine(detail::calculate_merkle_pow2(start, mid), + calculate_merkle(mid, end)); +} + +// -------------------------------------------------------------------------- +// calculate_merkle: +// ----------------- +// takes a container or `std::span` of `digest_type`, returns the root digest +// for the sequence of digests in the container. +// -------------------------------------------------------------------------- +template +#if __cplusplus >= 202002L +requires std::random_access_iterator && + std::is_same_v, digest_type> +#endif +inline digest_type calculate_merkle(const Cont& ids) { + return calculate_merkle(ids.begin(), ids.end()); // cbegin not supported for std::span until C++23. +} + + +} /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/merkle_legacy.hpp b/libraries/chain/include/eosio/chain/merkle_legacy.hpp new file mode 100644 index 0000000000..5746f2e07f --- /dev/null +++ b/libraries/chain/include/eosio/chain/merkle_legacy.hpp @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +namespace eosio::chain { + +namespace detail { + + inline digest_type make_legacy_left_digest(const digest_type& val) { + digest_type left = val; + left._hash[0] &= 0xFFFFFFFFFFFFFF7FULL; + return left; + } + + inline digest_type make_legacy_right_digest(const digest_type& val) { + digest_type right = val; + right._hash[0] |= 0x0000000000000080ULL; + return right; + } + + inline bool is_legacy_left_digest(const digest_type& val) { + return (val._hash[0] & 0x0000000000000080ULL) == 0; + } + + inline bool is_legacy_right_digest(const digest_type& val) { + return (val._hash[0] & 0x0000000000000080ULL) != 0; + } + + inline auto make_legacy_digest_pair(const digest_type& l, const digest_type& r) { + return make_pair(make_legacy_left_digest(l), make_legacy_right_digest(r)); + }; + +} // namespace detail + +/** + * Calculates the merkle root of a set of digests, if ids is odd it will duplicate the last id. + * Uses make_legacy_digest_pair which before hashing sets the first bit of the previous hashes + * to 0 or 1 to indicate the side it is on. + */ +inline digest_type calculate_merkle_legacy( deque ids ) { + if( 0 == ids.size() ) { return digest_type(); } + + while( ids.size() > 1 ) { + if( ids.size() % 2 ) + ids.push_back(ids.back()); + + for (size_t i = 0; i < ids.size() / 2; i++) { + ids[i] = digest_type::hash(detail::make_legacy_digest_pair(ids[2 * i], ids[(2 * i) + 1])); + } + + ids.resize(ids.size() / 2); + } + + return ids.front(); +} + +} /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/producer_schedule.hpp b/libraries/chain/include/eosio/chain/producer_schedule.hpp index af4f513bc8..ae98a0f0b7 100644 --- a/libraries/chain/include/eosio/chain/producer_schedule.hpp +++ b/libraries/chain/include/eosio/chain/producer_schedule.hpp @@ -1,11 +1,12 @@ #pragma once #include #include +#include #include #include #include -namespace eosio { namespace chain { +namespace eosio::chain { namespace legacy { /** @@ -249,6 +250,12 @@ namespace eosio { namespace chain { uint32_t version = 0; ///< sequentially incrementing version number vector producers; + const producer_authority& get_scheduled_producer( block_timestamp_type t )const { + auto index = t.slot % (producers.size() * config::producer_repetitions); + index /= config::producer_repetitions; + return producers[index]; + } + friend bool operator == ( const producer_authority_schedule& a, const producer_authority_schedule& b ) { if( a.version != b.version ) return false; @@ -300,7 +307,7 @@ namespace eosio { namespace chain { return true; } -} } /// eosio::chain +} /// eosio::chain FC_REFLECT( eosio::chain::legacy::producer_key, (producer_name)(block_signing_key) ) FC_REFLECT( eosio::chain::legacy::producer_schedule_type, (version)(producers) ) diff --git a/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp b/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp index 1f1a49fef5..fc4a5d45b1 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_activation.hpp @@ -7,23 +7,6 @@ namespace eosio { namespace chain { struct protocol_feature_activation : fc::reflect_init { static constexpr uint16_t extension_id() { return 0; } static constexpr bool enforce_unique() { return true; } - - protocol_feature_activation() = default; - - protocol_feature_activation( const vector& pf ) - :protocol_features( pf ) - {} - - protocol_feature_activation( vector&& pf ) - :protocol_features( std::move(pf) ) - {} - - protocol_feature_activation(const protocol_feature_activation&) = default; - protocol_feature_activation(protocol_feature_activation&&) = default; - - protocol_feature_activation& operator=(protocol_feature_activation&&) = default; - protocol_feature_activation& operator=(const protocol_feature_activation&) = default; - void reflector_init(); vector protocol_features; diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index a7ede28ddc..0c42da5b9e 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -3,7 +3,7 @@ #include #include -namespace eosio { namespace chain { +namespace eosio::chain { class deep_mind_handler; @@ -38,6 +38,7 @@ enum class builtin_protocol_feature_t : uint32_t { bls_primitives = 21, disable_deferred_trxs_stage_1 = 22, disable_deferred_trxs_stage_2 = 23, + instant_finality = 24, reserved_private_fork_protocol_features = 500000, }; @@ -402,7 +403,7 @@ class protocol_feature_manager { std::optional read_builtin_protocol_feature( const std::filesystem::path& p ); protocol_feature_set initialize_protocol_features( const std::filesystem::path& p, bool populate_missing_builtins = true ); -} } // namespace eosio::chain +} // namespace eosio::chain FC_REFLECT(eosio::chain::protocol_feature_subjective_restrictions, (earliest_allowed_activation_time)(preactivation_required)(enabled)) diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp new file mode 100644 index 0000000000..6bf0a8b7a9 --- /dev/null +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -0,0 +1,221 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace eosio::chain::snapshot_detail { + + /** + * a fc::raw::unpack compatible version of the old block_state structure stored in + * version 2 snapshots + */ + struct snapshot_block_header_state_legacy_v2 { + static constexpr uint32_t minimum_version = 0; + static constexpr uint32_t maximum_version = 2; + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, + "snapshot_block_header_state_v2 is no longer needed"); + + struct schedule_info { + uint32_t schedule_lib_num = 0; /// last irr block num + digest_type schedule_hash; + legacy::producer_schedule_type schedule; + }; + + /// from block_header_state_legacy_common + uint32_t block_num = 0; + uint32_t dpos_proposed_irreversible_blocknum = 0; + uint32_t dpos_irreversible_blocknum = 0; + legacy::producer_schedule_type active_schedule; + incremental_merkle_tree_legacy blockroot_merkle; + flat_map producer_to_last_produced; + flat_map producer_to_last_implied_irb; + public_key_type block_signing_key; + vector confirm_count; + + // from block_header_state_legacy + block_id_type id; + signed_block_header header; + schedule_info pending_schedule; + protocol_feature_activation_set_ptr activated_protocol_features; + }; + + /** + * a fc::raw::unpack compatible version of the old block_state_legacy structure stored in + * version 3 to 6 snapshots + */ + struct snapshot_block_header_state_legacy_v3 { + static constexpr uint32_t minimum_version = 3; + static constexpr uint32_t maximum_version = 6; + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, + "snapshot_block_header_state_v3 is no longer needed"); + + /// from block_header_state_legacy_common + uint32_t block_num = 0; + uint32_t dpos_proposed_irreversible_blocknum = 0; + uint32_t dpos_irreversible_blocknum = 0; + producer_authority_schedule active_schedule; + incremental_merkle_tree_legacy blockroot_merkle; + flat_map producer_to_last_produced; + flat_map producer_to_last_implied_irb; + block_signing_authority valid_block_signing_authority; + vector confirm_count; + + // from block_header_state_legacy + block_id_type id; + signed_block_header header; + detail::schedule_info pending_schedule; + protocol_feature_activation_set_ptr activated_protocol_features; + vector additional_signatures; + + snapshot_block_header_state_legacy_v3() = default; + + explicit snapshot_block_header_state_legacy_v3(const block_state_legacy& bs) + : block_num(bs.block_num()) + , dpos_proposed_irreversible_blocknum(bs.dpos_proposed_irreversible_blocknum) + , dpos_irreversible_blocknum(bs.dpos_irreversible_blocknum) + , active_schedule(bs.active_schedule) + , blockroot_merkle(bs.blockroot_merkle) + , producer_to_last_produced(bs.producer_to_last_produced) + , producer_to_last_implied_irb(bs.producer_to_last_implied_irb) + , valid_block_signing_authority(bs.valid_block_signing_authority) + , confirm_count(bs.confirm_count) + , id(bs.id()) + , header(bs.header) + , pending_schedule(bs.pending_schedule) + , activated_protocol_features(bs.activated_protocol_features) + , additional_signatures(bs.additional_signatures) + {} + }; + + /** + * Snapshot V7 Data structures + * --------------------------- + */ + struct snapshot_block_state_legacy_v7 : public snapshot_block_header_state_legacy_v3 { + // additional member that can be present in `Transition Legacy Block` and + // is needed to convert to `Transition IF Block` (see https://github.com/AntelopeIO/leap/issues/2080) + using valid_t = uint32_t; // snapshot todo + std::optional valid; + + snapshot_block_state_legacy_v7() = default; + + explicit snapshot_block_state_legacy_v7(const block_state_legacy& bs) + : snapshot_block_header_state_legacy_v3(bs) + , valid(0) // snapshot todo + {} + }; + + struct snapshot_block_state_v7 { + // from block_header_state + block_id_type block_id; + block_header header; + protocol_feature_activation_set_ptr activated_protocol_features; + finality_core core; + finalizer_policy_ptr active_finalizer_policy; + proposer_policy_ptr active_proposer_policy; + flat_map proposer_policies; + flat_map finalizer_policies; + + // from block_state + std::optional valid; + + snapshot_block_state_v7() = default; + + explicit snapshot_block_state_v7(const block_state& bs) + : block_id(bs.block_id) + , header(bs.header) + , activated_protocol_features(bs.activated_protocol_features) + , core(bs.core) + , active_finalizer_policy(bs.active_finalizer_policy) + , active_proposer_policy(bs.active_proposer_policy) + , proposer_policies(bs.proposer_policies) + , finalizer_policies(bs.finalizer_policies) + , valid(bs.valid) + {} + }; + + struct snapshot_block_state_data_v7 { + static constexpr uint32_t minimum_version = 7; + static constexpr uint32_t maximum_version = 7; + + std::optional bs_l; + std::optional bs; + + snapshot_block_state_data_v7() = default; + + explicit snapshot_block_state_data_v7(const block_state_pair& p) + { + if (p.first) + bs_l = snapshot_block_state_legacy_v7(*p.first); + if (p.second) + bs = snapshot_block_state_v7(*p.second); + } + }; + +} + + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v2::schedule_info, + ( schedule_lib_num ) + ( schedule_hash ) + ( schedule ) +) + + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v2, + ( block_num ) + ( dpos_proposed_irreversible_blocknum ) + ( dpos_irreversible_blocknum ) + ( active_schedule ) + ( blockroot_merkle ) + ( producer_to_last_produced ) + ( producer_to_last_implied_irb ) + ( block_signing_key ) + ( confirm_count ) + ( id ) + ( header ) + ( pending_schedule ) + ( activated_protocol_features ) +) + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v3, + ( block_num ) + ( dpos_proposed_irreversible_blocknum ) + ( dpos_irreversible_blocknum ) + ( active_schedule ) + ( blockroot_merkle ) + ( producer_to_last_produced ) + ( producer_to_last_implied_irb ) + ( valid_block_signing_authority ) + ( confirm_count ) + ( id ) + ( header ) + ( pending_schedule ) + ( activated_protocol_features ) + ( additional_signatures ) +) + +FC_REFLECT_DERIVED( eosio::chain::snapshot_detail::snapshot_block_state_legacy_v7, + (eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v3), + (valid) + ) + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v7, + (block_id) + (header) + (activated_protocol_features) + (core) + (active_finalizer_policy) + (active_proposer_policy) + (proposer_policies) + (finalizer_policies) + (valid) + ) + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_data_v7, + (bs_l) + (bs) + ) diff --git a/libraries/chain/include/eosio/chain/thread_utils.hpp b/libraries/chain/include/eosio/chain/thread_utils.hpp index d3e9e8a261..b4e4d5a673 100644 --- a/libraries/chain/include/eosio/chain/thread_utils.hpp +++ b/libraries/chain/include/eosio/chain/thread_utils.hpp @@ -12,6 +12,74 @@ namespace eosio { namespace chain { + // should be defined for c++17, but clang++16 still has not implemented it +#ifdef __cpp_lib_hardware_interference_size + using std::hardware_constructive_interference_size; + using std::hardware_destructive_interference_size; +#else + // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ... + [[maybe_unused]] constexpr std::size_t hardware_constructive_interference_size = 64; + [[maybe_unused]] constexpr std::size_t hardware_destructive_interference_size = 64; +#endif + + // Use instead of std::atomic when std::atomic does not support type + template + class large_atomic { + alignas(hardware_destructive_interference_size) + mutable std::mutex mtx; + T value{}; + public: + T load() const { + std::lock_guard g(mtx); + return value; + } + void store(const T& v) { + std::lock_guard g(mtx); + value = v; + } + + class accessor { + std::lock_guard g; + T& v; + public: + accessor(std::mutex& m, T& v) + : g(m), v(v) {} + T& value() { return v; } + }; + + auto make_accessor() { return accessor{mtx, value}; } + }; + + template + class copyable_atomic { + std::atomic value; + public: + copyable_atomic() = default; + copyable_atomic(T v) noexcept + : value(v) {} + copyable_atomic(const copyable_atomic& rhs) + : value(rhs.value.load(std::memory_order_relaxed)) {} + copyable_atomic(copyable_atomic&& rhs) noexcept + : value(rhs.value.load(std::memory_order_relaxed)) {} + + T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept { return value.load(mo); } + void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept { value.store(v, mo); } + + template + friend DS& operator<<(DS& ds, const copyable_atomic& ca) { + fc::raw::pack(ds, ca.load(std::memory_order_relaxed)); + return ds; + } + + template + friend DS& operator>>(DS& ds, copyable_atomic& ca) { + T v; + fc::raw::unpack(ds, v); + ca.store(v, std::memory_order_relaxed); + return ds; + } + }; + /** * Wrapper class for thread pool of boost asio io_context run. * Also names threads so that tools like htop can see thread name. diff --git a/libraries/chain/include/eosio/chain/trace.hpp b/libraries/chain/include/eosio/chain/trace.hpp index 22957918b2..b615afc079 100644 --- a/libraries/chain/include/eosio/chain/trace.hpp +++ b/libraries/chain/include/eosio/chain/trace.hpp @@ -4,7 +4,7 @@ #include #include -namespace eosio { namespace chain { +namespace eosio::chain { struct account_delta { account_delta( const account_name& n, int64_t d):account(n),delta(d){} @@ -45,6 +45,43 @@ namespace eosio { namespace chain { std::optional except; std::optional error_code; std::vector return_value; + + digest_type digest_savanna() const { + assert(!!receipt); + const action_receipt& r = *receipt; + + digest_type::encoder e; + fc::raw::pack(e, r.receiver); + fc::raw::pack(e, r.recv_sequence); + fc::raw::pack(e, act.account); + fc::raw::pack(e, act.name); + fc::raw::pack(e, r.act_digest); + + { + digest_type::encoder e2; + fc::raw::pack(e2, r.global_sequence); + fc::raw::pack(e2, r.auth_sequence); + fc::raw::pack(e2, r.code_sequence); + fc::raw::pack(e2, r.abi_sequence); + fc::raw::pack(e, e2.result()); + } + return e.result(); + } + + digest_type digest_legacy()const { + assert(!!receipt); + const action_receipt& r = *receipt; + + digest_type::encoder e; + fc::raw::pack(e, r.receiver); + fc::raw::pack(e, r.act_digest); + fc::raw::pack(e, r.global_sequence); + fc::raw::pack(e, r.recv_sequence); + fc::raw::pack(e, r.auth_sequence); + fc::raw::pack(e, r.code_sequence); + fc::raw::pack(e, r.abi_sequence); + return e.result(); + } }; struct transaction_trace { @@ -80,7 +117,7 @@ namespace eosio { namespace chain { auth.permission == eosio::chain::config::active_name; } -} } /// namespace eosio::chain +} /// namespace eosio::chain FC_REFLECT( eosio::chain::account_delta, (account)(delta) ) diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp index 806e75d9dd..525794e929 100644 --- a/libraries/chain/include/eosio/chain/transaction_context.hpp +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -33,6 +33,52 @@ namespace eosio { namespace chain { friend controller_impl; }; + struct action_digests_t { + enum class store_which_t { legacy, savanna, both }; + + std::optional digests_l; // legacy + std::optional digests_s; // savanna + + action_digests_t(store_which_t sw) { + if (sw == store_which_t::legacy || sw == store_which_t::both) + digests_l = digests_t{}; + if (sw == store_which_t::savanna || sw == store_which_t::both) + digests_s = digests_t{}; + } + + void append(action_digests_t&& o) { + if (digests_l) + fc::move_append(*digests_l, std::move(*o.digests_l)); + if (digests_s) + fc::move_append(*digests_s, std::move(*o.digests_s)); + } + + void compute_and_append_digests_from(action_trace& trace) { + if (digests_l) + digests_l->emplace_back(trace.digest_legacy()); + if (digests_s) + digests_s->emplace_back(trace.digest_savanna()); + } + + store_which_t store_which() const { + if (digests_l && digests_s) + return store_which_t::both; + if (digests_l) + return store_which_t::legacy; + assert(digests_s); + return store_which_t::savanna; + } + + std::pair size() const { + return { digests_l ? digests_l->size() : 0, digests_s ? digests_s->size() : 0 }; + } + + void resize(std::pair sz) { + if (digests_l) digests_l->resize(sz.first); + if (digests_s) digests_s->resize(sz.second); + } + }; + class transaction_context { private: void init( uint64_t initial_net_usage); @@ -43,6 +89,7 @@ namespace eosio { namespace chain { const packed_transaction& t, const transaction_id_type& trx_id, // trx_id diff than t.id() before replace_deferred transaction_checktime_timer&& timer, + action_digests_t::store_which_t sad, fc::time_point start = fc::time_point::now(), transaction_metadata::trx_type type = transaction_metadata::trx_type::input); ~transaction_context(); @@ -140,8 +187,7 @@ namespace eosio { namespace chain { fc::time_point published; - - deque executed_action_receipt_digests; + action_digests_t executed_action_receipts; flat_set bill_to_accounts; flat_set validate_ram_usage; diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 102b9f0dd6..632b26a4e1 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -74,7 +74,7 @@ namespace eosio::chain { using public_key_type = fc::crypto::public_key; using private_key_type = fc::crypto::private_key; using signature_type = fc::crypto::signature; - + // configurable boost deque (for boost >= 1.71) performs much better than std::deque in our use cases using block_1024_option_t = boost::container::deque_options< boost::container::block_size<1024u> >::type; template diff --git a/libraries/chain/include/eosio/chain/unapplied_transaction_queue.hpp b/libraries/chain/include/eosio/chain/unapplied_transaction_queue.hpp index 4480c06abc..e1231bedcb 100644 --- a/libraries/chain/include/eosio/chain/unapplied_transaction_queue.hpp +++ b/libraries/chain/include/eosio/chain/unapplied_transaction_queue.hpp @@ -133,16 +133,9 @@ class unapplied_transaction_queue { } } - void add_forked( const branch_type& forked_branch ) { - // forked_branch is in reverse order - for( auto ritr = forked_branch.rbegin(), rend = forked_branch.rend(); ritr != rend; ++ritr ) { - const block_state_legacy_ptr& bsptr = *ritr; - for( auto itr = bsptr->trxs_metas().begin(), end = bsptr->trxs_metas().end(); itr != end; ++itr ) { - const auto& trx = *itr; - auto insert_itr = queue.insert( { trx, trx_enum_type::forked } ); - if( insert_itr.second ) added( insert_itr.first ); - } - } + void add_forked( const transaction_metadata_ptr& trx ) { + auto insert_itr = queue.insert( { trx, trx_enum_type::forked } ); + if( insert_itr.second ) added( insert_itr.first ); } void add_aborted( deque aborted_trxs ) { diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp index 8577ec9958..819a7f0c31 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp @@ -277,7 +277,8 @@ inline constexpr auto get_intrinsic_table() { "env.bls_g2_map", "env.bls_fp_mod", "env.bls_fp_mul", - "env.bls_fp_exp" + "env.bls_fp_exp", + "env.set_finalizers" ); } inline constexpr std::size_t find_intrinsic_index(std::string_view hf) { diff --git a/libraries/chain/include/eosio/chain/webassembly/interface.hpp b/libraries/chain/include/eosio/chain/webassembly/interface.hpp index 7a5123f3ca..c94a3aa0eb 100644 --- a/libraries/chain/include/eosio/chain/webassembly/interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/interface.hpp @@ -173,6 +173,26 @@ namespace webassembly { */ int64_t set_proposed_producers_ex(uint64_t packed_producer_format, legacy_span packed_producer_schedule); + /** + * Submits a finalizer set change to Hotstuff. + * + * // format for packed finalizer_policy + * struct abi_finalizer_authority { + * std::string description; + * uint64_t fweight = 0; // weight that this finalizer's vote has for meeting fthreshold + * std::array public_key_g1_affine_le; + * }; + * struct abi_finalizer_policy { + * uint64_t fthreshold = 0; + * std::vector finalizers; + * }; + * + * @ingroup privileged + * + * @param packed_finalizer_policy - a serialized finalizer_policy object. + */ + void set_finalizers(span packed_finalizer_policy); + /** * Retrieve the blockchain config parameters. * diff --git a/libraries/chain/merkle.cpp b/libraries/chain/merkle.cpp deleted file mode 100644 index e4211f7bfd..0000000000 --- a/libraries/chain/merkle.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -namespace eosio { namespace chain { - -/** - * in order to keep proofs concise, before hashing we set the first bit - * of the previous hashes to 0 or 1 to indicate the side it is on - * - * this relieves our proofs from having to indicate left vs right contactenation - * as the node values will imply it - */ - -digest_type make_canonical_left(const digest_type& val) { - digest_type canonical_l = val; - canonical_l._hash[0] &= 0xFFFFFFFFFFFFFF7FULL; - return canonical_l; -} - -digest_type make_canonical_right(const digest_type& val) { - digest_type canonical_r = val; - canonical_r._hash[0] |= 0x0000000000000080ULL; - return canonical_r; -} - -bool is_canonical_left(const digest_type& val) { - return (val._hash[0] & 0x0000000000000080ULL) == 0; -} - -bool is_canonical_right(const digest_type& val) { - return (val._hash[0] & 0x0000000000000080ULL) != 0; -} - - -digest_type merkle(deque ids) { - if( 0 == ids.size() ) { return digest_type(); } - - while( ids.size() > 1 ) { - if( ids.size() % 2 ) - ids.push_back(ids.back()); - - for (size_t i = 0; i < ids.size() / 2; i++) { - ids[i] = digest_type::hash(make_canonical_pair(ids[2 * i], ids[(2 * i) + 1])); - } - - ids.resize(ids.size() / 2); - } - - return ids.front(); -} - -} } // eosio::chain diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index 5a270c6bbd..94339b7e00 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -308,6 +308,26 @@ retires a deferred transaction is invalid. */ {builtin_protocol_feature_t::disable_deferred_trxs_stage_1} } ) + ( builtin_protocol_feature_t::instant_finality, builtin_protocol_feature_spec{ + "INSTANT_FINALITY", + fc::variant("bd496b9e85ce61dcddeee4576ea185add87844238da992a9ee6df2a2bdb357c2").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: INSTANT_FINALITY +Depends on: DISALLOW_EMPTY_PRODUCER_SCHEDULE + WTMSIG_BLOCK_SIGNATURES + ACTION_RETURN_VALUE + BLS_PRIMITIVES2 + +Once this protocol feature is activated, the first subsequent block including a `set_finalizers` +host function call will trigger a switch to the Savanna consensus algorithm. +*/ + { builtin_protocol_feature_t::disallow_empty_producer_schedule, + builtin_protocol_feature_t::wtmsig_block_signatures, + builtin_protocol_feature_t::action_return_value, + builtin_protocol_feature_t::bls_primitives + } + } ) ; @@ -583,8 +603,8 @@ retires a deferred transaction is invalid. } EOS_THROW( protocol_feature_validation_exception, - "Not all the builtin dependencies of the builtin protocol feature with codename '${codename}' and digest of ${digest} were satisfied.", - ("missing_dependencies", missing_builtins_with_names) + "Not all the builtin dependencies of the builtin protocol feature with codename '${codename}' and digest of ${digest} were satisfied. Missing dependencies: ${missing_dependencies}", + ("codename", f.builtin_feature_codename)("digest",feature_digest)("missing_dependencies", missing_builtins_with_names) ); } @@ -964,22 +984,23 @@ retires a deferred transaction is invalid. auto file_path = p / filename; EOS_ASSERT( !std::filesystem::exists( file_path ), plugin_exception, - "Could not save builtin protocol feature with codename '${codename}' because a file at the following path already exists: ${path}", + "Could not save builtin protocol feature with codename '${codename}' because a file at " + "the following path already exists: ${path}", ("codename", builtin_protocol_feature_codename( f.get_codename() )) - ("path", file_path) + ("path", file_path) ); if( fc::json::save_to_file( f, file_path ) ) { ilog( "Saved default specification for builtin protocol feature '${codename}' (with digest of '${digest}') to: ${path}", ("codename", builtin_protocol_feature_codename(f.get_codename())) - ("digest", feature_digest) - ("path", file_path) + ("digest", feature_digest) + ("path", file_path) ); } else { elog( "Error occurred while writing default specification for builtin protocol feature '${codename}' (with digest of '${digest}') to: ${path}", ("codename", builtin_protocol_feature_codename(f.get_codename())) - ("digest", feature_digest) - ("path", file_path) + ("digest", feature_digest) + ("path", file_path) ); } }; diff --git a/libraries/chain/resource_limits.cpp b/libraries/chain/resource_limits.cpp index b11eea1dba..4c5e4b6cb3 100644 --- a/libraries/chain/resource_limits.cpp +++ b/libraries/chain/resource_limits.cpp @@ -117,7 +117,7 @@ void resource_limits_manager::set_block_parameters(const elastic_limit_parameter c.cpu_limit_parameters = cpu_limit_parameters; c.net_limit_parameters = net_limit_parameters; - // set_block_parameters is called by controller::finalize_block, + // set_block_parameters is called by controller::finish_block, // where transaction specific logging is not possible if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_config(c); @@ -359,7 +359,7 @@ void resource_limits_manager::process_account_limit_updates() { multi_index.remove(*itr); } - // process_account_limit_updates is called by controller::finalize_block, + // process_account_limit_updates is called by controller::finish_block, // where transaction specific logging is not possible if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_state(state); @@ -381,7 +381,7 @@ void resource_limits_manager::process_block_usage(uint32_t block_num) { state.update_virtual_net_limit(config); state.pending_net_usage = 0; - // process_block_usage is called by controller::finalize_block, + // process_block_usage is called by controller::finish, // where transaction specific logging is not possible if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_state(state); diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index a535f0579e..2199b838a3 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -12,7 +12,7 @@ #include #include -namespace eosio { namespace chain { +namespace eosio::chain { transaction_checktime_timer::transaction_checktime_timer(platform_timer& timer) : expired(timer.expired), _timer(timer) { @@ -40,6 +40,7 @@ namespace eosio { namespace chain { const packed_transaction& t, const transaction_id_type& trx_id, transaction_checktime_timer&& tmr, + action_digests_t::store_which_t store_which, fc::time_point s, transaction_metadata::trx_type type) :control(c) @@ -48,6 +49,7 @@ namespace eosio { namespace chain { ,undo_session() ,trace(std::make_shared()) ,start(s) + ,executed_action_receipts(store_which) ,transaction_timer(std::move(tmr)) ,trx_type(type) ,net_usage(trace->net_usage) @@ -830,4 +832,4 @@ namespace eosio { namespace chain { } -} } /// eosio::chain +} /// eosio::chain diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index f9a8456745..134a27bdf5 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include @@ -150,6 +152,52 @@ namespace eosio { namespace chain { namespace webassembly { } } + // format for packed_finalizer_policy + struct finalizer_authority { + std::string description; + uint64_t weight = 0; // weight that this finalizer's vote has for meeting fthreshold + std::vector public_key; // Affine little endian non-montgomery g1, cdt/abi_serializer has issues with std::array, size 96 + }; + struct finalizer_policy { + uint64_t threshold = 0; + std::vector finalizers; + }; + + void interface::set_finalizers(span packed_finalizer_policy) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_finalizers not allowed in a readonly transaction"); + fc::datastream ds( packed_finalizer_policy.data(), packed_finalizer_policy.size() ); + finalizer_policy abi_finpol; + fc::raw::unpack(ds, abi_finpol); + + std::vector& finalizers = abi_finpol.finalizers; + + EOS_ASSERT( finalizers.size() <= config::max_finalizers, wasm_execution_error, "Finalizer policy exceeds the maximum finalizer count for this chain" ); + EOS_ASSERT( finalizers.size() > 0, wasm_execution_error, "Finalizers cannot be empty" ); + + std::set unique_finalizer_keys; + + uint64_t weight_sum = 0; + + chain::finalizer_policy finpol; + finpol.threshold = abi_finpol.threshold; + for (auto& f: finalizers) { + EOS_ASSERT( f.description.size() <= config::max_finalizer_description_size, wasm_execution_error, + "Finalizer description greater than ${s}", ("s", config::max_finalizer_description_size) ); + EOS_ASSERT(std::numeric_limits::max() - weight_sum >= f.weight, wasm_execution_error, "sum of weights causes uint64_t overflow"); + weight_sum += f.weight; + EOS_ASSERT(f.public_key.size() == 96, wasm_execution_error, "Invalid bls public key length"); + fc::crypto::blslib::bls_public_key pk(std::span(f.public_key.data(), 96)); + EOS_ASSERT( unique_finalizer_keys.insert(pk).second, wasm_execution_error, "Duplicate public key: ${pk}", ("pk", pk.to_string()) ); + finpol.finalizers.push_back(chain::finalizer_authority{.description = std::move(f.description), + .weight = f.weight, + .public_key{pk}}); + } + + EOS_ASSERT( weight_sum >= finpol.threshold && finpol.threshold > weight_sum / 2, wasm_execution_error, "Finalizer policy threshold (${t}) must be greater than half of the sum of the weights (${w}), and less than or equal to the sum of the weights", ("t", finpol.threshold)("w", weight_sum) ); + + context.control.set_proposed_finalizers( std::move(finpol) ); + } + uint32_t interface::get_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) const { auto& gpo = context.control.get_global_properties(); @@ -224,3 +272,6 @@ namespace eosio { namespace chain { namespace webassembly { }); } }}} // ns eosio::chain::webassembly + +FC_REFLECT(eosio::chain::webassembly::finalizer_authority, (description)(weight)(public_key)); +FC_REFLECT(eosio::chain::webassembly::finalizer_policy, (threshold)(finalizers)); diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index 3a41448c19..f3deb55286 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -365,6 +365,7 @@ REGISTER_LEGACY_HOST_FUNCTION(get_blockchain_parameters_packed, privileged_check REGISTER_LEGACY_HOST_FUNCTION(set_blockchain_parameters_packed, privileged_check); REGISTER_HOST_FUNCTION(is_privileged, privileged_check); REGISTER_HOST_FUNCTION(set_privileged, privileged_check); +REGISTER_HOST_FUNCTION(set_finalizers, privileged_check); // softfloat api REGISTER_INJECTED_HOST_FUNCTION(_eosio_f32_add); diff --git a/libraries/libfc/CMakeLists.txt b/libraries/libfc/CMakeLists.txt index 78a888532f..e4344030a3 100644 --- a/libraries/libfc/CMakeLists.txt +++ b/libraries/libfc/CMakeLists.txt @@ -35,7 +35,6 @@ set( fc_sources src/crypto/crc.cpp src/crypto/city.cpp src/crypto/base58.cpp - src/crypto/base64.cpp src/crypto/bigint.cpp src/crypto/hex.cpp src/crypto/sha1.cpp @@ -53,6 +52,10 @@ set( fc_sources src/crypto/public_key.cpp src/crypto/private_key.cpp src/crypto/signature.cpp + src/crypto/bls_private_key.cpp + src/crypto/bls_public_key.cpp + src/crypto/bls_signature.cpp + src/crypto/bls_utils.cpp src/crypto/modular_arithmetic.cpp src/crypto/blake2.cpp src/crypto/k1_recover.cpp diff --git a/libraries/libfc/include/fc/crypto/base64.hpp b/libraries/libfc/include/fc/crypto/base64.hpp index 34dd35ad0b..5e025fdc0f 100644 --- a/libraries/libfc/include/fc/crypto/base64.hpp +++ b/libraries/libfc/include/fc/crypto/base64.hpp @@ -1,16 +1,372 @@ +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch +*/ +/** + * Copyright (C) 2023 Kevin Heifner + * + * Modified to be header only. + * Templated for std::string, std::string_view, std::vector and other char containers. + */ + #pragma once + +#include + +#include #include #include -#include +#include namespace fc { -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); -inline std::string base64_encode(char const* bytes_to_encode, unsigned int in_len) { return base64_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64_encode( const std::string& enc ); -std::vector base64_decode( std::string_view encoded_string); - -std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len); -inline std::string base64url_encode(char const* bytes_to_encode, unsigned int in_len) { return base64url_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64url_encode( const std::string& enc ); -std::vector base64url_decode( std::string_view encoded_string); -} // namespace fc + +// Interface: +// Defaults allow for use: +// std::string s = "foobar"; +// std::string encoded = base64_encode(s); +// std::string_view sv = "foobar"; +// std::string encoded = base64_encode(sv); +// std::vector vc = {'f', 'o', 'o'}; +// std::string encoded = base64_encode(vc); +// +// Also allows for user provided char containers and specified return types: +// std::string s = "foobar"; +// std::vector encoded = base64_encode>(s); + +template +RetString base64_encode(const String& s, bool url = false); + +template +RetString base64_encode_pem(const String& s); + +template +RetString base64_encode_mime(const String& s); + +template +RetString base64_decode(const String& s, bool remove_linebreaks = false, bool url = false); + +template +RetString base64_encode(const unsigned char* s, size_t len, bool url = false); + +// Convenient methods for existing Leap uses + +std::string base64_encode(char const* s, unsigned int len); +std::vector base64_decode( const std::string& s); +std::string base64url_encode(const char* s, size_t len); +std::string base64url_encode(const std::string& s); +std::vector base64url_decode(const std::string& s); + +namespace detail { + // + // Depending on the url parameter in base64_chars, one of + // two sets of base64 characters needs to be chosen. + // They differ in their last two characters. + // +constexpr const char* to_base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + +constexpr unsigned char from_base64_chars[2][256] = { +{ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}, +{ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}}; + +inline unsigned int pos_of_char(const unsigned char chr, bool url) { + // + // Return the position of chr within base64_encode() + // + + if (from_base64_chars[url][chr] != 64) return from_base64_chars[url][chr]; + + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + // Original version throw std::runtime_error("Input is not valid base64-encoded data."); + // Throw FC assert and the same error text to match existing Leap usages. + FC_ASSERT(false, "encountered non-base64 character"); +} + +template +inline RetString insert_linebreaks(const String& str, size_t distance) { + // + // Provided by https://github.com/JomaCorpFX, adapted by Rene & Kevin + // + if (!str.size()) { + return RetString{}; + } + + if (distance < str.size()) { + size_t pos = distance; + String s{str}; + while (pos < s.size()) { + s.insert(pos, "\n"); + pos += distance + 1; + } + return s; + } else { + return str; + } +} + +template +inline RetString encode_with_line_breaks(String s) { + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +inline RetString encode_pem(String s) { + return encode_with_line_breaks(s); +} + +template +inline RetString encode_mime(String s) { + return encode_with_line_breaks(s); +} + +template +inline RetString encode(String s, bool url) { + return base64_encode(reinterpret_cast(s.data()), s.size(), url); +} + +} // namespace detail + +template +inline RetString base64_encode(const unsigned char* bytes_to_encode, size_t in_len, bool url) { + size_t len_encoded = (in_len + 2) / 3 * 4; + + const unsigned char trailing_char = '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = detail::to_base64_chars[url]; + + RetString ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos + 1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos + 2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); + } else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + if (!url) ret.push_back(trailing_char); + } + } else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + if (!url) ret.push_back(trailing_char); + if (!url) ret.push_back(trailing_char); + } + + pos += 3; + } + + return ret; +} + +namespace detail { + +template +inline RetString decode(const String& encoded_string, bool remove_linebreaks, bool url) { + static_assert(!std::is_same_v); + + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) + return RetString{}; + + if (remove_linebreaks) { + String copy{encoded_string}; + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false, url); + } + + size_t length_of_string = encoded_string.size(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + RetString ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string && encoded_string.at(pos) != '=') { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos + 1), url); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast(((pos_of_char(encoded_string.at(pos + 0), url)) << 2) + ((pos_of_char_1 & 0x30) >> 4))); + + if ((pos + 2 < length_of_string) && + // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos + 2) != '=') { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos + 2), url); + ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); + + if ((pos + 3 < length_of_string) && + encoded_string.at(pos + 3) != '=') { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string.at(pos + 3), url))); + } + } + + pos += 4; + } + + return ret; +} + +} // namespace detail + +template +inline RetString base64_decode(const String& s, bool remove_linebreaks, bool url) { + return detail::decode(s, remove_linebreaks, url); +} + +template +inline RetString base64_encode(const String& s, bool url) { + return detail::encode(s, url); +} + +template +inline RetString base64_encode_pem (const String& s) { + return detail::encode_pem(s); +} + +template +inline RetString base64_encode_mime(const String& s) { + return detail::encode_mime(s); +} + +// Convenient methods for existing Leap uses +inline std::string base64_encode(char const* s, unsigned int len) { + return base64_encode((unsigned char const*)s, len, false); +} + +inline std::vector base64_decode( const std::string& s) { + return detail::decode, std::string>(s, false, false); +} + +inline std::string base64url_encode(const char* s, size_t len) { + return base64_encode((unsigned char const*)s, len, true); +} + +inline std::string base64url_encode(const std::string& s) { + return base64_encode((unsigned char const*)s.data(), s.size(), true); +} + +inline std::vector base64url_decode(const std::string& s) { + return detail::decode>(s, false, true); +} +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/bls_common.hpp b/libraries/libfc/include/fc/crypto/bls_common.hpp new file mode 100644 index 0000000000..132d6822fa --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bls_common.hpp @@ -0,0 +1,34 @@ +#pragma once +#include + +namespace fc::crypto::blslib { + + template + static Container deserialize_base64url(const std::string& data_str) { + using wrapper = checksummed_data; + wrapper wrapped; + + auto bin = fc::base64url_decode(data_str); + fc::datastream unpacker(bin.data(), bin.size()); + fc::raw::unpack(unpacker, wrapped); + FC_ASSERT(!unpacker.remaining(), "decoded base64url length too long"); + auto checksum = wrapper::calculate_checksum(wrapped.data, nullptr); + FC_ASSERT(checksum == wrapped.check); + + return wrapped.data; + } + + template + static std::string serialize_base64url(const Container& data) { + using wrapper = checksummed_data; + wrapper wrapped; + + wrapped.data = data; + wrapped.check = wrapper::calculate_checksum(wrapped.data, nullptr); + auto packed = raw::pack( wrapped ); + auto data_str = fc::base64url_encode( packed.data(), packed.size()); + + return data_str; + } + +} // fc::crypto::blslib diff --git a/libraries/libfc/include/fc/crypto/bls_private_key.hpp b/libraries/libfc/include/fc/crypto/bls_private_key.hpp new file mode 100644 index 0000000000..c99f49346f --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bls_private_key.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include + +namespace fc::crypto::blslib { + + namespace config { + const std::string bls_private_key_prefix = "PVT_BLS_"; + }; + + class bls_private_key + { + public: + + bls_private_key() = default; + bls_private_key( bls_private_key&& ) = default; + bls_private_key( const bls_private_key& ) = default; + explicit bls_private_key(std::span seed ) { + _sk = bls12_381::secret_key(seed); + } + explicit bls_private_key(const std::string& base64urlstr); + + bls_private_key& operator=( const bls_private_key& ) = default; + + std::string to_string() const; + + bls_public_key get_public_key() const; + + bls_signature sign( std::span msg ) const; + bls_signature proof_of_possession() const; + + static bls_private_key generate(); + + private: + std::array _sk; + friend bool operator == ( const bls_private_key& pk1, const bls_private_key& pk2); + friend struct reflector; + }; // bls_private_key + +} // fc::crypto::blslib + +namespace fc { + void to_variant(const crypto::blslib::bls_private_key& var, variant& vo); + + void from_variant(const variant& var, crypto::blslib::bls_private_key& vo); +} // namespace fc + +FC_REFLECT(crypto::blslib::bls_private_key, (_sk) ) diff --git a/libraries/libfc/include/fc/crypto/bls_public_key.hpp b/libraries/libfc/include/fc/crypto/bls_public_key.hpp new file mode 100644 index 0000000000..81a6538fe1 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bls_public_key.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include +#include + + + +namespace fc::crypto::blslib { + + namespace config { + const std::string bls_public_key_prefix = "PUB_BLS_"; + }; + + // Immutable after construction (although operator= is provided). + // Atributes are not const because FC_REFLECT only works for non-const members. + // Provides an efficient wrapper around bls12_381::g1. + // Serialization form: + // Non-Montgomery form and little-endian encoding for the field elements. + // Affine form for the group element (the z component is 1 and not included in the serialization). + // Binary serialization encodes size(96), x component, followed by y component. + // Cached g1 in Jacobian Montgomery is used for efficient BLS math. + // Keeping the serialized data allows for efficient serialization without the expensive conversion + // from Jacobian Montgomery to Affine Non-Montgomery. + class bls_public_key : fc::reflect_init { + public: + bls_public_key() = default; + bls_public_key(bls_public_key&&) = default; + bls_public_key(const bls_public_key&) = default; + bls_public_key& operator=(const bls_public_key& rhs) = default; + bls_public_key& operator=(bls_public_key&& rhs) = default; + + // throws if unable to convert to valid bls12_381::g1 + explicit bls_public_key(std::span affine_non_montgomery_le); + + // affine non-montgomery base64url with bls_public_key_prefix + explicit bls_public_key(const std::string& base64urlstr); + + // affine non-montgomery base64url with bls_public_key_prefix + std::string to_string() const; + + const bls12_381::g1& jacobian_montgomery_le() const { return _jacobian_montgomery_le; } + const std::array& affine_non_montgomery_le() const { return _affine_non_montgomery_le; } + + bool equal(const bls_public_key& pkey) const { + return _jacobian_montgomery_le.equal(pkey._jacobian_montgomery_le); + } + + auto operator<=>(const bls_public_key& rhs) const { + return _affine_non_montgomery_le <=> rhs._affine_non_montgomery_le; + } + auto operator==(const bls_public_key& rhs) const { + return _affine_non_montgomery_le == rhs._affine_non_montgomery_le; + } + + template + friend T& operator<<(T& ds, const bls_public_key& sig) { + // Serialization as variable length array when it is stored as a fixed length array. This makes for easier deserialization by external tools + fc::raw::pack(ds, fc::unsigned_int(static_cast(sizeof(sig._affine_non_montgomery_le)))); + ds.write(reinterpret_cast(sig._affine_non_montgomery_le.data()), sizeof(sig._affine_non_montgomery_le)); + return ds; + } + + template + friend T& operator>>(T& ds, bls_public_key& sig) { + // Serialization as variable length array when it is stored as a fixed length array. This makes for easier deserialization by external tools + fc::unsigned_int size; + fc::raw::unpack( ds, size ); + FC_ASSERT(size.value == sizeof(sig._affine_non_montgomery_le)); + ds.read(reinterpret_cast(sig._affine_non_montgomery_le.data()), sizeof(sig._affine_non_montgomery_le)); + sig._jacobian_montgomery_le = from_affine_bytes_le(sig._affine_non_montgomery_le); + return ds; + } + + static bls12_381::g1 from_affine_bytes_le(const std::array& affine_non_montgomery_le); + private: + std::array _affine_non_montgomery_le{}; + bls12_381::g1 _jacobian_montgomery_le; // cached g1 + }; + +} // fc::crypto::blslib + +namespace fc { + void to_variant(const crypto::blslib::bls_public_key& var, variant& vo); + void from_variant(const variant& var, crypto::blslib::bls_public_key& vo); +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/bls_signature.hpp b/libraries/libfc/include/fc/crypto/bls_signature.hpp new file mode 100644 index 0000000000..d8c2191d4e --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bls_signature.hpp @@ -0,0 +1,145 @@ +#pragma once +#include +#include +#include +#include + + +namespace fc::crypto::blslib { + + namespace config { + const std::string bls_signature_prefix = "SIG_BLS_"; + }; + + // Immutable after construction (although operator= is provided). + // Provides an efficient wrapper around bls12_381::g2. + // Serialization form: + // Non-Montgomery form and little-endian encoding for the field elements. + // Affine form for the group element (the z component is 1 and not included in the serialization). + // Binary serialization encodes size(192), x component, followed by y component. + // Cached g2 in Jacobian Montgomery is used for efficient BLS math. + // Keeping the serialized data allows for efficient serialization without the expensive conversion + // from Jacobian Montgomery to Affine Non-Montgomery. + class bls_signature { + public: + bls_signature() = default; + bls_signature(bls_signature&&) = default; + bls_signature(const bls_signature&) = default; + bls_signature& operator=(const bls_signature&) = default; + bls_signature& operator=(bls_signature&&) = default; + + // throws if unable to convert to valid bls12_381::g2 + explicit bls_signature(std::span affine_non_montgomery_le); + + // affine non-montgomery base64url with bls_signature_prefix + explicit bls_signature(const std::string& base64urlstr); + + // affine non-montgomery base64url with bls_signature_prefix + std::string to_string() const; + + const bls12_381::g2& jacobian_montgomery_le() const { return _jacobian_montgomery_le; } + const std::array& affine_non_montgomery_le() const { return _affine_non_montgomery_le; } + + bool equal(const bls_signature& sig) const { + return _jacobian_montgomery_le.equal(sig._jacobian_montgomery_le); + } + + template + friend T& operator<<(T& ds, const bls_signature& sig) { + // Serialization as variable length array when it is stored as a fixed length array. This makes for easier deserialization by external tools + fc::raw::pack(ds, fc::unsigned_int(static_cast(sizeof(sig._affine_non_montgomery_le)))); + ds.write(reinterpret_cast(sig._affine_non_montgomery_le.data()), sizeof(sig._affine_non_montgomery_le)); + return ds; + } + + // Could use FC_REFLECT, but to make it obvious serialization matches bls_aggregate_signature implement via operator + template + friend T& operator>>(T& ds, bls_signature& sig) { + // Serialization as variable length array when it is stored as a fixed length array. This makes for easier deserialization by external tools + fc::unsigned_int size; + fc::raw::unpack( ds, size ); + FC_ASSERT(size.value == sizeof(sig._affine_non_montgomery_le)); + ds.read(reinterpret_cast(sig._affine_non_montgomery_le.data()), sizeof(sig._affine_non_montgomery_le)); + sig._jacobian_montgomery_le = to_jacobian_montgomery_le(sig._affine_non_montgomery_le); + return ds; + } + + static bls12_381::g2 to_jacobian_montgomery_le(const std::array& affine_non_montgomery_le); + private: + std::array _affine_non_montgomery_le{}; + bls12_381::g2 _jacobian_montgomery_le; // cached g2 + }; + + // See bls_signature comment above + class bls_aggregate_signature { + public: + bls_aggregate_signature() = default; + bls_aggregate_signature(bls_aggregate_signature&&) = default; + bls_aggregate_signature(const bls_aggregate_signature&) = default; + bls_aggregate_signature& operator=(const bls_aggregate_signature&) = default; + bls_aggregate_signature& operator=(bls_aggregate_signature&&) = default; + + // affine non-montgomery base64url with bls_signature_prefix + explicit bls_aggregate_signature(const std::string& base64urlstr); + + explicit bls_aggregate_signature(const bls_signature& sig) + : _jacobian_montgomery_le(sig.jacobian_montgomery_le()) {} + + // aggregate signature into this + void aggregate(const bls_signature& sig) { + _jacobian_montgomery_le.addAssign(sig.jacobian_montgomery_le()); + } + // aggregate signature into this + void aggregate(const bls_aggregate_signature& sig) { + _jacobian_montgomery_le.addAssign(sig.jacobian_montgomery_le()); + } + + // affine non-montgomery base64url with bls_signature_prefix + // Expensive as conversion from Jacobian Montgomery to Affine Non-Montgomery needed + std::string to_string() const; + + const bls12_381::g2& jacobian_montgomery_le() const { return _jacobian_montgomery_le; } + + bool equal( const bls_aggregate_signature& sig) const { + return _jacobian_montgomery_le.equal(sig._jacobian_montgomery_le); + } + + template + friend T& operator<<(T& ds, const bls_aggregate_signature& sig) { + std::array affine_non_montgomery_le = + sig._jacobian_montgomery_le.toAffineBytesLE(bls12_381::from_mont::yes); + // Serialization as variable length array when it is stored as a fixed length array. + // This makes for easier deserialization by external tools + fc::raw::pack(ds, fc::unsigned_int(static_cast(sizeof(affine_non_montgomery_le)))); + ds.write(reinterpret_cast(affine_non_montgomery_le.data()), sizeof(affine_non_montgomery_le)); + return ds; + } + + // Could use FC_REFLECT, but to make it obvious serialization matches bls_signature implement via operator + template + friend T& operator>>(T& ds, bls_aggregate_signature& sig) { + // Serialization as variable length array when it is stored as a fixed length array. + // This makes for easier deserialization by external tools + fc::unsigned_int size; + fc::raw::unpack( ds, size ); + std::array affine_non_montgomery_le; + FC_ASSERT(size.value == sizeof(affine_non_montgomery_le)); + ds.read(reinterpret_cast(affine_non_montgomery_le.data()), sizeof(affine_non_montgomery_le)); + sig._jacobian_montgomery_le = bls_signature::to_jacobian_montgomery_le(affine_non_montgomery_le); + return ds; + } + + private: + bls12_381::g2 _jacobian_montgomery_le; + }; + +} // fc::crypto::blslib + +namespace fc { + + void to_variant(const crypto::blslib::bls_signature& var, variant& vo); + void from_variant(const variant& var, crypto::blslib::bls_signature& vo); + void to_variant(const crypto::blslib::bls_aggregate_signature& var, variant& vo); + void from_variant(const variant& var, crypto::blslib::bls_aggregate_signature& vo); + +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/bls_utils.hpp b/libraries/libfc/include/fc/crypto/bls_utils.hpp new file mode 100644 index 0000000000..6f3ad4f41c --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bls_utils.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include + +namespace fc::crypto::blslib { + + bool verify(const bls_public_key& pubkey, + std::span message, + const bls_signature& signature); + +} // fc::crypto::blslib diff --git a/libraries/libfc/include/fc/crypto/elliptic.hpp b/libraries/libfc/include/fc/crypto/elliptic.hpp index c16e645a4a..cf1fef4305 100644 --- a/libraries/libfc/include/fc/crypto/elliptic.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic.hpp @@ -127,13 +127,8 @@ namespace fc { { return a.get_secret() == b.get_secret(); } - inline friend bool operator!=( const private_key& a, const private_key& b ) - { - return a.get_secret() != b.get_secret(); - } - inline friend bool operator<( const private_key& a, const private_key& b ) - { - return a.get_secret() < b.get_secret(); + inline friend std::strong_ordering operator<=>( const private_key& a, const private_key& b ) { + return a.get_secret() <=> b.get_secret(); } unsigned int fingerprint() const { return get_public_key().fingerprint(); } diff --git a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp index 1ada2a9d64..44a6cb9608 100644 --- a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp @@ -117,13 +117,8 @@ namespace fc { { return a.get_secret() == b.get_secret(); } - inline friend bool operator!=( const private_key& a, const private_key& b ) - { - return a.get_secret() != b.get_secret(); - } - inline friend bool operator<( const private_key& a, const private_key& b ) - { - return a.get_secret() < b.get_secret(); + inline friend std::strong_ordering operator<=>( const private_key& a, const private_key& b ) { + return a.get_secret() <=> b.get_secret(); } private: diff --git a/libraries/libfc/include/fc/crypto/private_key.hpp b/libraries/libfc/include/fc/crypto/private_key.hpp index a52cb992d7..2e753f3de0 100644 --- a/libraries/libfc/include/fc/crypto/private_key.hpp +++ b/libraries/libfc/include/fc/crypto/private_key.hpp @@ -53,7 +53,7 @@ namespace fc { namespace crypto { storage_type _storage; private_key( storage_type&& other_storage ) - :_storage(other_storage) + :_storage(std::move(other_storage)) {} friend bool operator==( const private_key& p1, const private_key& p2 ); diff --git a/libraries/libfc/include/fc/crypto/sha256.hpp b/libraries/libfc/include/fc/crypto/sha256.hpp index dea882413d..39042a9cc3 100644 --- a/libraries/libfc/include/fc/crypto/sha256.hpp +++ b/libraries/libfc/include/fc/crypto/sha256.hpp @@ -1,4 +1,6 @@ #pragma once +#include +#include #include #include #include @@ -22,6 +24,10 @@ class sha256 char* data(); size_t data_size() const { return 256 / 8; } + std::span to_uint8_span() const { + return {reinterpret_cast(data()), reinterpret_cast(data()) + data_size()}; + } + bool empty()const { return (_hash[0] | _hash[1] | _hash[2] | _hash[3]) == 0; } @@ -67,12 +73,10 @@ class sha256 } friend sha256 operator << ( const sha256& h1, uint32_t i ); friend sha256 operator >> ( const sha256& h1, uint32_t i ); - friend bool operator == ( const sha256& h1, const sha256& h2 ); - friend bool operator != ( const sha256& h1, const sha256& h2 ); friend sha256 operator ^ ( const sha256& h1, const sha256& h2 ); - friend bool operator >= ( const sha256& h1, const sha256& h2 ); - friend bool operator > ( const sha256& h1, const sha256& h2 ); - friend bool operator < ( const sha256& h1, const sha256& h2 ); + + friend bool operator == ( const sha256& h1, const sha256& h2 ); + friend std::strong_ordering operator <=> ( const sha256& h1, const sha256& h2 ); uint32_t pop_count()const { @@ -125,6 +129,11 @@ namespace std } }; + inline std::ostream& operator<<(std::ostream& os, const fc::sha256& r) { + os << "sha256(" << r.str() << ")"; + return os; + } + } namespace boost diff --git a/libraries/libfc/include/fc/io/cfile.hpp b/libraries/libfc/include/fc/io/cfile.hpp index 7e79b59942..f3cd05ded1 100644 --- a/libraries/libfc/include/fc/io/cfile.hpp +++ b/libraries/libfc/include/fc/io/cfile.hpp @@ -150,6 +150,14 @@ class cfile { } } + void truncate() { + const int fd = fileno(); + if( -1 == ftruncate(fd, 0) ) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to truncate file, error: " + std::to_string( errno ) ); + } + } + void flush() { if( 0 != fflush( _file.get() ) ) { int err = ferror( _file.get() ); diff --git a/libraries/libfc/include/fc/io/datastream.hpp b/libraries/libfc/include/fc/io/datastream.hpp index 88984a3d4f..b17279481a 100644 --- a/libraries/libfc/include/fc/io/datastream.hpp +++ b/libraries/libfc/include/fc/io/datastream.hpp @@ -295,6 +295,7 @@ inline datastream& operator>>(datastream& ds, uint8_t& d) { ds.read((char*)&d, sizeof(d) ); return ds; } + /* template inline datastream& operator<<(datastream& ds, const boost::multiprecision::number& n) { diff --git a/libraries/libfc/include/fc/io/raw.hpp b/libraries/libfc/include/fc/io/raw.hpp index cc4317f797..e24e1a52f4 100644 --- a/libraries/libfc/include/fc/io/raw.hpp +++ b/libraries/libfc/include/fc/io/raw.hpp @@ -17,6 +17,7 @@ #include #include +#include #include namespace fc { @@ -34,6 +35,8 @@ namespace fc { template void unpack( Stream& s, Int<256>& n ); template void pack( Stream& s, const boost::multiprecision::number& n ); template void unpack( Stream& s, boost::multiprecision::number& n ); + template void pack( Stream& s, const boost::dynamic_bitset& bs ); + template void unpack( Stream& s, boost::dynamic_bitset& bs ); template inline void pack( Stream& s, const Arg0& a0, const Args&... args ) { @@ -564,6 +567,34 @@ namespace fc { } } + template + inline void pack( Stream& s, const boost::dynamic_bitset& value ) { + const auto num_blocks = value.num_blocks(); + FC_ASSERT( num_blocks <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)num_blocks) ); + + // convert bitset to a vector of blocks + std::vector blocks; + blocks.resize(num_blocks); + boost::to_block_range(value, blocks.begin()); + + // pack the blocks + for (const auto& b: blocks) { + fc::raw::pack( s, b ); + } + } + + template + inline void unpack( Stream& s, boost::dynamic_bitset& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + std::vector blocks((size_t)size.value); + for( uint64_t i = 0; i < size.value; ++i ) { + fc::raw::unpack( s, blocks[i] ); + } + value = { blocks.cbegin(), blocks.cend() }; + } + template inline void pack( Stream& s, const std::vector& value ) { FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); diff --git a/libraries/libfc/include/fc/variant_dynamic_bitset.hpp b/libraries/libfc/include/fc/variant_dynamic_bitset.hpp new file mode 100644 index 0000000000..9f337c5277 --- /dev/null +++ b/libraries/libfc/include/fc/variant_dynamic_bitset.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace fc +{ + template void to_variant( const boost::dynamic_bitset& bs, fc::variant& v ) { + auto num_blocks = bs.num_blocks(); + if ( num_blocks > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "number of blocks of dynamic_bitset cannot be greather than MAX_NUM_ARRAY_ELEMENTS" ); + + std::vector blocks(num_blocks); + boost::to_block_range(bs, blocks.begin()); + + v = fc::variant(blocks); + } + + template void from_variant( const fc::variant& v, boost::dynamic_bitset& bs ) { + const std::vector& vars = v.get_array(); + auto num_vars = vars.size(); + if( num_vars > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "number of variants for dynamic_bitset cannot be greather than MAX_NUM_ARRAY_ELEMENTS" ); + + std::vector blocks; + blocks.reserve(num_vars); + for( const auto& var: vars ) { + blocks.push_back( var.as() ); + } + + bs = { blocks.cbegin(), blocks.cend() }; + } +} // namespace fc diff --git a/libraries/libfc/include/fc/variant_object.hpp b/libraries/libfc/include/fc/variant_object.hpp index bd03279d2f..7194f45033 100644 --- a/libraries/libfc/include/fc/variant_object.hpp +++ b/libraries/libfc/include/fc/variant_object.hpp @@ -26,10 +26,10 @@ namespace fc public: entry(); entry( std::string k, variant v ); - entry( entry&& e ); + entry( entry&& e ) noexcept ; entry( const entry& e); entry& operator=(const entry&); - entry& operator=(entry&&); + entry& operator=(entry&&) noexcept ; const std::string& key()const; const variant& value()const; @@ -80,12 +80,12 @@ namespace fc *this = variant_object( std::move(key), variant(std::forward(val)) ); } variant_object( const variant_object& ); - variant_object( variant_object&& ); + variant_object( variant_object&& ) noexcept ; variant_object( const mutable_variant_object& ); variant_object( mutable_variant_object&& ); - variant_object& operator=( variant_object&& ); + variant_object& operator=( variant_object&& ) noexcept ; variant_object& operator=( const variant_object& ); variant_object& operator=( mutable_variant_object&& ); @@ -233,7 +233,7 @@ namespace fc set( std::move(key), variant(std::forward(val)) ); } - mutable_variant_object( mutable_variant_object&& ); + mutable_variant_object( mutable_variant_object&& ) noexcept ; mutable_variant_object( const mutable_variant_object& ); explicit mutable_variant_object( const variant_object& ); /* @@ -242,7 +242,7 @@ namespace fc */ explicit mutable_variant_object( variant_object&& ); - mutable_variant_object& operator=( mutable_variant_object&& ); + mutable_variant_object& operator=( mutable_variant_object&& ) noexcept ; mutable_variant_object& operator=( const mutable_variant_object& ); mutable_variant_object& operator=( const variant_object& ); /** diff --git a/libraries/libfc/src/crypto/base64.cpp b/libraries/libfc/src/crypto/base64.cpp deleted file mode 100644 index ae6669ffdd..0000000000 --- a/libraries/libfc/src/crypto/base64.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include -#include -#include -/* - base64.cpp and base64.h - - Copyright (C) 2004-2008 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ - -namespace fc { - -static constexpr char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; -static constexpr char base64url_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; - -static_assert(sizeof(base64_chars) == sizeof(base64url_chars), "base64 and base64url must have the same amount of chars"); - -static inline void throw_on_nonbase64(unsigned char c, const char* const b64_chars) { - FC_ASSERT(isalnum(c) || (c == b64_chars[sizeof(base64_chars)-3]) || (c == b64_chars[sizeof(base64_chars)-2]), "encountered non-base64 character"); -} - -std::string base64_encode_impl(unsigned char const* bytes_to_encode, unsigned int in_len, const char* const b64_chars) { - - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for(i = 0; (i <4) ; i++) - ret += b64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += b64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} - -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode_impl(bytes_to_encode, in_len, base64_chars); -} - -std::string base64_encode( const std::string& enc ) { - char const* s = enc.c_str(); - return base64_encode( (unsigned char const*)s, enc.size() ); -} - -std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode_impl(bytes_to_encode, in_len, base64url_chars); -} - -std::string base64url_encode( const std::string& enc ) { - char const* s = enc.c_str(); - return base64url_encode( (unsigned char const*)s, enc.size() ); -} - -std::vector base64_decode_impl(std::string_view encoded_string, const char* const b64_chars) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::vector ret; - ret.reserve(in_len / 4 * 3); - - while (in_len-- && encoded_string[in_] != '=') { - throw_on_nonbase64(encoded_string[in_], b64_chars); - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = strchr(b64_chars, char_array_4[i]) - b64_chars; - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret.push_back(char_array_3[i]); - i = 0; - } - } - - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <4; j++) - char_array_4[j] = strchr(b64_chars, char_array_4[j]) - b64_chars; - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]); - } - - return ret; -} - -std::vector base64_decode(std::string_view encoded_string) { - return base64_decode_impl(encoded_string, base64_chars); -} - -std::vector base64url_decode(std::string_view encoded_string) { - return base64_decode_impl(encoded_string, base64url_chars); -} - -} // namespace fc - diff --git a/libraries/libfc/src/crypto/bigint.cpp b/libraries/libfc/src/crypto/bigint.cpp index 8574fa7022..e15d865692 100644 --- a/libraries/libfc/src/crypto/bigint.cpp +++ b/libraries/libfc/src/crypto/bigint.cpp @@ -211,7 +211,7 @@ namespace fc { void to_variant( const bigint& bi, variant& v ) { std::vector ve = bi; - v = fc::variant(base64_encode((unsigned char*)ve.data(),ve.size())); + v = fc::variant(base64_encode((const unsigned char*)ve.data(),ve.size())); } /** decodes the big int as base64 string, or a number */ diff --git a/libraries/libfc/src/crypto/bls_private_key.cpp b/libraries/libfc/src/crypto/bls_private_key.cpp new file mode 100644 index 0000000000..d6de273111 --- /dev/null +++ b/libraries/libfc/src/crypto/bls_private_key.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +namespace fc::crypto::blslib { + + using from_mont = bls12_381::from_mont; + + bls_public_key bls_private_key::get_public_key() const + { + bls12_381::g1 pk = bls12_381::public_key(_sk); + return bls_public_key(pk.toAffineBytesLE(from_mont::yes)); + } + + bls_signature bls_private_key::proof_of_possession() const + { + bls12_381::g2 proof = bls12_381::pop_prove(_sk); + return bls_signature(proof.toAffineBytesLE(from_mont::yes)); + } + + bls_signature bls_private_key::sign( std::span message ) const + { + bls12_381::g2 sig = bls12_381::sign(_sk, message); + return bls_signature(sig.toAffineBytesLE(from_mont::yes)); + } + + bls_private_key bls_private_key::generate() { + std::vector v(32); + rand_bytes(reinterpret_cast(&v[0]), 32); + return bls_private_key(v); + } + + static std::array priv_parse_base64url(const std::string& base64urlstr) + { + auto res = std::mismatch(config::bls_private_key_prefix.begin(), config::bls_private_key_prefix.end(), + base64urlstr.begin()); + FC_ASSERT(res.first == config::bls_private_key_prefix.end(), "BLS Private Key has invalid format : ${str}", ("str", base64urlstr)); + + auto data_str = base64urlstr.substr(config::bls_private_key_prefix.size()); + + std::array bytes = fc::crypto::blslib::deserialize_base64url>(data_str); + + return bytes; + } + + bls_private_key::bls_private_key(const std::string& base64urlstr) + :_sk(priv_parse_base64url(base64urlstr)) + {} + + std::string bls_private_key::to_string() const + { + std::string data_str = fc::crypto::blslib::serialize_base64url>(_sk); + + return config::bls_private_key_prefix + data_str; + } + + bool operator == ( const bls_private_key& pk1, const bls_private_key& pk2) { + return pk1._sk == pk2._sk; + } + +} // fc::crypto::blslib + +namespace fc +{ + void to_variant(const crypto::blslib::bls_private_key& var, variant& vo) + { + vo = var.to_string(); + } + + void from_variant(const variant& var, crypto::blslib::bls_private_key& vo) + { + vo = crypto::blslib::bls_private_key(var.as_string()); + } + +} // fc diff --git a/libraries/libfc/src/crypto/bls_public_key.cpp b/libraries/libfc/src/crypto/bls_public_key.cpp new file mode 100644 index 0000000000..107402db56 --- /dev/null +++ b/libraries/libfc/src/crypto/bls_public_key.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +namespace fc::crypto::blslib { + + inline std::array deserialize_base64url(const std::string& base64urlstr) { + auto res = std::mismatch(config::bls_public_key_prefix.begin(), config::bls_public_key_prefix.end(), base64urlstr.begin()); + FC_ASSERT(res.first == config::bls_public_key_prefix.end(), "BLS Public Key has invalid format : ${str}", ("str", base64urlstr)); + auto data_str = base64urlstr.substr(config::bls_public_key_prefix.size()); + return fc::crypto::blslib::deserialize_base64url>(data_str); + } + + bls12_381::g1 bls_public_key::from_affine_bytes_le(const std::array& affine_non_montgomery_le) { + std::optional g1 = + bls12_381::g1::fromAffineBytesLE(affine_non_montgomery_le, {.check_valid = true, .to_mont = true}); + FC_ASSERT(g1); + return *g1; + } + + inline std::array from_span(std::span affine_non_montgomery_le) { + std::array r; + std::ranges::copy(affine_non_montgomery_le, r.begin()); + return r; + } + + bls_public_key::bls_public_key(std::span affine_non_montgomery_le) + : _affine_non_montgomery_le(from_span(affine_non_montgomery_le)) + , _jacobian_montgomery_le(from_affine_bytes_le(_affine_non_montgomery_le)) { + } + + bls_public_key::bls_public_key(const std::string& base64urlstr) + : _affine_non_montgomery_le(deserialize_base64url(base64urlstr)) + , _jacobian_montgomery_le(from_affine_bytes_le(_affine_non_montgomery_le)) { + } + + std::string bls_public_key::to_string() const { + std::string data_str = fc::crypto::blslib::serialize_base64url>(_affine_non_montgomery_le); + return config::bls_public_key_prefix + data_str; + } + +} // fc::crypto::blslib + +namespace fc { + + void to_variant(const crypto::blslib::bls_public_key& var, variant& vo) { + vo = var.to_string(); + } + + void from_variant(const variant& var, crypto::blslib::bls_public_key& vo) { + vo = crypto::blslib::bls_public_key(var.as_string()); + } + +} // fc diff --git a/libraries/libfc/src/crypto/bls_signature.cpp b/libraries/libfc/src/crypto/bls_signature.cpp new file mode 100644 index 0000000000..f580dca2e0 --- /dev/null +++ b/libraries/libfc/src/crypto/bls_signature.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +namespace fc::crypto::blslib { + + bls12_381::g2 bls_signature::to_jacobian_montgomery_le(const std::array& affine_non_montgomery_le) { + auto g2 = bls12_381::g2::fromAffineBytesLE(affine_non_montgomery_le, {.check_valid = true, .to_mont = true}); + FC_ASSERT(g2, "Invalid bls_signature"); + return *g2; + } + + inline std::array from_span(std::span affine_non_montgomery_le) { + std::array r; + std::ranges::copy(affine_non_montgomery_le, r.begin()); + return r; + } + + bls_signature::bls_signature(std::span affine_non_montgomery_le) + : _affine_non_montgomery_le(from_span(affine_non_montgomery_le)) + , _jacobian_montgomery_le(to_jacobian_montgomery_le(_affine_non_montgomery_le)) { + } + + static std::array sig_parse_base64url(const std::string& base64urlstr) { + try { + auto res = std::mismatch(config::bls_signature_prefix.begin(), config::bls_signature_prefix.end(), base64urlstr.begin()); + FC_ASSERT(res.first == config::bls_signature_prefix.end(), "BLS Signature has invalid format : ${str}", ("str", base64urlstr)); + auto data_str = base64urlstr.substr(config::bls_signature_prefix.size()); + return fc::crypto::blslib::deserialize_base64url>(data_str); + } FC_RETHROW_EXCEPTIONS( warn, "error parsing bls_signature", ("str", base64urlstr ) ) + } + + bls_signature::bls_signature(const std::string& base64urlstr) + : _affine_non_montgomery_le(sig_parse_base64url(base64urlstr)) + , _jacobian_montgomery_le(to_jacobian_montgomery_le(_affine_non_montgomery_le)) { + } + + std::string bls_signature::to_string() const { + std::string data_str = fc::crypto::blslib::serialize_base64url>(_affine_non_montgomery_le); + return config::bls_signature_prefix + data_str; + } + + bls_aggregate_signature::bls_aggregate_signature(const std::string& base64urlstr) + : _jacobian_montgomery_le(bls_signature::to_jacobian_montgomery_le(sig_parse_base64url(base64urlstr))) { + } + + std::string bls_aggregate_signature::to_string() const { + std::array affine_non_montgomery_le = _jacobian_montgomery_le.toAffineBytesLE(bls12_381::from_mont::yes); + std::string data_str = fc::crypto::blslib::serialize_base64url>(affine_non_montgomery_le); + return config::bls_signature_prefix + data_str; + } + +} // fc::crypto::blslib + +namespace fc { + + void to_variant(const crypto::blslib::bls_signature& var, variant& vo) { + vo = var.to_string(); + } + void from_variant(const variant& var, crypto::blslib::bls_signature& vo) { + vo = crypto::blslib::bls_signature(var.as_string()); + } + + void to_variant(const crypto::blslib::bls_aggregate_signature& var, variant& vo) { + vo = var.to_string(); + } + void from_variant(const variant& var, crypto::blslib::bls_aggregate_signature& vo) { + vo = crypto::blslib::bls_aggregate_signature(var.as_string()); + } +} // fc diff --git a/libraries/libfc/src/crypto/bls_utils.cpp b/libraries/libfc/src/crypto/bls_utils.cpp new file mode 100644 index 0000000000..68948bfbf2 --- /dev/null +++ b/libraries/libfc/src/crypto/bls_utils.cpp @@ -0,0 +1,11 @@ +#include + +namespace fc::crypto::blslib { + + bool verify(const bls_public_key& pubkey, + std::span message, + const bls_signature& signature) { + return bls12_381::verify(pubkey.jacobian_montgomery_le(), message, signature.jacobian_montgomery_le()); + }; + +} // fc::crypto::blslib diff --git a/libraries/libfc/src/crypto/sha256.cpp b/libraries/libfc/src/crypto/sha256.cpp index 5e6f226d8c..0a1f05a5f4 100644 --- a/libraries/libfc/src/crypto/sha256.cpp +++ b/libraries/libfc/src/crypto/sha256.cpp @@ -86,17 +86,8 @@ namespace fc { result._hash[3] = h1._hash[3] ^ h2._hash[3]; return result; } - bool operator >= ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; - } - bool operator > ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; - } - bool operator < ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; - } - bool operator != ( const sha256& h1, const sha256& h2 ) { - return !(h1 == h2); + std::strong_ordering operator <=> ( const sha256& h1, const sha256& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) <=> 0; } bool operator == ( const sha256& h1, const sha256& h2 ) { // idea to not use memcmp, from: diff --git a/libraries/libfc/src/variant_object.cpp b/libraries/libfc/src/variant_object.cpp index 61954b595f..af49e79bd6 100644 --- a/libraries/libfc/src/variant_object.cpp +++ b/libraries/libfc/src/variant_object.cpp @@ -9,7 +9,7 @@ namespace fc variant_object::entry::entry() {} variant_object::entry::entry( std::string k, variant v ) : _key(fc::move(k)),_value(fc::move(v)) {} - variant_object::entry::entry( entry&& e ) : _key(fc::move(e._key)),_value(fc::move(e._value)) {} + variant_object::entry::entry( entry&& e ) noexcept : _key(fc::move(e._key)),_value(fc::move(e._value)) {} variant_object::entry::entry( const entry& e ) : _key(e._key),_value(e._value) {} variant_object::entry& variant_object::entry::operator=( const variant_object::entry& e ) { @@ -20,8 +20,7 @@ namespace fc } return *this; } - variant_object::entry& variant_object::entry::operator=( variant_object::entry&& e ) - { + variant_object::entry& variant_object::entry::operator=( variant_object::entry&& e ) noexcept { fc_swap( _key, e._key ); fc_swap( _value, e._value ); return *this; @@ -51,7 +50,7 @@ namespace fc variant_object::iterator variant_object::begin() const { - FC_ASSERT( _key_value != nullptr ); + assert( _key_value != nullptr ); return _key_value->begin(); } @@ -109,14 +108,14 @@ namespace fc variant_object::variant_object( const variant_object& obj ) :_key_value( obj._key_value ) { - FC_ASSERT( _key_value != nullptr ); + assert( _key_value != nullptr ); } - variant_object::variant_object( variant_object&& obj) - : _key_value( fc::move(obj._key_value) ) + variant_object::variant_object( variant_object&& obj) noexcept: + _key_value( fc::move(obj._key_value) ) { obj._key_value = std::make_shared>(); - FC_ASSERT( _key_value != nullptr ); + assert( _key_value != nullptr ); } variant_object::variant_object( const mutable_variant_object& obj ) @@ -127,15 +126,14 @@ namespace fc variant_object::variant_object( mutable_variant_object&& obj ) : _key_value(fc::move(obj._key_value)) { - FC_ASSERT( _key_value != nullptr ); + assert( _key_value != nullptr ); } - variant_object& variant_object::operator=( variant_object&& obj ) - { + variant_object& variant_object::operator=( variant_object&& obj ) noexcept { if (this != &obj) { fc_swap(_key_value, obj._key_value ); - FC_ASSERT( _key_value != nullptr ); + assert( _key_value != nullptr ); } return *this; } @@ -301,7 +299,7 @@ namespace fc { } - mutable_variant_object::mutable_variant_object( mutable_variant_object&& obj ) + mutable_variant_object::mutable_variant_object( mutable_variant_object&& obj ) noexcept : _key_value(fc::move(obj._key_value)) { } @@ -322,7 +320,7 @@ namespace fc return *this; } - mutable_variant_object& mutable_variant_object::operator=( mutable_variant_object&& obj ) + mutable_variant_object& mutable_variant_object::operator=( mutable_variant_object&& obj ) noexcept { if (this != &obj) { diff --git a/libraries/libfc/test/CMakeLists.txt b/libraries/libfc/test/CMakeLists.txt index 8dec2cfc80..4b079be425 100644 --- a/libraries/libfc/test/CMakeLists.txt +++ b/libraries/libfc/test/CMakeLists.txt @@ -8,14 +8,17 @@ add_executable( test_fc crypto/test_webauthn.cpp io/test_cfile.cpp io/test_json.cpp + io/test_raw.cpp io/test_tracked_storage.cpp network/test_message_buffer.cpp scoped_exit/test_scoped_exit.cpp static_variant/test_static_variant.cpp variant/test_variant.cpp + variant/test_variant_dynamic_bitset.cpp variant_estimated_size/test_variant_estimated_size.cpp test_base64.cpp test_escape_str.cpp + test_bls.cpp main.cpp ) target_link_libraries( test_fc fc ) diff --git a/libraries/libfc/test/crypto/test_webauthn.cpp b/libraries/libfc/test/crypto/test_webauthn.cpp index 5a7f429c28..7a30a0ade0 100644 --- a/libraries/libfc/test/crypto/test_webauthn.cpp +++ b/libraries/libfc/test/crypto/test_webauthn.cpp @@ -188,14 +188,12 @@ BOOST_AUTO_TEST_CASE(challenge_wrong_base64_chars) try { }); } FC_LOG_AND_RETHROW(); -//valid signature but replace the padding '=' with '.' +//valid signature but append an invalid base64url character '.' BOOST_AUTO_TEST_CASE(challenge_base64_dot_padding) try { webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); std::string b64 = fc::base64url_encode(d.data(), d.data_size()); - char& end = b64.back(); - BOOST_REQUIRE(end == '='); - end = '.'; + b64.append("."); std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + b64 + "\"}"; @@ -207,13 +205,12 @@ BOOST_AUTO_TEST_CASE(challenge_base64_dot_padding) try { }); } FC_LOG_AND_RETHROW(); -//valid signature but remove padding +//valid signature but append valid paddings == to verify they are ignored BOOST_AUTO_TEST_CASE(challenge_no_padding) try { webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); std::string b64 = fc::base64url_encode(d.data(), d.data_size()); - BOOST_REQUIRE(b64.back() == '='); - b64.resize(b64.size() - 1); + b64.append("=="); std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + b64 + "\"}"; @@ -228,8 +225,6 @@ BOOST_AUTO_TEST_CASE(challenge_extra_bytes) try { webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); std::string b64 = fc::base64url_encode(d.data(), d.data_size()); - BOOST_REQUIRE(b64.back() == '='); - b64.resize(b64.size() - 1); b64.append("abcd"); std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + b64 + "\"}"; diff --git a/libraries/libfc/test/io/test_raw.cpp b/libraries/libfc/test/io/test_raw.cpp new file mode 100644 index 0000000000..0ff902a97a --- /dev/null +++ b/libraries/libfc/test/io/test_raw.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(raw_test_suite) + + +BOOST_AUTO_TEST_CASE(dynamic_bitset_test) +{ + constexpr uint8_t bits = 0b00011110; + boost::dynamic_bitset bs1(8, bits); // bit set size 8 + + char buff[4]; + datastream ds(buff, sizeof(buff)); + + fc::raw::pack( ds, bs1 ); + + boost::dynamic_bitset bs2(8); + ds.seekp(0); + fc::raw::unpack( ds, bs2 ); + + // 0b00011110 + BOOST_CHECK(!bs2.test(0)); + BOOST_CHECK(bs2.test(1)); + BOOST_CHECK(bs2.test(2)); + BOOST_CHECK(bs2.test(2)); + BOOST_CHECK(bs2.test(3)); + BOOST_CHECK(bs2.test(4)); + BOOST_CHECK(!bs2.test(5)); + BOOST_CHECK(!bs2.test(6)); + BOOST_CHECK(!bs2.test(7)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/test_bls.cpp b/libraries/libfc/test/test_bls.cpp new file mode 100644 index 0000000000..c46c202469 --- /dev/null +++ b/libraries/libfc/test/test_bls.cpp @@ -0,0 +1,366 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using std::cout; + +using namespace fc::crypto::blslib; + +BOOST_AUTO_TEST_SUITE(bls_test) + +// can we use BLS stuff? + +// Example seed, used to generate private key. Always use +// a secure RNG with sufficient entropy to generate a seed (at least 32 bytes). +std::vector seed_1 = { 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, + 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, + 12, 62, 89, 110, 182, 9, 44, 20, 254, 22}; + +std::vector seed_2 = { 6, 51, 22, 89, 11, 15, 4, 61, 127, 241, 79, + 26, 88, 52, 1, 6, 18, 79, 10, 8, 36, 182, + 154, 35, 75, 156, 215, 41, 29, 90, 125, 233}; + +std::vector message_1 = { 51, 23, 56, 93, 212, 129, 128, 27, + 251, 12, 42, 129, 210, 9, 34, 98}; // Message is passed in as a byte vector + + +std::vector message_2 = { 16, 38, 54, 125, 71, 214, 217, 78, + 73, 23, 127, 235, 8, 94, 41, 53}; // Message is passed in as a byte vector + +fc::sha256 message_3 = fc::sha256("1097cf48a15ba1c618237d3d79f3c684c031a9844c27e6b95c6d27d8a5f401a1"); + + +//test a single key signature + verification +BOOST_AUTO_TEST_CASE(bls_sig_verif) try { + + bls_private_key sk = bls_private_key(seed_1); + bls_public_key pk = sk.get_public_key(); + + bls_signature signature = sk.sign(message_1); + + // Verify the signature + bool ok = verify(pk, message_1, signature); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + +//test a single key signature + verification of digest_type +BOOST_AUTO_TEST_CASE(bls_sig_verif_digest) try { + + bls_private_key sk = bls_private_key(seed_1); + bls_public_key pk = sk.get_public_key(); + + std::vector v = std::vector(message_3.data(), message_3.data() + 32); + + bls_signature signature = sk.sign(v); + + // Verify the signature + bool ok = verify(pk, v, signature); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +//test a single key signature + verification of hotstuff tuple +BOOST_AUTO_TEST_CASE(bls_sig_verif_hotstuff_types) try { + + bls_private_key sk = bls_private_key(seed_1); + bls_public_key pk = sk.get_public_key(); + + std::string cmt = "cm_prepare"; + uint32_t view_number = 264; + + std::string s_view_number = std::to_string(view_number); + std::string c_s = cmt + s_view_number; + + fc::sha256 h1 = fc::sha256::hash(c_s); + fc::sha256 h2 = fc::sha256::hash( std::make_pair( h1, message_3 ) ); + + std::vector v = std::vector(h2.data(), h2.data() + 32); + + bls_signature signature = sk.sign(v); + + bls12_381::g1 agg_pk = pk.jacobian_montgomery_le(); + bls_aggregate_signature agg_signature{signature}; + + for (int i = 1 ; i< 21 ;i++){ + agg_pk = bls12_381::aggregate_public_keys(std::array{agg_pk, pk.jacobian_montgomery_le()}); + agg_signature.aggregate(signature); + } + + // Verify the signature + bool ok = bls12_381::verify(agg_pk, v, agg_signature.jacobian_montgomery_le()); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +//test public keys + signatures aggregation + verification +BOOST_AUTO_TEST_CASE(bls_agg_sig_verif) try { + + bls_private_key sk1 = bls_private_key(seed_1); + bls_public_key pk1 = sk1.get_public_key(); + + bls_signature sig1 = sk1.sign(message_1); + + bls_private_key sk2 = bls_private_key(seed_2); + bls_public_key pk2 = sk2.get_public_key(); + + bls_signature sig2 = sk2.sign(message_1); + + bls12_381::g1 agg_key = bls12_381::aggregate_public_keys(std::array{pk1.jacobian_montgomery_le(), pk2.jacobian_montgomery_le()}); + bls_aggregate_signature agg_sig; + agg_sig.aggregate(sig1); + agg_sig.aggregate(sig2); + + // Verify the signature + bool ok = bls12_381::verify(agg_key, message_1, agg_sig.jacobian_montgomery_le()); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +//test signature aggregation + aggregate tree verification +BOOST_AUTO_TEST_CASE(bls_agg_tree_verif) try { + + bls_private_key sk1 = bls_private_key(seed_1); + bls_public_key pk1 = sk1.get_public_key(); + + bls_signature sig1 = sk1.sign(message_1); + + bls_private_key sk2 = bls_private_key(seed_2); + bls_public_key pk2 = sk2.get_public_key(); + + bls_signature sig2 = sk2.sign(message_2); + + bls_aggregate_signature agg_sig; + agg_sig.aggregate(sig1); + agg_sig.aggregate(sig2); + + std::vector pubkeys = {pk1.jacobian_montgomery_le(), pk2.jacobian_montgomery_le()}; + std::vector> messages = {message_1, message_2}; + + // Verify the signature + bool ok = bls12_381::aggregate_verify(pubkeys, messages, agg_sig.jacobian_montgomery_le()); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + +//test random key generation, signature + verification +BOOST_AUTO_TEST_CASE(bls_key_gen) try { + + bls_private_key sk = bls_private_key::generate(); + bls_public_key pk = sk.get_public_key(); + + bls_signature signature = sk.sign(message_1); + + // Verify the signature + bool ok = verify(pk, message_1, signature); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +//test wrong key and wrong signature +BOOST_AUTO_TEST_CASE(bls_bad_sig_verif) try { + + bls_private_key sk1 = bls_private_key(seed_1); + bls_public_key pk1 = sk1.get_public_key(); + + bls_signature sig1 = sk1.sign(message_1); + + bls_private_key sk2 = bls_private_key(seed_2); + bls_public_key pk2 = sk2.get_public_key(); + + bls_signature sig2 = sk2.sign(message_1); + + // Verify the signature + bool ok1 = verify(pk1, message_1, sig2); //verify wrong key / signature + bool ok2 = verify(pk2, message_1, sig1); //verify wrong key / signature + + BOOST_CHECK_EQUAL(ok1, false); + BOOST_CHECK_EQUAL(ok2, false); + + +} FC_LOG_AND_RETHROW(); + +//test bls private key base58 encoding / decoding / serialization / deserialization +BOOST_AUTO_TEST_CASE(bls_private_key_serialization) try { + + bls_private_key sk = bls_private_key(seed_1); + + bls_public_key pk = sk.get_public_key(); + + std::string priv_base58_str = sk.to_string(); + + bls_private_key sk2 = bls_private_key(priv_base58_str); + + bls_signature signature = sk2.sign(message_1); + + // Verify the signature + bool ok = verify(pk, message_1, signature); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +//test bls public key and bls signature base58 encoding / decoding / serialization / deserialization +BOOST_AUTO_TEST_CASE(bls_pub_key_sig_serialization) try { + + bls_private_key sk = bls_private_key(seed_1); + bls_public_key pk = sk.get_public_key(); + + bls_signature signature = sk.sign(message_1); + + std::string pk_string = pk.to_string(); + std::string signature_string = signature.to_string(); + + bls_public_key pk2 = bls_public_key(pk_string); + bls_signature signature2 = bls_signature(signature_string); + + bool ok = verify(pk2, message_1, signature2); + + BOOST_CHECK_EQUAL(ok, true); + +} FC_LOG_AND_RETHROW(); + + +BOOST_AUTO_TEST_CASE(bls_binary_keys_encoding_check) try { + + bls_private_key sk = bls_private_key(seed_1); + + bool ok1 = bls_private_key(sk.to_string()) == sk; + + std::string priv_str = sk.to_string(); + + bool ok2 = bls_private_key(priv_str).to_string() == priv_str; + + bls_public_key pk = sk.get_public_key(); + + bool ok3 = bls_public_key(pk.to_string()).equal(pk); + + std::string pub_str = pk.to_string(); + + bool ok4 = bls_public_key(pub_str).to_string() == pub_str; + + bls_signature sig = sk.sign(message_1); + + bool ok5 = bls_signature(sig.to_string()).equal(sig); + + std::string sig_str = sig.to_string(); + + bool ok6 = bls_signature(sig_str).to_string() == sig_str; + + bool ok7 = verify(pk, message_1, bls_signature(sig.to_string())); + bool ok8 = verify(pk, message_1, sig); + + BOOST_CHECK_EQUAL(ok1, true); //succeeds + BOOST_CHECK_EQUAL(ok2, true); //succeeds + BOOST_CHECK_EQUAL(ok3, true); //succeeds + BOOST_CHECK_EQUAL(ok4, true); //succeeds + BOOST_CHECK_EQUAL(ok5, true); //fails + BOOST_CHECK_EQUAL(ok6, true); //succeeds + BOOST_CHECK_EQUAL(ok7, true); //succeeds + BOOST_CHECK_EQUAL(ok8, true); //succeeds + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(bls_regenerate_check) try { + + bls_private_key sk1 = bls_private_key(seed_1); + bls_private_key sk2 = bls_private_key(seed_1); + + BOOST_CHECK_EQUAL(sk1.to_string(), sk2.to_string()); + + bls_public_key pk1 = sk1.get_public_key(); + bls_public_key pk2 = sk2.get_public_key(); + + BOOST_CHECK_EQUAL(pk1.to_string(), pk2.to_string()); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(bls_prefix_encoding_check) try { + + //test no_throw for correctly encoded keys + BOOST_CHECK_NO_THROW(bls_private_key("PVT_BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc")); + BOOST_CHECK_NO_THROW(bls_public_key("PUB_BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg")); + BOOST_CHECK_NO_THROW(bls_signature("SIG_BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg")); + + //test no pivot delimiter + BOOST_CHECK_THROW(bls_private_key("PVTBLSvh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUBBLS82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIGBLSRrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + + //test first prefix validation + BOOST_CHECK_THROW(bls_private_key("XYZ_BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("XYZ_BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("XYZ_BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + + //test second prefix validation + BOOST_CHECK_THROW(bls_private_key("PVT_XYZ_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUB_XYZ_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIG_XYZ_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + + //test missing prefix + BOOST_CHECK_THROW(bls_private_key("vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + + //test incomplete prefix + BOOST_CHECK_THROW(bls_private_key("PVT_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUB_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIG_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_private_key("BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + + //test invalid data / invalid checksum + BOOST_CHECK_THROW(bls_private_key("PVT_BLS_wh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUB_BLS_92P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIG_BLS_SrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_private_key("PVT_BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5zc"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUB_BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhdg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIG_BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJug"), fc::assert_exception); + BOOST_CHECK_THROW(bls_private_key("PVT_BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yd"), fc::assert_exception); + BOOST_CHECK_THROW(bls_public_key("PUB_BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhTg"), fc::assert_exception); + BOOST_CHECK_THROW(bls_signature("SIG_BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJUg"), fc::assert_exception); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(bls_variant) try { + bls_private_key prk("PVT_BLS_vh0bYgBLOLxs_h9zvYNtj20yj8UJxWeFFAtDUW2_pG44e5yc"); + bls_public_key pk("PUB_BLS_82P3oM1u0IEv64u9i4vSzvg1-QDl4Fb2n50Mp8Sk7Fr1Tz0MJypzL39nSd5VPFgFC9WqrjopRbBm1Pf0RkP018fo1k2rXaJY7Wtzd9RKlE8PoQ6XhDm4PyZlIupQg_gOuiMhcg"); + bls_signature sig("SIG_BLS_RrwvP79LxfahskX-ceZpbgrJ1aUkSSIzE2sMFj0twuhK8QwjcGMvT2tZ_-QMHvAV83tWZYOs7SEvoyteCKGD_Tk6YySkw1HONgvVeNWM8ZwuNgonOHkegNNPIXSIvWMTczfkg2lEtEh-ngBa5t9-4CvZ6aOjg29XPVvu6dimzHix-9E0M53YkWZ-gW5GDkkOLoN2FMxjXaELmhuI64xSeSlcWLFfZa6TMVTctBFWsHDXm1ZMkURoB83dokKHEi4OQTbJtg"); + + fc::variant v; + std::string s; + v = prk; + s = fc::json::to_string(v, {}); + BOOST_CHECK_EQUAL(s, "\"" + prk.to_string() + "\""); + + v = pk; + s = fc::json::to_string(v, {}); + BOOST_CHECK_EQUAL(s, "\"" + pk.to_string() + "\""); + + v = sig; + s = fc::json::to_string(v, {}); + BOOST_CHECK_EQUAL(s, "\"" + sig.to_string() + "\""); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/test_hotstuff.cpp.old b/libraries/libfc/test/test_hotstuff.cpp.old new file mode 100644 index 0000000000..4f2ad3060b --- /dev/null +++ b/libraries/libfc/test/test_hotstuff.cpp.old @@ -0,0 +1,106 @@ +#define BOOST_TEST_MODULE hotstuff +#include + +#include + +#include + +fc::sha256 message_1 = fc::sha256("000000000000000118237d3d79f3c684c031a9844c27e6b95c6d27d8a5f401a1"); +fc::sha256 message_2 = fc::sha256("0000000000000002fb2129a8f7c9091ae983bc817002ffab21cd98eab2147029"); + +struct proposal_height { + + fc::sha256 block_id; + + uint32_t phase_counter; + + int operator >(proposal_height x){ + if(block_id>x.block_id || (block_id==x.block_id && phase_counter>x.phase_counter )) return 1; + else return 0; + } + + int operator >=(proposal_height x){ + if(block_id>x.block_id || (block_id==x.block_id && phase_counter>=x.phase_counter )) return 1; + else return 0; + } + + int operator <(proposal_height x){ + return !(*this>=x); + } + + int operator <=(proposal_height x){ + return !(*this>x); + } + + int operator == (proposal_height x){ + if(block_id==x.block_id && phase_counter==x.phase_counter ) return 1; + else return 0; + } + + int operator != (proposal_height x){ + return !(*this==x); + } + + + +}; + +using std::cout; + +BOOST_AUTO_TEST_SUITE(hotstuff) + +BOOST_AUTO_TEST_CASE(hotstuff_1) try { + + proposal_height p1 = {message_1, 0}; + proposal_height p2 = {message_1, 0}; + + BOOST_CHECK_EQUAL(p1 > p2, false); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(hotstuff_2) try { + + proposal_height p1 = {message_1, 1}; + proposal_height p2 = {message_1, 0}; + + BOOST_CHECK_EQUAL(p1 > p2, true); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(hotstuff_3) try { + + proposal_height p1 = {message_1, 1}; + proposal_height p2 = {message_1, 1}; + + BOOST_CHECK_EQUAL(p1 <= p2, true); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(hotstuff_4) try { + + proposal_height p1 = {message_1, 1}; + proposal_height p2 = {message_2, 1}; + + BOOST_CHECK_EQUAL(p1 < p2, true); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(hotstuff_5) try { + + proposal_height p1 = {message_1, 1}; + proposal_height p2 = {message_1, 1}; + + BOOST_CHECK_EQUAL(p1 == p2, true); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(hotstuff_6) try { + + proposal_height p1 = {message_1, 1}; + proposal_height p2 = {message_1, 1}; + + BOOST_CHECK_EQUAL(p1 != p2, false); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/variant/test_variant.cpp b/libraries/libfc/test/variant/test_variant.cpp index 8be5d99232..022f564e1b 100644 --- a/libraries/libfc/test/variant/test_variant.cpp +++ b/libraries/libfc/test/variant/test_variant.cpp @@ -1,8 +1,9 @@ -#include - #include #include #include + +#include + #include using namespace fc; diff --git a/libraries/libfc/test/variant/test_variant_dynamic_bitset.cpp b/libraries/libfc/test/variant/test_variant_dynamic_bitset.cpp new file mode 100644 index 0000000000..e03d9285ca --- /dev/null +++ b/libraries/libfc/test/variant/test_variant_dynamic_bitset.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include + +#include + +using namespace fc; +using std::string; + +BOOST_AUTO_TEST_SUITE(dynamic_bitset_test_suite) + +BOOST_AUTO_TEST_CASE(dynamic_bitset_test) +{ + constexpr uint8_t bits = 0b0000000001010100; + boost::dynamic_bitset bs(16, bits); // 2 blocks of uint8_t + + fc::mutable_variant_object mu; + mu("bs", bs); + + // a vector of 2 blocks + const variants& vars = mu["bs"].get_array(); + BOOST_CHECK_EQUAL(vars.size(), 2u); + + // blocks can be in any order + if (vars[0].as() == bits ) { + BOOST_CHECK_EQUAL(vars[1].as(), 0u); + } else if (vars[1].as() == bits ) { + BOOST_CHECK_EQUAL(vars[0].as(), 0u); + } else { + BOOST_CHECK(false); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/state_history/abi.cpp b/libraries/state_history/abi.cpp index d778d0c7fc..5cffe16fbf 100644 --- a/libraries/state_history/abi.cpp +++ b/libraries/state_history/abi.cpp @@ -33,6 +33,19 @@ extern const char* const state_history_plugin_abi = R"({ { "name": "fetch_deltas", "type": "bool" } ] }, + { + "name": "get_blocks_request_v1", "fields": [ + { "name": "start_block_num", "type": "uint32" }, + { "name": "end_block_num", "type": "uint32" }, + { "name": "max_messages_in_flight", "type": "uint32" }, + { "name": "have_positions", "type": "block_position[]" }, + { "name": "irreversible_only", "type": "bool" }, + { "name": "fetch_block", "type": "bool" }, + { "name": "fetch_traces", "type": "bool" }, + { "name": "fetch_deltas", "type": "bool" }, + { "name": "fetch_finality_data", "type": "bool" } + ] + }, { "name": "get_blocks_ack_request_v0", "fields": [ { "name": "num_messages", "type": "uint32" } @@ -49,6 +62,18 @@ extern const char* const state_history_plugin_abi = R"({ { "name": "deltas", "type": "bytes?" } ] }, + { + "name": "get_blocks_result_v1", "fields": [ + { "name": "head", "type": "block_position" }, + { "name": "last_irreversible", "type": "block_position" }, + { "name": "this_block", "type": "block_position?" }, + { "name": "prev_block", "type": "block_position?" }, + { "name": "block", "type": "bytes?" }, + { "name": "traces", "type": "bytes?" }, + { "name": "deltas", "type": "bytes?" }, + { "name": "finality_data", "type": "bytes?" } + ] + }, { "name": "row", "fields": [ { "name": "present", "type": "bool" }, @@ -546,14 +571,23 @@ extern const char* const state_history_plugin_abi = R"({ { "type": "uint32", "name": "account_cpu_usage_average_window" }, { "type": "uint32", "name": "account_net_usage_average_window" } ] + }, + { + "name": "finality_data", "fields": [ + { "name": "major_version", "type": "uint32" }, + { "name": "minor_version", "type": "uint32" }, + { "name": "active_finalizer_policy_generation", "type": "uint32" }, + { "name": "action_mroot", "type": "checksum256" }, + { "name": "base_digest", "type": "checksum256" } + ] } ], "types": [ { "new_type_name": "transaction_id", "type": "checksum256" } ], "variants": [ - { "name": "request", "types": ["get_status_request_v0", "get_blocks_request_v0", "get_blocks_ack_request_v0"] }, - { "name": "result", "types": ["get_status_result_v0", "get_blocks_result_v0"] }, + { "name": "request", "types": ["get_status_request_v0", "get_blocks_request_v0", "get_blocks_ack_request_v0", "get_blocks_request_v1"] }, + { "name": "result", "types": ["get_status_result_v0", "get_blocks_result_v0", "get_blocks_result_v1"] }, { "name": "action_receipt", "types": ["action_receipt_v0"] }, { "name": "action_trace", "types": ["action_trace_v0", "action_trace_v1"] }, diff --git a/libraries/state_history/include/eosio/state_history/serialization.hpp b/libraries/state_history/include/eosio/state_history/serialization.hpp index b39698291c..9b6bad8e3f 100644 --- a/libraries/state_history/include/eosio/state_history/serialization.hpp +++ b/libraries/state_history/include/eosio/state_history/serialization.hpp @@ -709,16 +709,19 @@ datastream& operator<<(datastream& ds, const history_context_wrapper_sta template datastream& operator<<(datastream& ds, const eosio::state_history::get_blocks_result_v0& obj) { - fc::raw::pack(ds, obj.head); - fc::raw::pack(ds, obj.last_irreversible); - fc::raw::pack(ds, obj.this_block); - fc::raw::pack(ds, obj.prev_block); - history_pack_big_bytes(ds, obj.block); + ds << static_cast(obj); history_pack_big_bytes(ds, obj.traces); history_pack_big_bytes(ds, obj.deltas); return ds; } +template +datastream& operator<<(datastream& ds, const eosio::state_history::get_blocks_result_v1& obj) { + ds << static_cast(obj); + history_pack_big_bytes(ds, obj.finality_data); + return ds; +} + template datastream& operator<<(datastream& ds, const eosio::state_history::get_blocks_result_base& obj) { fc::raw::pack(ds, obj.head); diff --git a/libraries/state_history/include/eosio/state_history/types.hpp b/libraries/state_history/include/eosio/state_history/types.hpp index e86275ad8d..75957702af 100644 --- a/libraries/state_history/include/eosio/state_history/types.hpp +++ b/libraries/state_history/include/eosio/state_history/types.hpp @@ -104,6 +104,10 @@ struct get_blocks_request_v0 { bool fetch_deltas = false; }; +struct get_blocks_request_v1 : get_blocks_request_v0 { + bool fetch_finality_data = false; +}; + struct get_blocks_ack_request_v0 { uint32_t num_messages = 0; }; @@ -121,8 +125,14 @@ struct get_blocks_result_v0 : get_blocks_result_base { std::optional deltas; }; -using state_request = std::variant; -using state_result = std::variant; +struct get_blocks_result_v1 : get_blocks_result_v0 { + std::optional finality_data; +}; + +using state_request = std::variant; +using state_result = std::variant; +using get_blocks_request = std::variant; +using get_blocks_result = std::variant; } // namespace state_history } // namespace eosio @@ -133,7 +143,9 @@ FC_REFLECT(eosio::state_history::block_position, (block_num)(block_id)); FC_REFLECT_EMPTY(eosio::state_history::get_status_request_v0); FC_REFLECT(eosio::state_history::get_status_result_v0, (head)(last_irreversible)(trace_begin_block)(trace_end_block)(chain_state_begin_block)(chain_state_end_block)(chain_id)); FC_REFLECT(eosio::state_history::get_blocks_request_v0, (start_block_num)(end_block_num)(max_messages_in_flight)(have_positions)(irreversible_only)(fetch_block)(fetch_traces)(fetch_deltas)); +FC_REFLECT_DERIVED(eosio::state_history::get_blocks_request_v1, (eosio::state_history::get_blocks_request_v0), (fetch_finality_data)); FC_REFLECT(eosio::state_history::get_blocks_ack_request_v0, (num_messages)); FC_REFLECT(eosio::state_history::get_blocks_result_base, (head)(last_irreversible)(this_block)(prev_block)(block)); FC_REFLECT_DERIVED(eosio::state_history::get_blocks_result_v0, (eosio::state_history::get_blocks_result_base), (traces)(deltas)); +FC_REFLECT_DERIVED(eosio::state_history::get_blocks_result_v1, (eosio::state_history::get_blocks_result_v0), (finality_data)); // clang-format on diff --git a/libraries/testing/contracts/eosio.bios/eosio.bios.abi b/libraries/testing/contracts/eosio.bios/eosio.bios.abi index 02c3449d2e..e6597576a6 100644 --- a/libraries/testing/contracts/eosio.bios/eosio.bios.abi +++ b/libraries/testing/contracts/eosio.bios/eosio.bios.abi @@ -170,6 +170,42 @@ } ] }, + { + "name": "finalizer_authority", + "base": "", + "fields": [ + { + "name": "description", + "type": "string" + }, + { + "name": "weight", + "type": "uint64" + }, + { + "name": "public_key", + "type": "string" + }, + { + "name": "pop", + "type": "string" + } + ] + }, + { + "name": "finalizer_policy", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint64" + }, + { + "name": "finalizers", + "type": "finalizer_authority[]" + } + ] + }, { "name": "key_weight", "base": "", @@ -362,6 +398,16 @@ } ] }, + { + "name": "setfinalizer", + "base": "", + "fields": [ + { + "name": "finalizer_policy", + "type": "finalizer_policy" + } + ] + }, { "name": "setparams", "base": "", @@ -507,6 +553,11 @@ "type": "setcode", "ricardian_contract": "" }, + { + "name": "setfinalizer", + "type": "setfinalizer", + "ricardian_contract": "" + }, { "name": "setparams", "type": "setparams", diff --git a/libraries/testing/contracts/eosio.bios/eosio.bios.cpp b/libraries/testing/contracts/eosio.bios/eosio.bios.cpp index a5718bca17..f6ce5ed517 100644 --- a/libraries/testing/contracts/eosio.bios/eosio.bios.cpp +++ b/libraries/testing/contracts/eosio.bios/eosio.bios.cpp @@ -1,4 +1,6 @@ #include "eosio.bios.hpp" +#include +#include namespace eosiobios { @@ -17,6 +19,69 @@ void bios::setabi( name account, const std::vector& abi ) { } } +void bios::setfinalizer( const finalizer_policy& finalizer_policy ) { + // exensive checks are performed to make sure setfinalizer host function + // will never fail + + require_auth( get_self() ); + + check(finalizer_policy.finalizers.size() <= max_finalizers, "number of finalizers exceeds the maximum allowed"); + check(finalizer_policy.finalizers.size() > 0, "require at least one finalizer"); + + eosio::abi_finalizer_policy abi_finalizer_policy; + abi_finalizer_policy.fthreshold = finalizer_policy.threshold; + abi_finalizer_policy.finalizers.reserve(finalizer_policy.finalizers.size()); + + const std::string pk_prefix = "PUB_BLS"; + const std::string sig_prefix = "SIG_BLS"; + + // use raw affine format (bls_g1 is std::array) for uniqueness check + struct g1_hash { + std::size_t operator()(const eosio::bls_g1& g1) const { + std::hash hash_func; + return hash_func(g1.data()); + } + }; + struct g1_equal { + bool operator()(const eosio::bls_g1& lhs, const eosio::bls_g1& rhs) const { + return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; + } + }; + std::unordered_set unique_finalizer_keys; + + uint64_t weight_sum = 0; + + for (const auto& f: finalizer_policy.finalizers) { + check(f.description.size() <= max_finalizer_description_size, "Finalizer description greater than max allowed size"); + + // basic key format checks + check(f.public_key.substr(0, pk_prefix.length()) == pk_prefix, "public key shoud start with PUB_BLS"); + check(f.pop.substr(0, sig_prefix.length()) == sig_prefix, "proof of possession signature should start with SIG_BLS"); + + // check overflow + check(std::numeric_limits::max() - weight_sum >= f.weight, "sum of weights causes uint64_t overflow"); + weight_sum += f.weight; + + // decode_bls_public_key_to_g1 will fail ("check" function fails) + // if the key is invalid + const auto pk = eosio::decode_bls_public_key_to_g1(f.public_key); + // duplicate key check + check(unique_finalizer_keys.insert(pk).second, "duplicate public key"); + + const auto signature = eosio::decode_bls_signature_to_g2(f.pop); + + // proof of possession of private key check + check(eosio::bls_pop_verify(pk, signature), "proof of possession failed"); + + std::vector pk_vector(pk.begin(), pk.end()); + abi_finalizer_policy.finalizers.emplace_back(eosio::abi_finalizer_authority{f.description, f.weight, std::move(pk_vector)}); + } + + check(finalizer_policy.threshold > weight_sum / 2, "finalizer policy threshold must be greater than half of the sum of the weights"); + + set_finalizers(std::move(abi_finalizer_policy)); +} + void bios::onerror( ignore, ignore> ) { check( false, "the onerror action cannot be called directly" ); } @@ -55,4 +120,4 @@ void bios::reqactivated( const eosio::checksum256& feature_digest ) { check( is_feature_activated( feature_digest ), "protocol feature is not activated" ); } -} \ No newline at end of file +} diff --git a/libraries/testing/contracts/eosio.bios/eosio.bios.hpp b/libraries/testing/contracts/eosio.bios/eosio.bios.hpp index 37c387ab6d..19a7995ea2 100644 --- a/libraries/testing/contracts/eosio.bios/eosio.bios.hpp +++ b/libraries/testing/contracts/eosio.bios/eosio.bios.hpp @@ -125,6 +125,32 @@ namespace eosiobios { (schedule_version)(new_producers)) }; + struct finalizer_authority { + std::string description; + uint64_t weight = 0; // weight that this finalizer's vote has for meeting threshold + std::string public_key; // public key of the finalizer in base64 format + std::string pop; // proof of possession of private key in base64 format + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE(finalizer_authority, (description)(weight)(public_key)(pop)) + }; + + constexpr size_t max_finalizers = 64*1024; + constexpr size_t max_finalizer_description_size = 256; + + /** + * finalizer_policy + * + * List of finalizer authorties along with the threshold + */ + struct finalizer_policy { + uint64_t threshold = 0; // quorum threshold + std::vector finalizers; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE(finalizer_policy, (threshold)(finalizers)); + }; + /** * @defgroup eosiobios eosio.bios * @ingroup eosiocontracts @@ -317,6 +343,15 @@ namespace eosiobios { [[eosio::action]] void setprods( const std::vector& schedule ); + /** + * Propose new finalizer policy that, unless superseded by a later + * finalizer policy, will eventually become the active finalizer policy. + * + * @param finalizer_policy - proposed finalizer policy + */ + [[eosio::action]] + void setfinalizer( const finalizer_policy& finalizer_policy ); + /** * Set the blockchain parameters * @@ -387,6 +422,7 @@ namespace eosiobios { using setpriv_action = action_wrapper<"setpriv"_n, &bios::setpriv>; using setalimits_action = action_wrapper<"setalimits"_n, &bios::setalimits>; using setprods_action = action_wrapper<"setprods"_n, &bios::setprods>; + using setfinalizer_action = action_wrapper<"setfinalizer"_n, &bios::setfinalizer>; using setparams_action = action_wrapper<"setparams"_n, &bios::setparams>; using reqauth_action = action_wrapper<"reqauth"_n, &bios::reqauth>; using activate_action = action_wrapper<"activate"_n, &bios::activate>; diff --git a/libraries/testing/contracts/eosio.bios/eosio.bios.wasm b/libraries/testing/contracts/eosio.bios/eosio.bios.wasm index 4f684c0a62..10aaa977b4 100755 Binary files a/libraries/testing/contracts/eosio.bios/eosio.bios.wasm and b/libraries/testing/contracts/eosio.bios/eosio.bios.wasm differ diff --git a/libraries/testing/include/eosio/testing/bls_utils.hpp b/libraries/testing/include/eosio/testing/bls_utils.hpp new file mode 100644 index 0000000000..147910c59d --- /dev/null +++ b/libraries/testing/include/eosio/testing/bls_utils.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace eosio::testing { + + inline auto get_bls_private_key( eosio::chain::name keyname ) { + auto secret = fc::sha256::hash(keyname.to_string()); + std::vector seed(secret.data_size()); + memcpy(seed.data(), secret.data(), secret.data_size()); + return fc::crypto::blslib::bls_private_key(seed); + } + + inline std::tuple get_bls_key(eosio::chain::name keyname) { + const auto private_key = get_bls_private_key(keyname); + return { private_key, private_key.get_public_key(), private_key.proof_of_possession() }; + } + +} \ No newline at end of file diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index ef559ad985..6ca6dbf922 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -253,6 +253,24 @@ namespace eosio { namespace testing { transaction_trace_ptr set_producer_schedule(const vector& schedule); transaction_trace_ptr set_producers_legacy(const vector& producer_names); + // libtester uses 1 as weight of each of the finalizer, sets (2/3 finalizers + 1) + // as threshold, and makes all finalizers vote QC + std::pair> set_finalizers(const vector& finalizer_names); + + // Finalizer policy input to set up a test: weights, threshold and local finalizers + // which participate voting. + struct finalizer_policy_input { + struct finalizer_info { + account_name name; + uint64_t weight; + }; + + std::vector finalizers; + uint64_t threshold {0}; + std::vector local_finalizers; + }; + 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 = {} ); void set_authority( account_name account, permission_name perm, authority auth, @@ -320,6 +338,7 @@ namespace eosio { namespace testing { const account_name& account ) const; vector get_row_by_account( name code, name scope, name table, const account_name& act ) const; + vector get_row_by_id( name code, name scope, name table, uint64_t id ) const; map get_last_produced_block_map()const { return last_produced_block; }; void set_last_produced_block_map( const map& lpb ) { last_produced_block = lpb; } @@ -383,11 +402,12 @@ namespace eosio { namespace testing { return cfg; } - void schedule_protocol_features_wo_preactivation(const vector feature_digests); - void preactivate_protocol_features(const vector feature_digests); + void schedule_protocol_features_wo_preactivation(const vector& feature_digests); + void preactivate_protocol_features(const vector& feature_digests); void preactivate_builtin_protocol_features(const std::vector& features); void preactivate_all_builtin_protocol_features(); void preactivate_all_but_disable_deferred_trx(); + void preactivate_savanna_protocol_features(); static genesis_state default_genesis() { genesis_state genesis; @@ -399,7 +419,8 @@ namespace eosio { namespace testing { static std::pair default_config(const fc::temp_directory& tempdir, std::optional genesis_max_inline_action_size = std::optional{}) { controller::config cfg; - cfg.blocks_dir = tempdir.path() / config::default_blocks_dir_name; + cfg.finalizers_dir = tempdir.path() / config::default_finalizers_dir_name; + cfg.blocks_dir = tempdir.path() / config::default_blocks_dir_name; cfg.state_dir = tempdir.path() / config::default_state_dir_name; cfg.state_size = 1024*1024*16; cfg.state_guard_size = 0; @@ -438,6 +459,7 @@ namespace eosio { namespace testing { void _start_block(fc::time_point block_time); signed_block_ptr _finish_block(); + void _wait_for_vote_if_needed(controller& c); // Fields: protected: @@ -466,15 +488,15 @@ namespace eosio { namespace testing { } tester(controller::config config, const genesis_state& genesis) { - init(config, genesis); + init(std::move(config), genesis); } tester(controller::config config) { - init(config); + init(std::move(config)); } tester(controller::config config, protocol_feature_set&& pfs, const genesis_state& genesis) { - init(config, std::move(pfs), genesis); + init(std::move(config), std::move(pfs), genesis); } tester(const fc::temp_directory& tempdir, bool use_genesis) { @@ -565,27 +587,14 @@ namespace eosio { namespace testing { FC_ASSERT( vcfg.blocks_dir.filename().generic_string() != "." && vcfg.state_dir.filename().generic_string() != ".", "invalid path names in controller::config" ); + vcfg.finalizers_dir = vcfg.blocks_dir.parent_path() / std::string("v_").append( vcfg.finalizers_dir.filename().generic_string() ); vcfg.blocks_dir = vcfg.blocks_dir.parent_path() / std::string("v_").append( vcfg.blocks_dir.filename().generic_string() ); vcfg.state_dir = vcfg.state_dir.parent_path() / std::string("v_").append( vcfg.state_dir.filename().generic_string() ); vcfg.contracts_console = false; } - static unique_ptr create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog = nullptr) { - unique_ptr validating_node = std::make_unique(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); - validating_node->add_indices(); - if(dmlog) - { - validating_node->enable_deep_mind(dmlog); - } - if (use_genesis) { - validating_node->startup( [](){}, []() { return false; }, genesis ); - } - else { - validating_node->startup( [](){}, []() { return false; } ); - } - return validating_node; - } + static unique_ptr create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog = nullptr); validating_tester(const fc::temp_directory& tempdir, bool use_genesis) { auto def_conf = default_config(tempdir); @@ -619,10 +628,7 @@ namespace eosio { namespace testing { signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { auto sb = _produce_block(skip_time, false); - auto bsf = validating_node->create_block_state_future( sb->calculate_id(), sb ); - controller::block_report br; - validating_node->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ); - + validate_push_block(sb); return sb; } @@ -631,18 +637,16 @@ namespace eosio { namespace testing { } void validate_push_block(const signed_block_ptr& sb) { - auto bsf = validating_node->create_block_state_future( sb->calculate_id(), sb ); + auto btf = validating_node->create_block_handle_future( sb->calculate_id(), sb ); controller::block_report br; - validating_node->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ); + validating_node->push_block( br, btf.get(), {}, trx_meta_cache_lookup{} ); + _wait_for_vote_if_needed(*validating_node); } signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { unapplied_transactions.add_aborted( control->abort_block() ); auto sb = _produce_block(skip_time, true); - auto bsf = validating_node->create_block_state_future( sb->calculate_id(), sb ); - controller::block_report br; - validating_node->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ); - + validate_push_block(sb); return sb; } @@ -653,8 +657,8 @@ namespace eosio { namespace testing { bool validate() { - auto hbh = control->head_block_state()->header; - auto vn_hbh = validating_node->head_block_state()->header; + const auto& hbh = control->head_block_header(); + const auto& vn_hbh = validating_node->head_block_header(); bool ok = control->head_block_id() == validating_node->head_block_id() && hbh.previous == vn_hbh.previous && hbh.timestamp == vn_hbh.timestamp && diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 51672db7ad..719851a767 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ eosio::chain::asset core_from_string(const std::string& s) { namespace eosio { namespace testing { + fc::logger test_logger = fc::logger::get(); + // required by boost::unit_test::data std::ostream& operator<<(std::ostream& os, setup_policy p) { switch(p) { @@ -189,32 +192,32 @@ namespace eosio { namespace testing { } void base_tester::init(controller::config config, const snapshot_reader_ptr& snapshot) { - cfg = config; + cfg = std::move(config); open(snapshot); } void base_tester::init(controller::config config, const genesis_state& genesis) { - cfg = config; + cfg = std::move(config); open(genesis); } void base_tester::init(controller::config config) { - cfg = config; + cfg = std::move(config); open(default_genesis().compute_chain_id()); } void base_tester::init(controller::config config, protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot) { - cfg = config; + cfg = std::move(config); open(std::move(pfs), snapshot); } void base_tester::init(controller::config config, protocol_feature_set&& pfs, const genesis_state& genesis) { - cfg = config; + cfg = std::move(config); open(std::move(pfs), genesis); } void base_tester::init(controller::config config, protocol_feature_set&& pfs) { - cfg = config; + cfg = std::move(config); open(std::move(pfs), default_genesis().compute_chain_id()); } @@ -259,7 +262,9 @@ namespace eosio { namespace testing { builtin_protocol_feature_t::get_sender, builtin_protocol_feature_t::ram_restrictions, builtin_protocol_feature_t::webauthn_key, - builtin_protocol_feature_t::wtmsig_block_signatures + builtin_protocol_feature_t::wtmsig_block_signatures, + builtin_protocol_feature_t::bls_primitives, + builtin_protocol_feature_t::instant_finality }); produce_block(); set_bios_contract(); @@ -321,7 +326,7 @@ namespace eosio { namespace testing { control->add_indices(); if (lambda) lambda(); chain_transactions.clear(); - control->accepted_block.connect([this]( block_signal_params t ){ + control->accepted_block().connect([this]( block_signal_params t ){ const auto& [ block, id ] = t; FC_ASSERT( block ); for( auto receipt : block->transactions ) { @@ -357,11 +362,11 @@ namespace eosio { namespace testing { } void base_tester::push_block(signed_block_ptr b) { - auto bsf = control->create_block_state_future(b->calculate_id(), b); + auto btf = control->create_block_handle_future(b->calculate_id(), b); unapplied_transactions.add_aborted( control->abort_block() ); controller::block_report br; - control->push_block( br, bsf.get(), [this]( const branch_type& forked_branch ) { - unapplied_transactions.add_forked( forked_branch ); + control->push_block( br, btf.get(), [this]( const transaction_metadata_ptr& trx ) { + unapplied_transactions.add_forked( trx ); }, [this]( const transaction_id_type& id ) { return unapplied_transactions.get_trx( id ); } ); @@ -379,7 +384,6 @@ namespace eosio { namespace testing { signed_block_ptr base_tester::_produce_block( fc::microseconds skip_time, bool skip_pending_trxs, bool no_throw, std::vector& traces ) { - auto head = control->head_block_state(); auto head_time = control->head_block_time(); auto next_time = head_time + skip_time; @@ -419,7 +423,7 @@ namespace eosio { namespace testing { void base_tester::_start_block(fc::time_point block_time) { auto head_block_number = control->head_block_num(); - auto producer = control->head_block_state()->get_scheduled_producer(block_time); + auto producer = control->head_active_producers().get_scheduled_producer(block_time); auto last_produced_block_num = control->last_irreversible_block_num(); auto itr = last_produced_block.find(producer.producer_name); @@ -454,21 +458,22 @@ namespace eosio { namespace testing { signed_block_ptr base_tester::_finish_block() { FC_ASSERT( control->is_building_block(), "must first start a block before it can be finished" ); - auto producer = control->head_block_state()->get_scheduled_producer( control->pending_block_time() ); + auto auth = control->pending_block_signing_authority(); + auto producer_name = control->pending_block_producer(); vector signing_keys; - auto default_active_key = get_public_key( producer.producer_name, "active"); - producer.for_each_key([&](const public_key_type& key){ + auto default_active_key = get_public_key( producer_name, "active"); + producer_authority::for_each_key(auth, [&](const public_key_type& key){ const auto& iter = block_signing_private_keys.find(key); if(iter != block_signing_private_keys.end()) { signing_keys.push_back(iter->second); } else if (key == default_active_key) { - signing_keys.emplace_back( get_private_key( producer.producer_name, "active") ); + signing_keys.emplace_back( get_private_key( producer_name, "active") ); } }); controller::block_report br; - control->finalize_block( br, [&]( digest_type d ) { + control->assemble_and_complete_block( br, [&]( digest_type d ) { std::vector result; result.reserve(signing_keys.size()); for (const auto& k: signing_keys) @@ -477,10 +482,23 @@ namespace eosio { namespace testing { return result; } ); - control->commit_block(); - last_produced_block[control->head_block_state()->header.producer] = control->head_block_state()->id; + control->commit_block(br); + last_produced_block[producer_name] = control->head_block_id(); + + _wait_for_vote_if_needed(*control); - return control->head_block_state()->block; + return control->head_block(); + } + + void base_tester::_wait_for_vote_if_needed(controller& c) { + if (c.head_block()->is_proper_svnn_block()) { + // wait for this node's vote to be processed + size_t retrys = 200; + while (!c.node_has_voted_if_finalizer(c.head_block_id()) && --retrys) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + FC_ASSERT(retrys, "Never saw this nodes vote processed before timeout"); + } } signed_block_ptr base_tester::produce_block( std::vector& traces ) { @@ -528,7 +546,7 @@ namespace eosio { namespace testing { void base_tester::produce_min_num_of_blocks_to_spend_time_wo_inactive_prod(const fc::microseconds target_elapsed_time) { fc::microseconds elapsed_time; while (elapsed_time < target_elapsed_time) { - for(uint32_t i = 0; i < control->head_block_state()->active_schedule.producers.size(); i++) { + for(uint32_t i = 0; i < control->active_producers().producers.size(); i++) { const auto time_to_skip = fc::milliseconds(config::producer_repetitions * config::block_interval_ms); produce_block(time_to_skip); elapsed_time += time_to_skip; @@ -1029,8 +1047,11 @@ namespace eosio { namespace testing { return asset(result, asset_symbol); } - vector base_tester::get_row_by_account( name code, name scope, name table, const account_name& act ) const { + return get_row_by_id( code, scope, table, act.to_uint64_t() ); + } + + vector base_tester::get_row_by_id( name code, name scope, name table, uint64_t id ) const { vector data; const auto& db = control->db(); const auto* t_id = db.find( boost::make_tuple( code, scope, table ) ); @@ -1041,8 +1062,8 @@ namespace eosio { namespace testing { const auto& idx = db.get_index(); - auto itr = idx.lower_bound( boost::make_tuple( t_id->id, act.to_uint64_t() ) ); - if ( itr == idx.end() || itr->t_id != t_id->id || act.to_uint64_t() != itr->primary_key ) { + auto itr = idx.lower_bound( boost::make_tuple( t_id->id, id ) ); + if ( itr == idx.end() || itr->t_id != t_id->id || id != itr->primary_key ) { return data; } @@ -1051,7 +1072,6 @@ namespace eosio { namespace testing { return data; } - vector base_tester::to_uint8_vector(const string& s) { vector v(s.size()); copy(s.begin(), s.end(), v.begin()); @@ -1096,10 +1116,10 @@ namespace eosio { namespace testing { auto block = a.control->fetch_block_by_number(i); if( block ) { //&& !b.control->is_known_block(block->id()) ) { - auto bsf = b.control->create_block_state_future( block->calculate_id(), block ); + auto btf = b.control->create_block_handle_future( block->calculate_id(), block ); b.control->abort_block(); controller::block_report br; - b.control->push_block(br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{}); //, eosio::chain::validation_steps::created_block); + b.control->push_block(br, btf.get(), {}, trx_meta_cache_lookup{}); //, eosio::chain::validation_steps::created_block); } } }; @@ -1169,13 +1189,62 @@ namespace eosio { namespace testing { } + 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); + for (const auto& f: finalizer_names) { + finalizers_info.push_back({.name = f, .weight = 1}); + } + + finalizer_policy_input policy_input = { + .finalizers = finalizers_info, + .threshold = num_finalizers * 2 / 3 + 1, + .local_finalizers = finalizer_names + }; + + return set_finalizers(policy_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 ); + + // 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( + fc::mutable_variant_object() + ("description", f.name.to_string() + " description") + ("weight", f.weight) + ("public_key", pubkey.to_string()) + ("pop", pop.to_string())); + } + + control->set_node_finalizer_keys(local_finalizer_keys); + + fc::mutable_variant_object fin_policy_variant; + 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))), + priv_keys }; + } const table_id_object* base_tester::find_table( name code, name scope, name table ) { auto tid = control->db().find(boost::make_tuple(code, scope, table)); return tid; } - void base_tester::schedule_protocol_features_wo_preactivation(const vector feature_digests) { + void base_tester::schedule_protocol_features_wo_preactivation(const vector& feature_digests) { protocol_features_to_be_activated_wo_preactivation.insert( protocol_features_to_be_activated_wo_preactivation.end(), feature_digests.begin(), @@ -1183,13 +1252,26 @@ namespace eosio { namespace testing { ); } - void base_tester::preactivate_protocol_features(const vector feature_digests) { + void base_tester::preactivate_protocol_features(const vector& feature_digests) { for( const auto& feature_digest: feature_digests ) { push_action( config::system_account_name, "activate"_n, config::system_account_name, fc::mutable_variant_object()("feature_digest", feature_digest) ); } } + void base_tester::preactivate_savanna_protocol_features() { + const auto& pfm = control->get_protocol_feature_manager(); + const auto& d = pfm.get_builtin_digest(builtin_protocol_feature_t::instant_finality); + + // dependencies of builtin_protocol_feature_t::instant_finality + const auto& deps = pfm.get_builtin_digest(builtin_protocol_feature_t::disallow_empty_producer_schedule); + const auto& wtm = pfm.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures); + const auto& arv = pfm.get_builtin_digest(builtin_protocol_feature_t::action_return_value); + const auto& bls = pfm.get_builtin_digest(builtin_protocol_feature_t::bls_primitives); + + preactivate_protocol_features( {*deps, *wtm, *arv, *bls, *d} ); + } + void base_tester::preactivate_builtin_protocol_features(const std::vector& builtins) { const auto& pfm = control->get_protocol_feature_manager(); const auto& pfs = pfm.get_protocol_feature_set(); @@ -1277,6 +1359,23 @@ namespace eosio { namespace testing { execute_setup_policy(policy); } + unique_ptr validating_tester::create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog) { + unique_ptr validating_node = std::make_unique(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); + validating_node->add_indices(); + + if(dmlog) + { + validating_node->enable_deep_mind(dmlog); + } + if (use_genesis) { + validating_node->startup( [](){}, []() { return false; }, genesis ); + } + else { + validating_node->startup( [](){}, []() { return false; } ); + } + return validating_node; + } + bool fc_exception_message_is::operator()( const fc::exception& ex ) { auto message = ex.get_log().at( 0 ).get_message(); bool match = (message == expected); diff --git a/plugins/chain_api_plugin/chain.swagger.yaml b/plugins/chain_api_plugin/chain.swagger.yaml index 08d831fad0..5bef6ef0d0 100644 --- a/plugins/chain_api_plugin/chain.swagger.yaml +++ b/plugins/chain_api_plugin/chain.swagger.yaml @@ -186,30 +186,6 @@ paths: schema: description: Returns Nothing - /get_block_header_state: - post: - description: Retrieves the glock header state - operationId: get_block_header_state - requestBody: - content: - application/json: - schema: - type: object - required: - - block_num_or_id - properties: - block_num_or_id: - type: string - description: Provide a block_number or a block_id - - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "https://docs.eosnetwork.com/openapi/v2.0/BlockHeaderState.yaml" - /get_abi: post: description: Retrieves the ABI for a contract based on its account name diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index ee9fadfdc4..fc558536f5 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -132,7 +132,6 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params), CHAIN_RO_CALL_POST(get_block, fc::variant, 200, http_params_types::params_required), // _POST because get_block() returns a lambda to be executed on the http thread pool CHAIN_RO_CALL(get_block_info, 200, http_params_types::params_required), - CHAIN_RO_CALL(get_block_header_state, 200, http_params_types::params_required), CHAIN_RO_CALL_POST(get_account, chain_apis::read_only::get_account_results, 200, http_params_types::params_required), CHAIN_RO_CALL(get_code, 200, http_params_types::params_required), CHAIN_RO_CALL(get_code_hash, 200, http_params_types::params_required), @@ -180,9 +179,8 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL_WITH_400(get_transaction_status, 200, http_params_types::params_required), }, appbase::exec_queue::read_only); } - } - + void chain_api_plugin::plugin_shutdown() {} -} \ No newline at end of file +} diff --git a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp index 4fe1c247d4..496a33649e 100644 --- a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp +++ b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp @@ -16,9 +16,10 @@ namespace eosio::chain::plugin_interface { namespace channels { using rejected_block = channel_decl; using accepted_block_header = channel_decl; - using accepted_block = channel_decl; - using irreversible_block = channel_decl; + using accepted_block = channel_decl; + using irreversible_block = channel_decl; using applied_transaction = channel_decl; + using voted_block = channel_decl; } namespace methods { @@ -33,7 +34,7 @@ namespace eosio::chain::plugin_interface { namespace incoming { namespace methods { // synchronously push a block/trx to a single provider, block_state_legacy_ptr may be null - using block_sync = method_decl&, const block_state_legacy_ptr&), first_provider_policy>; + using block_sync = method_decl&), first_provider_policy>; using transaction_async = method_decl), first_provider_policy>; } } diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index f353b66803..c98d503664 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -154,6 +153,7 @@ class chain_plugin_impl { ,incoming_transaction_async_method(app().get_method()) {} + std::filesystem::path finalizers_dir; std::filesystem::path blocks_dir; std::filesystem::path state_dir; bool readonly = false; @@ -193,7 +193,6 @@ class chain_plugin_impl { std::optional applied_transaction_connection; std::optional block_start_connection; - std::optional _account_query_db; std::optional _trx_retry_db; chain_apis::trx_finality_status_processing_ptr _trx_finality_status_processing; @@ -267,6 +266,8 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip "All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore.") ("state-dir", bpo::value()->default_value(config::default_state_dir_name), "the location of the state directory (absolute path or relative to application data dir)") + ("finalizers-dir", bpo::value()->default_value(config::default_finalizers_dir_name), + "the location of the finalizers safety data directory (absolute path or relative to application data dir)") ("protocol-features-dir", bpo::value()->default_value("protocol_features"), "the location of the protocol_features directory (absolute path or relative to application config dir)") ("checkpoint", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") @@ -540,6 +541,14 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { } } + if( options.count( "finalizers-dir" )) { + auto fd = options.at( "finalizers-dir" ).as(); + if( fd.is_relative()) + finalizers_dir = app().data_dir() / fd; + else + finalizers_dir = fd; + } + if( options.count( "blocks-dir" )) { auto bld = options.at( "blocks-dir" ).as(); if( bld.is_relative()) @@ -593,6 +602,7 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { abi_serializer_max_time_us = fc::microseconds(options.at("abi-serializer-max-time-ms").as() * 1000); + chain_config->finalizers_dir = finalizers_dir; chain_config->blocks_dir = blocks_dir; chain_config->state_dir = state_dir; chain_config->read_only = readonly; @@ -1005,12 +1015,12 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { } ); // relay signals to channels - accepted_block_header_connection = chain->accepted_block_header.connect( + accepted_block_header_connection = chain->accepted_block_header().connect( [this]( const block_signal_params& t ) { accepted_block_header_channel.publish( priority::medium, t ); } ); - accepted_block_connection = chain->accepted_block.connect( [this]( const block_signal_params& t ) { + accepted_block_connection = chain->accepted_block().connect( [this]( const block_signal_params& t ) { const auto& [ block, id ] = t; if (_account_query_db) { _account_query_db->commit_block(block); @@ -1027,7 +1037,7 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { accepted_block_channel.publish( priority::high, t ); } ); - irreversible_block_connection = chain->irreversible_block.connect( [this]( const block_signal_params& t ) { + irreversible_block_connection = chain->irreversible_block().connect( [this]( const block_signal_params& t ) { const auto& [ block, id ] = t; if (_trx_retry_db) { @@ -1041,7 +1051,7 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { irreversible_block_channel.publish( priority::low, t ); } ); - applied_transaction_connection = chain->applied_transaction.connect( + applied_transaction_connection = chain->applied_transaction().connect( [this]( std::tuple t ) { const auto& [ trace, ptrx ] = t; if (_account_query_db) { @@ -1060,7 +1070,7 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { } ); if (_trx_finality_status_processing || _trx_retry_db) { - block_start_connection = chain->block_start.connect( + block_start_connection = chain->block_start().connect( [this]( uint32_t block_num ) { if (_trx_retry_db) { _trx_retry_db->on_block_start(block_num); @@ -1072,7 +1082,6 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { } chain->add_indices(); } FC_LOG_AND_RETHROW() - } void chain_plugin::plugin_initialize(const variables_map& options) { @@ -1140,6 +1149,7 @@ void chain_plugin_impl::plugin_shutdown() { applied_transaction_connection.reset(); block_start_connection.reset(); chain.reset(); + dlog("exit shutdown"); } void chain_plugin::plugin_shutdown() { @@ -1177,8 +1187,8 @@ chain_apis::read_only chain_plugin::get_read_only_api(const fc::microseconds& ht } -bool chain_plugin::accept_block(const signed_block_ptr& block, const block_id_type& id, const block_state_legacy_ptr& bsp ) { - return my->incoming_block_sync_method(block, id, bsp); +bool chain_plugin::accept_block(const signed_block_ptr& block, const block_id_type& id, const std::optional& obt ) { + return my->incoming_block_sync_method(block, id, obt); } void chain_plugin::accept_transaction(const chain::packed_transaction_ptr& trx, next_function next) { @@ -1794,9 +1804,9 @@ read_only::get_producers( const read_only::get_producers_params& params, const f read_only::get_producer_schedule_result read_only::get_producer_schedule( const read_only::get_producer_schedule_params& p, const fc::time_point& ) const { read_only::get_producer_schedule_result result; to_variant(db.active_producers(), result.active); - if(!db.pending_producers().producers.empty()) - to_variant(db.pending_producers(), result.pending); - auto proposed = db.proposed_producers(); + if (const auto* pending = db.next_producers()) // not applicable for instant-finality + to_variant(*pending, result.pending); + auto proposed = db.proposed_producers_legacy(); // empty for instant-finality if(proposed && !proposed->producers.empty()) to_variant(*proposed, result.proposed); return result; @@ -1978,9 +1988,9 @@ fc::variant read_only::convert_block( const chain::signed_block_ptr& block, abi_ fc::variant read_only::get_block_info(const read_only::get_block_info_params& params, const fc::time_point&) const { - signed_block_ptr block; + std::optional block; try { - block = db.fetch_block_by_number( params.block_num ); + block = db.fetch_block_header_by_number( params.block_num ); } catch (...) { // assert below will handle the invalid block num } @@ -2005,32 +2015,10 @@ fc::variant read_only::get_block_info(const read_only::get_block_info_params& pa ("ref_block_prefix", ref_block_prefix); } -fc::variant read_only::get_block_header_state(const get_block_header_state_params& params, const fc::time_point&) const { - block_state_legacy_ptr b; - std::optional block_num; - std::exception_ptr e; - try { - block_num = fc::to_uint64(params.block_num_or_id); - } catch( ... ) {} - - if( block_num ) { - b = db.fetch_block_state_by_number(*block_num); - } else { - try { - b = db.fetch_block_state_by_id(fc::variant(params.block_num_or_id).as()); - } EOS_RETHROW_EXCEPTIONS(chain::block_id_type_exception, "Invalid block ID: ${block_num_or_id}", ("block_num_or_id", params.block_num_or_id)) - } - - EOS_ASSERT( b, unknown_block_exception, "Could not find reversible block: ${block}", ("block", params.block_num_or_id)); - - fc::variant vo; - fc::to_variant( static_cast(*b), vo ); - return vo; -} - void read_write::push_block(read_write::push_block_params&& params, next_function next) { try { - app().get_method()(std::make_shared( std::move(params) ), std::optional{}, block_state_legacy_ptr{}); + auto b = std::make_shared( std::move(params) ); + app().get_method()(b, b->calculate_id(), std::optional{}); } catch ( boost::interprocess::bad_alloc& ) { handle_db_exhaustion(); } catch ( const std::bad_alloc& ) { diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 2a45273775..45576abe0e 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -1,4 +1,9 @@ #pragma once + +#include +#include +#include + #include #include #include @@ -12,15 +17,11 @@ #include #include #include +#include #include #include -#include -#include -#include - -#include #include namespace fc { class variant; } @@ -408,12 +409,6 @@ class read_only : public api_base { fc::variant get_block_info(const get_block_info_params& params, const fc::time_point& deadline) const; - struct get_block_header_state_params { - string block_num_or_id; - }; - - fc::variant get_block_header_state(const get_block_header_state_params& params, const fc::time_point& deadline) const; - struct get_table_rows_params { bool json = false; name code; @@ -978,7 +973,7 @@ class chain_plugin : public plugin { chain_apis::read_write get_read_write_api(const fc::microseconds& http_max_response_time); chain_apis::read_only get_read_only_api(const fc::microseconds& http_max_response_time) const; - bool accept_block( const chain::signed_block_ptr& block, const chain::block_id_type& id, const chain::block_state_legacy_ptr& bsp ); + bool accept_block( const chain::signed_block_ptr& block, const chain::block_id_type& id, const std::optional& obt ); void accept_transaction(const chain::packed_transaction_ptr& trx, chain::plugin_interface::next_function next); // Only call this after plugin_initialize()! @@ -1027,7 +1022,6 @@ FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_params, FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_results, (activated_protocol_features)(more) ) FC_REFLECT(eosio::chain_apis::read_only::get_raw_block_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_info_params, (block_num)) -FC_REFLECT(eosio::chain_apis::read_only::get_block_header_state_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_params, (block_num_or_id)(include_extensions)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_result, (id)(signed_block_header)(block_extensions)) diff --git a/plugins/chain_plugin/test/test_account_query_db.cpp b/plugins/chain_plugin/test/test_account_query_db.cpp index cd02c1627a..4bda8dfc5d 100644 --- a/plugins/chain_plugin/test/test_account_query_db.cpp +++ b/plugins/chain_plugin/test/test_account_query_db.cpp @@ -37,7 +37,7 @@ BOOST_FIXTURE_TEST_CASE(newaccount_test, validating_tester) { try { auto aq_db = account_query_db(*control); //link aq_db to the `accepted_block` signal on the controller - auto c2 = control->accepted_block.connect([&](const block_signal_params& t) { + auto c2 = control->accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; aq_db.commit_block( block ); }); @@ -63,7 +63,7 @@ BOOST_FIXTURE_TEST_CASE(updateauth_test, validating_tester) { try { auto aq_db = account_query_db(*control); //link aq_db to the `accepted_block` signal on the controller - auto c = control->accepted_block.connect([&](const block_signal_params& t) { + auto c = control->accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; aq_db.commit_block( block ); }); @@ -98,7 +98,7 @@ BOOST_FIXTURE_TEST_CASE(updateauth_test_multi_threaded, validating_tester) { try auto aq_db = account_query_db(*control); //link aq_db to the `accepted_block` signal on the controller - auto c = control->accepted_block.connect([&](const block_signal_params& t) { + auto c = control->accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; aq_db.commit_block( block ); }); @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(future_fork_test) { try { auto aq_db = account_query_db(*node_a.control); //link aq_db to the `accepted_block` signal on the controller - auto c = node_a.control->accepted_block.connect([&](const block_signal_params& t) { + auto c = node_a.control->accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; aq_db.commit_block( block ); }); @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(fork_test) { try { auto aq_db = account_query_db(*node_a.control); //link aq_db to the `accepted_block` signal on the controller - auto c = node_a.control->accepted_block.connect([&](const block_signal_params& t) { + auto c = node_a.control->accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; aq_db.commit_block( block ); }); diff --git a/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp b/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp index 770fe31e46..5bff8f075c 100644 --- a/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp +++ b/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp @@ -78,13 +78,13 @@ chain::block_id_type make_block_id( uint32_t block_num ) { return block_id; } -chain::transaction_trace_ptr make_transaction_trace( const packed_transaction_ptr trx, uint32_t block_number, const eosio::chain::block_state_legacy_ptr& bs_ptr, +chain::transaction_trace_ptr make_transaction_trace( const packed_transaction_ptr trx, uint32_t block_number, const eosio::chain::signed_block_ptr& b_ptr, chain::transaction_receipt_header::status_enum status = eosio::chain::transaction_receipt_header::executed ) { return std::make_shared(chain::transaction_trace{ trx->id(), block_number, chain::block_timestamp_type(fc::time_point::now()), - bs_ptr ? bs_ptr->id : std::optional {}, + b_ptr ? b_ptr->calculate_id() : std::optional {}, chain::transaction_receipt_header{status}, fc::microseconds(0), 0, @@ -98,7 +98,7 @@ chain::transaction_trace_ptr make_transaction_trace( const packed_transaction_pt }); } -auto make_block_state( uint32_t block_num ) { +auto make_block( uint32_t block_num ) { static uint64_t unique_num = 0; ++unique_num; chain::block_id_type block_id = make_block_id(block_num); @@ -113,43 +113,11 @@ auto make_block_state( uint32_t block_num ) { auto priv_key = get_private_key( block->producer, "active" ); auto pub_key = get_public_key( block->producer, "active" ); - auto prev = std::make_shared(); - auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), prev->blockroot_merkle.get_root())); - auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, prev->pending_schedule.schedule_hash )); + auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), block_id_type{})); + auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, digest_type{} )); block->producer_signature = priv_key.sign( sig_digest ); - std::vector signing_keys; - signing_keys.emplace_back( priv_key ); - auto signer = [&]( chain::digest_type d ) { - std::vector result; - result.reserve( signing_keys.size()); - for( const auto& k: signing_keys ) - result.emplace_back( k.sign( d )); - return result; - }; - chain::pending_block_header_state_legacy pbhs; - pbhs.producer = block->producer; - pbhs.timestamp = block->timestamp; - pbhs.previous = block->previous; - chain::producer_authority_schedule schedule = - {0, {chain::producer_authority{block->producer, - chain::block_signing_authority_v0{1, {{pub_key, 1}}}}}}; - pbhs.active_schedule = schedule; - pbhs.valid_block_signing_authority = chain::block_signing_authority_v0{1, {{pub_key, 1}}}; - auto bsp = std::make_shared( - std::move( pbhs ), - std::move( block ), - deque(), - chain::protocol_feature_set(), - []( chain::block_timestamp_type timestamp, - const fc::flat_set& cur_features, - const std::vector& new_features ) {}, - signer - ); - bsp->id = block_id; - bsp->block_num = block_num; - - return bsp; + return block; } std::string set_now(const char* date, const char* time) { @@ -172,9 +140,9 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { using trx_deque = eosio::chain::deque< std::tuple< chain::transaction_trace_ptr, packed_transaction_ptr > >; uint32_t bn = 20; - auto add = [&bn, &status](trx_deque& trx_pairs, const eosio::chain::block_state_legacy_ptr& bs_ptr) { + auto add = [&bn, &status](trx_deque& trx_pairs, const eosio::chain::signed_block_ptr& b_ptr) { auto trx = make_unique_trx(fc::seconds(2)); - auto trace = make_transaction_trace( trx, bn, bs_ptr); + auto trace = make_transaction_trace( trx, bn, b_ptr); trx_pairs.push_back(std::tuple(trace, trx)); status.signal_applied_transaction(trace, trx); }; @@ -183,12 +151,12 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { // Create speculative block to begin applying transactions locally status.signal_block_start(bn); - const eosio::chain::block_state_legacy_ptr no_bs; + const eosio::chain::signed_block_ptr no_b; - add(trx_pairs_20, no_bs); - add(trx_pairs_20, no_bs); - add(trx_pairs_20, no_bs); - add(trx_pairs_20, no_bs); + add(trx_pairs_20, no_b); + add(trx_pairs_20, no_b); + add(trx_pairs_20, no_b); + add(trx_pairs_20, no_b); auto cs = status.get_chain_state(); BOOST_CHECK(cs.head_id == eosio::chain::block_id_type{}); @@ -237,62 +205,62 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { //Make a real block start. Pull these before any updates to the trx/trace objects. // send block 20 - const auto bs_20 = make_block_state(bn); + const auto b_20 = make_block(bn); status.signal_block_start(bn); for (const auto& trx_tuple : trx_pairs_20) { const auto& trace = std::get<0>(trx_tuple); const auto& txn = std::get<1>(trx_tuple); - trace->producer_block_id = bs_20->id; - trace->block_time = bs_20->block->timestamp; + trace->producer_block_id = b_20->calculate_id(); + trace->block_time = b_20->timestamp; status.signal_applied_transaction(trace, txn); } // and 2 new transactions const auto block_20_time = set_now("2022-04-04", "04:44:44.500"); - add(trx_pairs_20, bs_20); - add(trx_pairs_20, bs_20); - status.signal_accepted_block(bs_20->block, bs_20->id); + add(trx_pairs_20, b_20); + add(trx_pairs_20, b_20); + status.signal_accepted_block(b_20, b_20->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_20->id); + BOOST_CHECK(cs.head_id == b_20->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_20[0])->producer_block_id); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_20[1])->producer_block_id); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_20[2])->producer_block_id); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_20[3])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_20->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_20->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_20->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_20->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK(fc::time_point_sec(ts->expiration) == (std::get<1>(trx_pairs_20[1])->expiration())); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -316,45 +284,45 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { const auto block_21_time = set_now("2022-04-04", "04:44:45.000"); trx_deque trx_pairs_21; bn = 21; - const auto bs_21 = make_block_state(bn); + const auto b_21 = make_block(bn); status.signal_block_start(bn); fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug); - add(trx_pairs_21, bs_21); - status.signal_accepted_block(bs_21->block, bs_21->id); + add(trx_pairs_21, b_21); + status.signal_accepted_block(b_21, b_21->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_21->id); + BOOST_CHECK(cs.head_id == b_21->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_21[0])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_21->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_21->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_20->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_20->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -374,8 +342,8 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -386,45 +354,45 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { trx_deque trx_pairs_22; bn = 22; - const auto bs_22 = make_block_state(bn); + const auto b_22 = make_block(bn); status.signal_block_start(bn); - add(trx_pairs_22, bs_22); - status.signal_accepted_block(bs_22->block, bs_22->id); + add(trx_pairs_22, b_22); + status.signal_accepted_block(b_22, b_22->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_22->id); + BOOST_CHECK(cs.head_id == b_22->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_22[0])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_22->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_22->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_20->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_20->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -444,65 +412,62 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_22[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22->block->timestamp); + BOOST_CHECK(ts->block_id == b_22->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); - - - // send block 22 const auto block_22_alt_time = set_now("2022-04-04", "04:44:46.000"); trx_deque trx_pairs_22_alt; bn = 22; - const auto bs_22_alt = make_block_state(bn); + const auto b_22_alt = make_block(bn); status.signal_block_start(bn); - add(trx_pairs_22_alt, bs_22_alt); - status.signal_accepted_block(bs_22_alt->block, bs_22_alt->id); + add(trx_pairs_22_alt, b_22_alt); + status.signal_accepted_block(b_22_alt, b_22_alt->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_22_alt->id); + BOOST_CHECK(cs.head_id == b_22_alt->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_22_alt[0])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_22_alt->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_22_alt->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_20->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_20->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -522,22 +487,22 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_22[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22->block->timestamp); + BOOST_CHECK(ts->block_id == b_22->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_22_alt[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_22_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_alt_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -549,45 +514,45 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { trx_deque trx_pairs_19; bn = 19; - const auto bs_19 = make_block_state(bn); + const auto b_19 = make_block(bn); status.signal_block_start(bn); - add(trx_pairs_19, bs_19); - status.signal_accepted_block(bs_19->block, bs_19->id); + add(trx_pairs_19, b_19); + status.signal_accepted_block(b_19, b_19->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_19->id); + BOOST_CHECK(cs.head_id == b_19->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_19[0])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_19->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_19->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_19->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_19->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_20->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_20->block->timestamp); + BOOST_CHECK(ts->block_id == b_20->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_20->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); @@ -607,30 +572,30 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug); ts = status.get_trx_state(std::get<1>(trx_pairs_22[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22->block->timestamp); + BOOST_CHECK(ts->block_id == b_22->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_time); BOOST_CHECK_EQUAL(ts->status, "FAILED"); ts = status.get_trx_state(std::get<1>(trx_pairs_22_alt[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_22_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_alt_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_19[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19->block->timestamp); + BOOST_CHECK(ts->block_id == b_19->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_19_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -647,62 +612,62 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { trx_pairs_19_alt.push_back(trx_pairs_20[3]); trx_pairs_19_alt.push_back(hold_pairs[0]); - const auto bs_19_alt = make_block_state(bn); - // const auto bs_19_alt = make_block_state(make_block_id(bn), std::vector{}); + const auto b_19_alt = make_block(bn); + // const auto b_19_alt = make_block(make_block_id(bn), std::vector{}); status.signal_block_start(bn); for (const auto& trx_tuple : trx_pairs_19_alt) { const auto& trace = std::get<0>(trx_tuple); const auto& txn = std::get<1>(trx_tuple); - trace->producer_block_id = bs_19_alt->id; - trace->block_time = bs_19_alt->block->timestamp; + trace->producer_block_id = b_19_alt->calculate_id(); + trace->block_time = b_19_alt->timestamp; status.signal_applied_transaction(trace, txn); } - status.signal_accepted_block(bs_19_alt->block, bs_19_alt->id); + status.signal_accepted_block(b_19_alt, b_19_alt->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_19_alt->id); + BOOST_CHECK(cs.head_id == b_19_alt->calculate_id()); BOOST_CHECK(cs.head_id == *std::get<0>(trx_pairs_19[0])->producer_block_id); - BOOST_CHECK(cs.head_block_timestamp == bs_19_alt->block->timestamp); + BOOST_CHECK(cs.head_block_timestamp == b_19_alt->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_19_alt->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_19_alt->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); ts = status.get_trx_state(std::get<1>(hold_pairs[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -715,30 +680,30 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug); ts = status.get_trx_state(std::get<1>(trx_pairs_22[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22->block->timestamp); + BOOST_CHECK(ts->block_id == b_22->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_22_alt[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_22_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_alt_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_19[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_19_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -750,47 +715,47 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { BOOST_REQUIRE(!ts); // irreversible - status.signal_irreversible_block(bs_19_alt->block, bs_19_alt->id); + status.signal_irreversible_block(b_19_alt, b_19_alt->calculate_id()); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == bs_19_alt->id); - BOOST_CHECK(cs.irr_id == bs_19_alt->id); - BOOST_CHECK(cs.irr_block_timestamp == bs_19_alt->block->timestamp); - BOOST_CHECK(cs.earliest_tracked_block_id == bs_19_alt->id); + BOOST_CHECK(cs.head_id == b_19_alt->calculate_id()); + BOOST_CHECK(cs.irr_id == b_19_alt->calculate_id()); + BOOST_CHECK(cs.irr_block_timestamp == b_19_alt->timestamp); + BOOST_CHECK(cs.earliest_tracked_block_id == b_19_alt->calculate_id()); ts = status.get_trx_state(std::get<1>(trx_pairs_20[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[1])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[2])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); ts = status.get_trx_state(std::get<1>(trx_pairs_20[3])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_20_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); ts = status.get_trx_state(std::get<1>(hold_pairs[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); @@ -803,30 +768,30 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { ts = status.get_trx_state(std::get<1>(trx_pairs_21[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_21->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_21->block->timestamp); + BOOST_CHECK(ts->block_id == b_21->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_21->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_21_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug); ts = status.get_trx_state(std::get<1>(trx_pairs_22[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22->block->timestamp); + BOOST_CHECK(ts->block_id == b_22->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_22_alt[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_22_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_22_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_22_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_22_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_22_alt_time); BOOST_CHECK_EQUAL(ts->status, "FORKED_OUT"); ts = status.get_trx_state(std::get<1>(trx_pairs_19[0])->id()); BOOST_REQUIRE(ts); - BOOST_CHECK(ts->block_id == bs_19_alt->id); - BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == bs_19_alt->block->timestamp); + BOOST_CHECK(ts->block_id == b_19_alt->calculate_id()); + BOOST_CHECK(block_timestamp_type(ts->block_timestamp) == b_19_alt->timestamp); BOOST_CHECK_EQUAL(ts->received.to_iso_string(), block_19_time); BOOST_CHECK_EQUAL(ts->status, "IRREVERSIBLE"); @@ -834,7 +799,7 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { namespace { using trx_deque = eosio::chain::deque< std::tuple< chain::transaction_trace_ptr, packed_transaction_ptr > >; - const eosio::chain::block_state_legacy_ptr no_bs; + const eosio::chain::signed_block_ptr no_b; struct block_frame { static uint32_t last_used_block_num; @@ -844,7 +809,7 @@ namespace { const std::string time; trx_deque pre_block; trx_deque block; - chain::block_state_legacy_ptr bs; + chain::signed_block_ptr b; std::string context; block_frame(trx_finality_status_processing& finality_status, const char* block_time, uint32_t block_num = 0) @@ -854,14 +819,14 @@ namespace { block_frame::last_used_block_num = bn; for (uint32_t i = 0; i < block_frame::num; ++i) { auto trx = make_unique_trx(fc::seconds(30)); - auto trace = make_transaction_trace( trx, bn, no_bs); + auto trace = make_transaction_trace( trx, bn, no_b); pre_block.push_back(std::tuple(trace, trx)); status.signal_applied_transaction(trace, trx); } - bs = make_block_state(bn); + b = make_block(bn); for (uint32_t i = 0; i < block_frame::num; ++i) { auto trx = make_unique_trx(fc::seconds(30)); - auto trace = make_transaction_trace( trx, bn, bs); + auto trace = make_transaction_trace( trx, bn, b); block.push_back(std::tuple(trace, trx)); status.signal_applied_transaction(trace, trx); } @@ -869,7 +834,7 @@ namespace { void verify_block(uint32_t begin = 0, uint32_t end = std::numeric_limits::max()) { context = "verify_block"; - verify(block, bs, begin, end); + verify(block, b, begin, end); } void verify_block_not_there(uint32_t begin = 0, uint32_t end = std::numeric_limits::max()) { @@ -879,7 +844,7 @@ namespace { void verify_spec_block(uint32_t begin = 0, uint32_t end = std::numeric_limits::max()) { context = "verify_spec_block"; - verify(pre_block, no_bs, begin, end); + verify(pre_block, no_b, begin, end); } void verify_spec_block_not_there(uint32_t begin = 0, uint32_t end = std::numeric_limits::max()) { @@ -898,7 +863,7 @@ namespace { status.signal_applied_transaction(trace, txn); } - status.signal_accepted_block(bs->block, bs->id); + status.signal_accepted_block(b, b->calculate_id()); } void send_spec_block() { @@ -914,11 +879,11 @@ namespace { } private: - void verify(const trx_deque& trx_pairs, const chain::block_state_legacy_ptr& bs, uint32_t begin, uint32_t end) { + void verify(const trx_deque& trx_pairs, const chain::signed_block_ptr& b, uint32_t begin, uint32_t end) { if (end == std::numeric_limits::max()) { end = block.size(); } - const auto id = bs ? bs->id : eosio::chain::transaction_id_type{}; + const auto id = b ? b->calculate_id() : eosio::chain::transaction_id_type{}; for (auto i = begin; i < end; ++i) { const auto& trx_pair = trx_pairs[i]; std::string msg = context + ": block_num==" + std::to_string(bn) + ", i==" + std::to_string(i) + ", id: " + std::string(std::get<1>(trx_pair)->id()); @@ -950,15 +915,6 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_storage_reduction) { try { const uint64_t max_storage = 10'000; trx_finality_status_processing status(max_storage, max_success_duration, max_failure_duration); - // auto verify_trx = [&status](trx_deque& trx_pairs, const eosio::chain::block_state_ptr& bs) { - // const auto id = bs ? bs->id : eosio::chain::transaction_id_type{}; - // for (const auto& trx_pair : trx_pairs) { - // auto ts = status.get_trx_state(std::get<1>(trx_pair)->id()); - // BOOST_REQUIRE(ts); - // BOOST_CHECK(ts->block_id == id); - // } - // }; - block_frame b_01(status, "04:44:00.500", 1); b_01.send_spec_block(); b_01.verify_spec_block(); @@ -1054,9 +1010,9 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_storage_reduction) { try { auto cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == b_11.bs->id); + BOOST_CHECK(cs.head_id == b_11.b->calculate_id()); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == b_01.bs->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_01.b->calculate_id()); // Test expects the next block range to exceed max_storage. Need to adjust // this test if this fails. @@ -1071,11 +1027,11 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_storage_reduction) { try { b_12.verify_block(); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == b_12.bs->id); - BOOST_CHECK(cs.head_block_timestamp == b_12.bs->block->timestamp); + BOOST_CHECK(cs.head_id == b_12.b->calculate_id()); + BOOST_CHECK(cs.head_block_timestamp == b_12.b->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); BOOST_CHECK(cs.irr_block_timestamp == eosio::chain::block_timestamp_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == b_03.bs->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_03.b->calculate_id()); b_01.verify_spec_block_not_there(); @@ -1124,16 +1080,6 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_lifespan) { try { const uint64_t max_storage = 10'000; trx_finality_status_processing status(max_storage, max_success_duration, max_failure_duration); - // auto verify_trx = [&status](trx_deque& trx_pairs, const eosio::chain::block_state_ptr& bs) { - // const auto id = bs ? bs->id : eosio::chain::transaction_id_type{}; - // for (const auto& trx_pair : trx_pairs) { - // auto ts = status.get_trx_state(std::get<1>(trx_pair)->id()); - // BOOST_REQUIRE(ts); - // BOOST_CHECK(ts->block_id == id); - // } - // }; - - block_frame b_01(status, "04:44:00.500", 1); b_01.send_spec_block(); b_01.verify_spec_block(); @@ -1191,9 +1137,9 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_lifespan) { try { b_01.verify_spec_block(); auto cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == b_06.bs->id); + BOOST_CHECK(cs.head_id == b_06.b->calculate_id()); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == b_02.bs->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_02.b->calculate_id()); block_frame b_07(status, "04:44:30.500"); @@ -1210,9 +1156,9 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_lifespan) { try { b_02.verify_spec_block(); cs = status.get_chain_state(); - BOOST_CHECK(cs.head_id == b_07.bs->id); + BOOST_CHECK(cs.head_id == b_07.b->calculate_id()); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); - BOOST_CHECK(cs.earliest_tracked_block_id == b_03.bs->id); + BOOST_CHECK(cs.earliest_tracked_block_id == b_03.b->calculate_id()); block_frame b_08(status, "04:44:35.500"); diff --git a/plugins/chain_plugin/test/test_trx_retry_db.cpp b/plugins/chain_plugin/test/test_trx_retry_db.cpp index f9810b30bd..3808602fad 100644 --- a/plugins/chain_plugin/test/test_trx_retry_db.cpp +++ b/plugins/chain_plugin/test/test_trx_retry_db.cpp @@ -135,7 +135,7 @@ uint64_t get_id( const packed_transaction_ptr& ptr ) { return get_id( ptr->get_transaction() ); } -auto make_block_state( uint32_t block_num, std::vector trxs ) { +auto make_block( uint32_t block_num, std::vector trxs ) { name producer = "kevinh"_n; chain::signed_block_ptr block = std::make_shared(); for( auto& trx : trxs ) { @@ -152,42 +152,11 @@ auto make_block_state( uint32_t block_num, std::vectorproducer, "active" ); auto pub_key = get_public_key( block->producer, "active" ); - auto prev = std::make_shared(); - auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), prev->blockroot_merkle.get_root())); - auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, prev->pending_schedule.schedule_hash )); + auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), digest_type{})); + auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, digest_type{} )); block->producer_signature = priv_key.sign( sig_digest ); - std::vector signing_keys; - signing_keys.emplace_back( priv_key ); - auto signer = [&]( chain::digest_type d ) { - std::vector result; - result.reserve( signing_keys.size()); - for( const auto& k: signing_keys ) - result.emplace_back( k.sign( d )); - return result; - }; - chain::pending_block_header_state_legacy pbhs; - pbhs.producer = block->producer; - pbhs.timestamp = block->timestamp; - pbhs.previous = block->previous; - chain::producer_authority_schedule schedule = - {0, {chain::producer_authority{block->producer, - chain::block_signing_authority_v0{1, {{pub_key, 1}}}}}}; - pbhs.active_schedule = schedule; - pbhs.valid_block_signing_authority = chain::block_signing_authority_v0{1, {{pub_key, 1}}}; - auto bsp = std::make_shared( - std::move( pbhs ), - std::move( block ), - deque(), - chain::protocol_feature_set(), - []( chain::block_timestamp_type timestamp, - const fc::flat_set& cur_features, - const std::vector& new_features ) {}, - signer - ); - bsp->block_num = block_num; - - return bsp; + return block; } } // anonymous namespace @@ -207,6 +176,7 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { genesis_state gs{}; { controller::config chain_config = controller::config(); + chain_config.finalizers_dir = temp; chain_config.blocks_dir = temp; chain_config.state_dir = temp; @@ -274,30 +244,30 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { trx_2_expired = true; } ); // signal block, nothing should be expired as now has not changed - auto bsp1 = make_block_state(1, {}); + auto bp1 = make_block(1, {}); trx_retry.on_block_start(1); - trx_retry.on_accepted_block(bsp1->block_num); - trx_retry.on_irreversible_block(bsp1->block); + trx_retry.on_accepted_block(bp1->block_num()); + trx_retry.on_irreversible_block(bp1); BOOST_CHECK(!trx_1_expired); BOOST_CHECK(!trx_2_expired); // increase time by 3 seconds to expire first pnow += boost::posix_time::seconds(3); fc::mock_time_traits::set_now(pnow); // signal block, first transaction should expire - auto bsp2 = make_block_state(2, {}); + auto bp2 = make_block(2, {}); trx_retry.on_block_start(2); - trx_retry.on_accepted_block(bsp2->block_num); - trx_retry.on_irreversible_block(bsp2->block); + trx_retry.on_accepted_block(bp2->block_num()); + trx_retry.on_irreversible_block(bp2); BOOST_CHECK(trx_1_expired); BOOST_CHECK(!trx_2_expired); // increase time by 2 seconds to expire second pnow += boost::posix_time::seconds(2); fc::mock_time_traits::set_now(pnow); // signal block, second transaction should expire - auto bsp3 = make_block_state(3, {}); + auto bp3 = make_block(3, {}); trx_retry.on_block_start(3); - trx_retry.on_accepted_block(bsp3->block_num); - trx_retry.on_irreversible_block(bsp3->block); + trx_retry.on_accepted_block(bp3->block_num()); + trx_retry.on_irreversible_block(bp3); BOOST_CHECK(trx_1_expired); BOOST_CHECK(trx_2_expired); BOOST_CHECK_EQUAL(0u, trx_retry.size()); @@ -326,18 +296,18 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { pnow += (pretry_interval - boost::posix_time::seconds(1)); fc::mock_time_traits::set_now(pnow); // signal block, transaction 3 should be sent - auto bsp4 = make_block_state(4, {}); + auto bp4 = make_block(4, {}); trx_retry.on_block_start(4); - trx_retry.on_accepted_block(bsp4->block_num); + trx_retry.on_accepted_block(bp4->block_num()); BOOST_CHECK( get_id(transactions_acked.pop().second) == 3 ); BOOST_CHECK_EQUAL( 0u, transactions_acked.size() ); // increase time by 1 seconds, so trx_4 is sent pnow += boost::posix_time::seconds(1); fc::mock_time_traits::set_now(pnow); // signal block, transaction 4 should be sent - auto bsp5 = make_block_state(5, {}); + auto bp5 = make_block(5, {}); trx_retry.on_block_start(5); - trx_retry.on_accepted_block(bsp5->block_num); + trx_retry.on_accepted_block(bp5->block_num()); BOOST_CHECK( get_id(transactions_acked.pop().second) == 4 ); BOOST_CHECK_EQUAL( 0u, transactions_acked.size() ); BOOST_CHECK(!trx_3_expired); @@ -345,12 +315,12 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { // go ahead and expire them now pnow += boost::posix_time::seconds(30); fc::mock_time_traits::set_now(pnow); - auto bsp6 = make_block_state(6, {}); + auto bp6 = make_block(6, {}); trx_retry.on_block_start(6); - trx_retry.on_accepted_block(bsp6->block_num); - trx_retry.on_irreversible_block(bsp4->block); - trx_retry.on_irreversible_block(bsp5->block); - trx_retry.on_irreversible_block(bsp6->block); + trx_retry.on_accepted_block(bp6->block_num()); + trx_retry.on_irreversible_block(bp4); + trx_retry.on_irreversible_block(bp5); + trx_retry.on_irreversible_block(bp6); BOOST_CHECK(trx_3_expired); BOOST_CHECK(trx_4_expired); BOOST_CHECK_EQUAL(0u, trx_retry.size()); @@ -376,9 +346,9 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { trx_6_variant = true; } ); // not in block 7, so not returned to user - auto bsp7 = make_block_state(7, {}); + auto bp7 = make_block(7, {}); trx_retry.on_block_start(7); - trx_retry.on_accepted_block(bsp7->block_num); + trx_retry.on_accepted_block(bp7->block_num()); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(!trx_6_variant); // 5,6 in block 8 @@ -389,37 +359,37 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { auto trace_6 = make_transaction_trace( trx_6, 8); trx_retry.on_applied_transaction(trace_5, trx_5); trx_retry.on_applied_transaction(trace_6, trx_6); - auto bsp8 = make_block_state(8, {trx_5, trx_6}); - trx_retry.on_accepted_block(bsp8->block_num); + auto bp8 = make_block(8, {trx_5, trx_6}); + trx_retry.on_accepted_block(bp8->block_num()); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(!trx_6_variant); // need 2 blocks before 6 returned to user pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp9 = make_block_state(9, {}); + auto bp9 = make_block(9, {}); trx_retry.on_block_start(9); - trx_retry.on_accepted_block(bsp9->block_num); + trx_retry.on_accepted_block(bp9->block_num()); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(!trx_6_variant); pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp10 = make_block_state(10, {}); + auto bp10 = make_block(10, {}); trx_retry.on_block_start(10); - trx_retry.on_accepted_block(bsp10->block_num); + trx_retry.on_accepted_block(bp10->block_num()); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(trx_6_variant); // now signal lib for trx_6 pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp11 = make_block_state(11, {}); + auto bp11 = make_block(11, {}); trx_retry.on_block_start(11); - trx_retry.on_accepted_block(bsp11->block_num); + trx_retry.on_accepted_block(bp11->block_num()); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(trx_6_variant); - trx_retry.on_irreversible_block(bsp7->block); + trx_retry.on_irreversible_block(bp7); BOOST_CHECK(!trx_5_variant); BOOST_CHECK(trx_6_variant); - trx_retry.on_irreversible_block(bsp8->block); + trx_retry.on_irreversible_block(bp8); BOOST_CHECK(trx_5_variant); BOOST_CHECK(trx_6_variant); BOOST_CHECK_EQUAL(0u, trx_retry.size()); @@ -454,9 +424,9 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { } ); // not in block 12 - auto bsp12 = make_block_state(12, {}); + auto bp12 = make_block(12, {}); trx_retry.on_block_start(12); - trx_retry.on_accepted_block(bsp12->block_num); + trx_retry.on_accepted_block(bp12->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); @@ -470,25 +440,25 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { trx_retry.on_applied_transaction(trace_7, trx_7); trx_retry.on_applied_transaction(trace_8, trx_8); trx_retry.on_applied_transaction(trace_9, trx_9); - auto bsp13 = make_block_state(13, {trx_7, trx_8, trx_9}); - trx_retry.on_accepted_block(bsp13->block_num); + auto bp13 = make_block(13, {trx_7, trx_8, trx_9}); + trx_retry.on_accepted_block(bp13->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); // need 3 blocks before 8 returned to user pnow += boost::posix_time::seconds(1); // new block, new time, 1st block fc::mock_time_traits::set_now(pnow); - auto bsp14 = make_block_state(14, {}); + auto bp14 = make_block(14, {}); trx_retry.on_block_start(14); - trx_retry.on_accepted_block(bsp14->block_num); + trx_retry.on_accepted_block(bp14->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); pnow += boost::posix_time::seconds(1); // new block, new time, 2nd block fc::mock_time_traits::set_now(pnow); - auto bsp15 = make_block_state(15, {}); + auto bp15 = make_block(15, {}); trx_retry.on_block_start(15); - trx_retry.on_accepted_block(bsp15->block_num); + trx_retry.on_accepted_block(bp15->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); @@ -499,85 +469,85 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { // should still be tracking them BOOST_CHECK_EQUAL(3u, trx_retry.size()); // now produce an empty 13 - auto bsp13b = make_block_state(13, {}); // now 13 has no traces - trx_retry.on_accepted_block(bsp13b->block_num); + auto bp13b = make_block(13, {}); // now 13 has no traces + trx_retry.on_accepted_block(bp13b->block_num()); // produced another empty block pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); trx_retry.on_block_start(14); // now produce an empty 14 - auto bsp14b = make_block_state(14, {}); // empty - trx_retry.on_accepted_block(bsp14b->block_num); + auto bp14b = make_block(14, {}); // empty + trx_retry.on_accepted_block(bp14b->block_num()); // produce block with 7,8 trx_retry.on_block_start(15); auto trace_7b = make_transaction_trace( trx_7, 15); auto trace_8b = make_transaction_trace( trx_8, 15); trx_retry.on_applied_transaction(trace_7b, trx_7); trx_retry.on_applied_transaction(trace_8b, trx_8); - auto bsp15b = make_block_state(15, {trx_7, trx_8}); - trx_retry.on_accepted_block(bsp15b->block_num); + auto bp15b = make_block(15, {trx_7, trx_8}); + trx_retry.on_accepted_block(bp15b->block_num()); // need 3 blocks before 8 returned to user pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp16 = make_block_state(16, {}); + auto bp16 = make_block(16, {}); trx_retry.on_block_start(16); - trx_retry.on_accepted_block(bsp16->block_num); + trx_retry.on_accepted_block(bp16->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp17 = make_block_state(17, {}); + auto bp17 = make_block(17, {}); trx_retry.on_block_start(17); - trx_retry.on_accepted_block(bsp17->block_num); + trx_retry.on_accepted_block(bp17->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(!trx_8_variant); BOOST_CHECK(!trx_9_expired); pnow += boost::posix_time::seconds(1); // new block, new time, 3rd one fc::mock_time_traits::set_now(pnow); - auto bsp18 = make_block_state(18, {}); + auto bp18 = make_block(18, {}); trx_retry.on_block_start(18); - trx_retry.on_accepted_block(bsp18->block_num); + trx_retry.on_accepted_block(bp18->block_num()); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(!trx_9_expired); - trx_retry.on_irreversible_block(bsp9->block); - trx_retry.on_irreversible_block(bsp10->block); - trx_retry.on_irreversible_block(bsp11->block); - trx_retry.on_irreversible_block(bsp12->block); - trx_retry.on_irreversible_block(bsp13b->block); - trx_retry.on_irreversible_block(bsp14b->block); + trx_retry.on_irreversible_block(bp9); + trx_retry.on_irreversible_block(bp10); + trx_retry.on_irreversible_block(bp11); + trx_retry.on_irreversible_block(bp12); + trx_retry.on_irreversible_block(bp13b); + trx_retry.on_irreversible_block(bp14b); BOOST_CHECK(!trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(!trx_9_expired); - trx_retry.on_irreversible_block(bsp15b->block); + trx_retry.on_irreversible_block(bp15b); BOOST_CHECK(trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(!trx_9_expired); // verify trx_9 expires pnow += boost::posix_time::seconds(21); // new block, new time, before expire fc::mock_time_traits::set_now(pnow); - auto bsp19 = make_block_state(19, {}); + auto bp19 = make_block(19, {}); trx_retry.on_block_start(19); - trx_retry.on_accepted_block(bsp19->block_num); - trx_retry.on_irreversible_block(bsp15->block); - trx_retry.on_irreversible_block(bsp16->block); - trx_retry.on_irreversible_block(bsp17->block); - trx_retry.on_irreversible_block(bsp18->block); - trx_retry.on_irreversible_block(bsp19->block); + trx_retry.on_accepted_block(bp19->block_num()); + trx_retry.on_irreversible_block(bp15); + trx_retry.on_irreversible_block(bp16); + trx_retry.on_irreversible_block(bp17); + trx_retry.on_irreversible_block(bp18); + trx_retry.on_irreversible_block(bp19); BOOST_CHECK(trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(!trx_9_expired); pnow += boost::posix_time::seconds(1); // new block, new time, trx_9 now expired fc::mock_time_traits::set_now(pnow); - auto bsp20 = make_block_state(20, {}); + auto bp20 = make_block(20, {}); trx_retry.on_block_start(20); - trx_retry.on_accepted_block(bsp20->block_num); + trx_retry.on_accepted_block(bp20->block_num()); // waits for LIB BOOST_CHECK(trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(!trx_9_expired); - trx_retry.on_irreversible_block(bsp20->block); + trx_retry.on_irreversible_block(bp20); BOOST_CHECK(trx_7_variant); BOOST_CHECK(trx_8_variant); BOOST_CHECK(trx_9_expired); @@ -606,15 +576,15 @@ BOOST_AUTO_TEST_CASE(trx_retry_logic) { auto trace_11 = make_transaction_trace( trx_11, 21); trx_retry.on_applied_transaction(trace_10, trx_10); trx_retry.on_applied_transaction(trace_11, trx_11); - auto bsp21 = make_block_state(21, {trx_10, trx_11}); - trx_retry.on_accepted_block(bsp21->block_num); + auto bp21 = make_block(21, {trx_10, trx_11}); + trx_retry.on_accepted_block(bp21->block_num()); BOOST_CHECK(trx_10_variant); BOOST_CHECK(!trx_11_variant); pnow += boost::posix_time::seconds(1); // new block, new time fc::mock_time_traits::set_now(pnow); - auto bsp22 = make_block_state(22, {}); + auto bp22 = make_block(22, {}); trx_retry.on_block_start(22); - trx_retry.on_accepted_block(bsp22->block_num); + trx_retry.on_accepted_block(bp22->block_num()); BOOST_CHECK(trx_10_variant); BOOST_CHECK(trx_11_variant); BOOST_CHECK_EQUAL(0u, trx_retry.size()); diff --git a/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp index 566233dce4..a2a0cf4357 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/net_plugin.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace eosio { using namespace appbase; @@ -26,7 +27,7 @@ namespace eosio { net_plugin(); virtual ~net_plugin(); - APPBASE_PLUGIN_REQUIRES((chain_plugin)) + APPBASE_PLUGIN_REQUIRES((chain_plugin)(producer_plugin)) virtual void set_program_options(options_description& cli, options_description& cfg) override; void handle_sighup() override; diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index d37fdbc18d..b371561fd2 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -1,7 +1,7 @@ #pragma once #include +#include #include -#include namespace eosio { using namespace chain; @@ -141,8 +141,9 @@ namespace eosio { notice_message, request_message, sync_request_message, - signed_block, // which = 7 - packed_transaction>; // which = 8 + signed_block, + packed_transaction, + vote_message>; } // namespace eosio diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 63d5e3532b..9d4c0f5b16 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -33,16 +33,6 @@ #include #include -// should be defined for c++17, but clang++16 still has not implemented it -#ifdef __cpp_lib_hardware_interference_size - using std::hardware_constructive_interference_size; - using std::hardware_destructive_interference_size; -#else - // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ... - [[maybe_unused]] constexpr std::size_t hardware_constructive_interference_size = 64; - [[maybe_unused]] constexpr std::size_t hardware_destructive_interference_size = 64; -#endif - using namespace eosio::chain::plugin_interface; using namespace std::chrono_literals; @@ -82,6 +72,7 @@ namespace eosio { using connection_ptr = std::shared_ptr; using connection_wptr = std::weak_ptr; + using send_buffer_type = std::shared_ptr>; static constexpr int64_t block_interval_ns = std::chrono::duration_cast(std::chrono::milliseconds(config::block_interval_ms)).count(); @@ -260,13 +251,17 @@ namespace eosio { bool verify_catchup( const connection_ptr& c, uint32_t num, const block_id_type& id ); // locks mutex public: + enum class closing_mode { + immediately, // closing connection immediately + handshake // sending handshake message + }; explicit sync_manager( uint32_t span, uint32_t sync_peer_limit, uint32_t min_blocks_distance ); static void send_handshakes(); bool syncing_from_peer() const { return sync_state == lib_catchup; } bool is_in_sync() const { return sync_state == in_sync; } void sync_reset_lib_num( const connection_ptr& conn, bool closing ); void sync_reassign_fetch( const connection_ptr& c, go_away_reason reason ); - void rejected_block( const connection_ptr& c, uint32_t blk_num ); + void rejected_block( const connection_ptr& c, uint32_t blk_num, closing_mode mode ); void sync_recv_block( const connection_ptr& c, const block_id_type& blk_id, uint32_t blk_num, bool blk_applied ); void recv_handshake( const connection_ptr& c, const handshake_message& msg, uint32_t nblk_combined_latency ); void sync_recv_notice( const connection_ptr& c, const notice_message& msg ); @@ -310,6 +305,8 @@ namespace eosio { bool have_txn( const transaction_id_type& tid ) const; void expire_txns(); + void bcast_vote_msg( const std::optional& exclude_peer, send_buffer_type msg ); + void add_unlinkable_block( signed_block_ptr b, const block_id_type& id ) { std::optional rm_blk_id = unlinkable_block_cache.add_unlinkable_block(std::move(b), id); if (rm_blk_id) { @@ -339,8 +336,9 @@ namespace eosio { constexpr auto def_keepalive_interval = 10000; constexpr auto message_header_size = sizeof(uint32_t); - constexpr uint32_t signed_block_which = fc::get_index(); // see protocol net_message - constexpr uint32_t packed_transaction_which = fc::get_index(); // see protocol net_message + + constexpr uint32_t signed_block_which = fc::get_index(); // see protocol net_message + constexpr uint32_t packed_transaction_which = fc::get_index(); // see protocol net_message class connections_manager { public: @@ -524,16 +522,22 @@ namespace eosio { public: void update_chain_info(); + void update_chain_info(const block_id_type& lib); chain_info_t get_chain_info() const; uint32_t get_chain_lib_num() const; uint32_t get_chain_head_num() const; void on_accepted_block_header( const signed_block_ptr& block, const block_id_type& id ); void on_accepted_block(); + void on_voted_block ( const vote_message& vote ); void transaction_ack(const std::pair&); void on_irreversible_block( const block_id_type& id, uint32_t block_num ); + void bcast_vote_message( const std::optional& exclude_peer, const chain::vote_message& msg ); + void warn_message( uint32_t sender_peer, const chain::hs_message_warning& code ); + + void start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection); void start_expire_timer(); void start_monitors(); @@ -663,9 +667,10 @@ namespace eosio { constexpr uint16_t proto_dup_node_id_goaway = 6; // eosio 2.1: support peer node_id based duplicate connection resolution constexpr uint16_t proto_leap_initial = 7; // leap client, needed because none of the 2.1 versions are supported constexpr uint16_t proto_block_range = 8; // include block range in notice_message + constexpr uint16_t proto_instant_finality = 9; // instant finality #pragma GCC diagnostic pop - constexpr uint16_t net_version_max = proto_leap_initial; + constexpr uint16_t net_version_max = proto_instant_finality; /** * Index by start_block_num @@ -708,10 +713,17 @@ namespace eosio { return _out_queue.empty(); } - bool ready_to_send() const { - fc::lock_guard g( _mtx ); + // called from connection strand + bool ready_to_send(uint32_t connection_id) const { + fc::unique_lock g( _mtx ); // if out_queue is not empty then async_write is in progress - return ((!_sync_write_queue.empty() || !_write_queue.empty()) && _out_queue.empty()); + bool async_write_in_progress = !_out_queue.empty(); + bool ready = ((!_sync_write_queue.empty() || !_write_queue.empty()) && !async_write_in_progress); + g.unlock(); + if (async_write_in_progress) { + fc_dlog(logger, "Connection - ${id} not ready to send data, async write in progress", ("id", connection_id)); + } + return ready; } // @param callback must not callback into queued_buffer @@ -1094,11 +1106,12 @@ namespace eosio { void handle_message( const block_id_type& id, signed_block_ptr ptr ); void handle_message( const packed_transaction& msg ) = delete; // packed_transaction_ptr overload used instead void handle_message( packed_transaction_ptr trx ); + void handle_message( const vote_message& msg ); // returns calculated number of blocks combined latency uint32_t calc_block_latency(); - void process_signed_block( const block_id_type& id, signed_block_ptr block, block_state_legacy_ptr bsp ); + void process_signed_block( const block_id_type& id, signed_block_ptr block, const std::optional& obt ); fc::variant_object get_logger_variant() const { fc::mutable_variant_object mvo; @@ -1174,8 +1187,13 @@ namespace eosio { peer_dlog( c, "handle sync_request_message" ); c->handle_message( msg ); } - }; + void operator()( const chain::vote_message& msg ) const { + // continue call to handle_message on connection strand + peer_dlog( c, "handle vote_message" ); + c->handle_message( msg ); + } + }; std::tuple split_host_port_type(const std::string& peer_add) { @@ -1266,7 +1284,7 @@ namespace eosio { { my_impl->mark_bp_connection(this); update_endpoints(); - fc_ilog( logger, "created connection ${c} to ${n}", ("c", connection_id)("n", endpoint) ); + fc_ilog( logger, "created connection - ${c} to ${n}", ("c", connection_id)("n", endpoint) ); } connection::connection(tcp::socket&& s, const string& listen_address, size_t block_sync_rate_limit) @@ -1281,7 +1299,8 @@ namespace eosio { last_handshake_sent() { update_endpoints(); - fc_dlog( logger, "new connection object created for peer ${address}:${port} from listener ${addr}", ("address", log_remote_endpoint_ip)("port", log_remote_endpoint_port)("addr", listen_address) ); + fc_dlog( logger, "new connection - ${c} object created for peer ${address}:${port} from listener ${addr}", + ("c", connection_id)("address", log_remote_endpoint_ip)("port", log_remote_endpoint_port)("addr", listen_address) ); } void connection::update_endpoints(const tcp::endpoint& endpoint) { @@ -1314,16 +1333,16 @@ namespace eosio { void connection::set_connection_type( const std::string& peer_add ) { auto [host, port, type] = split_host_port_type(peer_add); if( type.empty() ) { - fc_dlog( logger, "Setting connection ${c} type for: ${peer} to both transactions and blocks", ("c", connection_id)("peer", peer_add) ); + fc_dlog( logger, "Setting connection - ${c} type for: ${peer} to both transactions and blocks", ("c", connection_id)("peer", peer_add) ); connection_type = both; } else if( type == "trx" ) { - fc_dlog( logger, "Setting connection ${c} type for: ${peer} to transactions only", ("c", connection_id)("peer", peer_add) ); + fc_dlog( logger, "Setting connection - ${c} type for: ${peer} to transactions only", ("c", connection_id)("peer", peer_add) ); connection_type = transactions_only; } else if( type == "blk" ) { - fc_dlog( logger, "Setting connection ${c} type for: ${peer} to blocks only", ("c", connection_id)("peer", peer_add) ); + fc_dlog( logger, "Setting connection - ${c} type for: ${peer} to blocks only", ("c", connection_id)("peer", peer_add) ); connection_type = blocks_only; } else { - fc_wlog( logger, "Unknown connection ${c} type: ${t}, for ${peer}", ("c", connection_id)("t", type)("peer", peer_add) ); + fc_wlog( logger, "Unknown connection - ${c} type: ${t}, for ${peer}", ("c", connection_id)("t", type)("peer", peer_add) ); } } @@ -1655,7 +1674,7 @@ namespace eosio { // called from connection strand void connection::do_queue_write() { - if( !buffer_queue.ready_to_send() || closed() ) + if( !buffer_queue.ready_to_send(connection_id) || closed() ) return; connection_ptr c(shared_from_this()); @@ -1690,6 +1709,7 @@ namespace eosio { c->close(); return; } + peer_dlog(c, "async write complete"); c->bytes_sent += w; c->last_bytes_sent = c->get_time(); @@ -1788,8 +1808,6 @@ namespace eosio { //------------------------------------------------------------------------ - using send_buffer_type = std::shared_ptr>; - struct buffer_factory { /// caches result for subsequent calls, only provide same net_message instance for each invocation @@ -1890,7 +1908,7 @@ namespace eosio { } buffer_factory buff_factory; - auto send_buffer = buff_factory.get_send_buffer( m ); + const auto& send_buffer = buff_factory.get_send_buffer( m ); enqueue_buffer( send_buffer, close_after_send ); } @@ -1900,7 +1918,7 @@ namespace eosio { verify_strand_in_this_thread( strand, __func__, __LINE__ ); block_buffer_factory buff_factory; - auto sb = buff_factory.get_send_buffer( b ); + const auto& sb = buff_factory.get_send_buffer( b ); latest_blk_time = std::chrono::system_clock::now(); enqueue_buffer( sb, no_reason, to_sync_queue); return sb->size(); @@ -2425,17 +2443,21 @@ namespace eosio { } // called from connection strand - void sync_manager::rejected_block( const connection_ptr& c, uint32_t blk_num ) { + void sync_manager::rejected_block( const connection_ptr& c, uint32_t blk_num, closing_mode mode ) { c->block_status_monitor_.rejected(); // reset sync on rejected block fc::unique_lock g( sync_mtx ); sync_last_requested_num = 0; sync_next_expected_num = my_impl->get_chain_lib_num() + 1; - if( c->block_status_monitor_.max_events_violated()) { + if( mode == closing_mode::immediately || c->block_status_monitor_.max_events_violated()) { peer_wlog( c, "block ${bn} not accepted, closing connection", ("bn", blk_num) ); sync_source.reset(); g.unlock(); - c->close(); + if( mode == closing_mode::immediately ) { + c->close( false ); // do not reconnect + } else { + c->close(); + } } else { g.unlock(); peer_dlog(c, "rejected block ${bn}, sending handshake", ("bn", blk_num)); @@ -2508,7 +2530,11 @@ namespace eosio { if (sync_last_requested_num == 0) { // block was rejected sync_next_expected_num = my_impl->get_chain_lib_num() + 1; } else { - sync_next_expected_num = blk_num + 1; + if (blk_num == sync_next_expected_num) { + ++sync_next_expected_num; + } else if (blk_num < sync_next_expected_num) { + sync_next_expected_num = blk_num + 1; + } } } @@ -2618,12 +2644,12 @@ namespace eosio { block_buffer_factory buff_factory; const auto bnum = b->block_num(); my_impl->connections.for_each_block_connection( [this, &id, &bnum, &b, &buff_factory]( auto& cp ) { - fc_dlog( logger, "socket_is_open ${s}, state ${c}, syncing ${ss}, connection ${cid}", + fc_dlog( logger, "socket_is_open ${s}, state ${c}, syncing ${ss}, connection - ${cid}", ("s", cp->socket_is_open())("c", connection::state_str(cp->state()))("ss", cp->peer_syncing_from_us.load())("cid", cp->connection_id) ); if( !cp->current() ) return; if( !add_peer_block( id, cp->connection_id ) ) { - fc_dlog( logger, "not bcast block ${b} to connection ${cid}", ("b", bnum)("cid", cp->connection_id) ); + fc_dlog( logger, "not bcast block ${b} to connection - ${cid}", ("b", bnum)("cid", cp->connection_id) ); return; } @@ -2640,6 +2666,20 @@ namespace eosio { } ); } + void dispatch_manager::bcast_vote_msg( const std::optional& exclude_peer, send_buffer_type msg ) { + my_impl->connections.for_each_block_connection( [exclude_peer, msg{std::move(msg)}]( auto& cp ) { + if( !cp->current() ) return true; + if( exclude_peer.has_value() && cp->connection_id == exclude_peer.value() ) return true; + cp->strand.post( [cp, msg]() { + if (cp->protocol_version >= proto_instant_finality) { + peer_dlog(cp, "sending vote msg"); + cp->enqueue_buffer( msg, no_reason ); + } + }); + return true; + } ); + } + // called from c's connection strand void dispatch_manager::recv_block(const connection_ptr& c, const block_id_type& id, uint32_t bnum) { fc::unique_lock g( c->conn_mtx ); @@ -2674,7 +2714,7 @@ namespace eosio { } send_buffer_type sb = buff_factory.get_send_buffer( trx ); - fc_dlog( logger, "sending trx: ${id}, to connection ${cid}", ("id", trx->id())("cid", cp->connection_id) ); + fc_dlog( logger, "sending trx: ${id}, to connection - ${cid}", ("id", trx->id())("cid", cp->connection_id) ); cp->strand.post( [cp, sb{std::move(sb)}]() { cp->enqueue_buffer( sb, no_reason ); } ); @@ -2819,15 +2859,17 @@ namespace eosio { } void net_plugin_impl::create_session(tcp::socket&& socket, const string listen_address, size_t limit) { - uint32_t visitors = 0; - uint32_t from_addr = 0; boost::system::error_code rec; - const auto& paddr_add = socket.remote_endpoint(rec).address(); - string paddr_str; + const auto& rend = socket.remote_endpoint(rec); if (rec) { fc_ilog(logger, "Unable to get remote endpoint: ${m}", ("m", rec.message())); } else { - paddr_str = paddr_add.to_string(); + uint32_t visitors = 0; + uint32_t from_addr = 0; + const auto& paddr_add = rend.address(); + const auto paddr_port = rend.port(); + string paddr_str = paddr_add.to_string(); + string paddr_desc = paddr_str + ":" + std::to_string(paddr_port); connections.for_each_connection([&visitors, &from_addr, &paddr_str](const connection_ptr& conn) { if (conn->socket_is_open()) { if (conn->peer_address().empty()) { @@ -2844,11 +2886,11 @@ namespace eosio { visitors < connections.get_max_client_count())) { fc_ilog(logger, "Accepted new connection: " + paddr_str); - connections.any_of_supplied_peers([&listen_address, &paddr_str, &limit](const string& peer_addr) { + connections.any_of_supplied_peers([&listen_address, &paddr_str, &paddr_desc, &limit](const string& peer_addr) { auto [host, port, type] = split_host_port_type(peer_addr); if (host == paddr_str) { if (limit > 0) { - fc_dlog(logger, "Connection inbound to ${la} from ${a} is a configured p2p-peer-address and will not be throttled", ("la", listen_address)("a", paddr_str)); + fc_dlog(logger, "Connection inbound to ${la} from ${a} is a configured p2p-peer-address and will not be throttled", ("la", listen_address)("a", paddr_desc)); } limit = 0; return true; @@ -2865,10 +2907,10 @@ namespace eosio { } else { if (from_addr >= max_nodes_per_host) { - fc_dlog(logger, "Number of connections (${n}) from ${ra} exceeds limit ${l}", - ("n", from_addr + 1)("ra", paddr_str)("l", max_nodes_per_host)); + fc_dlog(logger, "Number of connections (${n}) from ${ra} exceeds limit ${l}, closing", + ("n", from_addr + 1)("ra", paddr_desc)("l", max_nodes_per_host)); } else { - fc_dlog(logger, "max_client_count ${m} exceeded", ("m", connections.get_max_client_count())); + fc_dlog(logger, "max_client_count ${m} exceeded, closing: ${ra}", ("m", connections.get_max_client_count())("ra", paddr_desc)); } // new_connection never added to connections and start_session not called, lifetime will end boost::system::error_code ec; @@ -3029,13 +3071,12 @@ namespace eosio { auto peek_ds = pending_message_buffer.create_peek_datastream(); unsigned_int which{}; fc::raw::unpack( peek_ds, which ); + if( which == signed_block_which ) { latest_blk_time = std::chrono::system_clock::now(); return process_next_block_message( message_length ); - } else if( which == packed_transaction_which ) { return process_next_trx_message( message_length ); - } else { auto ds = pending_message_buffer.create_datastream(); net_message msg; @@ -3065,7 +3106,7 @@ namespace eosio { if( my_impl->dispatcher.have_block( blk_id ) ) { peer_dlog( this, "canceling wait, already received block ${num}, id ${id}...", ("num", blk_num)("id", blk_id.str().substr(8,16)) ); - my_impl->sync_master->sync_recv_block( shared_from_this(), blk_id, blk_num, false ); + my_impl->sync_master->sync_recv_block( shared_from_this(), blk_id, blk_num, true ); cancel_wait(); pending_message_buffer.advance_read_ptr( message_length ); @@ -3093,7 +3134,6 @@ namespace eosio { } } else { block_sync_bytes_received += message_length; - my_impl->sync_master->sync_recv_block(shared_from_this(), blk_id, blk_num, false); uint32_t lib_num = my_impl->get_chain_lib_num(); if( blk_num <= lib_num ) { cancel_wait(); @@ -3101,6 +3141,7 @@ namespace eosio { pending_message_buffer.advance_read_ptr( message_length ); return true; } + my_impl->sync_master->sync_recv_block(shared_from_this(), blk_id, blk_num, false); } auto ds = pending_message_buffer.create_datastream(); @@ -3188,14 +3229,29 @@ namespace eosio { uint32_t lib_num = 0, head_num = 0; { fc::lock_guard g( chain_info_mtx ); - chain_info.lib_num = lib_num = cc.last_irreversible_block_num(); chain_info.lib_id = cc.last_irreversible_block_id(); - chain_info.head_num = head_num = cc.fork_db_head_block_num(); + chain_info.lib_num = lib_num = block_header::num_from_id(chain_info.lib_id); chain_info.head_id = cc.fork_db_head_block_id(); + chain_info.head_num = head_num = block_header::num_from_id(chain_info.head_id); } fc_dlog( logger, "updating chain info lib ${lib}, fork ${fork}", ("lib", lib_num)("fork", head_num) ); } + // call only from main application thread + void net_plugin_impl::update_chain_info(const block_id_type& lib) { + controller& cc = chain_plug->chain(); + uint32_t lib_num = 0, head_num = 0; + { + fc::lock_guard g( chain_info_mtx ); + chain_info.lib_num = lib_num = block_header::num_from_id(lib); + chain_info.lib_id = lib; + chain_info.head_id = cc.fork_db_head_block_id(); + chain_info.head_num = head_num = block_header::num_from_id(chain_info.head_id); + } + fc_dlog( logger, "updating chain info lib ${lib}, fork ${fork}", ("lib", lib_num)("fork", head_num) ); + } + + net_plugin_impl::chain_info_t net_plugin_impl::get_chain_info() const { fc::lock_guard g( chain_info_mtx ); return chain_info; @@ -3652,6 +3708,31 @@ namespace eosio { } } + void connection::handle_message( const vote_message& msg ) { + peer_dlog(this, "received vote: block #${bn}:${id}.., ${v}, key ${k}..", + ("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) + ("v", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8, 16))); + controller& cc = my_impl->chain_plug->chain(); + + switch( cc.process_vote_message(msg) ) { + case vote_status::success: + my_impl->bcast_vote_message(connection_id, msg); + break; + case vote_status::unknown_public_key: + case vote_status::invalid_signature: // close peer immediately + close( false ); // do not reconnect after closing + break; + case vote_status::unknown_block: // track the failure + peer_dlog(this, "vote unknown block #${bn}:${id}..", ("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16))); + block_status_monitor_.rejected(); + break; + case vote_status::duplicate: // do nothing + break; + default: + assert(false); // should never happen + } + } + size_t calc_trx_size( const packed_transaction_ptr& trx ) { return trx->get_estimated_size(); } @@ -3687,11 +3768,12 @@ namespace eosio { // called from connection strand void connection::handle_message( const block_id_type& id, signed_block_ptr ptr ) { // post to dispatcher strand so that we don't have multiple threads validating the block header + peer_dlog(this, "posting block ${n} to dispatcher strand", ("n", ptr->block_num())); my_impl->dispatcher.strand.post([id, c{shared_from_this()}, ptr{std::move(ptr)}, cid=connection_id]() mutable { controller& cc = my_impl->chain_plug->chain(); // may have come in on a different connection and posted into dispatcher strand before this one - if( my_impl->dispatcher.have_block( id ) || cc.fetch_block_state_by_id( id ) ) { // thread-safe + if( my_impl->dispatcher.have_block( id ) || cc.block_exists( id ) ) { // thread-safe my_impl->dispatcher.add_peer_block( id, c->connection_id ); c->strand.post( [c, id]() { my_impl->sync_master->sync_recv_block( c, id, block_header::num_from_id(id), false ); @@ -3699,40 +3781,48 @@ namespace eosio { return; } - block_state_legacy_ptr bsp; + std::optional obt; bool exception = false; + sync_manager::closing_mode close_mode = sync_manager::closing_mode::handshake; try { // this may return null if block is not immediately ready to be processed - bsp = cc.create_block_state( id, ptr ); + obt = cc.create_block_handle( id, ptr ); + } catch( const invalid_qc_claim &ex) { + exception = true; + close_mode = sync_manager::closing_mode::immediately; + fc_wlog( logger, "invalid QC claim exception, connection - ${cid}: #${n} ${id}...: ${m}", + ("cid", cid)("n", ptr->block_num())("id", id.str().substr(8,16))("m",ex.to_string())); } catch( const fc::exception& ex ) { exception = true; - fc_ilog( logger, "bad block exception connection ${cid}: #${n} ${id}...: ${m}", + fc_ilog( logger, "bad block exception connection - ${cid}: #${n} ${id}...: ${m}", ("cid", cid)("n", ptr->block_num())("id", id.str().substr(8,16))("m",ex.to_string())); } catch( ... ) { exception = true; - fc_wlog( logger, "bad block connection ${cid}: #${n} ${id}...: unknown exception", + fc_wlog( logger, "bad block connection - ${cid}: #${n} ${id}...: unknown exception", ("cid", cid)("n", ptr->block_num())("id", id.str().substr(8,16))); } if( exception ) { - c->strand.post( [c, id, blk_num=ptr->block_num()]() { - my_impl->sync_master->rejected_block( c, blk_num ); + c->strand.post( [c, id, blk_num=ptr->block_num(), close_mode]() { + my_impl->sync_master->rejected_block( c, blk_num, close_mode ); my_impl->dispatcher.rejected_block( id ); }); return; } - uint32_t block_num = bsp ? bsp->block_num : 0; + uint32_t block_num = obt ? obt->block_num() : 0; if( block_num != 0 ) { - fc_dlog( logger, "validated block header, broadcasting immediately, connection ${cid}, blk num = ${num}, id = ${id}", - ("cid", cid)("num", block_num)("id", bsp->id) ); - my_impl->dispatcher.add_peer_block( bsp->id, cid ); // no need to send back to sender - my_impl->dispatcher.bcast_block( bsp->block, bsp->id ); + assert(obt); + fc_dlog( logger, "validated block header, broadcasting immediately, connection - ${cid}, blk num = ${num}, id = ${id}", + ("cid", cid)("num", block_num)("id", obt->id()) ); + my_impl->dispatcher.add_peer_block( obt->id(), cid ); // no need to send back to sender + my_impl->dispatcher.bcast_block( obt->block(), obt->id() ); } - app().executor().post(priority::medium, exec_queue::read_write, [ptr{std::move(ptr)}, bsp{std::move(bsp)}, id, c{std::move(c)}]() mutable { - c->process_signed_block( id, std::move(ptr), std::move(bsp) ); + fc_dlog(logger, "posting block ${n} to app thread", ("n", ptr->block_num())); + app().executor().post(priority::medium, exec_queue::read_write, [ptr{std::move(ptr)}, obt{std::move(obt)}, id, c{std::move(c)}]() mutable { + c->process_signed_block( id, std::move(ptr), obt ); }); if( block_num != 0 ) { @@ -3743,7 +3833,7 @@ namespace eosio { } // called from application thread - void connection::process_signed_block( const block_id_type& blk_id, signed_block_ptr block, block_state_legacy_ptr bsp ) { + void connection::process_signed_block( const block_id_type& blk_id, signed_block_ptr block, const std::optional& obt ) { controller& cc = my_impl->chain_plug->chain(); uint32_t blk_num = block_header::num_from_id(blk_id); // use c in this method instead of this to highlight that all methods called on c-> must be thread safe @@ -3751,7 +3841,7 @@ namespace eosio { uint32_t lib = cc.last_irreversible_block_num(); try { - if( blk_num <= lib || cc.fetch_block_by_id(blk_id) ) { + if( blk_num <= lib || cc.validated_block_exists(blk_id) ) { c->strand.post( [sync_master = my_impl->sync_master.get(), &dispatcher = my_impl->dispatcher, c, blk_id, blk_num]() { dispatcher.add_peer_block( blk_id, c->connection_id ); @@ -3767,32 +3857,32 @@ namespace eosio { } fc::microseconds age( fc::time_point::now() - block->timestamp); - fc_dlog( logger, "received signed_block: #${n} block age in secs = ${age}, connection ${cid}, ${v}", - ("n", blk_num)("age", age.to_seconds())("cid", c->connection_id)("v", bsp ? "pre-validated" : "validation pending")("lib", lib) ); + fc_dlog( logger, "received signed_block: #${n} block age in secs = ${age}, connection - ${cid}, ${v}, lib #${lib}", + ("n", blk_num)("age", age.to_seconds())("cid", c->connection_id)("v", obt ? "header validated" : "header validation pending")("lib", lib) ); go_away_reason reason = no_reason; bool accepted = false; try { - accepted = my_impl->chain_plug->accept_block(block, blk_id, bsp); + accepted = my_impl->chain_plug->accept_block(block, blk_id, obt); my_impl->update_chain_info(); } catch( const unlinkable_block_exception &ex) { - fc_ilog(logger, "unlinkable_block_exception connection ${cid}: #${n} ${id}...: ${m}", + fc_ilog(logger, "unlinkable_block_exception connection - ${cid}: #${n} ${id}...: ${m}", ("cid", c->connection_id)("n", blk_num)("id", blk_id.str().substr(8,16))("m",ex.to_string())); reason = unlinkable; } catch( const block_validate_exception &ex ) { - fc_ilog(logger, "block_validate_exception connection ${cid}: #${n} ${id}...: ${m}", + fc_ilog(logger, "block_validate_exception connection - ${cid}: #${n} ${id}...: ${m}", ("cid", c->connection_id)("n", blk_num)("id", blk_id.str().substr(8,16))("m",ex.to_string())); reason = validation; } catch( const assert_exception &ex ) { - fc_wlog(logger, "block assert_exception connection ${cid}: #${n} ${id}...: ${m}", + fc_wlog(logger, "block assert_exception connection - ${cid}: #${n} ${id}...: ${m}", ("cid", c->connection_id)("n", blk_num)("id", blk_id.str().substr(8,16))("m",ex.to_string())); reason = fatal_other; } catch( const fc::exception &ex ) { - fc_ilog(logger, "bad block exception connection ${cid}: #${n} ${id}...: ${m}", + fc_ilog(logger, "bad block exception connection - ${cid}: #${n} ${id}...: ${m}", ("cid", c->connection_id)("n", blk_num)("id", blk_id.str().substr(8,16))("m",ex.to_string())); reason = fatal_other; } catch( ... ) { - fc_wlog(logger, "bad block connection ${cid}: #${n} ${id}...: unknown exception", + fc_wlog(logger, "bad block connection - ${cid}: #${n} ${id}...: unknown exception", ("cid", c->connection_id)("n", blk_num)("id", blk_id.str().substr(8,16))); reason = fatal_other; } @@ -3825,9 +3915,9 @@ namespace eosio { if( reason == unlinkable || reason == no_reason ) { dispatcher.add_unlinkable_block( std::move(block), blk_id ); } - // reason==no_reason means accept_block() return false because we are producing, don't call rejected_block which sends handshake + // reason==no_reason means accept_block() return false which is a fatal error, don't call rejected_block which sends handshake if( reason != no_reason ) { - sync_master->rejected_block( c, blk_num ); + sync_master->rejected_block( c, blk_num, sync_manager::closing_mode::handshake ); } dispatcher.rejected_block( blk_id ); }); @@ -3899,14 +3989,41 @@ namespace eosio { } void net_plugin_impl::on_accepted_block() { - on_pending_schedule(chain_plug->chain().pending_producers()); + if (const auto* next_producers = chain_plug->chain().next_producers()) { + on_pending_schedule(*next_producers); + } on_active_schedule(chain_plug->chain().active_producers()); } + // called from other threads including net threads + void net_plugin_impl::on_voted_block(const vote_message& msg) { + fc_dlog(logger, "on voted signal: block #${bn} ${id}.., ${t}, key ${k}..", + ("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) + ("t", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8, 16))); + bcast_vote_message(std::nullopt, msg); + } + + void net_plugin_impl::bcast_vote_message( const std::optional& exclude_peer, const chain::vote_message& msg ) { + buffer_factory buff_factory; + auto send_buffer = buff_factory.get_send_buffer( msg ); + + fc_dlog(logger, "bcast ${t} vote: block #${bn} ${id}.., ${v}, key ${k}..", + ("t", exclude_peer ? "received" : "our")("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) + ("v", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8,16))); + + dispatcher.strand.post( [this, exclude_peer, msg{std::move(send_buffer)}]() mutable { + dispatcher.bcast_vote_msg( exclude_peer, std::move(msg) ); + }); + } + + void net_plugin_impl::warn_message( uint32_t sender_peer, const chain::hs_message_warning& code ) { + // potentially react to (repeated) receipt of invalid, irrelevant, duplicate, etc. hotstuff messages from sender_peer (connection ID) here + } + // called from application thread void net_plugin_impl::on_irreversible_block( const block_id_type& id, uint32_t block_num) { fc_dlog( logger, "on_irreversible_block, blk num = ${num}, id = ${id}", ("num", block_num)("id", id) ); - update_chain_info(); + update_chain_info(id); } // called from application thread @@ -4253,6 +4370,7 @@ namespace eosio { set_producer_accounts(producer_plug->producer_accounts()); thread_pool.start( thread_pool_size, []( const fc::exception& e ) { + elog("Exception in net thread, exiting: ${e}", ("e", e.to_detail_string())); app().quit(); } ); @@ -4289,18 +4407,22 @@ namespace eosio { { chain::controller& cc = chain_plug->chain(); - cc.accepted_block_header.connect( [my = shared_from_this()]( const block_signal_params& t ) { + cc.accepted_block_header().connect( [my = shared_from_this()]( const block_signal_params& t ) { const auto& [ block, id ] = t; my->on_accepted_block_header( block, id ); } ); - cc.accepted_block.connect( [my = shared_from_this()]( const block_signal_params& t ) { + cc.accepted_block().connect( [my = shared_from_this()]( const block_signal_params& t ) { my->on_accepted_block(); } ); - cc.irreversible_block.connect( [my = shared_from_this()]( const block_signal_params& t ) { + cc.irreversible_block().connect( [my = shared_from_this()]( const block_signal_params& t ) { const auto& [ block, id ] = t; my->on_irreversible_block( id, block->block_num() ); } ); + + cc.voted_block().connect( [my = shared_from_this()]( const vote_message& vote ) { + my->on_voted_block(vote); + } ); } incoming_transaction_ack_subscription = app().get_channel().subscribe( @@ -4366,7 +4488,7 @@ namespace eosio { try { fc_ilog( logger, "shutdown.." ); - my->plugin_shutdown(); + my->plugin_shutdown(); app().executor().post( 0, [me = my](){} ); // keep my pointer alive until queue is drained fc_ilog( logger, "exit shutdown" ); } diff --git a/plugins/producer_plugin/CMakeLists.txt b/plugins/producer_plugin/CMakeLists.txt index 1353284054..454652e323 100644 --- a/plugins/producer_plugin/CMakeLists.txt +++ b/plugins/producer_plugin/CMakeLists.txt @@ -5,7 +5,7 @@ add_library( producer_plugin ${HEADERS} ) -target_link_libraries( producer_plugin chain_plugin http_client_plugin signature_provider_plugin appbase eosio_chain ) +target_link_libraries( producer_plugin chain_plugin http_client_plugin signature_provider_plugin appbase eosio_chain) target_include_directories( producer_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" ) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index ee4229d653..91f23c1798 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -78,6 +78,9 @@ fc::logger _transient_trx_successful_trace_log; const std::string transient_trx_failed_trace_logger_name("transient_trx_failure_tracing"); fc::logger _transient_trx_failed_trace_log; +const std::string hotstuff_logger_name("hotstuff"); +fc::logger hotstuff_logger; + namespace eosio { static auto _producer_plugin = application::register_plugin(); @@ -280,19 +283,8 @@ struct block_time_tracker { } void report(uint32_t block_num, account_name producer, producer_plugin::speculative_block_metrics& metrics) { - using namespace std::string_literals; - assert(!paused); auto now = fc::time_point::now(); - if( _log.is_enabled( fc::log_level::debug ) ) { - auto diff = now - clear_time_point - block_idle_time - trx_success_time - trx_fail_time - transient_trx_time - other_time; - fc_dlog( _log, "Block #${n} ${p} trx idle: ${i}us out of ${t}us, success: ${sn}, ${s}us, fail: ${fn}, ${f}us, " - "transient: ${ttn}, ${tt}us, other: ${o}us${rest}", - ("n", block_num)("p", producer) - ("i", block_idle_time)("t", now - clear_time_point)("sn", trx_success_num)("s", trx_success_time) - ("fn", trx_fail_num)("f", trx_fail_time) - ("ttn", transient_trx_num)("tt", transient_trx_time) - ("o", other_time)("rest", diff.count() > 5 ? ", diff: "s + std::to_string(diff.count()) + "us"s : ""s ) ); - } + report(block_num, producer, now); metrics.block_producer = producer; metrics.block_num = block_num; metrics.block_total_time_us = (now - clear_time_point).count(); @@ -306,6 +298,21 @@ struct block_time_tracker { metrics.block_other_time_us = other_time.count(); } + void report(uint32_t block_num, account_name producer, const fc::time_point& now = fc::time_point::now()) { + using namespace std::string_literals; + assert(!paused); + if( _log.is_enabled( fc::log_level::debug ) ) { + auto diff = now - clear_time_point - block_idle_time - trx_success_time - trx_fail_time - transient_trx_time - other_time; + fc_dlog( _log, "Block #${n} ${p} trx idle: ${i}us out of ${t}us, success: ${sn}, ${s}us, fail: ${fn}, ${f}us, " + "transient: ${ttn}, ${tt}us, other: ${o}us${rest}", + ("n", block_num)("p", producer) + ("i", block_idle_time)("t", now - clear_time_point)("sn", trx_success_num)("s", trx_success_time) + ("fn", trx_fail_num)("f", trx_fail_time) + ("ttn", transient_trx_num)("tt", transient_trx_time) + ("o", other_time)("rest", diff.count() > 5 ? ", diff: "s + std::to_string(diff.count()) + "us"s : ""s ) ); + } + } + void clear() { assert(!paused); block_idle_time = trx_fail_time = trx_success_time = transient_trx_time = other_time = fc::microseconds{}; @@ -489,8 +496,9 @@ class producer_plugin_impl : public std::enable_shared_from_this _signature_providers; - std::set _producers; - boost::asio::deadline_timer _timer; + chain::bls_pub_priv_key_map_t _finalizer_keys; // public, private + std::set _producers; + boost::asio::deadline_timer _timer; block_timing_util::producer_watermarks _producer_watermarks; pending_block_mode _pending_block_mode = pending_block_mode::speculating; unapplied_transaction_queue _unapplied_transactions; @@ -663,39 +671,43 @@ class producer_plugin_impl : public std::enable_shared_from_this& block_id, const block_state_legacy_ptr& bsp) { - auto& chain = chain_plug->chain(); - if (in_producing_mode()) { - fc_wlog(_log, "dropped incoming block #${num} id: ${id}", ("num", block->block_num())("id", block_id ? (*block_id).str() : "UNKNOWN")); - return false; - } - - // start a new speculative block, speculative start_block may have been interrupted - auto ensure = fc::make_scoped_exit([this]() { schedule_production_loop(); }); - + bool on_incoming_block(const signed_block_ptr& block, const block_id_type& id, const std::optional& obt) { auto now = fc::time_point::now(); - const auto& id = block_id ? *block_id : block->calculate_id(); - auto blk_num = block->block_num(); + _time_tracker.add_idle_time(now); + + assert(block); + auto blk_num = block->block_num(); if (now - block->timestamp < fc::minutes(5) || (blk_num % 1000 == 0)) // only log every 1000 during sync fc_dlog(_log, "received incoming block ${n} ${id}", ("n", blk_num)("id", id)); - _time_tracker.add_idle_time(now); + auto& chain = chain_plug->chain(); - EOS_ASSERT(block->timestamp < (now + fc::seconds(7)), block_from_the_future, "received a block from the future, ignoring it: ${id}", ("id", id)); + // de-dupe here... no point in aborting block if we already know the block; avoid exception in create_block_handle_future + if (chain.validated_block_exists(id)) { + _time_tracker.add_other_time(); + return true; // return true because the block was already accepted + } - /* de-dupe here... no point in aborting block if we already know the block */ - auto existing = chain.fetch_block_by_id(id); - if (existing) { - return true; // return true because the block is valid - } + EOS_ASSERT(block->timestamp < (now + fc::seconds(7)), block_from_the_future, "received a block from the future, ignoring it: ${id}", ("id", id)); // start processing of block - std::future bsf; - if (!bsp) { - bsf = chain.create_block_state_future(id, block); + std::future btf; + if (!obt) { + btf = chain.create_block_handle_future(id, block); } + if (in_producing_mode()) { + fc_ilog(_log, "producing, incoming block #${num} id: ${id}", ("num", blk_num)("id", id)); + const block_handle& bh = obt ? *obt : btf.get(); + chain.accept_block(bh); + _time_tracker.add_other_time(); + return true; // return true because block was accepted + } + + // start a new speculative block + auto ensure = fc::make_scoped_exit([this]() { schedule_production_loop(); }); + // abort the pending block abort_block(); @@ -708,11 +720,11 @@ class producer_plugin_impl : public std::enable_shared_from_thisheader.timestamp.next().to_time_point() >= now) { + if (chain.head_block_timestamp().next().to_time_point() >= now) { _production_enabled = true; } - if (now - block->timestamp < fc::minutes(5) || (blk_num % 1000 == 0)) { - ilog("Received block ${id}... #${n} @ ${t} signed by ${p} " - "[trxs: ${count}, lib: ${lib}, confirmed: ${confs}, net: ${net}, cpu: ${cpu}, elapsed: ${elapsed}, time: ${time}, latency: " - "${latency} ms]", - ("p", block->producer)("id", id.str().substr(8, 16))("n", blk_num)("t", block->timestamp) - ("count", block->transactions.size())("lib", chain.last_irreversible_block_num()) - ("confs", block->confirmed)("net", br.total_net_usage)("cpu", br.total_cpu_usage_us) - ("elapsed", br.total_elapsed_time)("time", br.total_time)("latency", (now - block->timestamp).count() / 1000)); - if (chain.get_read_mode() != db_read_mode::IRREVERSIBLE && hbs->id != id && hbs->block != nullptr) { // not applied to head - ilog("Block not applied to head ${id}... #${n} @ ${t} signed by ${p} " - "[trxs: ${count}, dpos: ${dpos}, confirmed: ${confs}, net: ${net}, cpu: ${cpu}, elapsed: ${elapsed}, time: ${time}, " - "latency: ${latency} ms]", - ("p", hbs->block->producer)("id", hbs->id.str().substr(8, 16))("n", hbs->block_num)("t", hbs->block->timestamp) - ("count", hbs->block->transactions.size())("dpos", hbs->dpos_irreversible_blocknum)("confs", hbs->block->confirmed) - ("net", br.total_net_usage)("cpu", br.total_cpu_usage_us)("elapsed", br.total_elapsed_time)("time", br.total_time) - ("latency", (now - hbs->block->timestamp).count() / 1000)); - } - } - if (_update_incoming_block_metrics) { + if (_update_incoming_block_metrics) { // only includes those blocks pushed, not those that are accepted and processed internally _update_incoming_block_metrics({.trxs_incoming_total = block->transactions.size(), .cpu_usage_us = br.total_cpu_usage_us, .total_elapsed_time_us = br.total_elapsed_time.count(), @@ -1013,14 +1006,10 @@ void new_chain_banner(const eosio::chain::controller& db) "*******************************\n" "\n"; - if( db.head_block_state()->header.timestamp.to_time_point() < (fc::time_point::now() - fc::milliseconds(200 * config::block_interval_ms))) - { + if( db.head_block_time() < (fc::time_point::now() - fc::milliseconds(200 * config::block_interval_ms))) { std::cerr << "Your genesis seems to have an old timestamp\n" - "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n" - "\n" - ; + "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n\n"; } - return; } producer_plugin::producer_plugin() @@ -1042,7 +1031,8 @@ void producer_plugin::set_program_options( boost::program_options::options_description producer_options; producer_options.add_options() - ("enable-stale-production,e", boost::program_options::bool_switch()->notifier([this](bool e){my->_production_enabled = e;}), "Enable block production, even if the chain is stale.") + ("enable-stale-production,e", boost::program_options::bool_switch()->notifier([this](bool e){my->_production_enabled = e;}), + "Enable block production, even if the chain is stale.") ("pause-on-startup,x", boost::program_options::bool_switch()->notifier([this](bool p){my->_pause_production = p;}), "Start this node in a state where production is paused") ("max-transaction-time", bpo::value()->default_value(config::block_interval_ms-1), "Setting this value (in milliseconds) will restrict the allowed transaction execution time to a value potentially lower than the on-chain consensus max_transaction_cpu_usage value.") @@ -1130,14 +1120,25 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia const std::vector key_spec_pairs = options["signature-provider"].as>(); for (const auto& key_spec_pair : key_spec_pairs) { try { - const auto& [pubkey, provider] = app().get_plugin().signature_provider_for_specification(key_spec_pair); - _signature_providers[pubkey] = provider; + const auto v = app().get_plugin().signature_provider_for_specification(key_spec_pair); + if (v) { + const auto& [pubkey, provider] = *v; + _signature_providers[pubkey] = provider; + } + const auto bls = app().get_plugin().bls_public_key_for_specification(key_spec_pair); + if (bls) { + const auto& [pubkey, privkey] = *bls; + _finalizer_keys[pubkey.to_string()] = privkey.to_string(); + } } catch(secure_enclave_exception& e) { elog("Error with Secure Enclave signature provider: ${e}; ignoring ${val}", ("e", e.top_message())("val", key_spec_pair)); + throw; } catch (fc::exception& e) { elog("Malformed signature provider: \"${val}\": ${e}, ignoring!", ("val", key_spec_pair)("e", e)); + throw; } catch (...) { elog("Malformed signature provider: \"${val}\", ignoring!", ("val", key_spec_pair)); + throw; } } } @@ -1274,8 +1275,8 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia ilog("read-only-threads ${s}, max read-only trx time to be enforced: ${t} us", ("s", _ro_thread_pool_size)("t", _ro_max_trx_time_us)); _incoming_block_sync_provider = app().get_method().register_provider( - [this](const signed_block_ptr& block, const std::optional& block_id, const block_state_legacy_ptr& bsp) { - return on_incoming_block(block, block_id, bsp); + [this](const signed_block_ptr& block, const block_id_type& block_id, const std::optional& obt) { + return on_incoming_block(block, block_id, obt); }); _incoming_transaction_async_provider = @@ -1328,26 +1329,31 @@ void producer_plugin_impl::plugin_startup() { EOS_ASSERT(_producers.empty() || chain.get_read_mode() != chain::db_read_mode::IRREVERSIBLE, plugin_config_exception, "node cannot have any producer-name configured because block production is impossible when read_mode is \"irreversible\""); + EOS_ASSERT(_finalizer_keys.empty() || chain.get_read_mode() != chain::db_read_mode::IRREVERSIBLE, plugin_config_exception, + "node cannot have any finalizers configured because finalization is impossible when read_mode is \"irreversible\""); + EOS_ASSERT(_producers.empty() || chain.get_validation_mode() == chain::validation_mode::FULL, plugin_config_exception, "node cannot have any producer-name configured because block production is not safe when validation_mode is not \"full\""); EOS_ASSERT(_producers.empty() || chain_plug->accept_transactions(), plugin_config_exception, "node cannot have any producer-name configured because no block production is possible with no [api|p2p]-accepted-transactions"); - _accepted_block_connection.emplace(chain.accepted_block.connect([this](const block_signal_params& t) { + chain.set_node_finalizer_keys(_finalizer_keys); + + _accepted_block_connection.emplace(chain.accepted_block().connect([this](const block_signal_params& t) { const auto& [ block, id ] = t; on_block(block); })); - _accepted_block_header_connection.emplace(chain.accepted_block_header.connect([this](const block_signal_params& t) { + _accepted_block_header_connection.emplace(chain.accepted_block_header().connect([this](const block_signal_params& t) { const auto& [ block, id ] = t; on_block_header(block->producer, block->block_num(), block->timestamp); })); - _irreversible_block_connection.emplace(chain.irreversible_block.connect([this](const block_signal_params& t) { + _irreversible_block_connection.emplace(chain.irreversible_block().connect([this](const block_signal_params& t) { const auto& [ block, id ] = t; on_irreversible_block(block); })); - _block_start_connection.emplace(chain.block_start.connect([this, &chain](uint32_t bs) { + _block_start_connection.emplace(chain.block_start().connect([this, &chain](uint32_t bs) { try { _snapshot_scheduler.on_start_block(bs, chain); } catch (const snapshot_execution_exception& e) { @@ -1431,6 +1437,7 @@ void producer_plugin::handle_sighup() { fc::logger::update(trx_logger_name, _trx_log); fc::logger::update(transient_trx_successful_trace_logger_name, _transient_trx_successful_trace_log); fc::logger::update(transient_trx_failed_trace_logger_name, _transient_trx_failed_trace_log); + fc::logger::update( hotstuff_logger_name, hotstuff_logger ); } void producer_plugin::pause() { @@ -1766,9 +1773,14 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { if (!chain_plug->accept_transactions()) return start_block_result::waiting_for_block; - const auto& hbs = chain.head_block_state(); + abort_block(); + + chain.maybe_switch_forks([this](const transaction_metadata_ptr& trx) { _unapplied_transactions.add_forked(trx); }, + [this](const transaction_id_type& id) { return _unapplied_transactions.get_trx(id); }); + + uint32_t head_block_num = chain.head_block_num(); - if (chain.get_terminate_at_block() > 0 && chain.get_terminate_at_block() <= chain.head_block_num()) { + if (chain.get_terminate_at_block() > 0 && chain.get_terminate_at_block() <= head_block_num) { ilog("Reached configured maximum block ${num}; terminating", ("num", chain.get_terminate_at_block())); app().quit(); return start_block_result::failed; @@ -1776,12 +1788,12 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { const fc::time_point now = fc::time_point::now(); const block_timestamp_type block_time = calculate_pending_block_time(); - const uint32_t pending_block_num = hbs->block_num + 1; + const uint32_t pending_block_num = head_block_num + 1; _pending_block_mode = pending_block_mode::producing; - // Not our turn - const auto& scheduled_producer = hbs->get_scheduled_producer(block_time); + // copy as reference is invalidated by abort_block() below + const producer_authority scheduled_producer = chain.active_producers().get_scheduled_producer(block_time); const auto current_watermark = _producer_watermarks.get_watermark(scheduled_producer.producer_name); @@ -1817,10 +1829,10 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { // determine if our watermark excludes us from producing at this point if (current_watermark) { const block_timestamp_type block_timestamp{block_time}; - if (current_watermark->first > hbs->block_num) { + if (current_watermark->first > head_block_num) { elog("Not producing block because \"${producer}\" signed a block at a higher block number (${watermark}) than the current " "fork's head (${head_block_num})", - ("producer", scheduled_producer.producer_name)("watermark", current_watermark->first)("head_block_num", hbs->block_num)); + ("producer", scheduled_producer.producer_name)("watermark", current_watermark->first)("head_block_num", head_block_num)); _pending_block_mode = pending_block_mode::speculating; } else if (current_watermark->second >= block_timestamp) { elog("Not producing block because \"${producer}\" signed a block at the next block time or later (${watermark}) than the pending " @@ -1871,26 +1883,25 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { try { uint16_t blocks_to_confirm = 0; - if (in_producing_mode()) { + auto block_state = chain.head_block_state_legacy(); // null means savanna is active + if (in_producing_mode() && block_state) { // only if savanna not enabled // determine how many blocks this producer can confirm // 1) if it is not a producer from this node, assume no confirmations (we will discard this block anyway) // 2) if it is a producer on this node that has never produced, the conservative approach is to assume no - // confirmations to make sure we don't double sign after a crash TODO: make these watermarks durable? + // confirmations to make sure we don't double sign after a crash // 3) if it is a producer on this node where this node knows the last block it produced, safely set it -UNLESS- // 4) the producer on this node's last watermark is higher (meaning on a different fork) if (current_watermark) { - auto watermark_bn = current_watermark->first; - if (watermark_bn < hbs->block_num) { - blocks_to_confirm = (uint16_t)(std::min(std::numeric_limits::max(), (uint32_t)(hbs->block_num - watermark_bn))); + uint32_t watermark_bn = current_watermark->first; + if (watermark_bn < head_block_num) { + blocks_to_confirm = (uint16_t)(std::min(std::numeric_limits::max(), (head_block_num - watermark_bn))); } } // can not confirm irreversible blocks - blocks_to_confirm = (uint16_t)(std::min(blocks_to_confirm, (uint32_t)(hbs->block_num - hbs->dpos_irreversible_blocknum))); + blocks_to_confirm = (uint16_t)(std::min(blocks_to_confirm, (head_block_num - block_state->dpos_irreversible_blocknum))); } - abort_block(); - auto features_to_activate = chain.get_preactivated_protocol_features(); if (in_producing_mode() && _protocol_features_to_activate.size() > 0) { bool drop_features_to_activate = false; @@ -1949,7 +1960,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { try { chain::subjective_billing& subjective_bill = chain.get_mutable_subjective_billing(); - _account_fails.report_and_clear(hbs->block_num, subjective_bill); + _account_fails.report_and_clear(pending_block_num, subjective_bill); if (!remove_expired_trxs(preprocess_deadline)) return start_block_result::exhausted; @@ -2488,7 +2499,7 @@ void producer_plugin_impl::schedule_production_loop() { chain::controller& chain = chain_plug->chain(); fc_dlog(_log, "Waiting till another block is received and scheduling Speculative/Production Change"); auto wake_time = block_timing_util::calculate_producer_wake_up_time(_produce_block_cpu_effort, chain.head_block_num(), calculate_pending_block_time(), - _producers, chain.head_block_state()->active_schedule.producers, + _producers, chain.head_active_producers().producers, _producer_watermarks); schedule_delayed_production_loop(weak_from_this(), wake_time); } else { @@ -2507,7 +2518,7 @@ void producer_plugin_impl::schedule_production_loop() { fc_dlog(_log, "Speculative Block Created; Scheduling Speculative/Production Change"); EOS_ASSERT(chain.is_building_block(), missing_pending_block_state, "speculating without pending_block_state"); auto wake_time = block_timing_util::calculate_producer_wake_up_time(fc::microseconds{config::block_interval_us}, chain.pending_block_num(), chain.pending_block_timestamp(), - _producers, chain.head_block_state()->active_schedule.producers, + _producers, chain.head_active_producers().producers, _producer_watermarks); if (wake_time && fc::time_point::now() > *wake_time) { // if wake time has already passed then use the block deadline instead @@ -2603,6 +2614,7 @@ static auto maybe_make_debug_time_logger() -> std::optional> relevant_providers; relevant_providers.reserve(_signature_providers.size()); @@ -2634,7 +2646,7 @@ void producer_plugin_impl::produce_block() { // idump( (fc::time_point::now() - chain.pending_block_time()) ); controller::block_report br; - chain.finalize_block(br, [&](const digest_type& d) { + chain.assemble_and_complete_block(br, [&](const digest_type& d) { auto debug_logger = maybe_make_debug_time_logger(); vector sigs; sigs.reserve(relevant_providers.size()); @@ -2646,27 +2658,16 @@ void producer_plugin_impl::produce_block() { return sigs; }); - chain.commit_block(); - - block_state_legacy_ptr new_bs = chain.head_block_state(); - producer_plugin::produced_block_metrics metrics; br.total_time += fc::time_point::now() - start; + chain.commit_block(br); - ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} " - "[trxs: ${count}, lib: ${lib}, confirmed: ${confs}, net: ${net}, cpu: ${cpu}, elapsed: ${et}, time: ${tt}]", - ("p", new_bs->header.producer)("id", new_bs->id.str().substr(8, 16))("n", new_bs->block_num)("t", new_bs->header.timestamp) - ("count", new_bs->block->transactions.size())("lib", chain.last_irreversible_block_num())("net", br.total_net_usage) - ("cpu", br.total_cpu_usage_us)("et", br.total_elapsed_time)("tt", br.total_time)("confs", new_bs->header.confirmed)); - - _time_tracker.add_other_time(); - _time_tracker.report(new_bs->block_num, new_bs->block->producer, metrics); - _time_tracker.clear(); - + const auto& new_b = chain.head_block(); if (_update_produced_block_metrics) { + producer_plugin::produced_block_metrics metrics; metrics.unapplied_transactions_total = _unapplied_transactions.size(); metrics.subjective_bill_account_size_total = chain.get_subjective_billing().get_account_cache_size(); metrics.scheduled_trxs_total = chain.db().get_index().size(); - metrics.trxs_produced_total = new_bs->block->transactions.size(); + metrics.trxs_produced_total = new_b->transactions.size(); metrics.cpu_usage_us = br.total_cpu_usage_us; metrics.total_elapsed_time_us = br.total_elapsed_time.count(); metrics.total_time_us = br.total_time.count(); @@ -2674,7 +2675,14 @@ void producer_plugin_impl::produce_block() { metrics.last_irreversible = chain.last_irreversible_block_num(); metrics.head_block_num = chain.head_block_num(); _update_produced_block_metrics(metrics); + + _time_tracker.add_other_time(); + _time_tracker.report(new_b->block_num(), new_b->producer, metrics); + } else { + _time_tracker.add_other_time(); + _time_tracker.report(new_b->block_num(), new_b->producer); } + _time_tracker.clear(); } void producer_plugin::received_block(uint32_t block_num) { diff --git a/plugins/producer_plugin/test/test_trx_full.cpp b/plugins/producer_plugin/test/test_trx_full.cpp index 51abbf3a3e..1a51570515 100644 --- a/plugins/producer_plugin/test/test_trx_full.cpp +++ b/plugins/producer_plugin/test/test_trx_full.cpp @@ -129,7 +129,7 @@ BOOST_AUTO_TEST_CASE(producer) { std::deque all_blocks; std::promise empty_blocks_promise; std::future empty_blocks_fut = empty_blocks_promise.get_future(); - auto ab = chain_plug->chain().accepted_block.connect( [&](const chain::block_signal_params& t) { + auto ab = chain_plug->chain().accepted_block().connect( [&](const chain::block_signal_params& t) { const auto& [ block, id ] = t; static int num_empty = std::numeric_limits::max(); all_blocks.push_back( block ); @@ -140,7 +140,7 @@ BOOST_AUTO_TEST_CASE(producer) { num_empty = 10; } } ); - auto bs = chain_plug->chain().block_start.connect( [&]( uint32_t bn ) { + auto bs = chain_plug->chain().block_start().connect( [&]( uint32_t bn ) { } ); std::atomic num_acked = 0; diff --git a/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp b/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp index 14fbc6f6e6..5378bb4cdd 100644 --- a/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp +++ b/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include namespace eosio { @@ -23,8 +25,12 @@ class signature_provider_plugin : public appbase::plugin; - std::pair signature_provider_for_specification(const std::string& spec) const; - signature_provider_type signature_provider_for_private_key(const chain::private_key_type priv) const; + // @return empty optional for BLS specs + std::optional> signature_provider_for_specification(const std::string& spec) const; + signature_provider_type signature_provider_for_private_key(const chain::private_key_type& priv) const; + + // @return empty optional for non-BLS specs + std::optional> bls_public_key_for_specification(const std::string& spec) const; private: std::unique_ptr my; diff --git a/plugins/signature_provider_plugin/signature_provider_plugin.cpp b/plugins/signature_provider_plugin/signature_provider_plugin.cpp index 2d4e31d9be..31d928ee55 100644 --- a/plugins/signature_provider_plugin/signature_provider_plugin.cpp +++ b/plugins/signature_provider_plugin/signature_provider_plugin.cpp @@ -37,6 +37,43 @@ class signature_provider_plugin_impl { return app().get_plugin().get_client().post_sync(keosd_url, params, deadline).as(); }; } + + // public_key spec_type spec_data + std::tuple parse_spec(const std::string& spec) const { + auto delim = spec.find("="); + EOS_ASSERT(delim != std::string::npos, chain::plugin_config_exception, "Missing \"=\" in the key spec pair"); + // public_key can be base64 encoded with trailing `=` + // e.g. --signature-provider PUB_BLS_FmgkiuA===KEY:PVT_BLS_NZhJZHFu + while( spec.size() > delim+1 && spec[delim+1] == '=' ) + ++delim; + EOS_ASSERT(delim < spec.size() + 1, chain::plugin_config_exception, "Missing spec data in the key spec pair"); + auto pub_key_str = spec.substr(0, delim); + auto spec_str = spec.substr(delim + 1); + + auto spec_delim = spec_str.find(":"); + EOS_ASSERT(spec_delim != std::string::npos, chain::plugin_config_exception, "Missing \":\" in the key spec pair"); + auto spec_type_str = spec_str.substr(0, spec_delim); + auto spec_data = spec_str.substr(spec_delim + 1); + return {std::move(pub_key_str), std::move(spec_type_str), std::move(spec_data)}; + } + + std::optional> + signature_provider_for_specification(const std::string& spec) const { + auto [pub_key_str, spec_type_str, spec_data] = parse_spec(spec); + if( pub_key_str.starts_with("PUB_BLS") && spec_type_str == "KEY" ) + return {}; + + auto pubkey = chain::public_key_type(pub_key_str); + + if(spec_type_str == "KEY") { + chain::private_key_type priv(spec_data); + EOS_ASSERT(pubkey == priv.get_public_key(), chain::plugin_config_exception, "Private key does not match given public key for ${pub}", ("pub", pubkey)); + return std::make_pair(pubkey, make_key_signature_provider(priv)); + } + else if(spec_type_str == "KEOSD") + return std::make_pair(pubkey, make_keosd_signature_provider(spec_data, pubkey)); + EOS_THROW(chain::plugin_config_exception, "Unsupported key provider type \"${t}\"", ("t", spec_type_str)); + } }; signature_provider_plugin::signature_provider_plugin():my(new signature_provider_plugin_impl()){} @@ -52,11 +89,11 @@ void signature_provider_plugin::set_program_options(options_description&, option const char* const signature_provider_plugin::signature_provider_help_text() const { return "Key=Value pairs in the form =\n" "Where:\n" - " \tis a string form of a vaild EOSIO public key\n\n" - " \tis a string in the form :\n\n" - " \tis KEY, KEOSD, or SE\n\n" - " KEY: \tis a string form of a valid EOSIO private key which maps to the provided public key\n\n" - " KEOSD: \tis the URL where keosd is available and the approptiate wallet(s) are unlocked\n\n" + " \tis a string form of a valid Antelope public key, including BLS finalizer key\n" + " \tis a string in the form :\n" + " \tis KEY, KEOSD, or SE\n" + " KEY: \tis a string form of a valid Antelope private key which maps to the provided public key\n" + " KEOSD: \tis the URL where keosd is available and the appropriate wallet(s) are unlocked\n\n" ; } @@ -65,33 +102,24 @@ void signature_provider_plugin::plugin_initialize(const variables_map& options) my->_keosd_provider_timeout_us = fc::milliseconds( options.at("keosd-provider-timeout").as() ); } -std::pair + +std::optional> signature_provider_plugin::signature_provider_for_specification(const std::string& spec) const { - auto delim = spec.find("="); - EOS_ASSERT(delim != std::string::npos, chain::plugin_config_exception, "Missing \"=\" in the key spec pair"); - auto pub_key_str = spec.substr(0, delim); - auto spec_str = spec.substr(delim + 1); - - auto spec_delim = spec_str.find(":"); - EOS_ASSERT(spec_delim != std::string::npos, chain::plugin_config_exception, "Missing \":\" in the key spec pair"); - auto spec_type_str = spec_str.substr(0, spec_delim); - auto spec_data = spec_str.substr(spec_delim + 1); - - auto pubkey = chain::public_key_type(pub_key_str); - - if(spec_type_str == "KEY") { - chain::private_key_type priv(spec_data); - EOS_ASSERT(pubkey == priv.get_public_key(), chain::plugin_config_exception, "Private key does not match given public key for ${pub}", ("pub", pubkey)); - return std::make_pair(pubkey, my->make_key_signature_provider(priv)); - } - else if(spec_type_str == "KEOSD") - return std::make_pair(pubkey, my->make_keosd_signature_provider(spec_data, pubkey)); - EOS_THROW(chain::plugin_config_exception, "Unsupported key provider type \"${t}\"", ("t", spec_type_str)); + return my->signature_provider_for_specification(spec); } signature_provider_plugin::signature_provider_type -signature_provider_plugin::signature_provider_for_private_key(const chain::private_key_type priv) const { +signature_provider_plugin::signature_provider_for_private_key(const chain::private_key_type& priv) const { return my->make_key_signature_provider(priv); } +std::optional> +signature_provider_plugin::bls_public_key_for_specification(const std::string& spec) const { + auto [pub_key_str, spec_type_str, spec_data] = my->parse_spec(spec); + if( pub_key_str.starts_with("PUB_BLS") && spec_type_str == "KEY" ) { + return std::make_pair(fc::crypto::blslib::bls_public_key{pub_key_str}, fc::crypto::blslib::bls_private_key{spec_data}); + } + return {}; } + +} // namespace eosio diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/session.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/session.hpp index 94da1e3e1d..1fbb2ed023 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/session.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/session.hpp @@ -28,7 +28,7 @@ struct session_base { virtual void send_update(const chain::signed_block_ptr& block, const chain::block_id_type& id) = 0; virtual ~session_base() = default; - std::optional current_request; + std::optional current_request; bool need_to_send_update = false; }; @@ -165,7 +165,12 @@ class blocks_ack_request_send_queue_entry : public send_queue_entry_base { , req(std::move(r)) {} void send_entry() override { - session->current_request->max_messages_in_flight += req.num_messages; + assert(session->current_request); + assert(std::holds_alternative(*session->current_request) || + std::holds_alternative(*session->current_request)); + + std::visit([&](auto& request) { request.max_messages_in_flight += req.num_messages; }, + *session->current_request); session->send_update(false); } }; @@ -173,10 +178,10 @@ class blocks_ack_request_send_queue_entry : public send_queue_entry_base { template class blocks_request_send_queue_entry : public send_queue_entry_base { std::shared_ptr session; - eosio::state_history::get_blocks_request_v0 req; + eosio::state_history::get_blocks_request req; public: - blocks_request_send_queue_entry(std::shared_ptr s, state_history::get_blocks_request_v0&& r) + blocks_request_send_queue_entry(std::shared_ptr s, state_history::get_blocks_request&& r) : session(std::move(s)) , req(std::move(r)) {} @@ -189,7 +194,7 @@ class blocks_request_send_queue_entry : public send_queue_entry_base { template class blocks_result_send_queue_entry : public send_queue_entry_base, public std::enable_shared_from_this> { std::shared_ptr session; - state_history::get_blocks_result_v0 r; + state_history::get_blocks_result result; std::vector data; std::optional stream; @@ -237,7 +242,7 @@ class blocks_result_send_queue_entry : public send_queue_entry_base, public std: } template - void send_log(uint64_t entry_size, bool is_deltas, Next&& next) { + void send_log(uint64_t entry_size, bool fin, Next&& next) { if (entry_size) { data.resize(16); // should be at least for 1 byte (optional) + 10 bytes (variable sized uint64_t) fc::datastream ds(data.data(), data.size()); @@ -248,10 +253,10 @@ class blocks_result_send_queue_entry : public send_queue_entry_base, public std: data = {'\0'}; // optional false } - async_send(is_deltas && entry_size == 0, data, - [is_deltas, entry_size, next = std::forward(next), me=this->shared_from_this()]() mutable { + async_send(fin && entry_size == 0, data, + [fin, entry_size, next = std::forward(next), me=this->shared_from_this()]() mutable { if (entry_size) { - me->async_send_buf(is_deltas, [me, next = std::move(next)]() { + me->async_send_buf(fin, [me, next = std::move(next)]() { next(); }); } else @@ -259,35 +264,72 @@ class blocks_result_send_queue_entry : public send_queue_entry_base, public std: }); } - void send_deltas() { + // last to be sent if result is get_blocks_result_v1 + void send_finality_data() { + assert(std::holds_alternative(result)); stream.reset(); - send_log(session->get_delta_log_entry(r, stream), true, [me=this->shared_from_this()]() { + send_log(session->get_finality_data_log_entry(std::get(result), stream), true, [me=this->shared_from_this()]() { me->stream.reset(); me->session->session_mgr.pop_entry(); }); } + // second to be sent if result is get_blocks_result_v1; + // last to be sent if result is get_blocks_result_v0 + void send_deltas() { + stream.reset(); + std::visit(chain::overloaded{ + [&](state_history::get_blocks_result_v0& r) { + send_log(session->get_delta_log_entry(r, stream), true, [me=this->shared_from_this()]() { + me->stream.reset(); + me->session->session_mgr.pop_entry();}); }, + [&](state_history::get_blocks_result_v1& r) { + send_log(session->get_delta_log_entry(r, stream), false, [me=this->shared_from_this()]() { + me->send_finality_data(); }); }}, + result); + } + + // first to be sent void send_traces() { stream.reset(); - send_log(session->get_trace_log_entry(r, stream), false, [me=this->shared_from_this()]() { + send_log(session->get_trace_log_entry(result, stream), false, [me=this->shared_from_this()]() { me->send_deltas(); }); } + template + void pack_result_base(const T& result, uint32_t variant_index) { + // pack the state_result{get_blocks_result} excluding the fields `traces` and `deltas`, + // and `finality_data` if get_blocks_result_v1 + fc::datastream ss; + + fc::raw::pack(ss, fc::unsigned_int(variant_index)); // pack the variant index of state_result{result} + fc::raw::pack(ss, static_cast(result)); + data.resize(ss.tellp()); + fc::datastream ds(data.data(), data.size()); + fc::raw::pack(ds, fc::unsigned_int(variant_index)); // pack the variant index of state_result{result} + fc::raw::pack(ds, static_cast(result)); + } + public: - blocks_result_send_queue_entry(std::shared_ptr s, state_history::get_blocks_result_v0&& r) + blocks_result_send_queue_entry(std::shared_ptr s, state_history::get_blocks_result&& r) : session(std::move(s)), - r(std::move(r)) {} + result(std::move(r)) {} void send_entry() override { - // pack the state_result{get_blocks_result} excluding the fields `traces` and `deltas` - fc::datastream ss; - fc::raw::pack(ss, fc::unsigned_int(1)); // pack the variant index of state_result{r} - fc::raw::pack(ss, static_cast(r)); - data.resize(ss.tellp()); - fc::datastream ds(data.data(), data.size()); - fc::raw::pack(ds, fc::unsigned_int(1)); // pack the variant index of state_result{r} - fc::raw::pack(ds, static_cast(r)); + std::visit( + chain::overloaded{ + [&](state_history::get_blocks_result_v0& r) { + static_assert(std::is_same_v>); + pack_result_base(r, 1); // 1 for variant index of get_blocks_result_v0 in state_result + }, + [&](state_history::get_blocks_result_v1& r) { + static_assert(std::is_same_v>); + pack_result_base(r, 2); // 2 for variant index of get_blocks_result_v1 in state_result + } + }, + result + ); async_send(false, data, [me=this->shared_from_this()]() { me->send_traces(); @@ -379,28 +421,32 @@ struct session : session_base, std::enable_shared_from_this& buf) { - if (result.traces.has_value()) { - auto& optional_log = plugin.get_trace_log(); + uint64_t get_log_entry_impl(const eosio::state_history::get_blocks_result& result, + bool has_value, + std::optional& optional_log, + std::optional& buf) { + if (has_value) { if( optional_log ) { buf.emplace( optional_log->create_locked_decompress_stream() ); - return optional_log->get_unpacked_entry( result.this_block->block_num, *buf ); + return std::visit([&](auto& r) { return optional_log->get_unpacked_entry( r.this_block->block_num, *buf ); }, result); } } return 0; } - uint64_t get_delta_log_entry(const eosio::state_history::get_blocks_result_v0& result, + uint64_t get_trace_log_entry(const eosio::state_history::get_blocks_result& result, std::optional& buf) { - if (result.deltas.has_value()) { - auto& optional_log = plugin.get_chain_state_log(); - if( optional_log ) { - buf.emplace( optional_log->create_locked_decompress_stream() ); - return optional_log->get_unpacked_entry( result.this_block->block_num, *buf ); - } - } - return 0; + return std::visit([&](auto& r) { return get_log_entry_impl(r, r.traces.has_value(), plugin.get_trace_log(), buf); }, result); + } + + uint64_t get_delta_log_entry(const eosio::state_history::get_blocks_result& result, + std::optional& buf) { + return std::visit([&](auto& r) { return get_log_entry_impl(r, r.deltas.has_value(), plugin.get_chain_state_log(), buf); }, result); + } + + uint64_t get_finality_data_log_entry(const eosio::state_history::get_blocks_result_v1& result, + std::optional& buf) { + return get_log_entry_impl(result, result.finality_data.has_value(), plugin.get_finality_data_log(), buf); } void process(state_history::get_status_request_v0&) { @@ -419,6 +465,14 @@ struct session : session_base, std::enable_shared_from_thisshared_from_this(); + auto entry_ptr = std::make_unique>(self, std::move(req)); + session_mgr.add_send_queue(std::move(self), std::move(entry_ptr)); + } + void process(state_history::get_blocks_ack_request_v0& req) { fc_dlog(plugin.get_logger(), "received get_blocks_ack_request_v0 = ${req}", ("req", req)); if (!current_request) { @@ -454,7 +508,7 @@ struct session : session_base, std::enable_shared_from_this(req) || + std::holds_alternative(req)); + std::visit( [&](auto& request) { update_current_request_impl(request); }, req ); current_request = std::move(req); } - void send_update(state_history::get_blocks_result_v0 result, const chain::signed_block_ptr& block, const chain::block_id_type& id) { + template // get_blocks_result_v0 or get_blocks_result_v1 + void send_update(state_history::get_blocks_request_v0& request, bool fetch_finality_data, T&& result, const chain::signed_block_ptr& block, const chain::block_id_type& id) { need_to_send_update = true; - if (!current_request || !current_request->max_messages_in_flight) { - session_mgr.pop_entry(false); - return; - } result.last_irreversible = plugin.get_last_irreversible(); uint32_t current = - current_request->irreversible_only ? result.last_irreversible.block_num : result.head.block_num; + request.irreversible_only ? result.last_irreversible.block_num : result.head.block_num; - if (to_send_block_num > current || to_send_block_num >= current_request->end_block_num) { - fc_dlog( plugin.get_logger(), "Not sending, to_send_block_num: ${s}, current: ${c} current_request.end_block_num: ${b}", - ("s", to_send_block_num)("c", current)("b", current_request->end_block_num) ); + fc_dlog( plugin.get_logger(), "irreversible_only: ${i}, last_irreversible: ${p}, head.block_num: ${h}", ("i", request.irreversible_only)("p", result.last_irreversible.block_num)("h", result.head.block_num)); + if (to_send_block_num > current || to_send_block_num >= request.end_block_num) { + fc_dlog( plugin.get_logger(), "Not sending, to_send_block_num: ${s}, current: ${c} request.end_block_num: ${b}", + ("s", to_send_block_num)("c", current)("b", request.end_block_num) ); session_mgr.pop_entry(false); return; } @@ -512,7 +570,7 @@ struct session : session_base, std::enable_shared_from_thisblock_id; ++itr; - if (itr == current_request->have_positions.end()) + if (itr == request.have_positions.end()) position_it.reset(); if(block_id_seen_by_client == *block_id) { @@ -527,14 +585,19 @@ struct session : session_base, std::enable_shared_from_thisfetch_block) { + if (request.fetch_block) { uint32_t block_num = block ? block->block_num() : 0; // block can be nullptr in testing plugin.get_block(to_send_block_num, block_num, block, result.block); } - if (current_request->fetch_traces && plugin.get_trace_log()) + if (request.fetch_traces && plugin.get_trace_log()) result.traces.emplace(); - if (current_request->fetch_deltas && plugin.get_chain_state_log()) + if (request.fetch_deltas && plugin.get_chain_state_log()) result.deltas.emplace(); + if constexpr (std::is_same_v) { + if (fetch_finality_data && plugin.get_finality_data_log()) { + result.finality_data.emplace(); + } + } } ++to_send_block_num; @@ -550,31 +613,60 @@ struct session : session_base, std::enable_shared_from_thisblock_id} : fc::variant{})); } - --current_request->max_messages_in_flight; + --request.max_messages_in_flight; need_to_send_update = to_send_block_num <= current && - to_send_block_num < current_request->end_block_num; + to_send_block_num < request.end_block_num; std::make_shared>(this->shared_from_this(), std::move(result))->send_entry(); } + bool no_request_or_not_max_messages_in_flight() { + if (!current_request) + return true; + + uint32_t max_messages_in_flight = std::visit( + [&](auto& request) -> uint32_t { return request.max_messages_in_flight; }, + *current_request); + + return !max_messages_in_flight; + } + + void send_update(const state_history::block_position& head, const chain::signed_block_ptr& block, const chain::block_id_type& id) { + if (no_request_or_not_max_messages_in_flight()) { + session_mgr.pop_entry(false); + return; + } + + assert(current_request); + assert(std::holds_alternative(*current_request) || + std::holds_alternative(*current_request)); + + std::visit(eosio::chain::overloaded{ + [&](eosio::state_history::get_blocks_request_v0& request) { + state_history::get_blocks_result_v0 result; + result.head = head; + send_update(request, false, std::move(result), block, id); }, + [&](eosio::state_history::get_blocks_request_v1& request) { + state_history::get_blocks_result_v1 result; + result.head = head; + send_update(request, request.fetch_finality_data, std::move(result), block, id); } }, + *current_request); + } + void send_update(const chain::signed_block_ptr& block, const chain::block_id_type& id) override { - if (!current_request || !current_request->max_messages_in_flight) { + if (no_request_or_not_max_messages_in_flight()) { session_mgr.pop_entry(false); return; } auto block_num = block->block_num(); - state_history::get_blocks_result_v0 result; - result.head = {block_num, id}; to_send_block_num = std::min(block_num, to_send_block_num); - send_update(std::move(result), block, id); + send_update(state_history::block_position{block_num, id}, block, id); } void send_update(bool changed) override { if (changed || need_to_send_update) { - state_history::get_blocks_result_v0 result; - result.head = plugin.get_block_head(); - send_update(std::move(result), nullptr, chain::block_id_type{}); + send_update(plugin.get_block_head(), nullptr, chain::block_id_type{}); } else { session_mgr.pop_entry(false); } diff --git a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_plugin.hpp b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_plugin.hpp index 017dda0b12..a9070115d1 100644 --- a/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_plugin.hpp +++ b/plugins/state_history_plugin/include/eosio/state_history_plugin/state_history_plugin.hpp @@ -31,6 +31,7 @@ class state_history_plugin : public plugin { const state_history_log* trace_log() const; const state_history_log* chain_state_log() const; + const state_history_log* finality_data_log() const; private: state_history_ptr my; diff --git a/plugins/state_history_plugin/state_history_plugin.cpp b/plugins/state_history_plugin/state_history_plugin.cpp index c40a695e2f..966fe464d9 100644 --- a/plugins/state_history_plugin/state_history_plugin.cpp +++ b/plugins/state_history_plugin/state_history_plugin.cpp @@ -54,6 +54,7 @@ struct state_history_plugin_impl : std::enable_shared_from_this trace_log; std::optional chain_state_log; + std::optional finality_data_log; uint32_t first_available_block = 0; bool trace_debug_mode = false; std::optional applied_transaction_connection; @@ -84,6 +85,7 @@ struct state_history_plugin_impl : std::enable_shared_from_this& get_trace_log() { return trace_log; } std::optional& get_chain_state_log(){ return chain_state_log; } + std::optional& get_finality_data_log(){ return finality_data_log; } boost::asio::io_context& get_ship_executor() { return thread_pool.get_executor(); } @@ -115,15 +117,16 @@ struct state_history_plugin_impl : std::enable_shared_from_this get_block_id(uint32_t block_num) { - std::optional id; if( trace_log ) { - id = trace_log->get_block_id( block_num ); - if( id ) + if ( auto id = trace_log->get_block_id( block_num ) ) return id; } if( chain_state_log ) { - id = chain_state_log->get_block_id( block_num ); - if( id ) + if( auto id = chain_state_log->get_block_id( block_num ) ) + return id; + } + if( finality_data_log ) { + if( auto id = finality_data_log->get_block_id( block_num ) ) return id; } try { @@ -206,7 +209,8 @@ struct state_history_plugin_impl : std::enable_shared_from_this(*block), block->block_num()); + store_chain_state(id, block->previous, block->block_num()); + store_finality_data(id, block->previous); } catch (const fc::exception& e) { fc_elog(_log, "fc::exception: ${details}", ("details", e.to_detail_string())); // Both app().quit() and exception throwing are required. Without app().quit(), @@ -256,7 +260,7 @@ struct state_history_plugin_impl : std::enable_shared_from_thisempty(); @@ -264,12 +268,29 @@ struct state_history_plugin_impl : std::enable_shared_from_thispack_and_write_entry(header, block_header.previous, [this, fresh](auto&& buf) { + .magic = ship_magic(ship_current_version, 0), .block_id = id, .payload_size = 0}; + chain_state_log->pack_and_write_entry(header, previous_id, [this, fresh](auto&& buf) { pack_deltas(buf, chain_plug->chain().db(), fresh); }); } // store_chain_state + // called from main thread + void store_finality_data(const block_id_type& id, const block_id_type& previous_id) { + if (!finality_data_log) + return; + + std::optional finality_data = chain_plug->chain().head_finality_data(); + if (!finality_data.has_value()) + return; + + state_history_log_header header{ + .magic = ship_magic(ship_current_version, 0), .block_id = id, .payload_size = 0}; + finality_data_log->pack_and_write_entry(header, previous_id, [finality_data](auto&& buf) { + fc::datastream ds{buf}; + fc::raw::pack(ds, *finality_data); + }); + } + ~state_history_plugin_impl() { } @@ -302,6 +323,7 @@ void state_history_plugin::set_program_options(options_description& cli, options cli.add_options()("delete-state-history", bpo::bool_switch()->default_value(false), "clear state history files"); options("trace-history", bpo::bool_switch()->default_value(false), "enable trace history"); options("chain-state-history", bpo::bool_switch()->default_value(false), "enable chain state history"); + options("finality-data-history", bpo::bool_switch()->default_value(false), "enable finality data history"); options("state-history-endpoint", bpo::value()->default_value("127.0.0.1:8080"), "the endpoint upon which to listen for incoming connections. Caution: only expose this port to " "your internal network."); @@ -324,17 +346,17 @@ void state_history_plugin_impl::plugin_initialize(const variables_map& options) chain.set_disable_replay_opts(true); } - applied_transaction_connection.emplace(chain.applied_transaction.connect( + applied_transaction_connection.emplace(chain.applied_transaction().connect( [&](std::tuple t) { on_applied_transaction(std::get<0>(t), std::get<1>(t)); })); accepted_block_connection.emplace( - chain.accepted_block.connect([&](const block_signal_params& t) { + chain.accepted_block().connect([&](const block_signal_params& t) { const auto& [ block, id ] = t; on_accepted_block(block, id); })); block_start_connection.emplace( - chain.block_start.connect([&](uint32_t block_num) { on_block_start(block_num); })); + chain.block_start().connect([&](uint32_t block_num) { on_block_start(block_num); })); auto dir_option = options.at("state-history-dir").as(); std::filesystem::path state_history_dir; @@ -393,6 +415,8 @@ void state_history_plugin_impl::plugin_initialize(const variables_map& options) trace_log.emplace("trace_history", state_history_dir , ship_log_conf); if (options.at("chain-state-history").as()) chain_state_log.emplace("chain_state_history", state_history_dir, ship_log_conf); + if (options.at("finality-data-history").as()) + finality_data_log.emplace("finality_data_history", state_history_dir, ship_log_conf); } FC_LOG_AND_RETHROW() } // state_history_plugin::plugin_initialize @@ -406,10 +430,10 @@ void state_history_plugin_impl::plugin_startup() { try { const auto& chain = chain_plug->chain(); update_current(); - auto bsp = chain.head_block_state(); - if( bsp && chain_state_log && chain_state_log->empty() ) { + uint32_t block_num = chain.head_block_num(); + if( block_num > 0 && chain_state_log && chain_state_log->empty() ) { fc_ilog( _log, "Storing initial state on startup, this can take a considerable amount of time" ); - store_chain_state( bsp->id, bsp->header, bsp->block_num ); + store_chain_state( chain.head_block_id(), chain.head_block_header().previous, block_num ); fc_ilog( _log, "Done storing initial state on startup" ); } first_available_block = chain.earliest_available_block_num(); @@ -423,6 +447,11 @@ void state_history_plugin_impl::plugin_startup() { if( first_state_block > 0 ) first_available_block = std::min( first_available_block, first_state_block ); } + if (finality_data_log) { + auto first_state_block = finality_data_log->block_range().first; + if( first_state_block > 0 ) + first_available_block = std::min( first_available_block, first_state_block ); + } fc_ilog(_log, "First available block for SHiP ${b}", ("b", first_available_block)); listen(); // use of executor assumes only one thread @@ -465,4 +494,9 @@ const state_history_log* state_history_plugin::chain_state_log() const { return log ? std::addressof(*log) : nullptr; } +const state_history_log* state_history_plugin::finality_data_log() const { + const auto& log = my->get_finality_data_log(); + return log ? std::addressof(*log) : nullptr; +} + } // namespace eosio diff --git a/plugins/state_history_plugin/tests/session_test.cpp b/plugins/state_history_plugin/tests/session_test.cpp index bcfb2a219d..d064ab2cb4 100644 --- a/plugins/state_history_plugin/tests/session_test.cpp +++ b/plugins/state_history_plugin/tests/session_test.cpp @@ -30,6 +30,8 @@ namespace net = boost::asio; // from namespace bio = boost::iostreams; using tcp = boost::asio::ip::tcp; // from +using namespace eosio::state_history; + namespace eosio::state_history { template @@ -75,19 +77,23 @@ fc::datastream& operator>>(fc::datastream& ds, eosio::state_history::get unpack_big_bytes(ds, obj.deltas); return ds; } + +template +fc::datastream& operator>>(fc::datastream& ds, eosio::state_history::get_blocks_result_v1& obj) { + fc::raw::unpack(ds, obj.head); + fc::raw::unpack(ds, obj.last_irreversible); + fc::raw::unpack(ds, obj.this_block); + fc::raw::unpack(ds, obj.prev_block); + unpack_big_bytes(ds, obj.block); + unpack_big_bytes(ds, obj.traces); + unpack_big_bytes(ds, obj.deltas); + unpack_big_bytes(ds, obj.finality_data); + return ds; +} } // namespace eosio::state_history //------------------------------------------------------------------------------ -std::unordered_map block_ids; -fc::sha256 block_id_for(const uint32_t bnum, const std::string& nonce = {}) { - if (auto it = block_ids.find(bnum); it != block_ids.end()) - return it->second; - fc::sha256 m = fc::sha256::hash(fc::sha256::hash(std::to_string(bnum)+nonce)); - m._hash[0] = fc::endian_reverse_u32(bnum); - block_ids[bnum] = m; - return m; -} // Report a failure void fail(beast::error_code ec, char const* what) { std::cerr << what << ": " << ec.message() << "\n"; } @@ -103,20 +109,26 @@ struct mock_state_history_plugin { fc::temp_directory log_dir; std::optional trace_log; std::optional state_log; + std::optional finality_data_log; std::atomic stopping = false; eosio::session_manager session_mgr{ship_ioc}; + std::unordered_map block_ids; constexpr static uint32_t default_frame_size = 1024; std::optional& get_trace_log() { return trace_log; } std::optional& get_chain_state_log() { return state_log; } + std::optional& get_finality_data_log() { return finality_data_log; } fc::sha256 get_chain_id() const { return {}; } boost::asio::io_context& get_ship_executor() { return ship_ioc; } - void setup_state_history_log(eosio::state_history_log_config conf = {}) { + void setup_state_history_log(bool fetch_finality_data, eosio::state_history_log_config conf = {}) { trace_log.emplace("ship_trace", log_dir.path(), conf); state_log.emplace("ship_state", log_dir.path(), conf); + if( fetch_finality_data ) { + finality_data_log.emplace("ship_finality_data", log_dir.path(), conf); + } } fc::logger logger = fc::logger::get(DEFAULT_LOGGER); @@ -132,6 +144,15 @@ struct mock_state_history_plugin { return fc::time_point{}; } + fc::sha256 block_id_for(const uint32_t bnum, const std::string& nonce = {}) { + if (auto it = block_ids.find(bnum); it != block_ids.end()) + return it->second; + fc::sha256 m = fc::sha256::hash(fc::sha256::hash(std::to_string(bnum)+nonce)); + m._hash[0] = fc::endian_reverse_u32(bnum); + block_ids[bnum] = m; + return m; + } + std::optional get_block_id(uint32_t block_num) { std::optional id; if( trace_log ) { @@ -144,6 +165,11 @@ struct mock_state_history_plugin { if( id ) return id; } + if( finality_data_log ) { + id = finality_data_log->get_block_id( block_num ); + if( id ) + return id; + } return block_id_for(block_num); } @@ -287,36 +313,36 @@ struct state_history_test_fixture { BOOST_CHECK(fc::raw::pack(status) == fc::raw::pack(received_status)); } - void add_to_log(uint32_t index, uint32_t type, std::vector&& decompressed_data) { + void add_to_log(uint32_t index, uint32_t type, std::vector&& decompressed_data, bool fetch_finality_data) { uint64_t decompressed_byte_count = decompressed_data.size() * sizeof(int32_t); auto compressed = zlib_compress((const char*)decompressed_data.data(), decompressed_byte_count); eosio::state_history_log_header header; - header.block_id = block_id_for(index); + header.block_id = server.block_id_for(index); header.payload_size = compressed.size() + sizeof(type); if (type == 1) { header.payload_size += sizeof(uint64_t); } - std::unique_lock gt(server.trace_log->_mx); - server.trace_log->write_entry(header, block_id_for(index - 1), [&](auto& f) { - f.write((const char*)&type, sizeof(type)); - if (type == 1) { - f.write((const char*)&decompressed_byte_count, sizeof(decompressed_byte_count)); - } - f.write(compressed.data(), compressed.size()); - }); - gt.unlock(); - std::unique_lock gs(server.state_log->_mx); - server.state_log->write_entry(header, block_id_for(index - 1), [&](auto& f) { - f.write((const char*)&type, sizeof(type)); - if (type == 1) { - f.write((const char*)&decompressed_byte_count, sizeof(decompressed_byte_count)); - } - f.write(compressed.data(), compressed.size()); - }); - gs.unlock(); + auto write_log = [&](std::optional& log) { + assert(log); + std::unique_lock lk(log->_mx); + log->write_entry(header, server.block_id_for(index - 1), [&](auto& f) { + f.write((const char*)&type, sizeof(type)); + if (type == 1) { + f.write((const char*)&decompressed_byte_count, sizeof(decompressed_byte_count)); + } + f.write(compressed.data(), compressed.size()); + }); + lk.unlock(); + }; + + write_log(server.trace_log); + write_log(server.state_log); + if( fetch_finality_data ) { + write_log(server.finality_data_log); + } if (written_data.size() < index) written_data.resize(index); @@ -326,17 +352,16 @@ struct state_history_test_fixture { ~state_history_test_fixture() { ws.close(websocket::close_code::normal); } }; -void store_read_test_case(uint64_t data_size, eosio::state_history_log_config config) { +void store_read_test_case(test_server& server, uint64_t data_size, eosio::state_history_log_config config) { fc::temp_directory log_dir; eosio::state_history_log log("ship", log_dir.path(), config); - eosio::state_history_log_header header; - header.block_id = block_id_for(1); + header.block_id = server.block_id_for(1); header.payload_size = 0; auto data = generate_data(data_size); - log.pack_and_write_entry(header, block_id_for(0), + log.pack_and_write_entry(header, server.block_id_for(0), [&](auto&& buf) { bio::write(buf, (const char*)data.data(), data.size() * sizeof(data[0])); }); // make sure the current file position is at the end of file @@ -344,7 +369,6 @@ void store_read_test_case(uint64_t data_size, eosio::state_history_log_config co log.get_log_file().seek_end(0); BOOST_REQUIRE_EQUAL(log.get_log_file().tellp(), pos); - eosio::locked_decompress_stream buf = log.create_locked_decompress_stream(); log.get_unpacked_entry(1, buf); @@ -358,40 +382,40 @@ void store_read_test_case(uint64_t data_size, eosio::state_history_log_config co BOOST_CHECK(std::equal(decompressed.begin(), decompressed.end(), (const char*)data.data())); } -BOOST_AUTO_TEST_CASE(store_read_entry_no_prune) { - store_read_test_case(1024, {}); +BOOST_FIXTURE_TEST_CASE(store_read_entry_no_prune, state_history_test_fixture) { + store_read_test_case(server, 1024, {}); } -BOOST_AUTO_TEST_CASE(store_read_big_entry_no_prune) { +BOOST_FIXTURE_TEST_CASE(store_read_big_entry_no_prune, state_history_test_fixture) { // test the case where the uncompressed data size exceeds 4GB - store_read_test_case( (1ULL<< 32) + (1ULL << 20), {}); + store_read_test_case(server, (1ULL<< 32) + (1ULL << 20), {}); } -BOOST_AUTO_TEST_CASE(store_read_entry_prune_enabled) { - store_read_test_case(1024, eosio::state_history::prune_config{.prune_blocks = 100}); +BOOST_FIXTURE_TEST_CASE(store_read_entry_prune_enabled, state_history_test_fixture) { + store_read_test_case(server, 1024, eosio::state_history::prune_config{.prune_blocks = 100}); } -BOOST_AUTO_TEST_CASE(store_with_existing) { +BOOST_FIXTURE_TEST_CASE(store_with_existing, state_history_test_fixture) { uint64_t data_size = 512; fc::temp_directory log_dir; eosio::state_history_log log("ship", log_dir.path(), {}); eosio::state_history_log_header header; - header.block_id = block_id_for(1); + header.block_id = server.block_id_for(1); header.payload_size = 0; auto data = generate_data(data_size); - log.pack_and_write_entry(header, block_id_for(0), + log.pack_and_write_entry(header, server.block_id_for(0), [&](auto&& buf) { bio::write(buf, (const char*)data.data(), data.size() * sizeof(data[0])); }); - header.block_id = block_id_for(2); - log.pack_and_write_entry(header, block_id_for(1), + header.block_id = server.block_id_for(2); + log.pack_and_write_entry(header, server.block_id_for(1), [&](auto&& buf) { bio::write(buf, (const char*)data.data(), data.size() * sizeof(data[0])); }); // Do not allow starting from scratch for existing - header.block_id = block_id_for(1); + header.block_id = server.block_id_for(1); BOOST_CHECK_EXCEPTION( - log.pack_and_write_entry(header, block_id_for(0), [&](auto&& buf) { bio::write(buf, (const char*)data.data(), data.size() * sizeof(data[0])); }), + log.pack_and_write_entry(header, server.block_id_for(0), [&](auto&& buf) { bio::write(buf, (const char*)data.data(), data.size() * sizeof(data[0])); }), eosio::chain::plugin_exception, []( const auto& e ) { return e.to_detail_string().find( "Existing ship log" ) != std::string::npos; @@ -399,97 +423,168 @@ BOOST_AUTO_TEST_CASE(store_with_existing) { ); } -BOOST_FIXTURE_TEST_CASE(test_session_no_prune, state_history_test_fixture) { - try { - // setup block head for the server - server.setup_state_history_log(); - uint32_t head_block_num = 3; - server.block_head = {head_block_num, block_id_for(head_block_num)}; - - // generate the log data used for traces and deltas - uint32_t n = mock_state_history_plugin::default_frame_size; - add_to_log(1, n * sizeof(uint32_t), generate_data(n)); // original data format - add_to_log(2, 0, generate_data(n)); // format to accommodate the compressed size greater than 4GB - add_to_log(3, 1, generate_data(n)); // format to encode decompressed size to avoid decompress entire data upfront. - - // send a get_status_request and verify the result is what we expected - verify_status(eosio::state_history::get_status_result_v0{ - .head = {head_block_num, block_id_for(head_block_num)}, - .last_irreversible = {head_block_num, block_id_for(head_block_num)}, - .trace_begin_block = 1, - .trace_end_block = head_block_num + 1, - .chain_state_begin_block = 1, - .chain_state_end_block = head_block_num + 1}); - - // send a get_blocks_request to server - send_request(eosio::state_history::get_blocks_request_v0{.start_block_num = 1, - .end_block_num = UINT32_MAX, - .max_messages_in_flight = UINT32_MAX, - .have_positions = {}, - .irreversible_only = false, - .fetch_block = true, - .fetch_traces = true, - .fetch_deltas = true}); +void send_request(state_history_test_fixture& fixture, bool fetch_finality_data, uint32_t start_block_num, const std::vector& have_positions) { + if( fetch_finality_data ) { + get_blocks_request_v1 req { .fetch_finality_data = true }; - eosio::state_history::state_result result; - // we should get 3 consecutive block result - for (int i = 0; i < 3; ++i) { - receive_result(result); + req.start_block_num = start_block_num; + req.end_block_num = UINT32_MAX; + req.max_messages_in_flight = UINT32_MAX; + req.have_positions = have_positions; + req.irreversible_only = false; + req.fetch_block = true; + req.fetch_traces = true; + req.fetch_deltas = true; + + fixture.send_request(req); + } else { + fixture.send_request(eosio::state_history::get_blocks_request_v0{ + .start_block_num = start_block_num, + .end_block_num = UINT32_MAX, + .max_messages_in_flight = UINT32_MAX, + .have_positions = have_positions, + .irreversible_only = false, + .fetch_block = true, + .fetch_traces = true, + .fetch_deltas = true} + ); + } +} + +void test_session_no_prune_impl(state_history_test_fixture& fixture, bool fetch_finality_data) { + // setup block head for the server + fixture.server.setup_state_history_log(fetch_finality_data); + uint32_t head_block_num = 3; + fixture.server.block_head = {head_block_num, fixture.server.block_id_for(head_block_num)}; + + // generate the log data used for traces, deltas, and finality_data if required + uint32_t n = mock_state_history_plugin::default_frame_size; + fixture.add_to_log(1, n * sizeof(uint32_t), generate_data(n), fetch_finality_data); // original data format + fixture.add_to_log(2, 0, generate_data(n), fetch_finality_data); // format to accommodate the compressed size greater than 4GB + fixture.add_to_log(3, 1, generate_data(n), fetch_finality_data); // format to encode decompressed size to avoid decompress entire data upfront. + + // send a get_status_request and verify the result is what we expected + fixture.verify_status(eosio::state_history::get_status_result_v0{ + .head = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .last_irreversible = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .trace_begin_block = 1, + .trace_end_block = head_block_num + 1, + .chain_state_begin_block = 1, + .chain_state_end_block = head_block_num + 1}); + + // send a get_blocks_request to server + send_request(fixture, fetch_finality_data, 1, {}); + + eosio::state_history::state_result result; + // we should get 3 consecutive block result + for (int i = 0; i < 3; ++i) { + fixture.receive_result(result); + + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(result)); + auto r = std::get(result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + BOOST_REQUIRE(r.traces.has_value()); + BOOST_REQUIRE(r.deltas.has_value()); + BOOST_REQUIRE(r.finality_data.has_value()); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); + BOOST_REQUIRE_EQUAL(traces.size(), data_size); + BOOST_REQUIRE_EQUAL(deltas.size(), data_size); + BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); + BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); + + auto finality_data = r.finality_data.value(); + BOOST_REQUIRE_EQUAL(finality_data.size(), data_size); + BOOST_REQUIRE(std::equal(finality_data.begin(), finality_data.end(), (const char*)data.data())); + } else { BOOST_REQUIRE(std::holds_alternative(result)); auto r = std::get(result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); BOOST_REQUIRE(r.traces.has_value()); BOOST_REQUIRE(r.deltas.has_value()); auto traces = r.traces.value(); auto deltas = r.deltas.value(); - auto& data = written_data[i]; + auto& data = fixture.written_data[i]; auto data_size = data.size() * sizeof(int32_t); BOOST_REQUIRE_EQUAL(traces.size(), data_size); BOOST_REQUIRE_EQUAL(deltas.size(), data_size); - BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); } } +} + +BOOST_FIXTURE_TEST_CASE(test_session_no_prune, state_history_test_fixture) { + try { + test_session_no_prune_impl(*this, false); + } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(test_split_log, state_history_test_fixture) { +BOOST_FIXTURE_TEST_CASE(test_session_no_prune_fetch_finality_data, state_history_test_fixture) { try { - // setup block head for the server - constexpr uint32_t head = 1023; - eosio::state_history::partition_config conf; - conf.stride = 25; - server.setup_state_history_log(conf); - uint32_t head_block_num = head; - server.block_head = {head_block_num, block_id_for(head_block_num)}; - - // generate the log data used for traces and deltas - uint32_t n = mock_state_history_plugin::default_frame_size; - add_to_log(1, n * sizeof(uint32_t), generate_data(n)); // original data format - add_to_log(2, 0, generate_data(n)); // format to accommodate the compressed size greater than 4GB - add_to_log(3, 1, generate_data(n)); // format to encode decompressed size to avoid decompress entire data upfront. - for (size_t i = 4; i <= head; ++i) { - add_to_log(i, 1, generate_data(n)); - } + test_session_no_prune_impl(*this, true); + } + FC_LOG_AND_RETHROW() +} - send_request(eosio::state_history::get_blocks_request_v0{.start_block_num = 1, - .end_block_num = UINT32_MAX, - .max_messages_in_flight = UINT32_MAX, - .have_positions = {}, - .irreversible_only = false, - .fetch_block = true, - .fetch_traces = true, - .fetch_deltas = true}); +void test_split_log_impl(state_history_test_fixture& fixture, bool fetch_finality_data) { + // setup block head for the server + constexpr uint32_t head = 1023; + eosio::state_history::partition_config conf; + conf.stride = 25; + fixture.server.setup_state_history_log(fetch_finality_data, conf); + uint32_t head_block_num = head; + fixture.server.block_head = {head_block_num, fixture.server.block_id_for(head_block_num)}; + + // generate the log data used for traces, deltas and finality_data + uint32_t n = mock_state_history_plugin::default_frame_size; + fixture.add_to_log(1, n * sizeof(uint32_t), generate_data(n), fetch_finality_data); // original data format + fixture.add_to_log(2, 0, generate_data(n), fetch_finality_data); // format to accommodate the compressed size greater than 4GB + fixture.add_to_log(3, 1, generate_data(n), fetch_finality_data); // format to encode decompressed size to avoid decompress entire data upfront. + for (size_t i = 4; i <= head; ++i) { + fixture.add_to_log(i, 1, generate_data(n), fetch_finality_data); + } - eosio::state_history::state_result result; - // we should get 1023 consecutive block result - eosio::chain::block_id_type prev_id; - for (uint32_t i = 0; i < head; ++i) { - receive_result(result); + send_request(fixture, fetch_finality_data, 1, {}); + + eosio::state_history::state_result result; + // we should get 1023 consecutive block result + eosio::chain::block_id_type prev_id; + for (uint32_t i = 0; i < head; ++i) { + fixture.receive_result(result); + + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(result)); + auto r = std::get(result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + if (i > 0) { + BOOST_TEST(prev_id.str() == r.prev_block->block_id.str()); + } + prev_id = r.this_block->block_id; + BOOST_REQUIRE(r.traces.has_value()); + BOOST_REQUIRE(r.deltas.has_value()); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); + BOOST_REQUIRE_EQUAL(traces.size(), data_size); + BOOST_REQUIRE_EQUAL(deltas.size(), data_size); + + BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); + BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); + + auto finality_data = r.finality_data.value(); + BOOST_REQUIRE(r.finality_data.has_value()); + BOOST_REQUIRE_EQUAL(finality_data.size(), data_size); + BOOST_REQUIRE(std::equal(finality_data.begin(), finality_data.end(), (const char*)data.data())); + } else { BOOST_REQUIRE(std::holds_alternative(result)); auto r = std::get(result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); if (i > 0) { BOOST_TEST(prev_id.str() == r.prev_block->block_id.str()); } @@ -498,7 +593,7 @@ BOOST_FIXTURE_TEST_CASE(test_split_log, state_history_test_fixture) { BOOST_REQUIRE(r.deltas.has_value()); auto traces = r.traces.value(); auto deltas = r.deltas.value(); - auto& data = written_data[i]; + auto& data = fixture.written_data[i]; auto data_size = data.size() * sizeof(int32_t); BOOST_REQUIRE_EQUAL(traces.size(), data_size); BOOST_REQUIRE_EQUAL(deltas.size(), data_size); @@ -507,64 +602,100 @@ BOOST_FIXTURE_TEST_CASE(test_split_log, state_history_test_fixture) { BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); } } - FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(test_session_with_prune, state_history_test_fixture) { +BOOST_FIXTURE_TEST_CASE(test_split_log, state_history_test_fixture) { try { - // setup block head for the server - server.setup_state_history_log( - eosio::state_history::prune_config{.prune_blocks = 2, .prune_threshold = 4 * 1024}); - - uint32_t head_block_num = 3; - server.block_head = {head_block_num, block_id_for(head_block_num)}; - - // generate the log data used for traces and deltas - uint32_t n = mock_state_history_plugin::default_frame_size; - add_to_log(1, n * sizeof(uint32_t), generate_data(n)); // original data format - add_to_log(2, 0, generate_data(n)); // format to accommodate the compressed size greater than 4GB - add_to_log(3, 1, generate_data(n)); // format to encode decompressed size to avoid decompress entire data upfront. - - // send a get_status_request and verify the result is what we expected - verify_status(eosio::state_history::get_status_result_v0{ - .head = {head_block_num, block_id_for(head_block_num)}, - .last_irreversible = {head_block_num, block_id_for(head_block_num)}, - .trace_begin_block = 2, - .trace_end_block = head_block_num + 1, - .chain_state_begin_block = 2, - .chain_state_end_block = head_block_num + 1}); - - // send a get_blocks_request to server - send_request(eosio::state_history::get_blocks_request_v0{.start_block_num = 1, - .end_block_num = UINT32_MAX, - .max_messages_in_flight = UINT32_MAX, - .have_positions = {}, - .irreversible_only = false, - .fetch_block = true, - .fetch_traces = true, - .fetch_deltas = true}); + test_split_log_impl(*this, false); + } + FC_LOG_AND_RETHROW() +} - eosio::state_history::state_result result; - // we should get 3 consecutive block result +BOOST_FIXTURE_TEST_CASE(test_split_log_fetch_finality_data, state_history_test_fixture) { + try { + test_split_log_impl(*this, true); + } + FC_LOG_AND_RETHROW() +} - receive_result(result); +void test_session_with_prune_impl(state_history_test_fixture& fixture, bool fetch_finality_data) { + // setup block head for the server + fixture.server.setup_state_history_log(fetch_finality_data, + eosio::state_history::prune_config{.prune_blocks = 2, .prune_threshold = 4 * 1024}); + + uint32_t head_block_num = 3; + fixture.server.block_head = {head_block_num, fixture.server.block_id_for(head_block_num)}; + + // generate the log data used for traces, deltas and finality_data + uint32_t n = mock_state_history_plugin::default_frame_size; + fixture.add_to_log(1, n * sizeof(uint32_t), generate_data(n), fetch_finality_data); // original data format + fixture.add_to_log(2, 0, generate_data(n), fetch_finality_data); // format to accommodate the compressed size greater than 4GB + fixture.add_to_log(3, 1, generate_data(n), fetch_finality_data); // format to encode decompressed size to avoid decompress entire data upfront. + + // send a get_status_request and verify the result is what we expected + fixture.verify_status(eosio::state_history::get_status_result_v0{ + .head = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .last_irreversible = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .trace_begin_block = 2, + .trace_end_block = head_block_num + 1, + .chain_state_begin_block = 2, + .chain_state_end_block = head_block_num + 1}); + + // send a get_blocks_request to fixture.server + send_request(fixture, fetch_finality_data, 1, {}); + + eosio::state_history::state_result result; + // we should get 3 consecutive block result + + fixture.receive_result(result); + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(result)); + auto r = std::get(result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + BOOST_REQUIRE(!r.traces.has_value()); + BOOST_REQUIRE(!r.deltas.has_value()); + BOOST_REQUIRE(!r.finality_data.has_value()); + } else { BOOST_REQUIRE(std::holds_alternative(result)); auto r = std::get(result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); BOOST_REQUIRE(!r.traces.has_value()); BOOST_REQUIRE(!r.deltas.has_value()); + } + + for (int i = 1; i < 3; ++i) { + fixture.receive_result(result); + + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(result)); + auto r = std::get(result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + BOOST_REQUIRE(r.traces.has_value()); + BOOST_REQUIRE(r.deltas.has_value()); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); + BOOST_REQUIRE_EQUAL(traces.size(), data_size); + BOOST_REQUIRE_EQUAL(deltas.size(), data_size); - for (int i = 1; i < 3; ++i) { - receive_result(result); + BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); + BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); + + BOOST_REQUIRE(r.finality_data.has_value()); + auto finality_data = r.finality_data.value(); + BOOST_REQUIRE_EQUAL(finality_data.size(), data_size); + BOOST_REQUIRE(std::equal(finality_data.begin(), finality_data.end(), (const char*)data.data())); + } else { BOOST_REQUIRE(std::holds_alternative(result)); auto r = std::get(result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); BOOST_REQUIRE(r.traces.has_value()); BOOST_REQUIRE(r.deltas.has_value()); - auto traces = r.traces.value(); - auto deltas = r.deltas.value(); - auto& data = written_data[i]; - auto data_size = data.size() * sizeof(int32_t); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); BOOST_REQUIRE_EQUAL(traces.size(), data_size); BOOST_REQUIRE_EQUAL(deltas.size(), data_size); @@ -572,57 +703,85 @@ BOOST_FIXTURE_TEST_CASE(test_session_with_prune, state_history_test_fixture) { BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); } } +} + +BOOST_FIXTURE_TEST_CASE(test_session_with_prune, state_history_test_fixture) { + try { + test_session_with_prune_impl(*this, false); + } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(test_session_fork, state_history_test_fixture) { +BOOST_FIXTURE_TEST_CASE(test_session_with_prune_fetch_finality_data, state_history_test_fixture) { try { - // setup block head for the server - server.setup_state_history_log(); - uint32_t head_block_num = 4; - server.block_head = {head_block_num, block_id_for(head_block_num)}; - - // generate the log data used for traces and deltas - uint32_t n = mock_state_history_plugin::default_frame_size; - add_to_log(1, n * sizeof(uint32_t), generate_data(n)); // original data format - add_to_log(2, 0, generate_data(n)); // format to accommodate the compressed size greater than 4GB - add_to_log(3, 1, generate_data(n)); // format to encode decompressed size to avoid decompress entire data upfront. - add_to_log(4, 1, generate_data(n)); // format to encode decompressed size to avoid decompress entire data upfront. - - // send a get_status_request and verify the result is what we expected - verify_status(eosio::state_history::get_status_result_v0{ - .head = {head_block_num, block_id_for(head_block_num)}, - .last_irreversible = {head_block_num, block_id_for(head_block_num)}, - .trace_begin_block = 1, - .trace_end_block = head_block_num + 1, - .chain_state_begin_block = 1, - .chain_state_end_block = head_block_num + 1}); - - // send a get_blocks_request to server - send_request(eosio::state_history::get_blocks_request_v0{ - .start_block_num = 1, - .end_block_num = UINT32_MAX, - .max_messages_in_flight = UINT32_MAX, - .have_positions = {}, - .irreversible_only = false, - .fetch_block = true, - .fetch_traces = true, - .fetch_deltas = true}); - - std::vector have_positions; - eosio::state_history::state_result result; - // we should get 4 consecutive block result - for (uint32_t i = 0; i < 4; ++i) { - receive_result(result); + test_session_with_prune_impl(*this, true); + } + FC_LOG_AND_RETHROW() +} + +void test_session_fork_impl(state_history_test_fixture& fixture, bool fetch_finality_data) { + fixture.server.setup_state_history_log(fetch_finality_data); + uint32_t head_block_num = 4; + fixture.server.block_head = {head_block_num, fixture.server.block_id_for(head_block_num)}; + + // generate the log data used for traces, deltas and finality_data + uint32_t n = mock_state_history_plugin::default_frame_size; + fixture.add_to_log(1, n * sizeof(uint32_t), generate_data(n), fetch_finality_data); // original data format + fixture.add_to_log(2, 0, generate_data(n), fetch_finality_data); // format to accommodate the compressed size greater than 4GB + fixture.add_to_log(3, 1, generate_data(n), fetch_finality_data); // format to encode decompressed size to avoid decompress entire data upfront. + fixture.add_to_log(4, 1, generate_data(n), fetch_finality_data); // format to encode decompressed size to avoid decompress entire data upfront. + + // send a get_status_request and verify the result is what we expected + fixture.verify_status(eosio::state_history::get_status_result_v0{ + .head = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .last_irreversible = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .trace_begin_block = 1, + .trace_end_block = head_block_num + 1, + .chain_state_begin_block = 1, + .chain_state_end_block = head_block_num + 1}); + + // send a get_blocks_request to fixture.server + send_request(fixture, fetch_finality_data, 1, {}); + + std::vector have_positions; + eosio::state_history::state_result result; + // we should get 4 consecutive block result + for (uint32_t i = 0; i < 4; ++i) { + fixture.receive_result(result); + + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(result)); + auto r = std::get(result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + BOOST_REQUIRE(r.traces.has_value()); + BOOST_REQUIRE(r.deltas.has_value()); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); + BOOST_REQUIRE_EQUAL(traces.size(), data_size); + BOOST_REQUIRE_EQUAL(deltas.size(), data_size); + BOOST_REQUIRE(r.this_block.has_value()); + BOOST_REQUIRE_EQUAL(r.this_block->block_num, i+1); + have_positions.push_back(*r.this_block); + + BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); + BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); + + BOOST_REQUIRE(r.finality_data.has_value()); + auto finality_data = r.finality_data.value(); + BOOST_REQUIRE_EQUAL(finality_data.size(), data_size); + BOOST_REQUIRE(std::equal(finality_data.begin(), finality_data.end(), (const char*)data.data())); + } else { BOOST_REQUIRE(std::holds_alternative(result)); auto r = std::get(result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); BOOST_REQUIRE(r.traces.has_value()); BOOST_REQUIRE(r.deltas.has_value()); - auto traces = r.traces.value(); - auto deltas = r.deltas.value(); - auto& data = written_data[i]; - auto data_size = data.size() * sizeof(int32_t); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); BOOST_REQUIRE_EQUAL(traces.size(), data_size); BOOST_REQUIRE_EQUAL(deltas.size(), data_size); BOOST_REQUIRE(r.this_block.has_value()); @@ -632,49 +791,67 @@ BOOST_FIXTURE_TEST_CASE(test_session_fork, state_history_test_fixture) { BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); } + } - // generate a fork that includes blocks 3,4 and verify new data retrieved - block_ids.extract(3); block_id_for(3, "fork"); - block_ids.extract(4); block_id_for(4, "fork"); - server.block_head = {head_block_num, block_id_for(head_block_num)}; - add_to_log(3, 0, generate_data(n)); - add_to_log(4, 1, generate_data(n)); - - // send a get_status_request and verify the result is what we expected - verify_status(eosio::state_history::get_status_result_v0{ - .head = {head_block_num, block_id_for(head_block_num)}, - .last_irreversible = {head_block_num, block_id_for(head_block_num)}, - .trace_begin_block = 1, - .trace_end_block = head_block_num + 1, - .chain_state_begin_block = 1, - .chain_state_end_block = head_block_num + 1}); - - // send a get_blocks_request to server starting at 5, will send 3,4 because of fork - send_request(eosio::state_history::get_blocks_request_v0{ - .start_block_num = 5, - .end_block_num = UINT32_MAX, - .max_messages_in_flight = UINT32_MAX, - .have_positions = std::move(have_positions), - .irreversible_only = false, - .fetch_block = true, - .fetch_traces = true, - .fetch_deltas = true}); - - eosio::state_history::state_result fork_result; - // we should now get data for fork 3,4 - for (uint32_t i = 2; i < 4; ++i) { - receive_result(fork_result); + // generate a fork that includes blocks 3,4 and verify new data retrieved + // setup block head for the server + fixture.server.block_ids.extract(3); fixture.server.block_id_for(3, "fork"); + fixture.server.block_ids.extract(4); fixture.server.block_id_for(4, "fork"); + fixture.server.block_head = {head_block_num, fixture.server.block_id_for(head_block_num)}; + fixture.add_to_log(3, 0, generate_data(n), fetch_finality_data); + fixture.add_to_log(4, 1, generate_data(n), fetch_finality_data); + + // send a get_status_request and verify the result is what we expected + fixture.verify_status(eosio::state_history::get_status_result_v0{ + .head = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .last_irreversible = {head_block_num, fixture.server.block_id_for(head_block_num)}, + .trace_begin_block = 1, + .trace_end_block = head_block_num + 1, + .chain_state_begin_block = 1, + .chain_state_end_block = head_block_num + 1}); + + // send a get_blocks_request to fixture.server starting at 5, will send 3,4 because of fork + send_request(fixture, fetch_finality_data, 5, std::move(have_positions)); + + eosio::state_history::state_result fork_result; + // we should now get data for fork 3,4 + for (uint32_t i = 2; i < 4; ++i) { + fixture.receive_result(fork_result); + + if( fetch_finality_data ) { + BOOST_REQUIRE(std::holds_alternative(fork_result)); + auto r = std::get(fork_result); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); + BOOST_REQUIRE(r.this_block.has_value()); + BOOST_REQUIRE_EQUAL(r.this_block->block_num, i+1); + BOOST_REQUIRE(r.traces.has_value()); + BOOST_REQUIRE(r.deltas.has_value()); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); + BOOST_REQUIRE_EQUAL(traces.size(), data_size); + BOOST_REQUIRE_EQUAL(deltas.size(), data_size); + + BOOST_REQUIRE(std::equal(traces.begin(), traces.end(), (const char*)data.data())); + BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); + + BOOST_REQUIRE(r.finality_data.has_value()); + auto finality_data = r.finality_data.value(); + BOOST_REQUIRE_EQUAL(finality_data.size(), data_size); + BOOST_REQUIRE(std::equal(finality_data.begin(), finality_data.end(), (const char*)data.data())); + } else { BOOST_REQUIRE(std::holds_alternative(fork_result)); auto r = std::get(fork_result); - BOOST_REQUIRE_EQUAL(r.head.block_num, server.block_head.block_num); + BOOST_REQUIRE_EQUAL(r.head.block_num, fixture.server.block_head.block_num); BOOST_REQUIRE(r.this_block.has_value()); BOOST_REQUIRE_EQUAL(r.this_block->block_num, i+1); BOOST_REQUIRE(r.traces.has_value()); BOOST_REQUIRE(r.deltas.has_value()); - auto traces = r.traces.value(); - auto deltas = r.deltas.value(); - auto& data = written_data[i]; - auto data_size = data.size() * sizeof(int32_t); + auto traces = r.traces.value(); + auto deltas = r.deltas.value(); + auto& data = fixture.written_data[i]; + auto data_size = data.size() * sizeof(int32_t); BOOST_REQUIRE_EQUAL(traces.size(), data_size); BOOST_REQUIRE_EQUAL(deltas.size(), data_size); @@ -682,5 +859,18 @@ BOOST_FIXTURE_TEST_CASE(test_session_fork, state_history_test_fixture) { BOOST_REQUIRE(std::equal(deltas.begin(), deltas.end(), (const char*)data.data())); } } +} + +BOOST_FIXTURE_TEST_CASE(test_session_fork, state_history_test_fixture) { + try { + test_session_fork_impl(*this, false); + } + FC_LOG_AND_RETHROW() +} + +BOOST_FIXTURE_TEST_CASE(test_session_fork_fetch_finality_data, state_history_test_fixture) { + try { + test_session_fork_impl(*this, true); + } FC_LOG_AND_RETHROW() } diff --git a/plugins/test_control_plugin/test_control_plugin.cpp b/plugins/test_control_plugin/test_control_plugin.cpp index 2bf43bdb55..e06a08de1f 100644 --- a/plugins/test_control_plugin/test_control_plugin.cpp +++ b/plugins/test_control_plugin/test_control_plugin.cpp @@ -32,12 +32,12 @@ class test_control_plugin_impl { void test_control_plugin_impl::connect() { _irreversible_block_connection.emplace( - _chain.irreversible_block.connect( [&]( const chain::block_signal_params& t ) { + _chain.irreversible_block().connect( [&]( const chain::block_signal_params& t ) { const auto& [ block, id ] = t; applied_irreversible_block( id ); } )); _accepted_block_connection = - _chain.accepted_block.connect( [&]( const chain::block_signal_params& t ) { + _chain.accepted_block().connect( [&]( const chain::block_signal_params& t ) { const auto& [ block, id ] = t; accepted_block( id ); } ); @@ -62,10 +62,10 @@ void test_control_plugin_impl::process_next_block_state(const chain::block_id_ty // Tests expect the shutdown only after signaling a producer shutdown and seeing a full production cycle const auto block_time = _chain.head_block_time() + fc::microseconds(chain::config::block_interval_us); // have to fetch bsp due to get_scheduled_producer call - const auto& bsp = _chain.fetch_block_state_by_id(id); - const auto& producer_authority = bsp->get_scheduled_producer(block_time); + + const auto& producer_authority = _chain.active_producers().get_scheduled_producer(block_time); const auto producer_name = producer_authority.producer_name; - const auto slot = bsp->block->timestamp.slot % chain::config::producer_repetitions; + const auto slot = _chain.head_block_timestamp().slot % chain::config::producer_repetitions; if (_producer != account_name()) { if( _producer != producer_name ) _clean_producer_sequence = true; if( _clean_producer_sequence ) { diff --git a/plugins/trace_api_plugin/test/include/eosio/trace_api/test_common.hpp b/plugins/trace_api_plugin/test/include/eosio/trace_api/test_common.hpp index a38b034036..371de9709d 100644 --- a/plugins/trace_api_plugin/test/include/eosio/trace_api/test_common.hpp +++ b/plugins/trace_api_plugin/test/include/eosio/trace_api/test_common.hpp @@ -53,8 +53,8 @@ namespace eosio::trace_api { return result; } - inline auto make_block_state( chain::block_id_type previous, uint32_t height, uint32_t slot, chain::name producer, - std::vector trxs ) { + inline auto make_block( chain::block_id_type previous, uint32_t height, uint32_t slot, chain::name producer, + std::vector trxs ) { chain::signed_block_ptr block = std::make_shared(); for( auto& trx : trxs ) { block->transactions.emplace_back( trx ); @@ -71,40 +71,11 @@ namespace eosio::trace_api { auto priv_key = get_private_key( block->producer, "active" ); auto pub_key = get_public_key( block->producer, "active" ); - auto prev = std::make_shared(); - auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), prev->blockroot_merkle.get_root())); - auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, prev->pending_schedule.schedule_hash )); + auto header_bmroot = chain::digest_type::hash( std::make_pair( block->digest(), chain::digest_type{})); + auto sig_digest = chain::digest_type::hash( std::make_pair( header_bmroot, chain::digest_type{} )); block->producer_signature = priv_key.sign( sig_digest ); - std::vector signing_keys; - signing_keys.emplace_back( std::move( priv_key )); - auto signer = [&]( chain::digest_type d ) { - std::vector result; - result.reserve( signing_keys.size()); - for( const auto& k: signing_keys ) - result.emplace_back( k.sign( d )); - return result; - }; - chain::pending_block_header_state_legacy pbhs; - pbhs.producer = block->producer; - pbhs.timestamp = block->timestamp; - chain::producer_authority_schedule schedule = {0, {chain::producer_authority{block->producer, - chain::block_signing_authority_v0{1, {{pub_key, 1}}}}}}; - pbhs.active_schedule = schedule; - pbhs.valid_block_signing_authority = chain::block_signing_authority_v0{1, {{pub_key, 1}}}; - auto bsp = std::make_shared( - std::move( pbhs ), - std::move( block ), - eosio::chain::deque(), - chain::protocol_feature_set(), - []( chain::block_timestamp_type timestamp, - const fc::flat_set& cur_features, - const std::vector& new_features ) {}, - signer - ); - bsp->block_num = height; - - return bsp; + return block; } inline void to_kv_helper(const fc::variant& v, std::function&& append){ diff --git a/plugins/trace_api_plugin/test/test_extraction.cpp b/plugins/trace_api_plugin/test/test_extraction.cpp index e053ce6c52..3c6fbe89ce 100644 --- a/plugins/trace_api_plugin/test/test_extraction.cpp +++ b/plugins/trace_api_plugin/test/test_extraction.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -136,8 +135,8 @@ struct extraction_test_fixture { extraction_impl.signal_applied_transaction(trace, ptrx); } - void signal_accepted_block( const chain::block_state_legacy_ptr& bsp ) { - extraction_impl.signal_accepted_block(bsp->block, bsp->id); + void signal_accepted_block( const chain::signed_block_ptr& bp ) { + extraction_impl.signal_accepted_block(bp, bp->calculate_id()); } // fixture data and methods @@ -168,10 +167,10 @@ BOOST_AUTO_TEST_SUITE(block_extraction) std::make_shared(ptrx1) ); // accept the block with one transaction - auto bsp1 = make_block_state( chain::block_id_type(), 1, 1, "bp.one"_n, + auto bp1 = make_block( chain::block_id_type(), 1, 1, "bp.one"_n, { chain::packed_transaction(ptrx1) } ); - signal_accepted_block( bsp1 ); - + signal_accepted_block( bp1 ); + const std::vector expected_action_traces { { { @@ -206,23 +205,23 @@ BOOST_AUTO_TEST_SUITE(block_extraction) { ptrx1.id(), expected_action_traces, - fc::enum_type{bsp1->block->transactions[0].status}, - bsp1->block->transactions[0].cpu_usage_us, - bsp1->block->transactions[0].net_usage_words, + fc::enum_type{bp1->transactions[0].status}, + bp1->transactions[0].cpu_usage_us, + bp1->transactions[0].net_usage_words, ptrx1.get_signatures(), make_trx_header(ptrx1.get_transaction()) } }; const block_trace_v2 expected_block_trace { - bsp1->id, + bp1->calculate_id(), 1, - bsp1->prev(), + bp1->previous, chain::block_timestamp_type(1), "bp.one"_n, - bsp1->block->transaction_mroot, - bsp1->block->action_mroot, - bsp1->block->schedule_version, + bp1->transaction_mroot, + bp1->action_mroot, + bp1->schedule_version, std::vector { expected_transaction_trace } @@ -232,7 +231,7 @@ BOOST_AUTO_TEST_SUITE(block_extraction) BOOST_REQUIRE(data_log.size() == 1u); BOOST_REQUIRE(std::holds_alternative(data_log.at(0))); BOOST_REQUIRE_EQUAL(std::get(data_log.at(0)), expected_block_trace); - BOOST_REQUIRE_EQUAL(id_log.at(bsp1->block_num).size(), bsp1->block->transactions.size()); + BOOST_REQUIRE_EQUAL(id_log.at(bp1->block_num()).size(), bp1->transactions.size()); } BOOST_FIXTURE_TEST_CASE(basic_multi_transaction_block, extraction_test_fixture) { @@ -260,9 +259,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) std::make_shared( ptrx3 ) ); // accept the block with three transaction - auto bsp1 = make_block_state( chain::block_id_type(), 1, 1, "bp.one"_n, + auto bp1 = make_block( chain::block_id_type(), 1, 1, "bp.one"_n, { chain::packed_transaction(ptrx1), chain::packed_transaction(ptrx2), chain::packed_transaction(ptrx3) } ); - signal_accepted_block( bsp1 ); + signal_accepted_block( bp1 ); const std::vector expected_action_trace1 { { @@ -305,9 +304,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) { ptrx1.id(), expected_action_trace1, - fc::enum_type{bsp1->block->transactions[0].status}, - bsp1->block->transactions[0].cpu_usage_us, - bsp1->block->transactions[0].net_usage_words, + fc::enum_type{bp1->transactions[0].status}, + bp1->transactions[0].cpu_usage_us, + bp1->transactions[0].net_usage_words, ptrx1.get_signatures(), make_trx_header(ptrx1.get_transaction()) } @@ -316,9 +315,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) { ptrx2.id(), expected_action_trace2, - fc::enum_type{bsp1->block->transactions[1].status}, - bsp1->block->transactions[1].cpu_usage_us, - bsp1->block->transactions[1].net_usage_words, + fc::enum_type{bp1->transactions[1].status}, + bp1->transactions[1].cpu_usage_us, + bp1->transactions[1].net_usage_words, ptrx2.get_signatures(), make_trx_header(ptrx2.get_transaction()) } @@ -327,9 +326,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) { ptrx3.id(), expected_action_trace3, - fc::enum_type{bsp1->block->transactions[2].status}, - bsp1->block->transactions[2].cpu_usage_us, - bsp1->block->transactions[2].net_usage_words, + fc::enum_type{bp1->transactions[2].status}, + bp1->transactions[2].cpu_usage_us, + bp1->transactions[2].net_usage_words, ptrx3.get_signatures(), make_trx_header(ptrx3.get_transaction()) } @@ -337,14 +336,14 @@ BOOST_AUTO_TEST_SUITE(block_extraction) }; const block_trace_v2 expected_block_trace { - bsp1->id, + bp1->calculate_id(), 1, - bsp1->prev(), + bp1->previous, chain::block_timestamp_type(1), "bp.one"_n, - bsp1->block->transaction_mroot, - bsp1->block->action_mroot, - bsp1->block->schedule_version, + bp1->transaction_mroot, + bp1->action_mroot, + bp1->schedule_version, expected_transaction_traces }; @@ -372,9 +371,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) signal_applied_transaction( onerror_trace, std::make_shared( transfer_trx ) ); - auto bsp1 = make_block_state( chain::block_id_type(), 1, 1, "bp.one"_n, + auto bp1 = make_block( chain::block_id_type(), 1, 1, "bp.one"_n, { chain::packed_transaction(transfer_trx) } ); - signal_accepted_block( bsp1 ); + signal_accepted_block( bp1 ); const std::vector expected_action_trace { { @@ -393,9 +392,9 @@ BOOST_AUTO_TEST_SUITE(block_extraction) { transfer_trx.id(), // transfer_trx.id() because that is the trx id known to the user expected_action_trace, - fc::enum_type{bsp1->block->transactions[0].status}, - bsp1->block->transactions[0].cpu_usage_us, - bsp1->block->transactions[0].net_usage_words, + fc::enum_type{bp1->transactions[0].status}, + bp1->transactions[0].cpu_usage_us, + bp1->transactions[0].net_usage_words, transfer_trx.get_signatures(), make_trx_header(transfer_trx.get_transaction()) } @@ -403,14 +402,14 @@ BOOST_AUTO_TEST_SUITE(block_extraction) }; const block_trace_v2 expected_block_trace { - bsp1->id, + bp1->calculate_id(), 1, - bsp1->prev(), + bp1->previous, chain::block_timestamp_type(1), "bp.one"_n, - bsp1->block->transaction_mroot, - bsp1->block->action_mroot, - bsp1->block->schedule_version, + bp1->transaction_mroot, + bp1->action_mroot, + bp1->schedule_version, expected_transaction_traces }; diff --git a/plugins/trace_api_plugin/trace_api_plugin.cpp b/plugins/trace_api_plugin/trace_api_plugin.cpp index 8aa7ea9556..4d51e35625 100644 --- a/plugins/trace_api_plugin/trace_api_plugin.cpp +++ b/plugins/trace_api_plugin/trace_api_plugin.cpp @@ -363,21 +363,21 @@ struct trace_api_plugin_impl { auto& chain = app().find_plugin()->chain(); applied_transaction_connection.emplace( - chain.applied_transaction.connect([this](std::tuple t) { + chain.applied_transaction().connect([this](std::tuple t) { emit_killer([&](){ extraction->signal_applied_transaction(std::get<0>(t), std::get<1>(t)); }); })); block_start_connection.emplace( - chain.block_start.connect([this](uint32_t block_num) { + chain.block_start().connect([this](uint32_t block_num) { emit_killer([&](){ extraction->signal_block_start(block_num); }); })); accepted_block_connection.emplace( - chain.accepted_block.connect([this](const chain::block_signal_params& t) { + chain.accepted_block().connect([this](const chain::block_signal_params& t) { emit_killer([&](){ const auto& [ block, id ] = t; extraction->signal_accepted_block(block, id); @@ -385,7 +385,7 @@ struct trace_api_plugin_impl { })); irreversible_block_connection.emplace( - chain.irreversible_block.connect([this](const chain::block_signal_params& t) { + chain.irreversible_block().connect([this](const chain::block_signal_params& t) { const auto& [ block, id ] = t; emit_killer([&](){ extraction->signal_irreversible_block(block->block_num()); diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index ad0b047cf2..d2bdf3079b 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -33,7 +33,6 @@ namespace eosio { namespace client { namespace http { const string get_raw_block_func = chain_func_base + "/get_raw_block"; const string get_block_header_func = chain_func_base + "/get_block_header"; const string get_block_info_func = chain_func_base + "/get_block_info"; - const string get_block_header_state_func = chain_func_base + "/get_block_header_state"; const string get_account_func = chain_func_base + "/get_account"; const string get_table_func = chain_func_base + "/get_table_rows"; const string get_table_by_scope_func = chain_func_base + "/get_table_by_scope"; diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 67167cc9a7..b272f156f1 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -3012,7 +3012,6 @@ int main( int argc, char** argv ) { get_block_params params; auto getBlock = get->add_subcommand("block", localized("Retrieve a full block from the blockchain")); getBlock->add_option("block", params.blockArg, localized("The number or ID of the block to retrieve"))->required(); - getBlock->add_flag("--header-state", params.get_bhs, localized("Get block header state from fork database instead") ); getBlock->add_flag("--info", params.get_binfo, localized("Get block info from the blockchain by block num only") ); getBlock->add_flag("--raw", params.get_braw, localized("Get raw block from the blockchain") ); getBlock->add_flag("--header", params.get_bheader, localized("Get block header from the blockchain") ); @@ -3020,7 +3019,7 @@ int main( int argc, char** argv ) { getBlock->callback([¶ms] { int num_flags = params.get_bhs + params.get_binfo + params.get_braw + params.get_bheader + params.get_bheader_extensions; - EOSC_ASSERT( num_flags <= 1, "ERROR: Only one of the following flags can be set: --header-state, --info, --raw, --header, --header-with-extensions." ); + EOSC_ASSERT( num_flags <= 1, "ERROR: Only one of the following flags can be set: --info, --raw, --header, --header-with-extensions." ); if (params.get_binfo) { std::optional block_num; try { @@ -3033,9 +3032,7 @@ int main( int argc, char** argv ) { std::cout << fc::json::to_pretty_string(call(get_block_info_func, arg)) << std::endl; } else { const auto arg = fc::variant_object("block_num_or_id", params.blockArg); - if (params.get_bhs) { - std::cout << fc::json::to_pretty_string(call(get_block_header_state_func, arg)) << std::endl; - } else if (params.get_braw) { + if (params.get_braw) { std::cout << fc::json::to_pretty_string(call(get_raw_block_func, arg)) << std::endl; } else if (params.get_bheader || params.get_bheader_extensions) { std::cout << fc::json::to_pretty_string( diff --git a/programs/leap-util/CMakeLists.txt b/programs/leap-util/CMakeLists.txt index af28a0d930..0a00136ce9 100644 --- a/programs/leap-util/CMakeLists.txt +++ b/programs/leap-util/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable( ${LEAP_UTIL_EXECUTABLE_NAME} main.cpp actions/subcommand.cpp actions/generic.cpp actions/blocklog.cpp actions/snapshot.cpp actions/chain.cpp) +add_executable( ${LEAP_UTIL_EXECUTABLE_NAME} main.cpp actions/subcommand.cpp actions/generic.cpp actions/blocklog.cpp actions/bls.cpp actions/snapshot.cpp actions/chain.cpp) if( UNIX AND NOT APPLE ) set(rt_library rt ) diff --git a/programs/leap-util/actions/blocklog.cpp b/programs/leap-util/actions/blocklog.cpp index eee37e87df..869d25732f 100644 --- a/programs/leap-util/actions/blocklog.cpp +++ b/programs/leap-util/actions/blocklog.cpp @@ -266,7 +266,7 @@ int blocklog_actions::read_log() { opt->first_block = block_logger.first_block_num(); } - eosio::chain::branch_type fork_db_branch; + block_branch_t fork_db_branch; if(std::filesystem::exists(std::filesystem::path(opt->blocks_dir) / config::reversible_blocks_dir_name / config::forkdb_filename)) { ilog("opening fork_db"); @@ -276,15 +276,15 @@ int blocklog_actions::read_log() { const flat_set& cur_features, const vector& new_features) {}); - fork_db_branch = fork_db.fetch_branch(fork_db.head()->id); + fork_db_branch = fork_db.fetch_branch_from_head(); if(fork_db_branch.empty()) { elog("no blocks available in reversible block database: only block_log blocks are available"); } else { auto first = fork_db_branch.rbegin(); auto last = fork_db_branch.rend() - 1; ilog("existing reversible fork_db block num ${first} through block num ${last} ", - ("first", (*first)->block_num)("last", (*last)->block_num)); - EOS_ASSERT(end->block_num() + 1 == (*first)->block_num, block_log_exception, + ("first", (*first)->block_num())("last", (*last)->block_num())); + EOS_ASSERT(end->block_num() + 1 == (*first)->block_num(), block_log_exception, "fork_db does not start at end of block log"); } } @@ -335,7 +335,7 @@ int blocklog_actions::read_log() { for(auto bitr = fork_db_branch.rbegin(); bitr != fork_db_branch.rend() && block_num <= opt->last_block; ++bitr) { if(opt->as_json_array && contains_obj) *out << ","; - auto next = (*bitr)->block; + auto& next = *bitr; print_block(next); ++block_num; contains_obj = true; diff --git a/programs/leap-util/actions/bls.cpp b/programs/leap-util/actions/bls.cpp new file mode 100644 index 0000000000..ef53a7c83f --- /dev/null +++ b/programs/leap-util/actions/bls.cpp @@ -0,0 +1,115 @@ +#include "bls.hpp" + +#include +#include +#include + +#include + +using namespace fc::crypto::blslib; +namespace bpo = boost::program_options; +using bpo::options_description; + +void bls_actions::setup(CLI::App& app) { + // callback helper with error code handling + auto err_guard = [this](int (bls_actions::*fun)()) { + try { + int rc = (this->*fun)(); + if(rc) throw(CLI::RuntimeError(rc)); + } catch(...) { + print_exception(); + throw(CLI::RuntimeError(-1)); + } + }; + + // main command + auto* sub = app.add_subcommand("bls", "BLS utility"); + sub->require_subcommand(); + + // Create subcommand + auto create = sub->add_subcommand("create", "Create BLS items"); + create->require_subcommand(); + + // sub-subcommand - key + auto* create_key = create->add_subcommand("key", "Create a new BLS keypair and print the public and private keys")->callback([err_guard]() { err_guard(&bls_actions::create_key); }); + create_key->add_option("-f,--file", opt->key_file, "Name of file to write private/public key output to. (Must be set, unless \"--to-console\" is passed"); + create_key->add_flag( "--to-console", opt->print_console, "Print private/public keys to console."); + + // sub-subcommand - pop (proof of possession) + auto* create_pop = create->add_subcommand("pop", "Create proof of possession of the corresponding private key for a given public key")->callback([err_guard]() { err_guard(&bls_actions::create_pop); }); + create_pop->add_option("-f,--file", opt->key_file, "Name of file storing the private key. (one and only one of \"-f,--file\" and \"--private-key\" must be set)"); + create_pop->add_option("--private-key", opt->private_key_str, "The private key. (one and only one of \"-f,--file\" and \"--private-key\" must be set)"); +} + +int bls_actions::create_key() { + if (opt->key_file.empty() && !opt->print_console) { + std::cerr << "ERROR: Either indicate a file using \"-f, --file\" or pass \"--to-console\"" << "\n"; + return -1; + } else if (!opt->key_file.empty() && opt->print_console) { + std::cerr << "ERROR: Only one of \"-f, --file\" or pass \"--to-console\" can be provided" << "\n"; + return -1; + } + + // create a private key and get its corresponding public key + const bls_private_key private_key = bls_private_key::generate(); + const bls_public_key public_key = private_key.get_public_key(); + + // generate proof of possession + const bls_signature pop = private_key.proof_of_possession(); + + // prepare output + std::string out_str = "Private key: " + private_key.to_string() + "\n"; + out_str += "Public key: " + public_key.to_string() + "\n"; + out_str += "Proof of Possession: " + pop.to_string() + "\n"; + if (opt->print_console) { + std::cout << out_str; + } else { + std::cout << "saving keys to " << opt->key_file << "\n"; + std::ofstream out( opt->key_file.c_str() ); + out << out_str; + } + + return 0; +} + +int bls_actions::create_pop() { + if (opt->key_file.empty() && opt->private_key_str.empty()) { + std::cerr << "ERROR: Either indicate a file using \"-f, --file\" or pass \"--private-key\"" << "\n"; + return -1; + } else if (!opt->key_file.empty() && !opt->private_key_str.empty()) { + std::cerr << "ERROR: Only one of \"-f, --file\" and \"--private-key\" can be provided" << "\n"; + return -1; + } + + std::string private_key_str; + if (!opt->private_key_str.empty()) { + private_key_str = opt->private_key_str; + } else { + std::ifstream key_file(opt->key_file); + + if (!key_file.is_open()) { + std::cerr << "ERROR: failed to open file " << opt->key_file << "\n"; + return -1; + } + + if (std::getline(key_file, private_key_str)) { + if (!key_file.eof()) { + std::cerr << "ERROR: file " << opt->key_file << " contains more than one line" << "\n"; + return -1; + } + } else { + std::cerr << "ERROR: file " << opt->key_file << " is empty" << "\n"; + return -1; + } + } + + // create private key object using input private key string + const bls_private_key private_key = bls_private_key(private_key_str); + const bls_public_key public_key = private_key.get_public_key(); + const bls_signature pop = private_key.proof_of_possession(); + + std::cout << "Proof of Possession: " << pop.to_string()<< "\n"; + std::cout << "Public key: " << public_key.to_string() << "\n"; + + return 0; +} diff --git a/programs/leap-util/actions/bls.hpp b/programs/leap-util/actions/bls.hpp new file mode 100644 index 0000000000..aba3545e5e --- /dev/null +++ b/programs/leap-util/actions/bls.hpp @@ -0,0 +1,26 @@ +#include "subcommand.hpp" +#include + +#include + +using namespace eosio::chain; + +struct bls_options { + std::string key_file; + std::string private_key_str; + + // flags + bool print_console{false}; +}; + +class bls_actions : public sub_command { +public: + void setup(CLI::App& app); + +protected: + int create_key(); + int create_pop(); + +private: + std::string generate_pop_str(const fc::crypto::blslib::bls_private_key& private_key); +}; diff --git a/programs/leap-util/actions/snapshot.cpp b/programs/leap-util/actions/snapshot.cpp index 419158da51..328fb1107a 100644 --- a/programs/leap-util/actions/snapshot.cpp +++ b/programs/leap-util/actions/snapshot.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include @@ -73,10 +72,12 @@ int snapshot_actions::run_subcommand() { const auto& temp_dir = dir.path(); std::filesystem::path state_dir = temp_dir / "state"; std::filesystem::path blocks_dir = temp_dir / "blocks"; + std::filesystem::path finalizers_dir = temp_dir / "finalizers"; std::unique_ptr control; controller::config cfg; cfg.blocks_dir = blocks_dir; - cfg.state_dir = state_dir; + cfg.finalizers_dir = finalizers_dir; + cfg.state_dir = state_dir; cfg.state_size = opt->db_size * 1024 * 1024; cfg.state_guard_size = opt->guard_size * 1024 * 1024; cfg.eosvmoc_tierup = wasm_interface::vm_oc_enable::oc_none; // wasm not used, no use to fire up oc diff --git a/programs/leap-util/main.cpp b/programs/leap-util/main.cpp index 908a760cb3..840cfa5b22 100644 --- a/programs/leap-util/main.cpp +++ b/programs/leap-util/main.cpp @@ -6,6 +6,7 @@ #include #include "actions/blocklog.hpp" +#include "actions/bls.hpp" #include "actions/chain.hpp" #include "actions/generic.hpp" #include "actions/snapshot.hpp" @@ -33,6 +34,10 @@ int main(int argc, char** argv) { auto blocklog_subcommand = std::make_shared(); blocklog_subcommand->setup(app); + // bls sc tree + auto bls_subcommand = std::make_shared(); + bls_subcommand->setup(app); + // snapshot sc tree auto snapshot_subcommand = std::make_shared(); snapshot_subcommand->setup(app); diff --git a/programs/nodeos/logging.json b/programs/nodeos/logging.json index 887d2ae13d..95de98eb68 100644 --- a/programs/nodeos/logging.json +++ b/programs/nodeos/logging.json @@ -154,6 +154,15 @@ "level": "info", "enabled": true, "additivity": false, + "appenders": [ + "stderr", + "net" + ] + },{ + "name": "hotstuff", + "level": "debug", + "enabled": true, + "additivity": false, "appenders": [ "stderr", "net" diff --git a/programs/nodeos/main.cpp b/programs/nodeos/main.cpp index 6e2feeba91..44f3572e06 100644 --- a/programs/nodeos/main.cpp +++ b/programs/nodeos/main.cpp @@ -147,6 +147,9 @@ enum return_codes { int main(int argc, char** argv) { + + ilog("nodeos started"); + try { appbase::scoped_app app; fc::scoped_exit> on_exit = [&]() { @@ -158,6 +161,10 @@ int main(int argc, char** argv) uint32_t short_hash = 0; fc::from_hex(eosio::version::version_hash(), (char*)&short_hash, sizeof(short_hash)); + app->set_stop_executor_cb([&app]() { + ilog("appbase quit called"); + app->get_io_service().stop(); + }); app->set_version(htonl(short_hash)); app->set_version_string(eosio::version::version_client()); app->set_full_version_string(eosio::version::version_full()); @@ -200,6 +207,7 @@ int main(int argc, char** argv) } catch( const node_management_success& e ) { return NODE_MANAGEMENT_SUCCESS; } catch( const fc::exception& e ) { + if( e.code() == fc::std_exception_code ) { if( e.top_message().find( "atabase dirty flag set" ) != std::string::npos ) { elog( "database dirty flag set (likely due to unclean shutdown): replay required" ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 12cfc0821f..993af3eff6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,15 +19,18 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/block_log_retain_blocks_test.py ${CMA configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bridge_for_fork_test_shape.json ${CMAKE_CURRENT_BINARY_DIR}/bridge_for_fork_test_shape.json COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cluster_launcher.py ${CMAKE_CURRENT_BINARY_DIR}/cluster_launcher.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/distributed-transactions-test.py ${CMAKE_CURRENT_BINARY_DIR}/distributed-transactions-test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/leap_util_bls_test.py ${CMAKE_CURRENT_BINARY_DIR}/leap_util_bls_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sample-cluster-map.json ${CMAKE_CURRENT_BINARY_DIR}/sample-cluster-map.json COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/restart-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/restart-scenarios-test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/terminate-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/terminate-scenarios-test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/liveness_test.py ${CMAKE_CURRENT_BINARY_DIR}/liveness_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_startup_catchup.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_startup_catchup.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_snapshot_diff_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_snapshot_diff_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_snapshot_forked_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_snapshot_forked_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_forked_chain_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_forked_chain_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_short_fork_take_over_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_short_fork_take_over_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_lib_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_lib_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_under_min_avail_ram.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_under_min_avail_ram.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_voting_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_voting_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_irreversible_mode_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_irreversible_mode_test.py COPYONLY) @@ -47,6 +50,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_producer_watermark_test.py ${C configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cli_test.py ${CMAKE_CURRENT_BINARY_DIR}/cli_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ship_test.py ${CMAKE_CURRENT_BINARY_DIR}/ship_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ship_streamer_test.py ${CMAKE_CURRENT_BINARY_DIR}/ship_streamer_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bridge_for_fork_test_shape.json ${CMAKE_CURRENT_BINARY_DIR}/bridge_for_fork_test_shape.json COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lib_advance_test.py ${CMAKE_CURRENT_BINARY_DIR}/lib_advance_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/http_plugin_test.py ${CMAKE_CURRENT_BINARY_DIR}/http_plugin_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/p2p_high_latency_test.py ${CMAKE_CURRENT_BINARY_DIR}/p2p_high_latency_test.py COPYONLY) @@ -59,6 +63,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/subjective_billing_test.py ${CMAKE_CU configure_file(${CMAKE_CURRENT_SOURCE_DIR}/get_account_test.py ${CMAKE_CURRENT_BINARY_DIR}/get_account_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_high_transaction_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_high_transaction_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_retry_transaction_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_retry_transaction_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/transition_to_if.py ${CMAKE_CURRENT_BINARY_DIR}/transition_to_if.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_test.py ${CMAKE_CURRENT_BINARY_DIR}/trx_finality_status_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_forked_test.py ${CMAKE_CURRENT_BINARY_DIR}/trx_finality_status_forked_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plugin_http_api_test.py ${CMAKE_CURRENT_BINARY_DIR}/plugin_http_api_test.py COPYONLY) @@ -92,10 +97,20 @@ add_test(NAME nodeos_sanity_test COMMAND tests/nodeos_run_test.py -v --sanity-te set_property(TEST nodeos_sanity_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_run_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_run_if_test COMMAND tests/nodeos_run_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_run_if_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_lib_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_lib_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_lib_if_test COMMAND tests/nodeos_lib_test.py -n 4 -p 3 -s ring -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_lib_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST block_log_util_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME block_log_util_if_test COMMAND tests/block_log_util_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST block_log_util_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST block_log_retain_blocks_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME block_log_retain_blocks_if_test COMMAND tests/block_log_retain_blocks_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST block_log_retain_blocks_if_test PROPERTY LABELS nonparallelizable_tests) option(ABIEOS_ONLY_LIBRARY "define and build the ABIEOS library" ON) set(ABIEOS_INSTALL_COMPONENT "dev") @@ -120,14 +135,27 @@ target_link_libraries(ship_streamer abieos Boost::program_options Boost::system add_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST cluster_launcher PROPERTY LABELS nonparallelizable_tests) +add_test(NAME cluster_launcher_if COMMAND tests/cluster_launcher.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST cluster_launcher_if PROPERTY LABELS nonparallelizable_tests) + +add_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST transition_to_if PROPERTY LABELS nonparallelizable_tests) +add_test(NAME transition_to_if_lr COMMAND tests/transition_to_if.py -v -p 20 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST transition_to_if_lr PROPERTY LABELS long_running_tests) add_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST ship_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} --unix-socket WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST ship_test_unix PROPERTY LABELS nonparallelizable_tests) +add_test(NAME ship_if_test COMMAND tests/ship_test.py -v --activate-if --num-clients 10 --num-requests 5000 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST ship_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST ship_streamer_test PROPERTY LABELS long_running_tests) +add_test(NAME ship_streamer_if_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST ship_streamer_if_test PROPERTY LABELS long_running_tests) +add_test(NAME ship_streamer_if_fetch_finality_data_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 --activate-if --finality-data-history ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST ship_streamer_if_fetch_finality_data_test PROPERTY LABELS long_running_tests) add_test(NAME p2p_dawn515_test COMMAND tests/p2p_tests/dawn_515/test.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST p2p_dawn515_test PROPERTY LABELS nonparallelizable_tests) @@ -142,10 +170,14 @@ add_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -p 2 set_property(TEST read-only-trx-basic-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-parallel-test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME read-only-trx-parallel-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 16 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -set_property(TEST read-only-trx-parallel-eos-vm-oc-test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME read-only-trx-parallel-no-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -set_property(TEST read-only-trx-parallel-no-oc-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME read-only-trx-basic-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 1 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST read-only-trx-basic-if-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME read-only-trx-parallel-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 3 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST read-only-trx-parallel-if-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME read-only-trx-parallel-if-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 16 --num-test-runs 3 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST read-only-trx-parallel-if-eos-vm-oc-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME read-only-trx-parallel-no-oc-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST read-only-trx-parallel-no-oc-if-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME subjective_billing_test COMMAND tests/subjective_billing_test.py -v -p 2 -n 4 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST subjective_billing_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME get_account_test COMMAND tests/get_account_test.py -v -p 2 -n 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -155,32 +187,58 @@ add_test(NAME distributed-transactions-test COMMAND tests/distributed-transactio set_property(TEST distributed-transactions-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME distributed-transactions-speculative-test COMMAND tests/distributed-transactions-test.py -d 2 -p 4 -n 6 --speculative -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST distributed-transactions-speculative-test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME distributed-transactions-if-test COMMAND tests/distributed-transactions-test.py -d 2 -p 4 -n 6 --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST distributed-transactions-if-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME restart-scenarios-test-resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST restart-scenarios-test-resync PROPERTY LABELS nonparallelizable_tests) +add_test(NAME restart-scenarios-if-test-resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST restart-scenarios-if-test-resync PROPERTY LABELS nonparallelizable_tests) add_test(NAME restart-scenarios-test-hard_replay COMMAND tests/restart-scenarios-test.py -c hardReplay -p4 -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST restart-scenarios-test-hard_replay PROPERTY LABELS nonparallelizable_tests) +add_test(NAME restart-scenarios-if-test-hard_replay COMMAND tests/restart-scenarios-test.py -c hardReplay -p4 -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST restart-scenarios-if-test-hard_replay PROPERTY LABELS nonparallelizable_tests) add_test(NAME restart-scenarios-test-none COMMAND tests/restart-scenarios-test.py -c none --kill-sig term -p4 -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST restart-scenarios-test-none PROPERTY LABELS nonparallelizable_tests) +add_test(NAME restart-scenarios-if-test-none COMMAND tests/restart-scenarios-test.py -c none --kill-sig term -p4 -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST restart-scenarios-if-test-none PROPERTY LABELS nonparallelizable_tests) add_test(NAME terminate-scenarios-test-resync COMMAND tests/terminate-scenarios-test.py -c resync --terminate-at-block 10 --kill-sig term ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST terminate-scenarios-test-resync PROPERTY LABELS nonparallelizable_tests) add_test(NAME terminate-scenarios-test-replay COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 10 --kill-sig term ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST terminate-scenarios-test-replay PROPERTY LABELS nonparallelizable_tests) add_test(NAME terminate-scenarios-test-hard_replay COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 10 --kill-sig term ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST terminate-scenarios-test-hard_replay PROPERTY LABELS nonparallelizable_tests) +add_test(NAME terminate-scenarios-if-test-resync COMMAND tests/terminate-scenarios-test.py -c resync --terminate-at-block 10 --kill-sig term --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST terminate-scenarios-if-test-resync PROPERTY LABELS nonparallelizable_tests) +add_test(NAME terminate-scenarios-if-test-replay COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 10 --kill-sig term --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST terminate-scenarios-if-test-replay PROPERTY LABELS nonparallelizable_tests) +add_test(NAME terminate-scenarios-if-test-hard_replay COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 10 --kill-sig term --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST terminate-scenarios-if-test-hard_replay PROPERTY LABELS nonparallelizable_tests) +add_test(NAME terminate-scenarios-if-test-replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 150 --kill-sig term --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST terminate-scenarios-if-test-replay-pass-transition PROPERTY LABELS nonparallelizable_tests) +add_test(NAME terminate-scenarios-if-test-hard_replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 150 --kill-sig term --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST terminate-scenarios-if-test-hard_replay-pass-transition PROPERTY LABELS nonparallelizable_tests) add_test(NAME validate_dirty_db_test COMMAND tests/validate-dirty-db.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST validate_dirty_db_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME keosd_auto_launch_test COMMAND tests/keosd_auto_launch_test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST keosd_auto_launch_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_snapshot_diff_test COMMAND tests/nodeos_snapshot_diff_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_snapshot_diff_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_snapshot_diff_if_test COMMAND tests/nodeos_snapshot_diff_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_snapshot_diff_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_snapshot_forked_test COMMAND tests/nodeos_snapshot_forked_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_snapshot_forked_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_snapshot_forked_if_test COMMAND tests/nodeos_snapshot_forked_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_snapshot_forked_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME trx_finality_status_test COMMAND tests/trx_finality_status_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST trx_finality_status_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME trx_finality_status_if_test COMMAND tests/trx_finality_status_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST trx_finality_status_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME trx_finality_status_forked_test COMMAND tests/trx_finality_status_forked_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST trx_finality_status_forked_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME trx_finality_status_forked_if_test COMMAND tests/trx_finality_status_forked_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST trx_finality_status_forked_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME db_modes_test COMMAND tests/db_modes_test.sh -v WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_tests_properties(db_modes_test PROPERTIES COST 6000) @@ -191,62 +249,100 @@ set_property(TEST nested_container_multi_index_test PROPERTY LABELS nonparalleli add_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST p2p_multiple_listen_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME p2p_multiple_listen_if_test COMMAND tests/p2p_multiple_listen_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST p2p_multiple_listen_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST p2p_no_listen_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST p2p_sync_throttle_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME p2p_sync_throttle_if_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST p2p_sync_throttle_if_test PROPERTY LABELS nonparallelizable_tests) # needs iproute-tc or iproute2 depending on platform #add_test(NAME p2p_high_latency_test COMMAND tests/p2p_high_latency_test.py -v WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) #set_property(TEST p2p_high_latency_test PROPERTY LABELS nonparallelizable_tests) +# This test is too much for CI/CD machines. We do run it with fewer nodes as a nonparallelizable_tests above #add_test(NAME distributed_transactions_lr_test COMMAND tests/distributed-transactions-test.py -d 2 -p 21 -n 21 -v WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) #set_property(TEST distributed_transactions_lr_test PROPERTY LABELS long_running_tests) +#add_test(NAME distributed_transactions_if_lr_test COMMAND tests/distributed-transactions-test.py -d 2 -p 21 -n 21 --activate-if -v WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +#set_property(TEST distributed_transactions_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_forked_chain_lr_test COMMAND tests/nodeos_forked_chain_test.py -v --wallet-port 9901 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_forked_chain_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_forked_chain_if_lr_test COMMAND tests/nodeos_forked_chain_test.py -v --activate-if --wallet-port 9901 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_forked_chain_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_contrl_c_test COMMAND tests/nodeos_contrl_c_test.py -v --wallet-port 9901 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_contrl_c_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_contrl_c_if_test COMMAND tests/nodeos_contrl_c_test.py --activate-if -v --wallet-port 9901 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_contrl_c_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_voting_lr_test COMMAND tests/nodeos_voting_test.py -v --wallet-port 9902 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_voting_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_voting_if_lr_test COMMAND tests/nodeos_voting_test.py -v --activate-if --wallet-port 9902 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_voting_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_under_min_avail_ram_lr_test COMMAND tests/nodeos_under_min_avail_ram.py -v --wallet-port 9904 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_under_min_avail_ram_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_under_min_avail_ram_if_lr_test COMMAND tests/nodeos_under_min_avail_ram.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_under_min_avail_ram_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_irreversible_mode_lr_test COMMAND tests/nodeos_irreversible_mode_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_irreversible_mode_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_irreversible_mode_if_lr_test COMMAND tests/nodeos_irreversible_mode_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_irreversible_mode_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_read_terminate_at_block_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_read_terminate_at_block_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_read_terminate_at_block_if_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_read_terminate_at_block_if_lr_test PROPERTY LABELS long_running_tests) + +add_test(NAME liveness_test COMMAND tests/liveness_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST liveness_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_chainbase_allocation_test COMMAND tests/nodeos_chainbase_allocation_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_chainbase_allocation_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_chainbase_allocation_if_test COMMAND tests/nodeos_chainbase_allocation_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_chainbase_allocation_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_startup_catchup_lr_test COMMAND tests/nodeos_startup_catchup.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_startup_catchup_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_startup_catchup_if_lr_test COMMAND tests/nodeos_startup_catchup.py -p3 --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_startup_catchup_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_short_fork_take_over_test COMMAND tests/nodeos_short_fork_take_over_test.py -v --wallet-port 9905 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_short_fork_take_over_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME nodeos_short_fork_take_over_if_test COMMAND tests/nodeos_short_fork_take_over_test.py -v --activate-if --wallet-port 9905 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_short_fork_take_over_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_extra_packed_data_test COMMAND tests/nodeos_extra_packed_data_test.py -v -p 2 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_extra_packed_data_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_producer_watermark_lr_test COMMAND tests/nodeos_producer_watermark_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_producer_watermark_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_producer_watermark_if_lr_test COMMAND tests/nodeos_producer_watermark_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_producer_watermark_if_lr_test PROPERTY LABELS long_running_tests) -add_test(NAME nodeos_high_transaction_lr_test COMMAND tests/nodeos_high_transaction_test.py -v -p 4 -n 8 --num-transactions 10000 --max-transactions-per-second 500 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME nodeos_high_transaction_lr_test COMMAND tests/nodeos_high_transaction_test.py -p 4 -n 8 --num-transactions 10000 --max-transactions-per-second 500 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_high_transaction_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_high_transaction_if_lr_test COMMAND tests/nodeos_high_transaction_test.py --activate-if -p 4 -n 8 --num-transactions 10000 --max-transactions-per-second 500 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_high_transaction_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_retry_transaction_lr_test COMMAND tests/nodeos_retry_transaction_test.py -v --num-transactions 100 --max-transactions-per-second 10 --total-accounts 5 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_retry_transaction_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_retry_transaction_if_lr_test COMMAND tests/nodeos_retry_transaction_test.py -v --activate-if --num-transactions 100 --max-transactions-per-second 10 --total-accounts 5 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_retry_transaction_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME cli_test COMMAND tests/cli_test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST cli_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME lib_advance_test COMMAND tests/lib_advance_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST lib_advance_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME lib_advance_if_test COMMAND tests/lib_advance_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST lib_advance_if_test PROPERTY LABELS nonparallelizable_tests) + +add_test(NAME leap_util_bls_test COMMAND tests/leap_util_bls_test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_tests_properties(http_plugin_test PROPERTIES TIMEOUT 100) @@ -272,9 +368,13 @@ set_property(TEST nodeos_repeat_transaction_lr_test PROPERTY LABELS long_running add_test(NAME light_validation_sync_test COMMAND tests/light_validation_sync_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST light_validation_sync_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME light_validation_sync_if_test COMMAND tests/light_validation_sync_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST light_validation_sync_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME auto_bp_peering_test COMMAND tests/auto_bp_peering_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST auto_bp_peering_test PROPERTY LABELS long_running_tests) +add_test(NAME auto_bp_peering_if_test COMMAND tests/auto_bp_peering_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST auto_bp_peering_if_test PROPERTY LABELS long_running_tests) add_test(NAME gelf_test COMMAND tests/gelf_test.py ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST gelf_test PROPERTY LABELS nonparallelizable_tests) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index 87ab5eab23..292adaae21 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -165,7 +165,9 @@ def setAlternateVersionLabels(self, file): # pylint: disable=too-many-statements def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="mesh", delay=2, onlyBios=False, dontBootstrap=False, totalProducers=None, sharedProducers=0, extraNodeosArgs="", specificExtraNodeosArgs=None, specificNodeosInstances=None, onlySetProds=False, - pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, nodeosLogPath=Path(Utils.TestLogRoot) / Path(f'{Path(sys.argv[0]).stem}{os.getpid()}'), genesisPath=None, + pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, + activateIF=False, biosFinalizer=True, + nodeosLogPath=Path(Utils.TestLogRoot) / Path(f'{Path(sys.argv[0]).stem}{os.getpid()}'), genesisPath=None, maximumP2pPerHost=0, maximumClients=25, prodsEnableTraceApi=True): """Launch cluster. pnodes: producer nodes count @@ -187,6 +189,8 @@ def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="m alternateVersionLabelsFile: Supply an alternate version labels file to use with associatedNodeLabels. associatedNodeLabels: Supply a dictionary of node numbers to use an alternate label for a specific node. loadSystemContract: indicate whether the eosio.system contract should be loaded + activateIF: Activate/enable instant-finality by setting finalizers + biosFinalizer: True if the biosNode should act as a finalizer genesisPath: set the path to a specific genesis.json to use maximumP2pPerHost: Maximum number of client nodes from any single IP address. Defaults to totalNodes if not set. maximumClients: Maximum number of clients from which connections are accepted, use 0 for no limit. Defaults to 25. @@ -477,6 +481,8 @@ def connectGroup(group, producerNodes, bridgeNodes) : node = Node(self.host, self.port + nodeNum, nodeNum, Path(instance.data_dir_name), Path(instance.config_dir_name), eosdcmd, unstarted=instance.dont_start, launch_time=launcher.launch_time, walletMgr=self.walletMgr, nodeosVers=self.nodeosVers) + node.keys = instance.keys + node.isProducer = len(instance.producers) > 0 if nodeNum == Node.biosNodeId: self.biosNode = node else: @@ -517,7 +523,7 @@ def connectGroup(group, producerNodes, bridgeNodes) : return True Utils.Print("Bootstrap cluster.") - if not self.bootstrap(self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract): + if not self.bootstrap(launcher, self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract, activateIF, biosFinalizer): Utils.Print("ERROR: Bootstrap failed.") return False @@ -989,7 +995,59 @@ def parseClusterKeys(totalNodes): Utils.Print(f'Found {len(producerKeys)} producer keys') return producerKeys - def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True): + def activateInstantFinality(self, biosFinalizer=True): + # call setfinalizer + numFins = 0 + for n in (self.nodes + [self.biosNode]): + if not n or not n.keys or not n.keys[0].blspubkey: + continue + if not n.isProducer: + continue + if n.nodeId == 'bios' and not biosFinalizer: + continue + numFins = numFins + 1 + + threshold = int(numFins * 2 / 3 + 1) + if threshold > 2 and threshold == numFins: + # nodes are often stopped, so do not require all node votes + threshold = threshold - 1 + if Utils.Debug: Utils.Print(f"threshold: {threshold}, numFins: {numFins}") + setFinStr = f'{{"finalizer_policy": {{' + setFinStr += f' "threshold": {threshold}, ' + setFinStr += f' "finalizers": [' + finNum = 1 + for n in (self.nodes + [self.biosNode]): + if not n or not n.keys or not n.keys[0].blspubkey: + continue + if not n.isProducer: + continue + if n.nodeId == 'bios' and not biosFinalizer: + continue + setFinStr += f' {{"description": "finalizer #{finNum}", ' + setFinStr += f' "weight":1, ' + setFinStr += f' "public_key": "{n.keys[0].blspubkey}", ' + setFinStr += f' "pop": "{n.keys[0].blspop}"' + setFinStr += f' }}' + if finNum != numFins: + setFinStr += f', ' + finNum = finNum + 1 + setFinStr += f' ]' + setFinStr += f'}}}}' + if Utils.Debug: Utils.Print("setfinalizers: %s" % (setFinStr)) + Utils.Print("Setting finalizers") + opts = "--permission eosio@active" + trans = self.biosNode.pushMessage("eosio", "setfinalizer", setFinStr, opts) + if trans is None or not trans[0]: + Utils.Print("ERROR: Failed to set finalizers") + return None + Node.validateTransaction(trans[1]) + transId = Node.getTransId(trans[1]) + if not self.biosNode.waitForTransFinalization(transId, timeout=21*12*3): + Utils.Print("ERROR: Failed to validate transaction %s got rolled into a LIB block on server port %d." % (transId, biosNode.port)) + return None + return True + + def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False, biosFinalizer=True): """Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers. Ensure nodes are inter-connected prior to this call. One way to validate this will be to check if every node has block 1.""" @@ -1025,12 +1083,11 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli Utils.Print("ERROR: Failed to import %s account keys into ignition wallet." % (eosioName)) return None - contract="eosio.bios" - contractDir= str(self.libTestingContractsPath / contract) - if PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): - contractDir=str(self.libTestingContractsPath / "old_versions" / "v1.7.0-develop-preactivate_feature" / contract) - else: - contractDir=str(self.libTestingContractsPath / "old_versions" / "v1.6.0-rc3" / contract) + if not PFSetupPolicy.hasPreactivateFeature(pfSetupPolicy): + return True + + contract="eosio.boot" + contractDir= str(self.unittestsContractsPath / contract) wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) @@ -1041,9 +1098,23 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli if pfSetupPolicy == PFSetupPolicy.FULL: biosNode.preactivateAllBuiltinProtocolFeature() - Node.validateTransaction(trans) + contract="eosio.bios" + contractDir= str(self.libTestingContractsPath / contract) + wasmFile="%s.wasm" % (contract) + abiFile="%s.abi" % (contract) + Utils.Print("Publish %s contract" % (contract)) + trans=biosNode.publishContract(eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if trans is None: + Utils.Print("ERROR: Failed to publish contract %s." % (contract)) + return None + + if activateIF: + if not self.activateInstantFinality(biosFinalizer=biosFinalizer): + Utils.Print("ERROR: Activate instant finality failed") + return None + Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) producerKeys.pop(eosioName) accounts=[] @@ -1090,7 +1161,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli if counts[keys["node"]] >= prodCount: Utils.Print(f'Count for this node exceeded: {counts[keys["node"]]}') continue - prodStanzas.append({ 'producer_name': keys['name'], 'block_signing_key': keys['public'] }) + prodStanzas.append({ 'producer_name': keys['name'], 'authority': ["block_signing_authority_v0", { 'threshold': 1, 'keys': [{ 'key': keys['public'], 'weight': 1 }]}]}) prodNames.append(keys["name"]) counts[keys["node"]] += 1 setProdsStr += json.dumps(prodStanzas) @@ -1138,6 +1209,10 @@ def createSystemAccount(accountName): if not biosNode.waitForTransactionsInBlock(transIds): Utils.Print('ERROR: Failed to validate creation of system accounts') return None + # + # Could activate instant finality here, but have to wait for finality which with all the producers takes a long time + # if activateIF: + # self.activateInstantFinality() eosioTokenAccount = copy.deepcopy(eosioAccount) eosioTokenAccount.name = 'eosio.token' @@ -1385,6 +1460,31 @@ def cleanup(self): for f in self.filesToCleanup: os.remove(f) + def setProds(self, producers): + """Call setprods with list of producers""" + setProdsStr = '{"schedule": [' + firstTime = True + for name in producers: + if firstTime: + firstTime = False + else: + setProdsStr += ',' + if not self.defProducerAccounts[name]: + Utils.Print(f"ERROR: no account key for {name}") + return None + key = self.defProducerAccounts[name].activePublicKey + setProdsStr += '{"producer_name":' + name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + key + ', "weight":1}]}]}' + + setProdsStr += ' ] }' + Utils.Print("setprods: %s" % (setProdsStr)) + opts = "--permission eosio@active" + # pylint: disable=redefined-variable-type + trans = self.biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) + if trans is None or not trans[0]: + Utils.Print("ERROR: Failed to set producer with cmd %s" % (setProdsStr)) + return None + return True + # Create accounts, if account does not already exist, and validates that the last transaction is received on root node def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000, validationNodeIndex=-1): if self.accounts is None: diff --git a/tests/TestHarness/Node.py b/tests/TestHarness/Node.py index ddd2584038..d23f42d2f9 100644 --- a/tests/TestHarness/Node.py +++ b/tests/TestHarness/Node.py @@ -10,6 +10,7 @@ import sys from pathlib import Path from typing import List +from dataclasses import InitVar, dataclass, field, is_dataclass, asdict from datetime import datetime from datetime import timedelta @@ -21,6 +22,14 @@ from .testUtils import unhandledEnumType from .testUtils import ReturnType +@dataclass +class KeyStrings(object): + pubkey: str + privkey: str + blspubkey: str = None + blsprivkey: str = None + blspop: str = None + # pylint: disable=too-many-public-methods class Node(Transactions): # Node number is used as an addend to determine the node listen ports. @@ -66,6 +75,7 @@ def __init__(self, host, port, nodeId: int, data_dir: Path, config_dir: Path, cm self.config_dir=config_dir self.launch_time=launch_time self.isProducer=False + self.keys: List[KeyStrings] = field(default_factory=list) self.configureVersion() def configureVersion(self): diff --git a/tests/TestHarness/TestHelper.py b/tests/TestHarness/TestHelper.py index 66bebd7cd4..fe98e97c1b 100644 --- a/tests/TestHarness/TestHelper.py +++ b/tests/TestHarness/TestHelper.py @@ -57,7 +57,7 @@ def createArgumentParser(includeArgs, applicationSpecificArgs=AppArgs(), suppres if "--nodes-file" in includeArgs: thGrp.add_argument("--nodes-file", type=str, help=argparse.SUPPRESS if suppressHelp else "File containing nodes info in JSON format.") if "-s" in includeArgs: - thGrp.add_argument("-s", type=str, help=argparse.SUPPRESS if suppressHelp else "topology", choices=["mesh"], default="mesh") + thGrp.add_argument("-s", type=str, help=argparse.SUPPRESS if suppressHelp else "topology", choices=['star', 'mesh', 'ring', 'line'], default="mesh") if "-c" in includeArgs: thGrp.add_argument("-c", type=str, help=argparse.SUPPRESS if suppressHelp else "chain strategy", choices=[Utils.SyncResyncTag, Utils.SyncReplayTag, Utils.SyncNoneTag, Utils.SyncHardReplayTag], @@ -97,6 +97,9 @@ def createArgumentParser(includeArgs, applicationSpecificArgs=AppArgs(), suppres if "--dont-launch" in includeArgs: thGrp.add_argument("--dont-launch", help=argparse.SUPPRESS if suppressHelp else "Don't launch own node. Assume node is already running.", action='store_true') + if "--activate-if" in includeArgs: + thGrp.add_argument("--activate-if", help=argparse.SUPPRESS if suppressHelp else "Activate instant finality during bios boot.", + action='store_true') if "--keep-logs" in includeArgs: thGrp.add_argument("--keep-logs", help=argparse.SUPPRESS if suppressHelp else "Don't delete /node_* folders, or other test specific log directories, upon test completion", action='store_true') diff --git a/tests/TestHarness/accounts.py b/tests/TestHarness/accounts.py index e81932e40e..e29d68ea9d 100644 --- a/tests/TestHarness/accounts.py +++ b/tests/TestHarness/accounts.py @@ -49,6 +49,9 @@ def __init__(self, name): self.ownerPublicKey=None self.activePrivateKey=None self.activePublicKey=None + self.blsFinalizerPrivateKey=None + self.blsFinalizerPublicKey=None + self.blsFinalizerPOP=None def __str__(self): @@ -84,12 +87,28 @@ def createAccountKeys(count: int) -> List[Account]: activePrivate=m.group(1) activePublic=m.group(2) + # Private key: PVT_BLS_kRhJJ2MsM+/CddO... + # Public key: PUB_BLS_lbUE8922wUfX0Iy5... + # Proof of Possession: SIG_BLS_3jwkVUUYahHgsnmnEA... + rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False) + pattern = r'(\w+[^:]*): ([^\n]+)' + matched = re.findall(pattern, rslts) + results = {} + for k, v in matched: + results[k.strip()] = v.strip() + assert "PVT_BLS_" in results["Private key"] + assert "PUB_BLS_" in results["Public key"] + assert "SIG_BLS_" in results["Proof of Possession"] + name=''.join(random.choice(string.ascii_lowercase) for _ in range(12)) account=Account(name) account.ownerPrivateKey=ownerPrivate account.ownerPublicKey=ownerPublic account.activePrivateKey=activePrivate account.activePublicKey=activePublic + account.blsFinalizerPrivateKey=results["Private key"] + account.blsFinalizerPublicKey=results["Public key"] + account.blsFinalizerPOP=results["Proof of Possession"] accounts.append(account) if Utils.Debug: Utils.Print("name: %s, key(owner): ['%s', '%s], key(active): ['%s', '%s']" % (name, ownerPublic, ownerPrivate, activePublic, activePrivate)) diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index 9fca7c85c4..e42cf649cb 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -30,6 +30,9 @@ def default(self, o): class KeyStrings(object): pubkey: str privkey: str + blspubkey: str = None + blsprivkey: str = None + blspop: str = None @dataclass class nodeDefinition: @@ -288,10 +291,13 @@ def bind_nodes(self): is_bios = node.name == 'bios' if is_bios: node.keys.append(KeyStrings('EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV', - '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')) + '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3', + 'PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q', + 'PVT_BLS_edLoUiiP2FfMem4la3Ek8zxIDjDjOFylRw9ymdeOVCC0CuXN', + 'SIG_BLS_L5MXQpJTX_v7cXDy4ML4fWVw_69MKuG5qTq7dD_Zb3Yuw1RbMXBOYXDoAbFF37gFmHudY3kkqXtETLs9nMTjTaTwgdDZWpFy1_csEIZT-xIOQttc76bpZhI67902g2sIDf6JSf9JtbhdTUc_HNbL7H2ZR2bqGS3YPbV5z7x24AR2vwTcJogMqLyy6H5nKQAEwIvXcL15gbs2EkH_ch-IZDdn4F0zUYifpOo-ovXY_CX_yL2rKIx_2a9IHg0pPrMOdfHs9A')) node.producers.append('eosio') else: - node.keys.append(KeyStrings(account.ownerPublicKey, account.ownerPrivateKey)) + node.keys.append(KeyStrings(account.ownerPublicKey, account.ownerPrivateKey, account.blsFinalizerPublicKey, account.blsFinalizerPrivateKey, account.blsFinalizerPOP)) if i < non_bios: count = per_node if extra: @@ -479,7 +485,10 @@ def make_custom(self): node = topo['nodes'][nodeName] self.network.nodes[nodeName].dont_start = node['dont_start'] for keyObj in node['keys']: - self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'])) + if 'blspubkey' in keyObj: + self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'], keyObj['blspubkey'], keyObj['blsprivkey'], keyObj['blspop'])) + else: + self.network.nodes[nodeName].keys.append(KeyStrings(keyObj['pubkey'], keyObj['privkey'])) for peer in node['peers']: self.network.nodes[nodeName].peers.append(peer) for producer in node['producers']: @@ -508,6 +517,8 @@ def construct_command_line(self, instance: nodeDefinition): a(a(eosdcmd, '--plugin'), 'eosio::producer_plugin') producer_keys = list(sum([('--signature-provider', f'{key.pubkey}=KEY:{key.privkey}') for key in instance.keys], ())) eosdcmd.extend(producer_keys) + finalizer_keys = list(sum([('--signature-provider', f'{key.blspubkey}=KEY:{key.blsprivkey}') for key in instance.keys if key.blspubkey is not None], ())) + eosdcmd.extend(finalizer_keys) producer_names = list(sum([('--producer-name', p) for p in instance.producers], ())) eosdcmd.extend(producer_names) else: diff --git a/tests/TestHarness/logging-template.json b/tests/TestHarness/logging-template.json index fff1143346..79250422fa 100644 --- a/tests/TestHarness/logging-template.json +++ b/tests/TestHarness/logging-template.json @@ -130,13 +130,21 @@ "stderr" ] },{ - "name": "state_history", - "level": "info", - "enabled": true, - "additivity": false, - "appenders": [ - "stderr" - ] + "name": "state_history", + "level": "info", + "enabled": true, + "additivity": false, + "appenders": [ + "stderr" + ] + },{ + "name": "hotstuff", + "level": "all", + "enabled": true, + "additivity": false, + "appenders": [ + "stderr" + ] },{ "name": "transaction", "level": "info", diff --git a/tests/TestHarness/queries.py b/tests/TestHarness/queries.py index d7b3695fd2..8b49ccc2a3 100644 --- a/tests/TestHarness/queries.py +++ b/tests/TestHarness/queries.py @@ -691,7 +691,8 @@ def getHeadBlockNum(self): def getIrreversibleBlockNum(self): info = self.getInfo(exitOnError=True) if info is not None: - Utils.Print("current lib: %d" % (info["last_irreversible_block_num"])) + if Utils.Debug: + Utils.Print("current lib: %d" % (info["last_irreversible_block_num"])) return info["last_irreversible_block_num"] def getBlockNum(self, blockType=BlockType.head): diff --git a/tests/auto_bp_peering_test.py b/tests/auto_bp_peering_test.py index a55bdd8807..666cbd5536 100755 --- a/tests/auto_bp_peering_test.py +++ b/tests/auto_bp_peering_test.py @@ -23,6 +23,7 @@ # Parse command line arguments args = TestHelper.parse_args({ "-v", + "--activate-if", "--dump-error-details", "--leave-running", "--keep-logs", @@ -30,6 +31,7 @@ }) Utils.Debug = args.v +activateIF=args.activate_if dumpErrorDetails = args.dump_error_details keepLogs = args.keep_logs @@ -83,6 +85,7 @@ def neighbors_in_schedule(name, schedule): totalNodes=totalNodes, pnodes=producerNodes, totalProducers=producerNodes, + activateIF=activateIF, topo="./tests/auto_bp_peering_test_shape.json", extraNodeosArgs=" --plugin eosio::net_api_plugin ", specificExtraNodeosArgs=specificNodeosArgs, @@ -94,7 +97,7 @@ def neighbors_in_schedule(name, schedule): cluster.nodes[nodeId].waitForProducer( "defproduceru", exitOnError=True, timeout=300) - # retrive the producer stable producer schedule + # retrieve the producer stable producer schedule scheduled_producers = [] schedule = cluster.nodes[0].processUrllibRequest( "chain", "get_producer_schedule") @@ -103,7 +106,7 @@ def neighbors_in_schedule(name, schedule): connection_check_failures = 0 for nodeId in range(0, producerNodes): - # retrive the connections in each node and check if each only connects to their neighbors in the schedule + # retrieve the connections in each node and check if each only connects to their neighbors in the schedule connections = cluster.nodes[nodeId].processUrllibRequest( "net", "connections") peers = [] diff --git a/tests/auto_bp_peering_test_shape.json b/tests/auto_bp_peering_test_shape.json index 93192a9769..d20bc95683 100644 --- a/tests/auto_bp_peering_test_shape.json +++ b/tests/auto_bp_peering_test_shape.json @@ -26,7 +26,10 @@ "keys": [ { "privkey":"5Jf4sTk7vwX1MYpLJ2eQFanVvKYXFqGBrCyANPukuP2BJ5WAAKZ", - "pubkey":"EOS58B33q9S7oNkgeFfcoW3VJYu4obfDiqn5RHGE2ige6jVjUhymR" + "pubkey":"EOS58B33q9S7oNkgeFfcoW3VJYu4obfDiqn5RHGE2ige6jVjUhymR", + "blspubkey":"PUB_BLS_UJ2L9snX88oXj-bkZmfeLuhtkm4oNs_YsSC3oxT4jXJpNxGfg9mTscXSR25WhfUI8j-jBSPm616PM2Rg31zZTgw3XQkA58EUOQmqK6WXYGNGy2FEsZVz-O1jow8CrPUZtry4kw", + "blsprivkey":"PVT_BLS_IRnV3LLJx2BrGcL-wO5fO6ebHaa_CxJhAu_Y_n1B-UB5KLM1", + "blspop":"SIG_BLS_3H6WQK06R0q5Nq3r1O0u5v3d-DkWUamw64qF_RNEYnBVCOkQGowtbnQYhSysUKcAKRPmE96baItL8CClGsWxIvwtUFcEs6iWJjaDcTjy4JoNLs3ZeeV3EbWRZoOC2r4Mfe7_15q4elv20Y3DllX9i-p2qdgOf-08TXKE7p-1SfouyEKUTETzJ7lSjq96b2UYAwfzA1KEs5Gc7-u_d91Tn1at_cp6fqPcCB4SbEjstgel8CiALRvDwECMeVb5C4ULQKeWWQ" } ], "peers": [ @@ -42,7 +45,10 @@ "keys": [ { "privkey":"5HviUPkTEtvF2B1nm8aZUnjma2TzgpKRjuXjwHyy3FME4xDbkZF", - "pubkey":"EOS5CbcTDgbks2ptTxvyCbT9HFbzX7PDHUY2wN4DDnVBhhQr2ZNDE" + "pubkey":"EOS5CbcTDgbks2ptTxvyCbT9HFbzX7PDHUY2wN4DDnVBhhQr2ZNDE", + "blspubkey":"PUB_BLS_EdlqWOk4RONNBZEOnY72l83OP3Z4FnqiYLeBivxB2Z7NTbnnm7L2amc9L13_-gkBJb-ScBe1JV_rvP2ukgGR2QaO2nD454ROmWqJ67ZnDcpEEb8ljPCLYfv34OGXs8QB0QVgVQ", + "blsprivkey":"PVT_BLS_YU2HRsl5Jddg_lifKijsyuY2-_fEZSCh1-1KOsN0I1VDOqzN", + "blspop":"SIG_BLS_7B4xP24HTTVHZJ9K5JaD2n7lWy3WniLM51T1uuou6RcQwTxj74_SrtamS6Q-9CUD7DoxUl-rmExjlaYwI90w75BD64FPfFAx6lQOq3GQr-VboMxo1dEPKKlZ8tvN8RYThu70L0ELAdBw4d4q5iMcKwrV4C7jhSTRR1kLPy3svYz0509qLMPdJZAbj_kG19cGldfFXzbXYoCsKwMoVyrGMnjsIGT4t4AafbgwebHbaLp9iUQf5kyVXmHJwLTnr80FP5vrdQ" } ], "peers": [ @@ -57,7 +63,10 @@ "keys": [ { "privkey":"5KkQbdxFHr8Pg1N3DEMDdU7emFgUTwQvh99FDJrodFhUbbsAtQT", - "pubkey":"EOS6Tkpf8kcDfa32WA9B4nTcEJ64ZdDMSNioDcaL6rzdMwnpzaWJB" + "pubkey":"EOS6Tkpf8kcDfa32WA9B4nTcEJ64ZdDMSNioDcaL6rzdMwnpzaWJB", + "blspubkey":"PUB_BLS_tK4VH4Qly4HNrNLaquIzE4UuyVSsXPvJu-ut7PrOppT-7B53_Z8BokJ_6livH3gMrrq6GfAO-dOEOzug2L5e2rSQ7es3XxZlq9V_Lf-QkkHWSa3JQi_IyT33geDMwb4MSJzKuA", + "blsprivkey":"PVT_BLS_ZPRnmZqJLJBmeHZrAF_jKSWM_Qzi-cvKixkGmdltV0lh6O-m", + "blspop":"SIG_BLS_setC7_N5dF3AO618oBup3fZ7O6Q50XWHN5i4A-BoAh1qHYh3zXDcjYQG5uPh2EkR9y9X_dX0IwnqZlk7mnysWLxCGxoVOHqYAIpvyQlhQwRwcrpn62NP9D6hFf50n1wXf1c4Oa24qxVcGHvPd8nJ2FxnGYmk-i12vBZoRWbImxlKIY_biJJyt92ezUGtVdcLC397cjIC6_Q2zlZDmWtrV-ZQzLe6eTXzew8g1p-3zIGpeifzSJKnaI4Uv_dQZ0IAAQ2JUQ" } ], "peers": [ @@ -72,7 +81,10 @@ "keys": [ { "privkey":"5JxTJJegQBpEL1p77TzkN1ompMB9gDwAfjM9chPzFCB4chxmwrE", - "pubkey":"EOS52ntDHqA2qj4xVo7KmxdezMRhvvBqpZBuKYJCsgihisxmywpAx" + "pubkey":"EOS52ntDHqA2qj4xVo7KmxdezMRhvvBqpZBuKYJCsgihisxmywpAx", + "blspubkey":"PUB_BLS_aQ3GWL36NlP-MWQzYlEcxHd87N3aSlnvZJCATq3xslpv9ZzBm5SfVOUye7EtJokHGJ1bwsHvHwfcKUuPX-CS4uoW79MZSTK7a6QDBDL7a2Y_8gK8pYNu4PaV-voDhgAWVSeH9w", + "blsprivkey":"PVT_BLS_wjSZErQAh_kO9f6eX1ObXqeZt-ETR35uSbqr_BgsKByNXR6D", + "blspop":"SIG_BLS_o0hk04oBRmDSwcxBT-TpCdWkw_ozi0U65jaNf_WkHCtVqoapCGF_nWkA653JlBgZLXV9yyKq6aDnj8zWxDVPj2EjE793i402iFcrVzTXWVpwjEliwgGobTweN6XfJWoVAQzCSOWU9Gfo58FcaG3SVUwLpiiDl5rzsQTLTDa6aq1D-tor4ZNRARLtQebDeLwJthY5whTNoU6e5lh9ZN9asr71kra_K34qubzXb379emLZ586Q9aW4uiHZKKNPPZoYOf1K0w" } ], "peers": [ @@ -87,7 +99,10 @@ "keys": [ { "privkey":"5K3h9XiAmrx9EuqD8CRxHgQwEVDaWpqrhrnpdvwHtVzwJFMhNmE", - "pubkey":"EOS7K5pQCk22ojetRdyumrqp6nJX6eiQiTWWcGkZAMGhoBxgcsxhK" + "pubkey":"EOS7K5pQCk22ojetRdyumrqp6nJX6eiQiTWWcGkZAMGhoBxgcsxhK", + "blspubkey":"PUB_BLS_kGOCEX1MM5Xl928OOvGLyNo3_GpV8av1HnoaCEGOD8bAu3MDvazu0gCZGA1G7msTh1ZTPMEMVdXMuRVS0tv_9bW9Ohz9XvgtjgbPpxxc_NaeENkGg4uDBOro0Rk8DCEW4ToLKA", + "blsprivkey":"PVT_BLS_EnQXObGKvYqfubrKjxpCqNkHeLlkQg7LERjDGm1RKjgyFZnk", + "blspop":"SIG_BLS_bXrzPVc-ahxOCWrcl-iWIMuS8ego54iz7vi38A8h_ViqtxklH9O3A2z0eiw5j40M08ejiTm7JbCY_GOwulv1oXb9SaLYQkCTZjzCVssDkghLBRTVCZW2oJmU9WbZXikNw6nkygTs5sUTtCda2a_M5jqY_Rw92_NWmbolgBNkFvMcAgSHexdETA-b7QgJX_oYBWkyP0Pt8LzO6bJueZSjH8wZ8VuPc9o8taY85mt_qgdOTbXVBG2m5ud0eAUps2UHAHt-Ig" } ], "peers": [ @@ -102,7 +117,10 @@ "keys": [ { "privkey":"5KNxJ5cGoMeDiPi7raQMx1oqkuB8afJSDa5UdwrkCSC5qqt6Lbf", - "pubkey":"EOS6kAMMmvYK9etVhffMBHXXZ16B1WPKLmUZ92iY9SMPWY2vwxutH" + "pubkey":"EOS6kAMMmvYK9etVhffMBHXXZ16B1WPKLmUZ92iY9SMPWY2vwxutH", + "blspubkey":"PUB_BLS_Wx9TJvBhG6a0UnT4yjzLsgmFq-r6tX7w2qldrF1GV7VNr7Ox2_HIQAt2i9nCGsITgKZbkvBTJpWEyUJ2Tf5_O6tD_iCeTV-lQGsFXY9J9e1U1IHUFpFMHGnjkbkiOUoM3wdIBw", + "blsprivkey":"PVT_BLS_495bAY4lV_pFUHQanCN7OFIcBUybRUH39dApM_WQwVGU5zun", + "blspop":"SIG_BLS_fbmz1rHVbcC1wza6MVPJajmeDc9yzH77eK34zG0HuNoWI60A5MPlxjHbUK9iwKkNfvWCnr6bd0RqSpddcX0GIii7ZU61wM1xEBvUqEqREjYIBQRLuVRfStMZLABSF0YKePZuLvnbPlIJlK2rhbv8ZvFLsrbtO8dLyIamwb0AjBnhNlOfMcN7ycwvxgkb_LYJJiytAjt2jf3XHtu6TdKZgGcaOR9SiAegnxs3Ur4OI_QAHHj1txi5UwwqJI5FB8UE3Ff3Pg" } ], "peers": [ @@ -117,7 +135,10 @@ "keys": [ { "privkey":"5KWn533nXMjUv6tYoNyV6U6UGgvC2hBprGvYhoR9Xr6cE6dJAKX", - "pubkey":"EOS5g9Db5mR5rhhEewywW79XAYzqhzt7FQcV765xwqGnwBntif9dT" + "pubkey":"EOS5g9Db5mR5rhhEewywW79XAYzqhzt7FQcV765xwqGnwBntif9dT", + "blspubkey":"PUB_BLS_THaXCNIL39xWP6zTDkPUVXXW3sGbzXaJ7-4XKhWpfEiZrR2uWZng2JWKCtqJzb8RwKLw9-PuUoW8v-qx4dlq_kNzm2zTfYxVBZKZfGFBwOf92hCd1S4SHtswc80ur6ATGkTY0w", + "blsprivkey":"PVT_BLS_UAFrWj1Zwj34R2YNv1FT7uow5ulvqs3f1CXIfn8rgBeAe0F-", + "blspop":"SIG_BLS_W2OfXNGQ3liMGCPTSwKrvTkLoBIHz8ZSAmwPqYQ41BZRgT00ypjQ8CxEqaOP6hsW6NctpG69gfjvjvOoGWyrmeRxL-tgP_knhCWAFBGU8EQ4EQFz4aVOR1wOjkTR1OUUzPzMnqyKo86ARQFydqep8ee2dyDBrPOyJZrF5wUubFAgm7_obhvQIkUCD7z-SoIQL2X2C35EYj_4pMQVf6sKtkUOL-guFbNIqbIzZMcBbhR6TBln-_hweAdeYlSqoJIX29PW7g" } ], "peers": [ @@ -132,7 +153,10 @@ "keys": [ { "privkey":"5JLyZdVL9GyhhnmFZNXB6xLWVWivsMCtxTQmDRMGj9scNebDynq", - "pubkey":"EOS6npG3MJ3UD2Dom5UCpZQ1ezJ7rZSJ9AX4cQjJvsHU23jNVGWja" + "pubkey":"EOS6npG3MJ3UD2Dom5UCpZQ1ezJ7rZSJ9AX4cQjJvsHU23jNVGWja", + "blspubkey":"PUB_BLS_F_AmqmPElNz7sc_VOKYt6ISzGx2WVHZmk6BELsVCge_t51X9DDakOwPPCHYWapgNqibc7wCXy-d8jek6UJzgqH3jLm1uM9PNnZu-8AbK3p7K4KJlMXfY1fr37oJJXUkUKdegxg", + "blsprivkey":"PVT_BLS_WElQJ1H7ODtihQ2VuZpNRhzsW-90u17FiQf3xD52J2KaN71k", + "blspop":"SIG_BLS_j7SWSUp5TQ0bBwlCwj1H2lZdehwfvjDoW56Il0pMLmwEvzzf_XeTKlqMAsa7cCUZA15cb-93J3cftd0np2jYF9nuBMsjn26anV5U4p-rsOpbkwyYCXoO6k1DQ52EpL4QRLKJ1MEQxqeRH23zw6GPv36R8FWbr8UhPDs1Fh_JIKVKC_bM6iI2TJBq263HZwMNATYXWKbiQZ7CNafSgrl-pTJen8kuPG8fHyfeG3fesb3Bd-MuVOwf-5sVC95x4mkEFRRqFA" } ], "peers": [ @@ -147,7 +171,10 @@ "keys": [ { "privkey":"5Jjkwz2aSsq6cVW74rifqQzDzs85gAEQh6MhL3stEeoY8idnwG2", - "pubkey":"EOS8NybfYVLoS2iN7oaVwxqbkfcze19enjv1HMPpg4RisF9EsSuY6" + "pubkey":"EOS8NybfYVLoS2iN7oaVwxqbkfcze19enjv1HMPpg4RisF9EsSuY6", + "blspubkey":"PUB_BLS_pQuFfH1I2ozll_e1F1vJYcKdANJpAUNJSqafTOy_mDBnu7IQJqVdXJ8dqINXb9YZSO0SbTdVNeuUR9Qfc9SDCm3jwwbSxZ9Y5bMUkyPzQBJ-dU3YXlHxO6C0-TTdkqQQIkq-5Q", + "blsprivkey":"PVT_BLS_FOmNTGQERlYcIM4LFGHpmjOrHM2p7mF57_maescNrSeNC3oa", + "blspop":"SIG_BLS_9VdsNkCqFCfoSon9JoTMNpYBKTwEz5wkVQEfq0i8uLiwLF6dZp5ulJuTZgtq7hEO2Ntl-jglz4gmp--tqEoVy3TclPAI1SFLUdGDjGdb4L4gBX8huG2MPUGXnYW_DgMZDXQbEYy43-XuWh_7JdjUoHc06Df1tqAOKdHdLSWUn_O-u7fGOH1cZgt6rFC2yZ8MLjbyQEekHcFXxUeO82hLV1kFp4qbKXEEhwnDmuHk45q0th4Niy4aHIwgtpTuwl8Y3c_2ag" } ], "peers": [ @@ -162,7 +189,10 @@ "keys": [ { "privkey":"5K233w6Hb74Q5Cz3ocRnu3svompUd7XyNmcpNkJjJRdfJmKeVM7", - "pubkey":"EOS7ay3hqpGCXn8JaDQEsYBJ12SgnkuKx2gz66o5cJmzTUZ7x5GDo" + "pubkey":"EOS7ay3hqpGCXn8JaDQEsYBJ12SgnkuKx2gz66o5cJmzTUZ7x5GDo", + "blspubkey":"PUB_BLS_MApkZKPemyPYdtW4-9snF_W2PkfFoVI3E-03p2eOaGZa1yV6ygYDj8VDw_goBwAEia-Iplp9z749b9NgLy7FfTeOSLOIMFyWBY7WgraYaIXxfJYxdw5Lnj5MwLxHvGgCrPD9KQ", + "blsprivkey":"PVT_BLS_6Nb45t9oclV32ovUYNgCLdIRe3PM6VuDeZP0XZezrTE6ecd9", + "blspop":"SIG_BLS_T06CVAbdS31OYmT5v0KZMS98eCMutYP3jR50SCf6kLovBt5oWzlpOA5q71sUiNwNeKWQiYBrmiO-CbgQkDUifQXTiDjeeTKqoaVV6aIHYtoo6BJbq2QvZ-xgI3rKskYBKppbHywt3nxI66NjjiA0ZIY2gZh1BhjOz4y75-janrLxomyLgZvFqNSVFCbl1g8FC_a3VIAeJdOq2LzjmRd2EufL-pzr719HrcwTo-gNjn9ChtrF6FrT_0E5Yq88IOcWhPNm1w" } ], "peers": [ @@ -177,7 +207,10 @@ "keys": [ { "privkey":"5K3T3rhvCHyL4SkiMvkehgLwo9iwwqGhYdDmM6aQKpJYkm1p65N", - "pubkey":"EOS63XTEVf3PSjNxRE7boZz3E35s1GDv2TzXfX1iapa7SJRi7T3ay" + "pubkey":"EOS63XTEVf3PSjNxRE7boZz3E35s1GDv2TzXfX1iapa7SJRi7T3ay", + "blspubkey":"PUB_BLS_5-Np2eSWddV1GfbEhlx1QqhL7xzquc_EdvbeqsJPcpEOWbJZ_sDq6FqGDDzmfFUV9c7o33idEFN5bmgpTk1TOvKxk8V-UFsw4sEGXt34h9QZzekpjhqN8U081jkwtbsXktJ1TA", + "blsprivkey":"PVT_BLS_sjsI5DNqkN1di7VeYSOBOHeaYBwB8yjbIMgSMwEVByZICuuU", + "blspop":"SIG_BLS_GrIFJG0YQNmddCi-sAxO7O-LirRBLKn63i1kWVz58j_YmQ3xDbUPBnVZf9p-vPUEzw_5xOtjSkKPHo7DBz3v62ILbpee2nvFSi8AWDcQdZaVC_MidK2EmMNXodnVZ8EBuVq6-CWgUGU6HyIGZF0P3SYlUZddFHgLwQjv4n3o8IjCpuBc8DQG72xm0iQoaS4QpeNYX53Ht4kwKEr3Df5rw_KajcrMUsZU2A_A6n7xQdUoYjF321bu3Eao04p3ZTwUguUYyg" } ], "peers": [ @@ -192,7 +225,10 @@ "keys": [ { "privkey":"5KFMjaCKHrTEKRNk9UTrxz3cu1vdNcVo9PsmaUX3JhNWNYmg7Ak", - "pubkey":"EOS5faXpYaxXSQM8B71jowJGTPRFiWSuemzN13mcPUBbYxk1b1VEU" + "pubkey":"EOS5faXpYaxXSQM8B71jowJGTPRFiWSuemzN13mcPUBbYxk1b1VEU", + "blspubkey":"PUB_BLS_M_lj7xiBWoOC8rIJBo9pu98xF4Sh4HhT4db68KPMlQQEdUuzaXrpEdUgMjvYKA8KzC0GVj_LetJ6jfKCMTThA0Wve9Q6T2Pz9eyxlZIMUkeug026U6WGO6iBytum5uAEQGL5Rw", + "blsprivkey":"PVT_BLS_4fpxXrwDB94lhA_NQiH_UBU1xrHRMaSTZx60heFetAK5hjam", + "blspop":"SIG_BLS_5ZtE6XzGsIFcB-yyxhxEo66N-EBPL1jqbnlapOqi2xspg07hKHe8EnRT7Y1i2fwT73ukAob4caAv2I9Vk9rT-kOj5VvIs4NUI5LJvjK-FEMnjq4QhIN-gmGmcw4GVc8R_n144YTk_BLMawkcHwDrbNXg2iWuWpiORryeRTVouxt_ax2MJ1pnBNZ4eXl2JPQHnzXVoV9K9ArOEMwfn3aEaKeLSCXFYzEtE9SHvGf-OpdB_EJ5Uaz0u0M_xO84jjQYFSWZXw" } ], "peers": [ @@ -207,7 +243,10 @@ "keys": [ { "privkey":"5KgUZEsvsiBrmBXvaK4GefQkhtwXKmEpjj4ChhBcyhbZkiLJ4gV", - "pubkey":"EOS7SaD1iNagvRTsAf6mAF42cEGRiLKoQZ5gtsjoUejamW4kEmddG" + "pubkey":"EOS7SaD1iNagvRTsAf6mAF42cEGRiLKoQZ5gtsjoUejamW4kEmddG", + "blspubkey":"PUB_BLS_Fn4mn2UNEjzFG584s_rEJ4g97aC4lYXrzOQlmd3ke1Uo_fqgy6MBoM4k9e0UKb0KG3UyehJkxqSrvxCSizOEc9cE0zw1stLCucDPZv3KcH4tvolL6WzZDcDYnRRtZqQWHWcjcw", + "blsprivkey":"PVT_BLS_XZtu5BaOUBl3VmApqGHBKZXb4WodmqbhBnOT9YrBfT2H-4lk", + "blspop":"SIG_BLS__BX43n8QaN1tuhGbB3rqElik-VejAA3Q5XGMlQy35SNrnjDActZiIowPVtZ3R1EXD7lEJEVJQGuJSKCpGpBPI1r6Lgvue54650qFppRcqK2c5SA8nbX7uZNN2a6SvocT54X0SE5jOc8GI5GRNmcp5-88umn77kIU_6ouLhkNgno-CdaRsh2o5trUJiVyGXMKfPXNcSvlgDNXLM7lSqE1nnzSqHAnQ0VIjShj--uPJDp5tCPZ9DImqO20JeXyWJ4XeOGIIg" } ], "peers": [ @@ -222,7 +261,10 @@ "keys": [ { "privkey":"5KAKiYtrxrhZzeiF2tb2kgyupdStyPcnZ7dfPQDUc9BLG6vigjc", - "pubkey":"EOS8gxmMjQD55oZmTfD5yBD2yPYQ4bRrJ16VVMkguRXroJK2FVWrt" + "pubkey":"EOS8gxmMjQD55oZmTfD5yBD2yPYQ4bRrJ16VVMkguRXroJK2FVWrt", + "blspubkey":"PUB_BLS_DKu2525wPup2hLrYrHYkGT4qNmW3-9jCtLbXfZEgc7zdw9_RHRaNNse3ofItgMMPKxdSBDhEzYdCqOUFL5UyIleix03WROMmVD4t_NGJ-nz8t9FoidXMdlfMSRhMs54LFA59Ag", + "blsprivkey":"PVT_BLS_P9rNgJbnlwyIxbSZyrf_RTRgO2lzMi0t3yco257fxx7xzqSw", + "blspop":"SIG_BLS_kPOTpF0scvc2yu6fRRNLEQENc8fHsTi-0fCzyp1XhShc5PCSxVDPKKv5yhrYcsUXygsva7j3QoSkpy_KKR_uzUHOZuaz78UqiV0R_oE4IYl79VGRvYNweZm5vupyqyEWBTxJQYgnMO7fBRPkYj8A4XNQAV1IxQhSzBLmurk1mGHF8At7Jss_m4JcELV62EYSBFk-R_JEnknkLl0jmpEy2gqk5KZAM_bCm7B8gVw5u4n0Fy9dxmdCLJF1iO-ednkGxyHLVA" } ], "peers": [ @@ -237,7 +279,10 @@ "keys": [ { "privkey":"5HsYvqVcuHkddyZF9h7iR4YxUMUmvoz3CRetNb4GveiRkehNpK6", - "pubkey":"EOS5JcdUMhAgBTiXpry8xzFNeMhJH36ULjLYCe3Gj8Fy4r2kEJW52" + "pubkey":"EOS5JcdUMhAgBTiXpry8xzFNeMhJH36ULjLYCe3Gj8Fy4r2kEJW52", + "blspubkey":"PUB_BLS_6KMOHO8KuxHzHN57x7I0JkTOVO83FYDNfagfjoR1iLzYvV2wdS_vCgQYACe2xIwSBmj4L3-v72D9oYI1rj_WQHbT0qwwnN8D4sjqLktjkRZnQOOOZEijUcmlBTAG7tsUGecCOg", + "blsprivkey":"PVT_BLS_o8y-Boab7BOBPSQM0PyZ-Qp7uUxrJTFQrzYz7q1Q1ww-JZeK", + "blspop":"SIG_BLS_RjmAv76ZFaK3fxJdbiHqt01SpfCHpUe8I-ZF0DT1H4jRd4_-aNqZovLP405h11gA1G1RAAqmfVMvzB-jUx3-BY6jFqqepPfU6MpAO9rQhM-x5f8KrinK2nSMFT1GE2UBJ22DyKVbEFtKJx9guHDbWumzdUkDFA5xyC3EYXGVBBvBba9SP3i-KFuCqtCMslUFJWBvOBr8NPvGfT4kWnn786JK8rw3ViSGErjG_If7vQso-0APpQO1kF0mn9ShY4EDnoeWHA" } ], "peers": [ @@ -252,7 +297,10 @@ "keys": [ { "privkey":"5KA9YksaqmuwAU4WRUFEuHHbm2oXURZWNi5EmHXow4MrzZakAUf", - "pubkey":"EOS6dG5iBiVUenpni6FMCa5KPjYF2DcTXVbUTywAnQx9XQwz58Wg5" + "pubkey":"EOS6dG5iBiVUenpni6FMCa5KPjYF2DcTXVbUTywAnQx9XQwz58Wg5", + "blspubkey":"PUB_BLS_0mn6ZeFkk2ordvG7ft590qER6awhrZPDbFBDGZ7EDaw2AGJCAMi7F3_PzkhTEkwYosv4JnaUVIUG1cpRiSmVMB1g-osN92_fJIKD9_QV9W_zDZs5YyDaf9BMlnGOoOsMqfOmOA", + "blsprivkey":"PVT_BLS_Txga4vv-RQ-tZCMbaEDiUcS6jsHg2_jUtj-bxUi9BG5uVkgn", + "blspop":"SIG_BLS_ADXUcACZq1075pbqsJKDj44QFObBwPcqbz7wMfaGbkzwOTt092eClC3xymrSZrgBb8hJg4qkeSXFUmxiw8Zr-rSqO1P6lcdnRUE0D9GNQGbDFscxUE6jwZlp6wH6hF8CEoyY6QJsfBOaShW2RHNiqeJtBFbFo9wliYeya5E8Qt176_lWBrnLQnie8-i5e7AW78T7JQAFMdlGHsF-QORXyvbigEFwlLFO0AfZ8P6pOXEBATQpU8Y3BMjVEhO_GqwKmOv8aw" } ], "peers": [ @@ -267,7 +315,10 @@ "keys": [ { "privkey":"5JUV8ELodV2N31nYpfkzjyzNTqSSJEW7A33yvC9vfcFEDsV2UJj", - "pubkey":"EOS5S4ECoqLgv1GK4NrAhdnv2TzteLEquGnurCbHX74v4mn4Z3bgY" + "pubkey":"EOS5S4ECoqLgv1GK4NrAhdnv2TzteLEquGnurCbHX74v4mn4Z3bgY", + "blspubkey":"PUB_BLS_no1gZTuy-0iL_FVrc6q4ow5KtTtdv6ZQ3Cq73Jwl32qRNC9AEQaWsESaoN4Y9NAVRmRGnbEekzgo6YlwbztPeoWhWzvHiOALTFKegRXlRxVbM4naOg33cZOSdS25i_MXywteRA", + "blsprivkey":"PVT_BLS_y_iMu9QYlZXK_Cdb-NEfSOQfJeWzm1-f-7p6V5MsiwsL1SQr", + "blspop":"SIG_BLS_uH6aANw9BXeNR10LpBPeR2Nly-syjCULNz1CyIGjzDbmnWP38nGG6mJgKkIsumwIHX3lyv_YuRKENR6NsHGeW_ZU_-UtLUYexcEcarsJXh1W5zWvAbkU0opglYj5KO8TDV6m3EObHH4l8aivkCesm5UWJd_WKCm0a33NNDn7ODLFqYTa8g3OEIW55p911M8IYt8siHTeLuc-igW282hUZhdtWnf5oEjKUqFTJHY_CXAARECZZ5DkjIT3WlFCnzcMjOgZLA" } ], "peers": [ @@ -282,7 +333,10 @@ "keys": [ { "privkey":"5KAx38nXk4Rck1XefFW8w7vPgzELVeieYUuCjeZJfPoi2jh2s2C", - "pubkey":"EOS5z4bsFKN14vJesR86ARJ2AvMaS4n39eE7ngKM3B38bYBCrLbZn" + "pubkey":"EOS5z4bsFKN14vJesR86ARJ2AvMaS4n39eE7ngKM3B38bYBCrLbZn", + "blspubkey":"PUB_BLS_0IObtkg4_B0hdndSghQqZf8qQ-aPRyhxNhGsY4CzIgJiMlVIl71Zh3vnl5yht0kM0ds_hgeDFzUo99-ApbXyxMieQ4LRFQBaXRSGCp50XArXtIYGe6PfLXjqqkXZdTAOALHT9g", + "blsprivkey":"PVT_BLS_be_XdiZXFYG55p9C6vQdTpMHYtTRqs1UTyzmSoP9xi-VhmjQ", + "blspop":"SIG_BLS_F_GHPJsIUGgGFC0lC1aBLxJkMAa7U6rB5XeehWOLoELWKMtbkhjU6hxzWeOXXgoXkIy2kgkhXPMe6T-K5ZyWqKb4vo1FzGEyl4Suscdr7hsHCNQHnm7GdmxD6BTfS1kHHT0nibtVMeyqWUzKXjQ6-AoZsy9GhsAgFmY804s4rKUJi6w0CJdaqF-6pL-_8-YCQGbVjseWyaJybP4qQX0dUfVDWn-SKAbrtXq3o8KA2vWyKestPoW-Zln9mlMGY2IUNq5SsA" } ], "peers": [ @@ -297,7 +351,10 @@ "keys": [ { "privkey":"5KhyQJFLkqtxrFyKtpEng9c8N5dD7gB4Q5q8LQL78e8J12LCiTz", - "pubkey":"EOS67KLwTrR9QYnUP6ETCcp4HyTits5VKALouRcnBkCuaRv7m6zg5" + "pubkey":"EOS67KLwTrR9QYnUP6ETCcp4HyTits5VKALouRcnBkCuaRv7m6zg5", + "blspubkey":"PUB_BLS_zqRtF3KMfXOc_8qRTrYIuNmgnJqX84U59Jr30NjXQrjXrF-U-AD9AWB0WD4gEvAUrGuI_fx2uG1Ds5rYnJ_iUbLMc0VCaxbME0i-lSFDm0TOkQC2xEZtdgOIYAJhwWcSGc23fw", + "blsprivkey":"PVT_BLS_eAKD3fopqzWjZOjwRViHPXsBj4J7vGlF2-a8Rfl1gxQaKds-", + "blspop":"SIG_BLS_AF9jzkVqU8XNGgzY75ACW4PWJZxRhd_8aBlXo9BtLIHaX_MJs5wryYrv-M3n5ysGq2aGQd58KHfN15zfdsYuJZzQ3WmQpB73orycC4rh55GOPa3475ObylJQxp1cAWoCQnZWkz2eOmT6W6tLqrO9qfyymgehog11uJNaGKuadI-HI9hE5oxcAslZSJqV_roAzX2tSKLbB0KJR8XPzG1Fv5-xKQZQzUlJCfRsOb1lK6nKKrnuQ8Gc6_f53ekwU0YHt3NXYg" } ], "peers": [ @@ -312,7 +369,10 @@ "keys": [ { "privkey":"5KRT1XwAy8RPFwH2SkwZdzACt6JaeFRefVTy4aBBLj9VtRzdcqH", - "pubkey":"EOS5tDpQJoHc4Y6cy4FapHdUCEXmZ4qwq3EF9q94c63dN2bNQWvj5" + "pubkey":"EOS5tDpQJoHc4Y6cy4FapHdUCEXmZ4qwq3EF9q94c63dN2bNQWvj5", + "blspubkey":"PUB_BLS_SvKo0P0TvkXaVXuA33W7QyeMJSiZ247rulSyf47ZSbGNw-v9Wn_ciRc8cL0Y8JgV0j_dFYi_szcYRDESAiU31MNVGvZRhUECnox0cSNxCCevo_o0Ptybn7iaeAUFYg0Jc7_oYA", + "blsprivkey":"PVT_BLS_wMBah2N0NEd5rR6grESurO3EaKRzJKRgWA9mtb9RJm4Gwsaf", + "blspop":"SIG_BLS_6DYG5dBLgvBk0SlQvpNKnwaVwGf-WeVrS1RvaqyywQqccJ1vHDrRqeXf74OpEDoA4W0Oyb7TxYnrrD2iBoOzwI5tR7TVTm28v4yRn2Zs5rJB-QFVM75Pyira4s-BpuAUPAFQjJPOO9BLoEmE_vN7E4ApBLIjWR6U6AUKULJ3a5bnwuTZ4PwAlgwLbQE60CAGTqEOBx5YdA0ibbMgCGk3E2zNXee4GIVDQcInRRyOjuUDvkaxhUXVWiT-1oP5L4gOb1xYHg" } ], "peers": [ @@ -327,7 +387,10 @@ "keys": [ { "privkey":"5J7Jg9E17ae1dAunZSMMtDKE1r1XtrmBKWmU4DGa7atZXCpy1RE", - "pubkey":"EOS5NhoG6oyNdGdbujArjVPMTviym3ygrYJ7ttBRcfCJdrxLrXqAV" + "pubkey":"EOS5NhoG6oyNdGdbujArjVPMTviym3ygrYJ7ttBRcfCJdrxLrXqAV", + "blspubkey":"PUB_BLS_6zr30kiVSVz1orVeEEeLMPy4tcCN7SXlWCXuXTBqTIAjeEQQ-WjTHGxA9ZtKsM4SqBKdi95dPw1G98isHprZ25qZx7iYFCq7Ayaw0L0TKOMv52GNFEQ6_Qzgp6lUQloCoBX9Kg", + "blsprivkey":"PVT_BLS_8wDapgL8B04AEWPcWrxgRSI3KRFEBcR9FzLIMwBStV6cjkCQ", + "blspop":"SIG_BLS__60s6pr8NfxhAEZ_0ycbgt4FbK8DwEy9KtIxSBBO-pz0oatUAX25AYSrHsfpisADagya5vqobmGJhRkcC-g3oUcZQqAIxFFG43_ECLIiW-d-hBN752Pu6y7Y1glFpCUU_GMbHZNS7Z8XhtPMxT-jweeQWLuGGQuTvTcx3HfSVgZunkXKEmdlkG3SjC6eBf8BZN6YXjKIvkoh7cIU2Qa4XcX9rNJ5kOUHyzjvr0hC1_T52opNOwpeA5hkD04uajQS41hbOQ" } ], "peers": [ diff --git a/tests/block_log_retain_blocks_test.py b/tests/block_log_retain_blocks_test.py index f9d592c46f..50b4fbf9cd 100755 --- a/tests/block_log_retain_blocks_test.py +++ b/tests/block_log_retain_blocks_test.py @@ -19,9 +19,10 @@ Print=Utils.Print errorExit=Utils.errorExit -args=TestHelper.parse_args({"--keep-logs" ,"--dump-error-details","-v","--leave-running","--unshared"}) +args=TestHelper.parse_args({"--keep-logs","--activate-if","--dump-error-details","-v","--leave-running","--unshared"}) debug=args.v dumpErrorDetails=args.dump_error_details +activateIF=args.activate_if seed=1 Utils.Debug=debug @@ -47,7 +48,7 @@ specificExtraNodeosArgs[1]=f' --block-log-retain-blocks 10 ' Print("Stand up cluster") - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/block_log_util_test.py b/tests/block_log_util_test.py index 042e1467aa..4cb8648190 100755 --- a/tests/block_log_util_test.py +++ b/tests/block_log_util_test.py @@ -29,9 +29,10 @@ def verifyBlockLog(expected_block_num, trimmedBlockLog): appArgs=AppArgs() -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}) +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}) Utils.Debug=args.v pnodes=2 +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) prodCount=2 @@ -49,7 +50,7 @@ def verifyBlockLog(expected_block_num, trimmedBlockLog): cluster.setWalletMgr(walletMgr) Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount) is False: + if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, activateIF=activateIF) is False: Utils.errorExit("Failed to stand up eos cluster.") Print("Validating system accounts after bootstrap") diff --git a/tests/bridge_for_fork_test_shape.json b/tests/bridge_for_fork_test_shape.json index 7b87e4102f..7d86b4070c 100644 --- a/tests/bridge_for_fork_test_shape.json +++ b/tests/bridge_for_fork_test_shape.json @@ -26,7 +26,10 @@ "keys": [ { "privkey":"5Jf4sTk7vwX1MYpLJ2eQFanVvKYXFqGBrCyANPukuP2BJ5WAAKZ", - "pubkey":"EOS58B33q9S7oNkgeFfcoW3VJYu4obfDiqn5RHGE2ige6jVjUhymR" + "pubkey":"EOS58B33q9S7oNkgeFfcoW3VJYu4obfDiqn5RHGE2ige6jVjUhymR", + "blspubkey":"PUB_BLS_Uf3df_EqPpR31ZkenPtwgGUtd69cahyuY2lc9jPwEta7Q6t7REV-Hd35hUIDel4N7pQdCGZdnVZzs_UmJghEjGhVHN1QVVAQjOca8Fs10D_jqTiUzffzqyBAvTHyZtoEEPyXkg", + "blsprivkey":"PVT_BLS_t2sZsoDWTQFIKg75bhJn8pBA0iDYcWyn3HlEfKIzTzKozgKO", + "blspop":"SIG_BLS_TnwBY4dpG54mCue3ZXwjCio0AIdWYwFdz5ipLdnXlg64FkYkhMUtkOdQIs1IYbMWOXlD6OnCP6jcCWi5VziWKNbLfMX64SdIkNPKOHrfE_8fBfIk9Onj7GbWx3q0LbYP7NfJQk1mk-gOjz1G3elZDDHt367YUgzYDKhtl1FSkfZzDRzDsCSei7H1MjLi_e0RVdUfgqAznGaq2Yss6gY-HzwzgHU4y-SNQpzdCuDlLEEIjkHq8fXuMiPWT2Dlt8kOML0uqg" } ], "peers": [ @@ -45,7 +48,10 @@ "keys": [ { "privkey":"5HviUPkTEtvF2B1nm8aZUnjma2TzgpKRjuXjwHyy3FME4xDbkZF", - "pubkey":"EOS5CbcTDgbks2ptTxvyCbT9HFbzX7PDHUY2wN4DDnVBhhQr2ZNDE" + "pubkey":"EOS5CbcTDgbks2ptTxvyCbT9HFbzX7PDHUY2wN4DDnVBhhQr2ZNDE", + "blspubkey":"PUB_BLS_Y8ndNvnrEpnzJcNUg49ncWDiDGRgR7WUmRRDR9yMURoS6zF14sPnbb-DsTGp0cEM628a4CmG6KXMhPJMqGZvb7RM_MGIwgbEhVaENL8rXeYLOuFDS375KHFgXxs2P5sZuaN7aA", + "blsprivkey":"PVT_BLS_A1Mifu5xyaxiveyjnZ-qN2zOt-5_KLMpjTrDI9udcQNV1NBR", + "blspop":"SIG_BLS_7D0OUU1h7E0AKkAmqV4v3Ot9oSPWJBOss4yDejr2x1g5G31cSSAYIAtqZOYC-ioNzddY7zkvTcbhKgBzv5a-G1HmV1pOCXXPJ5TL0iqU8Ks5abeEWCdhArGATmRQiSMYNcj9rMQcm3H6Z0pOlOdbDdt8Cg-SY_H4jEGmAY2ZqudAH_U8gS19aydJU-2uQq0SPIr2Okl-WNbc-q3NVQw6Y0sAHAwN4BOIHup2MJyDDDIbpSEkBchRp3zna1XJf6oBuUzpqQ" } ], "peers": [ @@ -64,7 +70,10 @@ "keys": [ { "privkey":"5KkQbdxFHr8Pg1N3DEMDdU7emFgUTwQvh99FDJrodFhUbbsAtQT", - "pubkey":"EOS6Tkpf8kcDfa32WA9B4nTcEJ64ZdDMSNioDcaL6rzdMwnpzaWJB" + "pubkey":"EOS6Tkpf8kcDfa32WA9B4nTcEJ64ZdDMSNioDcaL6rzdMwnpzaWJB", + "blspubkey":"PUB_BLS_Wf_O_QeyVhekDXS5q3qBxTyj_qxSrX_uiCY4z8ClpW0X2jrAVgAVHOQ9IR2H40QTWveD8QIGhhSbmSFPa0zFbs5k3yfnjfuuwpA7T1O13_LSdtxT19ehYiE4chZX6SUMJ09JFA", + "blsprivkey":"PVT_BLS_1ZLWim0k80ssXswSZp1T3ydHO9U3gLnKKlEBIDy8927XDLLj", + "blspop":"SIG_BLS_EL09aI3w-qCgarLM2Z5-T6sisSHBN0J4vMZxtGQklkOcAxgnCaPPXe0roxY4W0gVe2y6T01YrklmT_qZu2tAwqiNrVJcScY8QKvRSeczGBBab1MgnHvaAOuf6bA4JPAELIu2iPWfsS6-oLyLbNP5xtZpMXPHu3yaSJssXNOb5rcVs1KXaIUEagJeAlBBQEcKmFWfeAsJ_R8JDw4i9gSNmROzUjm6LVBpvB7vrnPDPFRA0BQ19H4FED6PtuFPShwJGVz4dg" } ], "peers": [ @@ -83,7 +92,10 @@ "keys": [ { "privkey":"5JxTJJegQBpEL1p77TzkN1ompMB9gDwAfjM9chPzFCB4chxmwrE", - "pubkey":"EOS52ntDHqA2qj4xVo7KmxdezMRhvvBqpZBuKYJCsgihisxmywpAx" + "pubkey":"EOS52ntDHqA2qj4xVo7KmxdezMRhvvBqpZBuKYJCsgihisxmywpAx", + "blspubkey":"PUB_BLS_C-FprIiry6X-8dlLYH7xUAhIuKXBQv56zJPgtcdmKeHf8AAy750eRrOYBtKG0-QEIN5l_yl9dTLvAYmOios6Q5t3ybWBUVVQ2WWcbZLVxzwBftLwYvo1zPXH7LHEE_sAgP1i7g", + "blsprivkey":"PVT_BLS_ubElmjajfsYP_9HRSpmV-Fi_IPWKTyJS4XFSWrU8ezMZ_mL_", + "blspop":"SIG_BLS_k3wrhVl2GUG_lGsPr9io-zoamPw7eiaxMDExk-yOqcpXtu0zALHoUWJRh0WOerAS1-_RQNhbi4q-BWO9IbiNWRKP9CYIhNIL6ochGHHy4aBmZ-IzEjfBrDt7inDtFTYY0Gl372e5OqPXAwi6J3GeHipXuzAiw7SV8XdWFefthxId4meKX6vw5_RWx4XQ4ScRYoCG7UQtIZkQPEsu1SfJGL6z-cfTTSq-naKbzp0QQYfqtQkFfmL7qQUH1iohnb0HbTbRbQ" } ], "peers": [ diff --git a/tests/cluster_launcher.py b/tests/cluster_launcher.py index 4a0d450daa..ec4734ff71 100755 --- a/tests/cluster_launcher.py +++ b/tests/cluster_launcher.py @@ -17,8 +17,8 @@ appArgs = AppArgs() appArgs.add(flag="--plugin",action='append',type=str,help="Run nodes with additional plugins") -args=TestHelper.parse_args({"-p","-n","-d","-s","--keep-logs","--prod-count" - ,"--dump-error-details","-v","--leave-running" +args=TestHelper.parse_args({"-p","-n","-d","-s","--keep-logs","--prod-count", + "--activate-if","--dump-error-details","-v","--leave-running" ,"--unshared"}, applicationSpecificArgs=appArgs) pnodes=args.p @@ -26,6 +26,7 @@ topo=args.s debug=args.v prod_count = args.prod_count +activateIF=args.activate_if total_nodes=args.n if args.n > 0 else pnodes dumpErrorDetails=args.dump_error_details @@ -48,7 +49,7 @@ else: extraNodeosArgs = '' if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, prodCount=prod_count, topo=topo, delay=delay, - extraNodeosArgs=extraNodeosArgs) is False: + activateIF=activateIF, extraNodeosArgs=extraNodeosArgs) is False: errorExit("Failed to stand up eos cluster.") testSuccessful=True diff --git a/tests/compute_transaction_test.py b/tests/compute_transaction_test.py index e257c3fb37..6488ee59e8 100755 --- a/tests/compute_transaction_test.py +++ b/tests/compute_transaction_test.py @@ -55,7 +55,7 @@ Print("Stand up cluster") extraNodeosArgs=" --http-max-response-time-ms 990000 --disable-subjective-api-billing false " - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay,extraNodeosArgs=extraNodeosArgs ) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, activateIF=True, topo=topo, delay=delay,extraNodeosArgs=extraNodeosArgs ) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/distributed-transactions-test.py b/tests/distributed-transactions-test.py index 985ad83168..9cda9456f8 100755 --- a/tests/distributed-transactions-test.py +++ b/tests/distributed-transactions-test.py @@ -23,7 +23,7 @@ appArgs = AppArgs() extraArgs = appArgs.add_bool(flag="--speculative", help="Run nodes in read-mode=speculative") -args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed", "--speculative" +args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed", "--speculative", "--activate-if" ,"--dump-error-details","-v","--leave-running","--keep-logs","--unshared"}, applicationSpecificArgs=appArgs) pnodes=args.p @@ -36,12 +36,13 @@ seed=args.seed dumpErrorDetails=args.dump_error_details speculative=args.speculative +activateIF=args.activate_if Utils.Debug=debug testSuccessful=False random.seed(seed) # Use a fixed seed for repeatability. -cluster=Cluster(unshared=args.unshared, keepRunning=True if nodesFile is not None else args.leave_running, keepLogs=args.keep_logs) +cluster=Cluster(unshared=args.unshared, keepRunning=True if nodesFile is not None else args.leave_running, keepLogs=args.keep_logs, loggingLevel="all") walletMgr=WalletMgr(True) try: @@ -67,7 +68,7 @@ if speculative: extraNodeosArgs = " --read-mode speculative " - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, extraNodeosArgs=extraNodeosArgs) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, extraNodeosArgs=extraNodeosArgs, activateIF=activateIF) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/get_account_test.py b/tests/get_account_test.py index c691c9a3a2..dd264a075a 100755 --- a/tests/get_account_test.py +++ b/tests/get_account_test.py @@ -55,7 +55,7 @@ Print("Stand up cluster") extraNodeosArgs=" --http-max-response-time-ms 990000 --disable-subjective-api-billing false " - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay,extraNodeosArgs=extraNodeosArgs ) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, activateIF=True, topo=topo, delay=delay,extraNodeosArgs=extraNodeosArgs ) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/hotstuff_tests.cpp b/tests/hotstuff_tests.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/leap_util_bls_test.py b/tests/leap_util_bls_test.py new file mode 100755 index 0000000000..fb7c77037f --- /dev/null +++ b/tests/leap_util_bls_test.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +import os +import re + +from TestHarness import Utils + +############################################################### +# leap_util_bls_test +# +# Test leap-util's BLS commands. +# - Create a key pair +# - Create a POP (Proof of Possession) +# - Error handlings +# +############################################################### + +Print=Utils.Print +testSuccessful=False + +def test_create_key_to_console(): + rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False) + check_create_key_results(rslts) + +def test_create_key_to_file(): + tmp_file = "tmp_key_file_dlkdx1x56pjy" + Utils.processLeapUtilCmd("bls create key --file {}".format(tmp_file), "create key to file", silentErrors=False) + + with open(tmp_file, 'r') as file: + rslts = file.read() + check_create_key_results(rslts) + + os.remove(tmp_file) + +def test_create_pop_from_command_line(): + # Create a pair of keys + rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False) + results = get_results(rslts) + + # save results + private_key = results["Private key"] + public_key = results["Public key"] + pop = results["Proof of Possession"] + + # use the private key to create POP + rslts = Utils.processLeapUtilCmd("bls create pop --private-key {}".format(private_key), "create pop from command line", silentErrors=False) + results = get_results(rslts) + + # check pop and public key are the same as those generated before + assert results["Public key"] == public_key + assert results["Proof of Possession"] == pop + +def test_create_pop_from_file(): + # Create a pair of keys + rslts = Utils.processLeapUtilCmd("bls create key --to-console", "create key to console", silentErrors=False) + results = get_results(rslts) + + # save results + private_key = results["Private key"] + public_key = results["Public key"] + pop = results["Proof of Possession"] + + # save private key to a file + private_key_file = "tmp_key_file_dlkdx1x56pjy" + with open(private_key_file, 'w') as file: + file.write(private_key) + + # use the private key file to create POP + rslts = Utils.processLeapUtilCmd("bls create pop --file {}".format(private_key_file), "create pop from command line", silentErrors=False) + os.remove(private_key_file) + results = get_results(rslts) + + # check pop and public key are the same as those generated before + assert results["Public key"] == public_key + assert results["Proof of Possession"] == pop + +def test_create_key_error_handling(): + # should fail with missing arguments (processLeapUtilCmd returning None) + assert Utils.processLeapUtilCmd("bls create key", "missing arguments") == None + + # should fail when both arguments are present + assert Utils.processLeapUtilCmd("bls create key --file out_file --to-console", "conflicting arguments") == None + +def test_create_pop_error_handling(): + # should fail with missing arguments (processLeapUtilCmd returning None) + assert Utils.processLeapUtilCmd("bls create pop", "missing arguments") == None + + # should fail when both arguments are present + assert Utils.processLeapUtilCmd("bls create pop --file private_key_file --private-key", "conflicting arguments") == None + + # should fail when private key file does not exist + temp_file = "aRandomFileT6bej2pjsaz" + if os.path.exists(temp_file): + os.remove(temp_file) + assert Utils.processLeapUtilCmd("bls create pop --file {}".format(temp_file), "private file not existing") == None + +def check_create_key_results(rslts): + results = get_results(rslts) + + # check each output has valid value + assert "PVT_BLS_" in results["Private key"] + assert "PUB_BLS_" in results["Public key"] + assert "SIG_BLS_" in results["Proof of Possession"] + +def get_results(rslts): + # sample output looks like + # Private key: PVT_BLS_kRhJJ2MsM+/CddO... + # Public key: PUB_BLS_lbUE8922wUfX0Iy5... + # Proof of Possession: SIG_BLS_3jwkVUUYahHgsnmnEA... + pattern = r'(\w+[^:]*): ([^\n]+)' + matched= re.findall(pattern, rslts) + + results = {} + for k, v in matched: + results[k.strip()] = v.strip() + + return results + +# tests start +try: + # test create key to console + test_create_key_to_console() + + # test create key to file + test_create_key_to_file() + + # test create pop from private key in command line + test_create_pop_from_command_line() + + # test create pop from private key in file + test_create_pop_from_file() + + # test error handling in create key + test_create_key_error_handling() + + # test error handling in create pop + test_create_pop_error_handling() + + testSuccessful=True +except Exception as e: + Print(e) + Utils.errorExit("exception during processing") + +exitCode = 0 if testSuccessful else 1 +exit(exitCode) diff --git a/tests/lib_advance_test.py b/tests/lib_advance_test.py index 5525855ebd..4d7d5fd1dd 100755 --- a/tests/lib_advance_test.py +++ b/tests/lib_advance_test.py @@ -23,7 +23,7 @@ errorExit=Utils.errorExit -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v totalProducerNodes=4 @@ -32,6 +32,7 @@ maxActiveProducers=3 totalProducers=maxActiveProducers cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=args.wallet_port @@ -53,6 +54,7 @@ # and the only connection between those 2 groups is through the bridge (node4) if cluster.launch(topo="./tests/bridge_for_fork_test_shape.json", pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducerNodes, loadSystemContract=False, + activateIF=activateIF, biosFinalizer=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") @@ -70,7 +72,7 @@ prodNodes=[ prodNode0, prodNode1, prodNode2, prodNode3 ] prodA=prodNode0 # defproducera - prodD=prodNode3 # defproducerc + prodD=prodNode3 # defproducerd # *** Identify a block where production is stable *** @@ -137,11 +139,13 @@ assert prodA.getIrreversibleBlockNum() > max(libProdABeforeKill, libProdDBeforeKill) assert prodD.getIrreversibleBlockNum() > max(libProdABeforeKill, libProdDBeforeKill) + # instant finality does not drop late blocks, but can still get unlinkable when syncing and getting a produced block + allowedUnlinkableBlocks = afterBlockNum-beforeBlockNum logFile = Utils.getNodeDataDir(prodNode3.nodeId) + "/stderr.txt" f = open(logFile) contents = f.read() - if contents.count("3030001 unlinkable_block_exception: Unlinkable block") > (afterBlockNum-beforeBlockNum): # a few are fine - errorExit(f"Node{prodNode3.nodeId} has more than {afterBlockNum-beforeBlockNum} unlinkable blocks: {logFile}.") + if contents.count("3030001 unlinkable_block_exception: Unlinkable block") > (allowedUnlinkableBlocks): + errorExit(f"Node{prodNode3.nodeId} has more than {allowedUnlinkableBlocks} unlinkable blocks: {logFile}.") testSuccessful=True finally: diff --git a/tests/light_validation_sync_test.py b/tests/light_validation_sync_test.py index df6e2e95d2..9e49b85464 100755 --- a/tests/light_validation_sync_test.py +++ b/tests/light_validation_sync_test.py @@ -18,9 +18,10 @@ ############################################################### # Parse command line arguments -args = TestHelper.parse_args({"-v","--dump-error-details","--leave-running","--keep-logs","--unshared"}) +args = TestHelper.parse_args({"-v","--activate-if","--dump-error-details","--leave-running","--keep-logs","--unshared"}) Utils.Debug = args.v dumpErrorDetails=args.dump_error_details +activateIF=args.activate_if dontKill=args.leave_running keepLogs=args.keep_logs @@ -37,6 +38,7 @@ totalProducers=1, totalNodes=2, loadSystemContract=False, + activateIF=activateIF, specificExtraNodeosArgs={ 1:"--validation-mode light"}) diff --git a/tests/liveness_test.py b/tests/liveness_test.py new file mode 100755 index 0000000000..f7ad30012f --- /dev/null +++ b/tests/liveness_test.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +import signal + +from TestHarness import Cluster, Node, TestHelper, Utils, WalletMgr + +############################################################### +# liveness_test -- Test IF liveness loss and recovery. Only applicable to Instant Finality. +# +# To save testing time, 2 producer nodes are set up in Mesh mode. +# Quorum is 2. At the beginning, LIB advances on both nodes. +# Kill one node to simulate loss of liveness because quorum cannot be met -- LIB stops advancing. +# Relaunch the killed node to verify recovery of liveness -- LIB resumes advancing. +# +############################################################### + +Print=Utils.Print +errorExit=Utils.errorExit + +args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running", + "--wallet-port","--unshared"}) +Utils.Debug=args.v +totalProducerNodes=2 +totalNodes=totalProducerNodes +totalProducers=2 +cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=True +dumpErrorDetails=args.dump_error_details +walletPort=args.wallet_port + +walletMgr=WalletMgr(True, port=walletPort) +testSuccessful=False + +try: + TestHelper.printSystemInfo("BEGIN") + + cluster.setWalletMgr(walletMgr) + Print("Stand up cluster") + + # *** setup topogrophy *** + # "mesh" shape connects nodeA and nodeA to each other + if cluster.launch(topo="mesh", pnodes=totalProducerNodes, + totalNodes=totalNodes, totalProducers=totalProducerNodes, loadSystemContract=False, + activateIF=activateIF, biosFinalizer=False) is False: + Utils.cmdError("launcher") + Utils.errorExit("Failed to stand up eos cluster.") + Print("Validating system accounts after bootstrap") + cluster.validateAccounts(None) + + prodA = cluster.getNode(0) + prodB = cluster.getNode(1) + + # verify nodes are in sync and advancing + cluster.biosNode.kill(signal.SIGTERM) + cluster.waitOnClusterSync(blockAdvancing=5) + + prodA.kill(signal.SIGTERM) + + # verify node A is killed + numPasses = 2 + blocksPerProducer = 12 + blocksPerRound = totalProducers * blocksPerProducer + count = blocksPerRound * numPasses + while prodA.verifyAlive() and count > 0: + Print("Wait for next block") + assert prodB.waitForNextBlock(timeout=6), "node B should continue to advance, even after node A is killed" + count -= 1 + assert not prodA.verifyAlive(), "node A should have been killed" + + # verify head still advances but not LIB on node B + assert prodB.waitForNextBlock(), "Head should continue to advance on node B without node A" + assert not prodB.waitForLibToAdvance(10), "LIB should not advance on node B without node A" + + # relaunch node A so that quorum can be met + Print("Relaunching node A to make quorum") + if not prodA.relaunch(): + errorExit(f"Failure - node A should have restarted") + + # verify LIB advances on both nodes + assert prodA.waitForNextBlock() + assert prodA.waitForLibToAdvance() + assert prodB.waitForLibToAdvance() + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful=testSuccessful, dumpErrorDetails=dumpErrorDetails) + +errorCode = 0 if testSuccessful else 1 +exit(errorCode) diff --git a/tests/nested_container_multi_index_test.py b/tests/nested_container_multi_index_test.py index 442fadb14a..59001eb48b 100755 --- a/tests/nested_container_multi_index_test.py +++ b/tests/nested_container_multi_index_test.py @@ -69,7 +69,7 @@ def create_action(action, data, contract_account, usr): (pnodes, total_nodes-pnodes, topo, delay)) Print("Stand up cluster") - if cluster.launch(pnodes=1, totalNodes=1) is False: + if cluster.launch(pnodes=1, totalNodes=1, activateIF=True) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/nodeos_chainbase_allocation_test.py b/tests/nodeos_chainbase_allocation_test.py index 4c2ec8ee21..cfc65f7197 100755 --- a/tests/nodeos_chainbase_allocation_test.py +++ b/tests/nodeos_chainbase_allocation_test.py @@ -16,9 +16,10 @@ ############################################################### # Parse command line arguments -args = TestHelper.parse_args({"-v","--dump-error-details","--leave-running","--keep-logs","--unshared"}) +args = TestHelper.parse_args({"-v","--activate-if","--dump-error-details","--leave-running","--keep-logs","--unshared"}) Utils.Debug = args.v dumpErrorDetails=args.dump_error_details +activateIF=args.activate_if walletMgr=WalletMgr(True) cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) @@ -43,6 +44,7 @@ prodCount=1, totalProducers=1, totalNodes=3, + activateIF=activateIF, loadSystemContract=False, specificExtraNodeosArgs={ 1:"--read-mode irreversible --plugin eosio::producer_api_plugin"}) @@ -61,7 +63,7 @@ nonProdNode.createAccount(newProducerAcc, cluster.eosioAccount, waitForTransBlock=True) setProdsStr = '{"schedule": [' - setProdsStr += '{"producer_name":' + newProducerAcc.name + ',"block_signing_key":' + newProducerAcc.activePublicKey + '}' + setProdsStr += '{"producer_name":' + newProducerAcc.name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + newProducerAcc.activePublicKey + ', "weight":1}]}]}' setProdsStr += ']}' cmd="push action -j eosio setprods '{}' -p eosio".format(setProdsStr) trans = producerNode.processCleosCmd(cmd, cmd, silentErrors=False) diff --git a/tests/nodeos_contrl_c_test.py b/tests/nodeos_contrl_c_test.py index e379a6bf0f..97c72c7944 100755 --- a/tests/nodeos_contrl_c_test.py +++ b/tests/nodeos_contrl_c_test.py @@ -18,7 +18,7 @@ Print = Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--wallet-port", "-v","--unshared"}) +args = TestHelper.parse_args({"--wallet-port","--activate-if","-v","--unshared"}) cluster=Cluster(unshared=args.unshared) totalProducerNodes=2 @@ -26,6 +26,7 @@ totalNodes=totalProducerNodes+totalNonProducerNodes maxActiveProducers=2 totalProducers=maxActiveProducers +activateIF=args.activate_if walletPort=args.wallet_port walletMgr=WalletMgr(True, port=walletPort) producerEndpoint = '127.0.0.1:8888' @@ -51,6 +52,7 @@ if cluster.launch(prodCount=1, topo="bridge", pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducers, + activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs, extraNodeosArgs=extraNodeosArgs) is False: Utils.cmdError("launcher") diff --git a/tests/nodeos_extra_packed_data_test.py b/tests/nodeos_extra_packed_data_test.py index 3d38aa0691..b816183fbd 100755 --- a/tests/nodeos_extra_packed_data_test.py +++ b/tests/nodeos_extra_packed_data_test.py @@ -8,9 +8,9 @@ from TestHarness.TestHelper import AppArgs ############################################################### -# nodeos_run_test +# nodeos_extra_packed_data_test # -# General test that tests a wide range of general use actions around nodeos and keosd +# Tests nodeos accepts trx with extra data packed at the end. # ############################################################### @@ -69,7 +69,7 @@ if cluster.launch(totalNodes=totalNodes, pnodes=pnodes, dontBootstrap=dontBootstrap, - pfSetupPolicy=PFSetupPolicy.PREACTIVATE_FEATURE_ONLY, + activateIF=True, specificExtraNodeosArgs=specificExtraNodeosArgs, associatedNodeLabels=associatedNodeLabels) is False: cmdError("launcher") diff --git a/tests/nodeos_forked_chain_test.py b/tests/nodeos_forked_chain_test.py index d70a0387c4..d2ba089e53 100755 --- a/tests/nodeos_forked_chain_test.py +++ b/tests/nodeos_forked_chain_test.py @@ -127,7 +127,7 @@ def getMinHeadAndLib(prodNodes): appArgs = AppArgs() extraArgs = appArgs.add(flag="--num-ship-clients", type=int, help="How many ship_streamers should be started", default=2) -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--prod-count","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v totalProducerNodes=2 @@ -135,6 +135,7 @@ def getMinHeadAndLib(prodNodes): totalNodes=totalProducerNodes+totalNonProducerNodes maxActiveProducers=21 totalProducers=maxActiveProducers +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details prodCount=args.prod_count walletPort=args.wallet_port @@ -166,7 +167,7 @@ def getMinHeadAndLib(prodNodes): # and the only connection between those 2 groups is through the bridge node if cluster.launch(prodCount=prodCount, topo="bridge", pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + totalNodes=totalNodes, totalProducers=totalProducers, activateIF=activateIF, biosFinalizer=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") @@ -597,11 +598,11 @@ def getBlock(self, blockNum): block_num = start_block_num for i in data: # fork can cause block numbers to be repeated - this_block_num = i['get_blocks_result_v0']['this_block']['block_num'] + this_block_num = i['get_blocks_result_v1']['this_block']['block_num'] if this_block_num < block_num: block_num = this_block_num assert block_num == this_block_num, f"{block_num} != {this_block_num}" - assert isinstance(i['get_blocks_result_v0']['block'], str) # verify block in result + assert isinstance(i['get_blocks_result_v1']['block'], str) # verify block in result block_num += 1 assert block_num-1 == end_block_num, f"{block_num-1} != {end_block_num}" diff --git a/tests/nodeos_high_transaction_test.py b/tests/nodeos_high_transaction_test.py index 0b0550408a..6f99cc6348 100755 --- a/tests/nodeos_high_transaction_test.py +++ b/tests/nodeos_high_transaction_test.py @@ -27,7 +27,7 @@ extraArgs = appArgs.add(flag="--max-transactions-per-second", type=int, help="How many transactions per second should be sent", default=500) extraArgs = appArgs.add(flag="--total-accounts", type=int, help="How many accounts should be involved in sending transfers. Must be greater than %d" % (minTotalAccounts), default=100) extraArgs = appArgs.add_bool(flag="--send-duplicates", help="If identical transactions should be sent to all nodes") -args = TestHelper.parse_args({"-p", "-n","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) +args = TestHelper.parse_args({"-p", "-n","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v totalProducerNodes=args.p @@ -37,6 +37,7 @@ totalNonProducerNodes=totalNodes-totalProducerNodes maxActiveProducers=totalProducerNodes totalProducers=totalProducerNodes +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) walletPort=TestHelper.DEFAULT_WALLET_PORT @@ -70,7 +71,7 @@ if cluster.launch(pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducers, - topo="ring") is False: + topo="ring", activateIF=activateIF) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") diff --git a/tests/nodeos_irreversible_mode_test.py b/tests/nodeos_irreversible_mode_test.py index 812e338695..144e8793c6 100755 --- a/tests/nodeos_irreversible_mode_test.py +++ b/tests/nodeos_irreversible_mode_test.py @@ -24,8 +24,9 @@ totalNodes = 20 # Parse command line arguments -args = TestHelper.parse_args({"-v","--dump-error-details","--leave-running","--keep-logs","--unshared"}) +args = TestHelper.parse_args({"-v","--activate-if","--dump-error-details","--leave-running","--keep-logs","--unshared"}) Utils.Debug = args.v +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details speculativeReadMode="head" blockLogRetainBlocks="10000" @@ -164,6 +165,8 @@ def relaunchNode(node: Node, chainArg="", addSwapFlags=None, relaunchAssertMessa totalProducers=numOfProducers, totalNodes=totalNodes, pnodes=1, + activateIF=activateIF, + biosFinalizer=False, topo="mesh", specificExtraNodeosArgs={ 0:"--enable-stale-production", diff --git a/tests/nodeos_lib_test.py b/tests/nodeos_lib_test.py new file mode 100755 index 0000000000..f2f7c289aa --- /dev/null +++ b/tests/nodeos_lib_test.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python3 + +from TestHarness import Account, Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr, CORE_SYMBOL, createAccountKeys + +import decimal +import re +import json +import os +import sys + +############################################################### +# nodeos_lib_test +# +# General test that tests a lib advances after a number of actions +# +############################################################### + +Print=Utils.Print +errorExit=Utils.errorExit +cmdError=Utils.cmdError + +args = TestHelper.parse_args({"-p","-n","-s","--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key" + ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" + ,"--activate-if","--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) +server=args.host +port=args.port +debug=args.v +defproduceraPrvtKey=args.defproducera_prvt_key +defproducerbPrvtKey=args.defproducerb_prvt_key +dumpErrorDetails=args.dump_error_details +dontLaunch=args.dont_launch +prodCount=args.prod_count +pnodes=args.p +topo=args.s +total_nodes = pnodes + 1 if args.n <= pnodes else args.n +onlyBios=args.only_bios +sanityTest=args.sanity_test +walletPort=args.wallet_port +activateIF=args.activate_if + +Utils.Debug=debug +localTest=True if server == TestHelper.LOCAL_HOST else False +cluster=Cluster(host=server, port=port, defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey,unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +errFileName=f"{cluster.nodeosLogPath}/node_00/stderr.txt" +if args.error_log_path: + errFileName=args.error_log_path +walletMgr=WalletMgr(True, port=walletPort, keepRunning=args.leave_running, keepLogs=args.keep_logs) +testSuccessful=False +dontBootstrap=sanityTest # intent is to limit the scope of the sanity test to just verifying that nodes can be started + +WalletdName=Utils.EosWalletName +ClientName="cleos" +timeout = .5 * 12 * 2 + 60 # time for finalization with 1 producer + 60 seconds padding +Utils.setIrreversibleTimeout(timeout) + +try: + TestHelper.printSystemInfo("BEGIN") + cluster.setWalletMgr(walletMgr) + Print("SERVER: %s" % (server)) + Print("PORT: %d" % (port)) + + if localTest and not dontLaunch: + Print("Stand up cluster") + + abs_path = os.path.abspath(os.getcwd() + '/unittests/contracts/eosio.token/eosio.token.abi') + traceNodeosArgs=" --http-max-response-time-ms 990000 --trace-rpc-abi eosio.token=" + abs_path + extraNodeosArgs=traceNodeosArgs + " --plugin eosio::prometheus_plugin --database-map-mode mapped_private " + specificNodeosInstances={0: "bin/nodeos"} + if cluster.launch(totalNodes=total_nodes, pnodes=pnodes, topo=topo, prodCount=prodCount, activateIF=activateIF, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: + cmdError("launcher") + errorExit("Failed to stand up eos cluster.") + else: + Print("Collecting cluster info.") + cluster.initializeNodes(defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey) + Print("Stand up %s" % (WalletdName)) + if walletMgr.launch() is False: + cmdError("%s" % (WalletdName)) + errorExit("Failed to stand up eos walletd.") + + if sanityTest: + testSuccessful=True + exit(0) + + Print("Validating system accounts after bootstrap") + cluster.validateAccounts(None) + + accounts=createAccountKeys(4) + if accounts is None: + errorExit("FAILURE - create keys") + testeraAccount=accounts[0] + testeraAccount.name="testera11111" + currencyAccount=accounts[1] + currencyAccount.name="currency1111" + exchangeAccount=accounts[2] + exchangeAccount.name="exchange1111" + # account to test newaccount with authority + testerbAccount=accounts[3] + testerbAccount.name="testerb11111" + testerbOwner = testerbAccount.ownerPublicKey + testerbAccount.ownerPublicKey = '{"threshold":1, "accounts":[{"permission":{"actor": "' + testeraAccount.name + '", "permission":"owner"}, "weight": 1}],"keys":[{"key": "' +testerbOwner + '", "weight": 1}],"waits":[]}' + + PRV_KEY1=testeraAccount.ownerPrivateKey + PUB_KEY1=testeraAccount.ownerPublicKey + PRV_KEY2=currencyAccount.ownerPrivateKey + PUB_KEY2=currencyAccount.ownerPublicKey + PRV_KEY3=exchangeAccount.activePrivateKey + PUB_KEY3=exchangeAccount.activePublicKey + + testeraAccount.activePrivateKey=currencyAccount.activePrivateKey=PRV_KEY3 + testeraAccount.activePublicKey=currencyAccount.activePublicKey=PUB_KEY3 + + exchangeAccount.ownerPrivateKey=PRV_KEY2 + exchangeAccount.ownerPublicKey=PUB_KEY2 + + testWalletName="test" + Print("Creating wallet \"%s\"." % (testWalletName)) + walletAccounts=[cluster.defproduceraAccount,cluster.defproducerbAccount] + if not dontLaunch: + walletAccounts.append(cluster.eosioAccount) + testWallet=walletMgr.create(testWalletName, walletAccounts) + + Print("Wallet \"%s\" password=%s." % (testWalletName, testWallet.password.encode("utf-8"))) + + for account in accounts: + Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) + if not walletMgr.importKey(account, testWallet): + cmdError("%s wallet import" % (ClientName)) + errorExit("Failed to import key for account %s" % (account.name)) + + defproduceraWalletName="defproducera" + Print("Creating wallet \"%s\"." % (defproduceraWalletName)) + defproduceraWallet=walletMgr.create(defproduceraWalletName) + + Print("Wallet \"%s\" password=%s." % (defproduceraWalletName, defproduceraWallet.password.encode("utf-8"))) + + defproduceraAccount=cluster.defproduceraAccount + defproducerbAccount=cluster.defproducerbAccount + + Print("Importing keys for account %s into wallet %s." % (defproduceraAccount.name, defproduceraWallet.name)) + if not walletMgr.importKey(defproduceraAccount, defproduceraWallet): + cmdError("%s wallet import" % (ClientName)) + errorExit("Failed to import key for account %s" % (defproduceraAccount.name)) + + Print("Locking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.lockWallet(testWallet): + cmdError("%s wallet lock" % (ClientName)) + errorExit("Failed to lock wallet %s" % (testWallet.name)) + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Getting open wallet list.") + wallets=walletMgr.getOpenWallets() + if len(wallets) == 0 or wallets[0] != testWallet.name or len(wallets) > 1: + Print("FAILURE - wallet list did not include %s" % (testWallet.name)) + errorExit("Unexpected wallet list: %s" % (wallets)) + + Print("Getting wallet keys.") + actualKeys=walletMgr.getKeys(testWallet) + expectedkeys=[] + for account in accounts: + expectedkeys.append(account.ownerPrivateKey) + expectedkeys.append(account.activePrivateKey) + noMatch=list(set(expectedkeys) - set(actualKeys)) + if len(noMatch) > 0: + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (defproduceraWallet.name)) + if not walletMgr.unlockWallet(defproduceraWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (defproduceraWallet.name)) + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + Print("Getting wallet keys.") + actualKeys=walletMgr.getKeys(defproduceraWallet) + expectedkeys=[defproduceraAccount.ownerPrivateKey] + noMatch=list(set(expectedkeys) - set(actualKeys)) + if len(noMatch) > 0: + errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) + + node=cluster.getNode(total_nodes-1) + + Print("Validating accounts before user accounts creation") + cluster.validateAccounts(None) + + Print("Create new account %s via %s" % (testeraAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(testeraAccount, cluster.defproduceraAccount, stakedDeposit=0, waitForTransBlock=True, exitOnError=True) + + Print("Create new account %s via %s" % (testerbAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(testerbAccount, cluster.defproduceraAccount, stakedDeposit=0, waitForTransBlock=False, exitOnError=True) + + Print("Create new account %s via %s" % (currencyAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(currencyAccount, cluster.defproduceraAccount, buyRAM=200000, stakedDeposit=5000, exitOnError=True) + + Print("Create new account %s via %s" % (exchangeAccount.name, cluster.defproduceraAccount.name)) + transId=node.createInitializeAccount(exchangeAccount, cluster.defproduceraAccount, buyRAM=200000, waitForTransBlock=True, exitOnError=True) + + Print("Validating accounts after user accounts creation") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + Print("Verify account %s" % (testeraAccount)) + if not node.verifyAccount(testeraAccount): + errorExit("FAILURE - account creation failed.", raw=True) + + transferAmount="97.5321 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % (transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", waitForTransBlock=True) + + expectedAmount=transferAmount + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + transferAmount="0.0100 {0}".format(CORE_SYMBOL) + Print("Force transfer funds %s from account %s to %s" % ( + transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", force=True, waitForTransBlock=True) + + expectedAmount="97.5421 {0}".format(CORE_SYMBOL) + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(testeraAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + Print("Validating accounts after some user transactions") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + Print("Locking all wallets.") + if not walletMgr.lockAllWallets(): + cmdError("%s wallet lock_all" % (ClientName)) + errorExit("Failed to lock all wallets") + + Print("Unlocking wallet \"%s\"." % (testWallet.name)) + if not walletMgr.unlockWallet(testWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (testWallet.name)) + + transferAmount="97.5311 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % ( + transferAmount, testeraAccount.name, currencyAccount.name)) + trans=node.transferFunds(testeraAccount, currencyAccount, transferAmount, "test transfer a->b", waitForTransBlock=True) + transId=Node.getTransId(trans) + + expectedAmount="98.0311 {0}".format(CORE_SYMBOL) # 5000 initial deposit + Print("Verify transfer, Expected: %s" % (expectedAmount)) + actualAmount=node.getAccountEosBalanceStr(currencyAccount.name) + if expectedAmount != actualAmount: + cmdError("FAILURE - transfer failed") + errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) + + node.waitForTransactionInBlock(transId) + + transaction=node.getTransaction(transId, exitOnError=True, delayedRetry=False) + + typeVal=None + amountVal=None + key="" + try: + key = "[actions][0][action]" + typeVal = transaction["actions"][0]["action"] + key = "[actions][0][params][quantity]" + amountVal = transaction["actions"][0]["params"]["quantity"] + amountVal = int(decimal.Decimal(amountVal.split()[0]) * 10000) + except (TypeError, KeyError) as e: + Print("transaction%s not found. Transaction: %s" % (key, transaction)) + raise + + if typeVal != "transfer" or amountVal != 975311: + errorExit("FAILURE - get transaction trans_id failed: %s %s %s" % (transId, typeVal, amountVal), raw=True) + + Print("Currency Contract Tests") + Print("verify no contract in place") + Print("Get code hash for account %s" % (currencyAccount.name)) + codeHash=node.getAccountCodeHash(currencyAccount.name) + if codeHash is None: + cmdError("%s get code currency1111" % (ClientName)) + errorExit("Failed to get code hash for account %s" % (currencyAccount.name)) + hashNum=int(codeHash, 16) + if hashNum != 0: + errorExit("FAILURE - get code currency1111 failed", raw=True) + + contractDir="unittests/contracts/eosio.token" + wasmFile="eosio.token.wasm" + abiFile="eosio.token.abi" + Print("Publish contract") + trans=node.publishContract(currencyAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if trans is None: + cmdError("%s set contract currency1111" % (ClientName)) + errorExit("Failed to publish contract.") + + Print("Get code hash for account %s" % (currencyAccount.name)) + codeHash = node.getAccountCodeHash(currencyAccount.name) + if codeHash is None: + cmdError("%s get code currency1111" % (ClientName)) + errorExit("Failed to get code hash for account %s" % (currencyAccount.name)) + hashNum = int(codeHash, 16) + if hashNum == 0: + errorExit("FAILURE - get code currency1111 failed", raw=True) + + Print("push create action to currency1111 contract") + contract="currency1111" + action="create" + data="{\"issuer\":\"currency1111\",\"maximum_supply\":\"100000.0000 CUR\",\"can_freeze\":\"0\",\"can_recall\":\"0\",\"can_whitelist\":\"0\"}" + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts) + try: + assert(trans) + assert(trans[0]) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed push create action to currency1111 contract assertion. %s" % (trans)) + raise + transId=Node.getTransId(trans[1]) + node.waitForTransactionInBlock(transId) + + Print("push issue action to currency1111 contract") + action="issue" + data="{\"to\":\"currency1111\",\"quantity\":\"100000.0000 CUR\",\"memo\":\"issue\"}" + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts) + try: + assert(trans) + assert(trans[0]) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed push issue action to currency1111 contract assertion. %s" % (trans)) + raise + transId=Node.getTransId(trans[1]) + node.waitForTransactionInBlock(transId) + + Print("Verify currency1111 contract has proper initial balance (via get table)") + contract="currency1111" + table="accounts" + row0=node.getTableRow(contract, currencyAccount.name, table, 0) + try: + assert(row0) + assert(row0["balance"] == "100000.0000 CUR") + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get table row assertion. %s" % (row0)) + raise + + Print("Verify currency1111 contract has proper initial balance (via get currency1111 balance)") + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + expected="100000.0000 CUR" + actual=amountStr + if actual != expected: + errorExit("FAILURE - currency1111 balance check failed. Expected: %s, Recieved %s" % (expected, actual), raw=True) + + Print("Verify currency1111 contract has proper total supply of CUR (via get currency1111 stats)") + res=node.getCurrencyStats(contract, "CUR", exitOnError=True) + try: + assert(res["CUR"]["supply"] == "100000.0000 CUR") + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get currecy stats assertion. %s" % (res)) + raise + + transferAmt=10 + totalTransfer=0 + contract="currency1111" + action="transfer" + for _ in range(5): + Print("push transfer action to currency1111 contract") + data="{\"from\":\"currency1111\",\"to\":\"defproducera\",\"quantity\":" + data +="\"00.00%s CUR\",\"memo\":\"test\"}" % (transferAmt) + opts="--permission currency1111@active" + trans=node.pushMessage(contract, action, data, opts, force=True) + if trans is None or not trans[0]: + cmdError("%s push message currency1111 transfer" % (ClientName)) + errorExit("Failed to push message to currency1111 contract") + transId=Node.getTransId(trans[1]) + totalTransfer = totalTransfer + transferAmt + + Print("verify transaction exists") + if not node.waitForTransactionInBlock(transId): + cmdError("%s get transaction trans_id" % (ClientName)) + errorExit("Failed to verify push message transaction id.") + + Print("read current contract balance") + amountStr=node.getTableAccountBalance("currency1111", defproduceraAccount.name) + + expectedDefproduceraBalance="0.00%s CUR" % (totalTransfer) + actual=amountStr + if actual != expectedDefproduceraBalance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedDefproduceraBalance, actual), raw=True) + + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + expExtension=100-totalTransfer + expectedCurrency1111Balance="99999.99%s CUR" % (expExtension) + actual=amountStr + if actual != expectedCurrency1111Balance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedCurrency1111Balance, actual), raw=True) + + amountStr=node.getCurrencyBalance("currency1111", currencyAccount.name, "CUR") + try: + assert(actual) + assert(isinstance(actual, str)) + actual=amountStr.strip() + assert(expectedCurrency1111Balance == actual) + except (AssertionError, KeyError) as _: + Print("ERROR: Failed get currecy balance assertion. (expected=<%s>, actual=<%s>)" % (expectedCurrency1111Balance, actual)) + raise + + Print("Test for block decoded packed transaction (issue 2932)") + blockNum=node.getBlockNumByTransId(transId) + assert(blockNum) + block=node.getBlock(blockNum, exitOnError=True) + + transactions=None + try: + transactions=block["transactions"] + assert(transactions) + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block. %s" % (block)) + raise + + myTrans=None + for trans in transactions: + assert(trans) + try: + myTransId=trans["trx"]["id"] + if transId == myTransId: + myTrans=trans["trx"]["transaction"] + assert(myTrans) + break + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block transactions. %s" % (trans)) + raise + + assert(myTrans) + try: + assert(myTrans["actions"][0]["name"] == "transfer") + assert(myTrans["actions"][0]["account"] == "currency1111") + assert(myTrans["actions"][0]["authorization"][0]["actor"] == "currency1111") + assert(myTrans["actions"][0]["authorization"][0]["permission"] == "active") + assert(myTrans["actions"][0]["data"]["from"] == "currency1111") + assert(myTrans["actions"][0]["data"]["to"] == "defproducera") + assert(myTrans["actions"][0]["data"]["quantity"] == "0.00%s CUR" % (transferAmt)) + assert(myTrans["actions"][0]["data"]["memo"] == "test") + except (AssertionError, TypeError, KeyError) as _: + Print("FAILURE - Failed to parse block transaction. %s" % (myTrans)) + raise + + Print("Unlocking wallet \"%s\"." % (defproduceraWallet.name)) + if not walletMgr.unlockWallet(defproduceraWallet): + cmdError("%s wallet unlock" % (ClientName)) + errorExit("Failed to unlock wallet %s" % (defproduceraWallet.name)) + + Print("push transfer action to currency1111 contract that would go negative") + contract="currency1111" + action="transfer" + data="{\"from\":\"defproducera\",\"to\":\"currency1111\",\"quantity\":" + data +="\"00.0151 CUR\",\"memo\":\"test\"}" + opts="--permission defproducera@active" + trans=node.pushMessage(contract, action, data, opts, True) + if trans is None or trans[0]: + cmdError("%s push message currency1111 transfer should have failed" % (ClientName)) + errorExit("Failed to reject invalid transfer message to currency1111 contract") + + Print("read current contract balance") + amountStr=node.getTableAccountBalance("currency1111", defproduceraAccount.name) + + actual=amountStr + if actual != expectedDefproduceraBalance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedDefproduceraBalance, actual), raw=True) + + amountStr=node.getTableAccountBalance("currency1111", currencyAccount.name) + + actual=amountStr + if actual != expectedCurrency1111Balance: + errorExit("FAILURE - Wrong currency1111 balance (expected=%s, actual=%s)" % (expectedCurrency1111Balance, actual), raw=True) + + Print("push another transfer action to currency1111 contract") + contract="currency1111" + action="transfer" + data="{\"from\":\"defproducera\",\"to\":\"currency1111\",\"quantity\":" + data +="\"00.00%s CUR\",\"memo\":\"test\"}" % (totalTransfer) + opts="--permission defproducera@active" + trans=node.pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + cmdError("%s push message currency1111 transfer" % (ClientName)) + errorExit("Failed to push message to currency1111 contract") + transId=Node.getTransId(trans[1]) + + simpleDB = Account("simpledb") + contractDir="contracts/simpledb" + wasmFile="simpledb.wasm" + abiFile="simpledb.abi" + Print("Setting simpledb contract without simpledb account was causing core dump in %s." % (ClientName)) + Print("Verify %s generates an error, but does not core dump." % (ClientName)) + retMap=node.publishContract(simpleDB, contractDir, wasmFile, abiFile, shouldFail=True) + if retMap is None: + errorExit("Failed to publish, but should have returned a details map") + if retMap["returncode"] == 0 or retMap["returncode"] == 139: # 139 SIGSEGV + errorExit("FAILURE - set contract simpledb failed", raw=True) + else: + Print("Test successful, %s returned error code: %d" % (ClientName, retMap["returncode"])) + + Print("set permission") + pType="transfer" + requirement="active" + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) + + Print("remove permission") + requirement="NULL" + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) + + Print("Get account defproducera") + account=node.getEosAccount(defproduceraAccount.name, exitOnError=True) + + Print("Verify non-JSON call works") + rawAccount = node.getEosAccount(defproduceraAccount.name, exitOnError=True, returnType=ReturnType.raw) + coreLiquidBalance = account['core_liquid_balance'] + match = re.search(r'\bliquid:\s*%s\s' % (coreLiquidBalance), rawAccount, re.MULTILINE | re.DOTALL) + assert match is not None, "did not find the core liquid balance (\"liquid:\") of %d in \"%s\"" % (coreLiquidBalance, rawAccount) + + Print("Get head block num.") + currentBlockNum=node.getHeadBlockNum() + Print("CurrentBlockNum: %d" % (currentBlockNum)) + Print("Request blocks 1-%d" % (currentBlockNum)) + start=1 + for blockNum in range(start, currentBlockNum+1): + block=node.getBlock(blockNum, silentErrors=False, exitOnError=True) + + Print("Request invalid block numbered %d. This will generate an expected error message." % (currentBlockNum+1000)) + currentBlockNum=node.getHeadBlockNum() # If the tests take too long, we could be far beyond currentBlockNum+1000 and that'll cause a block to be found. + block=node.getBlock(currentBlockNum+1000, silentErrors=True) + if block is not None: + errorExit("ERROR: Received block where not expected") + else: + Print("Success: No such block found") + + if localTest: + p = re.compile('Assert') + assertionsFound=False + with open(errFileName) as errFile: + for line in errFile: + if p.search(line): + assertionsFound=True + + if assertionsFound: + # Too many assertion logs, hard to validate how many are genuine. Make this a warning + # for now, hopefully the logs will get cleaned up in future. + Print(f"WARNING: Asserts in {errFileName}") + + Print("Validating accounts at end of test") + accounts=[testeraAccount, currencyAccount, exchangeAccount] + cluster.validateAccounts(accounts) + + # Verify "set code" and "set abi" work + Print("Verify set code and set abi work") + setCodeAbiAccount = Account("setcodeabi") + setCodeAbiAccount.ownerPublicKey = cluster.eosioAccount.ownerPublicKey + setCodeAbiAccount.activePublicKey = cluster.eosioAccount.ownerPublicKey + cluster.createAccountAndVerify(setCodeAbiAccount, cluster.eosioAccount, buyRAM=100000) + wasmFile="unittests/test-contracts/payloadless/payloadless.wasm" + abiFile="unittests/test-contracts/payloadless/payloadless.abi" + assert(node.setCodeOrAbi(setCodeAbiAccount, "code", wasmFile)) + assert(node.setCodeOrAbi(setCodeAbiAccount, "abi", abiFile)) + + Print("Verify lib advancing on all nodes") + for cur_node in cluster.getNodes(): + passed = cur_node.waitForLibToAdvance(timeout=6*60) + assert passed, Print("Node %d not advanced lib block within timeout") + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, dumpErrorDetails) + +errorCode = 0 if testSuccessful else 1 +exit(errorCode) diff --git a/tests/nodeos_producer_watermark_test.py b/tests/nodeos_producer_watermark_test.py index 178891b9a2..86decc9b3f 100755 --- a/tests/nodeos_producer_watermark_test.py +++ b/tests/nodeos_producer_watermark_test.py @@ -40,7 +40,7 @@ def setProds(sharedProdKey): key = cluster.defProducerAccounts[name].activePublicKey if name == "shrproducera": key = sharedProdKey - setProdsStr += ' { "producer_name": "%s", "block_signing_key": "%s" }' % (name, key) + setProdsStr += '{"producer_name":' + name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + key + ', "weight":1}]}]}' setProdsStr += ' ] }' Utils.Print("setprods: %s" % (setProdsStr)) @@ -147,11 +147,12 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): Print=Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--prod-count","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v totalNodes=3 cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details prodCount=args.prod_count walletPort=args.wallet_port @@ -169,7 +170,8 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): cluster.setWalletMgr(walletMgr) Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes, onlySetProds=True, sharedProducers=1) is False: + if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes, + onlySetProds=True, sharedProducers=1, activateIF=activateIF) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") diff --git a/tests/nodeos_read_terminate_at_block_test.py b/tests/nodeos_read_terminate_at_block_test.py index aace1a5645..6f34e19b4f 100755 --- a/tests/nodeos_read_terminate_at_block_test.py +++ b/tests/nodeos_read_terminate_at_block_test.py @@ -24,6 +24,7 @@ # Parse command line arguments args = TestHelper.parse_args({ "-v", + "--activate-if", "--dump-error-details", "--leave-running", "--keep-logs", @@ -31,6 +32,7 @@ }) Utils.Debug = args.v +activateIF=args.activate_if dumpErrorDetails = args.dump_error_details # Wrapper function to execute test @@ -195,6 +197,7 @@ def checkHeadOrSpeculative(head, lib): totalNodes=totalNodes, pnodes=1, topo="mesh", + activateIF=activateIF, specificExtraNodeosArgs=specificNodeosArgs, ) diff --git a/tests/nodeos_retry_transaction_test.py b/tests/nodeos_retry_transaction_test.py index d1b82388da..561fe6f6fc 100755 --- a/tests/nodeos_retry_transaction_test.py +++ b/tests/nodeos_retry_transaction_test.py @@ -31,7 +31,7 @@ extraArgs = appArgs.add(flag="--num-transactions", type=int, help="How many total transactions should be sent", default=1000) extraArgs = appArgs.add(flag="--max-transactions-per-second", type=int, help="How many transactions per second should be sent", default=50) extraArgs = appArgs.add(flag="--total-accounts", type=int, help="How many accounts should be involved in sending transfers. Must be greater than %d" % (minTotalAccounts), default=10) -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v totalProducerNodes=3 @@ -40,6 +40,7 @@ maxActiveProducers=totalProducerNodes totalProducers=totalProducerNodes cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=TestHelper.DEFAULT_WALLET_PORT blocksPerSec=2 @@ -77,7 +78,7 @@ # topo=ring all nodes are connected in a ring but also to the bios node if cluster.launch(pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducers, - topo="ring", + topo="ring", activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") @@ -134,9 +135,10 @@ startTime = time.perf_counter() Print("Create new accounts via %s" % (cluster.eosioAccount.name)) for account in accounts: - trans = node.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=(account == accounts[-1]), stakeNet=1000, stakeCPU=1000, buyRAM=1000, exitOnError=True) + trans = node.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, stakeNet=1000, stakeCPU=1000, buyRAM=1000, exitOnError=True) checkTransIds.append(Node.getTransId(trans)) + node.waitForTransactionsInBlock(checkTransIds) nextTime = time.perf_counter() Print("Create new accounts took %s sec" % (nextTime - startTime)) startTime = nextTime @@ -145,18 +147,20 @@ for account in accounts: transferAmount="1000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) - trans = node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer", waitForTransBlock=(account == accounts[-1]), reportStatus=False) + trans = node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer", waitForTransBlock=False, reportStatus=False) checkTransIds.append(Node.getTransId(trans)) + node.waitForTransactionsInBlock(checkTransIds) nextTime = time.perf_counter() Print("Transfer funds took %s sec" % (nextTime - startTime)) startTime = nextTime Print("Delegate Bandwidth to new accounts") for account in accounts: - trans=node.delegatebw(account, 200.0000, 200.0000, waitForTransBlock=(account == accounts[-1]), exitOnError=True, reportStatus=False) + trans=node.delegatebw(account, 200.0000, 200.0000, waitForTransBlock=False, exitOnError=True, reportStatus=False) checkTransIds.append(Node.getTransId(trans)) + node.waitForTransactionsInBlock(checkTransIds) nextTime = time.perf_counter() Print("Delegate Bandwidth took %s sec" % (nextTime - startTime)) startTime = nextTime diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index d131b993c9..e366dbcebf 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from TestHarness import Account, Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr, CORE_SYMBOL, createAccountKeys -from pathlib import Path import decimal import re @@ -22,7 +21,7 @@ args = TestHelper.parse_args({"--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key" ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" - ,"--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) + ,"--activate-if","--sanity-test","--wallet-port", "--error-log-path", "--unshared"}) server=args.host port=args.port debug=args.v @@ -34,6 +33,7 @@ onlyBios=args.only_bios sanityTest=args.sanity_test walletPort=args.wallet_port +activateIF=args.activate_if Utils.Debug=debug localTest=True if server == TestHelper.LOCAL_HOST else False @@ -63,7 +63,7 @@ traceNodeosArgs=" --http-max-response-time-ms 990000 --trace-rpc-abi eosio.token=" + abs_path extraNodeosArgs=traceNodeosArgs + " --plugin eosio::prometheus_plugin --database-map-mode mapped_private " specificNodeosInstances={0: "bin/nodeos"} - if cluster.launch(totalNodes=2, prodCount=prodCount, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: + if cluster.launch(totalNodes=2, prodCount=prodCount, activateIF=activateIF, onlyBios=onlyBios, dontBootstrap=dontBootstrap, extraNodeosArgs=extraNodeosArgs, specificNodeosInstances=specificNodeosInstances) is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") else: diff --git a/tests/nodeos_short_fork_take_over_test.py b/tests/nodeos_short_fork_take_over_test.py index f468cae52c..3e550c63e5 100755 --- a/tests/nodeos_short_fork_take_over_test.py +++ b/tests/nodeos_short_fork_take_over_test.py @@ -105,7 +105,7 @@ def getMinHeadAndLib(prodNodes): -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--prod-count","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v totalProducerNodes=2 @@ -114,6 +114,7 @@ def getMinHeadAndLib(prodNodes): maxActiveProducers=3 totalProducers=maxActiveProducers cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=args.wallet_port @@ -138,7 +139,7 @@ def getMinHeadAndLib(prodNodes): # "bridge" shape connects defprocera through defproducerk (in node0) to each other and defproducerl through defproduceru (in node01) # and the only connection between those 2 groups is through the bridge node if cluster.launch(prodCount=2, topo="bridge", pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + totalNodes=totalNodes, totalProducers=totalProducers, activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs, onlySetProds=True) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") diff --git a/tests/nodeos_snapshot_diff_test.py b/tests/nodeos_snapshot_diff_test.py index 4e0624030c..2185df1509 100755 --- a/tests/nodeos_snapshot_diff_test.py +++ b/tests/nodeos_snapshot_diff_test.py @@ -32,7 +32,7 @@ errorExit=Utils.errorExit appArgs=AppArgs() -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--wallet-port","--unshared"}, +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--wallet-port","--unshared"}, applicationSpecificArgs=appArgs) relaunchTimeout = 30 @@ -42,6 +42,7 @@ trxGeneratorCnt=2 startedNonProdNodes = 3 cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details prodCount=2 walletPort=args.wallet_port @@ -77,7 +78,7 @@ def removeState(nodeId): Print("Stand up cluster") if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, - loadSystemContract=True, maximumP2pPerHost=totalNodes+trxGeneratorCnt) is False: + activateIF=activateIF, loadSystemContract=True, maximumP2pPerHost=totalNodes+trxGeneratorCnt) is False: Utils.errorExit("Failed to stand up eos cluster.") Print("Create test wallet") @@ -181,7 +182,7 @@ def waitForBlock(node, blockNum, blockType=BlockType.head, timeout=None, reportI assert ret is not None, "Snapshot scheduling failed" Print("Wait for programmable node lib to advance") - waitForBlock(nodeProg, ret_head_block_num+1, blockType=BlockType.lib) + waitForBlock(nodeProg, ret_head_block_num, blockType=BlockType.lib) Print("Kill programmable node") nodeProg.kill(signal.SIGTERM) diff --git a/tests/nodeos_snapshot_forked_test.py b/tests/nodeos_snapshot_forked_test.py index 6afe803385..c61372c1e4 100755 --- a/tests/nodeos_snapshot_forked_test.py +++ b/tests/nodeos_snapshot_forked_test.py @@ -20,7 +20,7 @@ Print=Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--prod-count","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v totalProducerNodes=2 @@ -29,6 +29,7 @@ maxActiveProducers=3 totalProducers=maxActiveProducers cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=args.wallet_port @@ -80,7 +81,7 @@ def getSnapshotsCount(nodeId): # "bridge" shape connects defprocera through defproducerb (in node0) to each other and defproducerc is alone (in node01) # and the only connection between those 2 groups is through the bridge node if cluster.launch(prodCount=2, topo="bridge", pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + totalNodes=totalNodes, totalProducers=totalProducers, activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs, extraNodeosArgs=extraNodeosArgs) is False: Utils.cmdError("launcher") diff --git a/tests/nodeos_startup_catchup.py b/tests/nodeos_startup_catchup.py index 28364fe6c2..1ae2142f6d 100755 --- a/tests/nodeos_startup_catchup.py +++ b/tests/nodeos_startup_catchup.py @@ -32,7 +32,7 @@ extraArgs = appArgs.add(flag="--catchup-count", type=int, help="How many catchup-nodes to launch", default=10) extraArgs = appArgs.add(flag="--txn-gen-nodes", type=int, help="How many transaction generator nodes", default=2) args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running", - "-p","--wallet-port","--unshared"}, applicationSpecificArgs=appArgs) + "--activate-if","-p","--wallet-port","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v pnodes=args.p if args.p > 0 else 1 startedNonProdNodes = args.txn_gen_nodes if args.txn_gen_nodes >= 2 else 2 @@ -43,6 +43,7 @@ walletPort=args.wallet_port catchupCount=args.catchup_count if args.catchup_count > 0 else 1 totalNodes=startedNonProdNodes+pnodes+catchupCount +activateIF=args.activate_if walletMgr=WalletMgr(True, port=walletPort) testSuccessful=False @@ -56,7 +57,7 @@ cluster.setWalletMgr(walletMgr) Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, + if cluster.launch(prodCount=prodCount, activateIF=activateIF, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, unstartedNodes=catchupCount, loadSystemContract=True, maximumP2pPerHost=totalNodes+trxGeneratorCnt) is False: Utils.errorExit("Failed to stand up eos cluster.") @@ -132,9 +133,11 @@ def waitForNodeStarted(node): steadyStateAvg=steadyStateWindowTrxs / steadyStateWindowBlks Print("Validate transactions are generating") - minReqPctLeeway=0.9 + minReqPctLeeway=0.85 minRequiredTransactions=minReqPctLeeway*transactionsPerBlock - assert steadyStateAvg>=minRequiredTransactions, "Expected to at least receive %s transactions per block, but only getting %s" % (minRequiredTransactions, steadyStateAvg) + assert steadyStateAvg>=minRequiredTransactions, \ + (f"Expected to at least receive {minRequiredTransactions} transactions per block, " + f"but only getting {steadyStateAvg} for blocks {startBlockNum} - {endBlockNum}") Print("Cycle through catchup scenarios") twoRounds=21*2*12 @@ -195,7 +198,7 @@ def waitForNodeStarted(node): logFile = Utils.getNodeDataDir(catchupNodeNum) + "/stderr.txt" f = open(logFile) contents = f.read() - if contents.count("3030001 unlinkable_block_exception: Unlinkable block") > 10: # a few are fine + if contents.count("3030001 unlinkable_block_exception: Unlinkable block") > 15: # a few are fine errorExit(f"Node{catchupNodeNum} has unlinkable blocks: {logFile}.") testSuccessful=True diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 66b70d7bf7..a210d0b263 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -18,11 +18,12 @@ Print=Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--wallet-port","--unshared"}) +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--wallet-port","--unshared"}) Utils.Debug=args.v pNodes=4 totalNodes=5 cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=args.wallet_port @@ -42,7 +43,7 @@ maxRAMFlag="--chain-state-db-size-mb" maxRAMValue=1010 extraNodeosArgs=" %s %d %s %d --http-max-response-time-ms 990000 " % (minRAMFlag, minRAMValue, maxRAMFlag, maxRAMValue) - if cluster.launch(onlyBios=False, pnodes=pNodes, totalNodes=totalNodes, totalProducers=totalNodes, extraNodeosArgs=extraNodeosArgs) is False: + if cluster.launch(onlyBios=False, pnodes=pNodes, totalNodes=totalNodes, totalProducers=totalNodes, activateIF=activateIF, extraNodeosArgs=extraNodeosArgs) is False: Utils.cmdError("launcher") errorExit("Failed to stand up eos cluster.") diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index b9aabaa288..1b90fe40c8 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -136,12 +136,13 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): Print=Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--prod-count","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v prodNodes=4 totalNodes=5 cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details prodCount=args.prod_count walletPort=args.wallet_port @@ -157,7 +158,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): cluster.setWalletMgr(walletMgr) Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=prodNodes, totalNodes=totalNodes, totalProducers=prodNodes*21) is False: + if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=prodNodes, totalNodes=totalNodes, totalProducers=prodNodes*21, activateIF=activateIF) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") diff --git a/tests/p2p_multiple_listen_test.py b/tests/p2p_multiple_listen_test.py index 7f537e9d35..b5ddc9f4a6 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -15,12 +15,13 @@ errorExit=Utils.errorExit args=TestHelper.parse_args({"-p","-n","-d","--keep-logs" - ,"--dump-error-details","-v" + ,"--activate-if","--dump-error-details","-v" ,"--leave-running","--unshared"}) pnodes=args.p delay=args.d debug=args.v total_nodes=5 +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details Utils.Debug=debug @@ -42,7 +43,7 @@ '2': '--agent-name node-02 --p2p-peer-address localhost:9779 --plugin eosio::net_api_plugin', '4': '--agent-name node-04 --p2p-peer-address localhost:9876 --plugin eosio::net_api_plugin', } - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo='line', delay=delay, + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo='line', delay=delay, activateIF=activateIF, specificExtraNodeosArgs=specificArgs) is False: errorExit("Failed to stand up eos cluster.") diff --git a/tests/p2p_sync_throttle_test.py b/tests/p2p_sync_throttle_test.py index d05325f4c9..40b312e3ad 100755 --- a/tests/p2p_sync_throttle_test.py +++ b/tests/p2p_sync_throttle_test.py @@ -24,7 +24,7 @@ appArgs.add(flag='--plugin',action='append',type=str,help='Run nodes with additional plugins') appArgs.add(flag='--connection-cleanup-period',type=int,help='Interval in whole seconds to run the connection reaper and metric collection') -args=TestHelper.parse_args({"-d","--keep-logs" +args=TestHelper.parse_args({"-d","--keep-logs","--activate-if" ,"--dump-error-details","-v","--leave-running" ,"--unshared"}, applicationSpecificArgs=appArgs) @@ -33,6 +33,7 @@ debug=args.v prod_count = 2 total_nodes=4 +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details Utils.Debug=debug @@ -60,7 +61,7 @@ def extractPrometheusMetric(connID: str, metric: str, text: str): # Custom topology is a line of singlely connected nodes from highest node number in sequence to lowest, # the reverse of the usual TestHarness line topology. if cluster.launch(pnodes=pnodes, unstartedNodes=2, totalNodes=total_nodes, prodCount=prod_count, - topo='./tests/p2p_sync_throttle_test_shape.json', delay=delay, + topo='./tests/p2p_sync_throttle_test_shape.json', delay=delay, activateIF=activateIF, extraNodeosArgs=extraNodeosArgs) is False: errorExit("Failed to stand up eos cluster.") @@ -135,6 +136,7 @@ def extractPrometheusMetric(connID: str, metric: str, text: str): if len(response) < 100: # tolerate HTTPError as well (method returns only the exception code) errorLimit -= 1 + time.sleep(0.5) continue connPorts = prometheusHostPortPattern.findall(response) Print(connPorts) @@ -179,6 +181,7 @@ def extractPrometheusMetric(connID: str, metric: str, text: str): if len(connPorts) < 2: # wait for sending node to be connected errorLimit -= 1 + time.sleep(0.5) continue Print('Throttled Node Start State') throttledNodePortMap = {port: id for id, port in connPorts if port != '0'} diff --git a/tests/p2p_sync_throttle_test_shape.json b/tests/p2p_sync_throttle_test_shape.json index 8cfb5ce9a5..497a66f281 100644 --- a/tests/p2p_sync_throttle_test_shape.json +++ b/tests/p2p_sync_throttle_test_shape.json @@ -7,7 +7,10 @@ "keys": [ { "pubkey": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", - "privkey": "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" + "privkey": "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", + "blspubkey":"PUB_BLS_sGOyYNtpmmjfsNbQaiGJrPxeSg9sdx0nRtfhI_KnWoACXLL53FIf1HjpcN8wX0cYQyOE60NLSI9iPY8mIlT4GkiFMT3ez7j2IbBBzR0D1MthC0B_fYlgYWwjcbqCOowSaH48KA", + "blsprivkey":"PVT_BLS_QgHHJ5vprZcjG7P0xWoIdX4yKPQXoG4k3e28TpLIQicB7GL_", + "blspop":"SIG_BLS_HDzwmlF7wSJGetlSfhIGKVtjiMTeYoM4oCbNoHi1tyh0_KnZCsdLUzplexSXD80P0NAkCjlA6YFt2M5_JsZkRTqn2faFSnH6zwKIK9yr2cV3a14W4WcIC90mTP2D-HEPOBjM2gTmWCA0gYfPdV3tB3I0matrYh5R0I1FG0V6p_RVKacXMgV_M3lNUokRI84MPZlc8OVbJ0RbjoBnYylVeYtR31vSJvvk6RvykIjTktZOA0s32-TR5EcxuaFSsVQU7nSQxA" } ], "peers": [ @@ -30,7 +33,10 @@ "keys": [ { "pubkey": "EOS7D6jfN6bbJD9cYheyhnBT4bmUWc3Qf4Yphf5GBeAAy58okcwHU", - "privkey": "5KkmnyunnpCQzgFoLMEtU3j7BRBa5aWmsBNru49ke7LdnZKFhmt" + "privkey": "5KkmnyunnpCQzgFoLMEtU3j7BRBa5aWmsBNru49ke7LdnZKFhmt", + "blspubkey":"PUB_BLS_X6Wzge0CMkDLu0svywBWGBdIuMfol_hAG7zeukAddsbQsArgcuZ6tz3LLoLRurUMhz6ZpOHdYCPU0Rg8Fo8n4UDsT6pcHSmwWMKWIhyS-Ms0O_dYCRQ2Q5HLxBGMxyIWaltxlw", + "blsprivkey":"PVT_BLS_TvIkGjiwy3b5k9yc6YnwHPQp1n_9x8yP4mZQl5Ke1yvp2_vv", + "blspop":"SIG_BLS_Zzi_eRG51GhBgAFhnG048Pa3OjlenLwKtO03CBkZxQB4sdhyYWmqrJDdjpgPwvcPwbRK1jIlaUG9mJVPjJHrmocC-br8_t1EqLAHN3lyuyJ7UZWkzj2E339zNJ8aE28NmF4rmZ0UV3sUP54qZw9k75G7y0toL8djkMkPNzbz9OD0vZQDjQ-PVWQg11t-eP4MbFt8uONuk2NpEBEbT8JXPvnzh1e1-WBxId0Mra5-Pa1ca3zkrqgHdnpWKCUjBr0Kj8yZPg" } ], "peers": [], @@ -71,7 +77,10 @@ "keys": [ { "pubkey": "EOS5tZqxLB8y9q2yHkgcXU4QFBEV6QKN3NQ54ygaFLWHJbjqYzFhw", - "privkey": "5KBs4qR7T8shJjCJUeFQXd77iKrok5TCtZiQhWJpCpc1VRxpNAs" + "privkey": "5KBs4qR7T8shJjCJUeFQXd77iKrok5TCtZiQhWJpCpc1VRxpNAs", + "blspubkey":"PUB_BLS_UmHR2Ez-gUJVkptOXXlWBCSu2aPQ3EBk69L7IzXn-pAXiWv5gP6fgQv5Js4n3VcJL6TK1M9rB9wAPhnr7b6xdKg2_zWD62qUoal9GYmBS5doxlCdKDY8ZFj6fbGS02oY_-ItrQ", + "blsprivkey":"PVT_BLS_IRjJHkfSSII-mDq7iVOsznvka_sRMsmxJSJwXQyr5mqmERAV", + "blspop":"SIG_BLS_wzTA_EfQTVoWRO4HZqoyDcQGCnlvHCkqoZXVSRbwSf7az4U4nbveWgCMRCgQZsgEJbPt6-NslwwRXJDLnFN0Hnm8F5qhmsGlWMP9tH7syPibNvldJ0RUFDH7azSZulcJ2uMxQAobCB-21c3PiUQc8JbuJFbUp9klAnXIJP60P-PT6ZUNmhNjLqHl2IlMsq8ZdFPvHVF3Z8HpfhJVKedI4yTvzWAIIOW2uSHkOmKbLP_QYc2YLRHUWV56mM-hsRwP4-hWVA" } ], "peers": [ @@ -92,7 +101,10 @@ "keys": [ { "pubkey": "EOS5FBPf5EN9bYEqmsKfPx9bxyUZ9grDiE24zqLFXtPa6UpVzMjE7", - "privkey": "5HtVDiAsD24seDm5sdswTcdZpx672XbBW9gBkyrzbsj2j9Y9JeC" + "privkey": "5HtVDiAsD24seDm5sdswTcdZpx672XbBW9gBkyrzbsj2j9Y9JeC", + "blspubkey":"PUB_BLS_JzblSr2sf_UhxQjGxOtHbRCBkHgSB1RG4xUbKKl-fKtUjx6hyOHajnVQT4IvBF4PutlX7JTC14IqIjADlP-3_G2MXRhBlkB57r2u59OCwRQQEDqmVSADf6CoT8zFUXcSgHFw7w", + "blsprivkey":"PVT_BLS_QRxLAVbe2n7RaPWx2wHbur8erqUlAs-V_wXasGhjEA78KlBq", + "blspop":"SIG_BLS_Z5fJqFv6DIsHFhBFpkHmL_R48h80zVKQHtB5lrKGOVZTaSQNuVaXD_eHg7HBvKwY6zqgA_vryCLQo5W0Inu6HtLkGL2gYX2UHJjrZJZpfJSKG0ynqAZmyrCglxRLNm8KkFdGGR8oJXf5Yzyu7oautqTPniuKLBvNeQxGJGDOQtHSQ0uP3mD41pWzPFRoi10BUor9MbwUTQ7fO7Of4ZjhVM3IK4JrqX1RBXkDX83Wi9xFzs_fdPIyMqmgEzFgolgUa8XN4Q" } ], "peers": [ @@ -113,7 +125,10 @@ "keys": [ { "pubkey": "EOS8XH2gKxsef9zxmMHm4vaSvxQUhg7W4GC3nK2KSRxyYrNG5gZFS", - "privkey": "5JcoRRhDcgm51dkBrRTmErceTqrYhrq22UnmUjTZToMpH91B9N1" + "privkey": "5JcoRRhDcgm51dkBrRTmErceTqrYhrq22UnmUjTZToMpH91B9N1", + "blspubkey":"PUB_BLS_rYRa_-bT7uLOSAfPIBy6NlXFB0YxwROeSuqHzw6s-1cuK_-GJUKqp20ktyAnsO4ZuHdx3BEPDaLronpnL22MXKWM7bvZnkCfbGCD6OzizQqxXkM9N5z5R-OUA4Ime6cF5YTSFg", + "blsprivkey":"PVT_BLS_GQjR0E8Hu8KrsTCvLKnlOCIwQijAj2-5KDizQwF-bAY6pise", + "blspop":"SIG_BLS_syFMuifUnX2zQQKr0cuHYzQQjsuPrNG75_z6y8fOyYg_twqMICZ0kT7ObbwIOUsLfXx9PVb4-QLEgUYGSRg1NSfeHGjIGkhea82wa3ayfI8elUEU1MStKbeKpys7xUAQz1PEgwcz5dClq3HyLQmMAjpoL74N_Znf0KiNEVZMte-DLF7x_6sAfp_834LthyYHjZYTmdG7belyzlYHKJb6upnZy9nR_zoKpx9jeTd3tzVhoTCuAN6aFw68D_ItY5cWiY2dhA" } ], "peers": [ @@ -129,4 +144,4 @@ "_dot_label": "localhost:9879\ntestnet_03\nprod=" } } -} \ No newline at end of file +} diff --git a/tests/plugin_http_api_test.py b/tests/plugin_http_api_test.py index c11a5cc21f..f9628847cc 100755 --- a/tests/plugin_http_api_test.py +++ b/tests/plugin_http_api_test.py @@ -336,25 +336,6 @@ def test_ChainApi(self) : ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) self.assertEqual(ret_json["payload"]["block_num"], 1) - # get_block_header_state with empty parameter - command = "get_block_header_state" - ret_json = self.nodeos.processUrllibRequest(resource, command, endpoint=endpoint) - self.assertEqual(ret_json["code"], 400) - self.assertEqual(ret_json["error"]["code"], 3200006) - # get_block_header_state with empty content parameter - ret_json = self.nodeos.processUrllibRequest(resource, command, self.empty_content_dict, endpoint=endpoint) - self.assertEqual(ret_json["code"], 400) - self.assertEqual(ret_json["error"]["code"], 3200006) - # get_block_header_state with invalid parameter - ret_json = self.nodeos.processUrllibRequest(resource, command, self.http_post_invalid_param, endpoint=endpoint) - self.assertEqual(ret_json["code"], 400) - self.assertEqual(ret_json["error"]["code"], 3200006) - # get_block_header_state with valid parameter, the irreversible is not available, unknown block number - payload = {"block_num_or_id":1} - ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) - self.assertEqual(ret_json["code"], 400) - self.assertEqual(ret_json["error"]["code"], 3100002) - # get_account with empty parameter command = "get_account" ret_json = self.nodeos.processUrllibRequest(resource, command, endpoint=endpoint) diff --git a/tests/prod_preactivation_test.py b/tests/prod_preactivation_test.py index 46b4376b49..4b032ea32f 100755 --- a/tests/prod_preactivation_test.py +++ b/tests/prod_preactivation_test.py @@ -3,9 +3,11 @@ import decimal import re import time +import json from TestHarness import Cluster, Node, ReturnType, TestHelper, Utils, WalletMgr from TestHarness.Cluster import PFSetupPolicy +from TestHarness.accounts import Account ############################################################### # prod_preactivation_test @@ -18,8 +20,8 @@ cmdError=Utils.cmdError args = TestHelper.parse_args({"--host","--port","--defproducera_prvt_key","--defproducerb_prvt_key" - ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios" - ,"--sanity-test","--wallet-port","--unshared"}) + ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running" + ,"--wallet-port","--unshared"}) server=args.host port=args.port debug=args.v @@ -28,8 +30,6 @@ dumpErrorDetails=args.dump_error_details dontLaunch=args.dont_launch prodCount=2 -onlyBios=args.only_bios -sanityTest=args.sanity_test walletPort=args.wallet_port Utils.Debug=debug @@ -37,7 +37,6 @@ cluster=Cluster(host=server, port=port, defproduceraPrvtKey=defproduceraPrvtKey, defproducerbPrvtKey=defproducerbPrvtKey, unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) walletMgr=WalletMgr(True, port=walletPort) testSuccessful=False -dontBootstrap=sanityTest WalletdName=Utils.EosWalletName ClientName="cleos" @@ -50,14 +49,65 @@ if localTest and not dontLaunch: Print("Stand up cluster") - if cluster.launch(pnodes=prodCount, totalNodes=prodCount, prodCount=1, onlyBios=onlyBios, - dontBootstrap=dontBootstrap, - pfSetupPolicy=PFSetupPolicy.NONE, extraNodeosArgs=" --plugin eosio::producer_api_plugin --http-max-response-time-ms 990000 ") is False: + if cluster.launch(pnodes=prodCount, totalNodes=prodCount, prodCount=1, + pfSetupPolicy=PFSetupPolicy.NONE, extraNodeosArgs=" --plugin eosio::producer_api_plugin --http-max-response-time-ms 990000 ") is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") - Print("Validating system accounts after bootstrap") - cluster.validateAccounts(None) + # setup producers using bios contract that does not need preactivate_feature + contract="eosio.bios" + contractDir="libraries/testing/contracts/old_versions/v1.6.0-rc3/%s" % (contract) + wasmFile="%s.wasm" % (contract) + abiFile="%s.abi" % (contract) + retMap = cluster.biosNode.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) + if retMap is None: + errorExit("publish of contract failed") + + producerKeys=cluster.parseClusterKeys(prodCount) + + eosioName="eosio" + eosioKeys=producerKeys[eosioName] + eosioAccount=Account(eosioName) + eosioAccount.ownerPrivateKey=eosioKeys["private"] + eosioAccount.ownerPublicKey=eosioKeys["public"] + eosioAccount.activePrivateKey=eosioKeys["private"] + eosioAccount.activePublicKey=eosioKeys["public"] + + Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys())) + producerKeys.pop(eosioName) + accounts=[] + for name, keys in producerKeys.items(): + initx = Account(name) + initx.ownerPrivateKey=keys["private"] + initx.ownerPublicKey=keys["public"] + initx.activePrivateKey=keys["private"] + initx.activePublicKey=keys["public"] + trans=cluster.biosNode.createAccount(initx, eosioAccount, 0) + if trans is None: + errorExit("ERROR: Failed to create account %s" % (name)) + Node.validateTransaction(trans) + accounts.append(initx) + + counts = dict.fromkeys(range(prodCount), 0) # initialize node prods count to 0 + setProdsStr = '{"schedule": ' + prodStanzas = [] + prodNames = [] + for name, keys in list(producerKeys.items())[:21]: + if counts[keys["node"]] >= prodCount: + Utils.Print(f'Count for this node exceeded: {counts[keys["node"]]}') + continue + prodStanzas.append({'producer_name': keys['name'], 'block_signing_key': keys['public']}) + prodNames.append(keys["name"]) + counts[keys["node"]] += 1 + setProdsStr += json.dumps(prodStanzas) + setProdsStr += ' }' + if Utils.Debug: Utils.Print("setprods: %s" % (setProdsStr)) + Utils.Print("Setting producers: %s." % (", ".join(prodNames))) + opts = "--permission eosio@active" + # pylint: disable=redefined-variable-type + trans = cluster.biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) + if trans is None or not trans[0]: + errorExit("ERROR: Failed to set producer %s." % (keys["name"])) node = cluster.getNode(0) resource = "producer" @@ -97,7 +147,7 @@ abiFile="%s.abi" % (contract) Print("publish a new bios contract %s should fails because env.is_feature_activated unresolveable" % (contractDir)) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=True) outPut = retMap["output"].decode("utf-8") if outPut.find("unresolveable") < 0: @@ -125,6 +175,7 @@ if secwait <= 0: errorExit("No producer of node 0") + resource = "producer" command = "schedule_protocol_feature_activations" payload = {"protocol_features_to_activate":[digest]} @@ -139,7 +190,7 @@ time.sleep(0.6) Print("publish a new bios contract %s should fails because node1 is not producing block yet" % (contractDir)) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=True) if retMap["output"].decode("utf-8").find("unresolveable") < 0: errorExit("bios contract not result in expected unresolveable error") @@ -156,7 +207,9 @@ errorExit("No blocks produced by node 1") time.sleep(0.6) - retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True, shouldFail=False) + if retMap is None: + errorExit("publish of new contract failed") Print("sucessfully set new contract with new intrinsic!!!") testSuccessful=True diff --git a/tests/read_only_trx_test.py b/tests/read_only_trx_test.py index 6af91c5a83..eeb9a73046 100755 --- a/tests/read_only_trx_test.py +++ b/tests/read_only_trx_test.py @@ -29,7 +29,7 @@ appArgs.add(flag="--wasm-runtime", type=str, help="if set to eos-vm-oc, must compile with EOSIO_EOS_VM_OC_DEVELOPER", default="eos-vm-jit") args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed" - ,"--dump-error-details","-v","--leave-running" + ,"--activate-if","--dump-error-details","-v","--leave-running" ,"--keep-logs","--unshared"}, applicationSpecificArgs=appArgs) pnodes=args.p @@ -44,6 +44,7 @@ nodesFile=args.nodes_file dontLaunch=nodesFile is not None seed=args.seed +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details numTestRuns=args.num_test_runs @@ -122,7 +123,7 @@ def startCluster(): specificExtraNodeosArgs[pnodes]+=" --wasm-runtime " specificExtraNodeosArgs[pnodes]+=args.wasm_runtime extraNodeosArgs=" --http-max-response-time-ms 990000 --disable-subjective-api-billing false " - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, specificExtraNodeosArgs=specificExtraNodeosArgs, extraNodeosArgs=extraNodeosArgs ) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs, extraNodeosArgs=extraNodeosArgs ) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/restart-scenarios-test.py b/tests/restart-scenarios-test.py index 8af30bac1d..b5f86fd964 100755 --- a/tests/restart-scenarios-test.py +++ b/tests/restart-scenarios-test.py @@ -18,7 +18,7 @@ errorExit=Utils.errorExit args=TestHelper.parse_args({"-p","-d","-s","-c","--kill-sig","--kill-count","--keep-logs" - ,"--dump-error-details","-v","--leave-running","--unshared"}) + ,"--activate-if","--dump-error-details","-v","--leave-running","--unshared"}) pnodes=args.p topo=args.s delay=args.d @@ -27,6 +27,7 @@ total_nodes = pnodes killCount=args.kill_count if args.kill_count > 0 else 1 killSignal=args.kill_sig +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details seed=1 @@ -48,7 +49,7 @@ pnodes, topo, delay, chainSyncStrategyStr)) Print("Stand up cluster") - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, activateIF=activateIF) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") diff --git a/tests/ship_streamer.cpp b/tests/ship_streamer.cpp index 94a3c40fc9..0c2be248a3 100644 --- a/tests/ship_streamer.cpp +++ b/tests/ship_streamer.cpp @@ -32,6 +32,7 @@ int main(int argc, char* argv[]) { bool fetch_block = false; bool fetch_traces = false; bool fetch_deltas = false; + bool fetch_finality_data = false; cli.add_options() ("help,h", bpo::bool_switch(&help)->default_value(false), "Print this help message and exit.") @@ -42,6 +43,7 @@ int main(int argc, char* argv[]) { ("fetch-block", bpo::bool_switch(&fetch_block)->default_value(fetch_block), "Fetch blocks") ("fetch-traces", bpo::bool_switch(&fetch_traces)->default_value(fetch_traces), "Fetch traces") ("fetch-deltas", bpo::bool_switch(&fetch_deltas)->default_value(fetch_deltas), "Fetch deltas") + ("fetch-finality-data", bpo::bool_switch(&fetch_finality_data)->default_value(fetch_finality_data), "Fetch finality data") ; bpo::variables_map varmap; bpo::store(bpo::parse_command_line(argc, argv, cli), varmap); @@ -86,8 +88,12 @@ int main(int argc, char* argv[]) { // bool fetch_traces = false; // bool fetch_deltas = false; //}; + //struct get_blocks_request_v1 : get_blocks_request_v0 { + // bool fetch_finality_data = false; + //}; request_writer.StartArray(); - request_writer.String("get_blocks_request_v0"); + + request_writer.String("get_blocks_request_v1"); // always send out latest version of request request_writer.StartObject(); request_writer.Key("start_block_num"); request_writer.Uint(start_block_num); @@ -106,6 +112,8 @@ int main(int argc, char* argv[]) { request_writer.Bool(fetch_traces); request_writer.Key("fetch_deltas"); request_writer.Bool(fetch_deltas); + request_writer.Key("fetch_finality_data"); + request_writer.Bool(fetch_finality_data); request_writer.EndObject(); request_writer.EndArray(); @@ -128,7 +136,7 @@ int main(int argc, char* argv[]) { eosio::check(!result_document.HasParseError(), "Failed to parse result JSON from abieos"); eosio::check(result_document.IsArray(), "result should have been an array (variant) but it's not"); eosio::check(result_document.Size() == 2, "result was an array but did not contain 2 items like a variant should"); - eosio::check(std::string(result_document[0].GetString()) == "get_blocks_result_v0", "result type doesn't look like get_blocks_result_v0"); + eosio::check(std::string(result_document[0].GetString()) == "get_blocks_result_v1", "result type doesn't look like get_blocks_result_v1"); eosio::check(result_document[1].IsObject(), "second item in result array is not an object"); eosio::check(result_document[1].HasMember("head"), "cannot find 'head' in result"); eosio::check(result_document[1]["head"].IsObject(), "'head' is not an object"); @@ -144,7 +152,7 @@ int main(int argc, char* argv[]) { } else { std::cout << "," << std::endl; } - std::cout << "{ \"get_blocks_result_v0\":" << std::endl; + std::cout << "{ \"get_blocks_result_v1\":" << std::endl; rapidjson::StringBuffer result_sb; rapidjson::PrettyWriter result_writer(result_sb); diff --git a/tests/ship_streamer_test.py b/tests/ship_streamer_test.py index 7b38f501ec..8b92397ef7 100755 --- a/tests/ship_streamer_test.py +++ b/tests/ship_streamer_test.py @@ -7,16 +7,16 @@ import signal import sys -from TestHarness import Cluster, TestHelper, Utils, WalletMgr, CORE_SYMBOL, createAccountKeys +from TestHarness import Cluster, TestHelper, Utils, WalletMgr from TestHarness.TestHelper import AppArgs ############################################################### # ship_streamer_test # -# This test sets up 2 producing nodes and one "bridge" node using test_control_api_plugin. -# One producing node has 3 of the elected producers and the other has 1 of the elected producers. -# All the producers are named in alphabetical order, so that the 3 producers, in the one production node, are -# scheduled first, followed by the 1 producer in the other producer node. Each producing node is only connected +# This test sets up 4 producing nodes and one "bridge" node using test_control_api_plugin. +# One side of bridge has 3 of the elected producers and the other has 1 of the elected producers. +# All the producers are named in alphabetical order, so that the 3 producers, in the one production side, are +# scheduled first, followed by the 1 producer in the other producer node. Each producing side is only connected # to the other producing node via the "bridge" node. # The bridge node has the test_control_api_plugin, that the test uses to kill # the "bridge" node to generate a fork. @@ -31,14 +31,16 @@ appArgs = AppArgs() extraArgs = appArgs.add(flag="--num-clients", type=int, help="How many ship_streamers should be started", default=1) -args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) +extraArgs = appArgs.add_bool(flag="--finality-data-history", help="Enable finality data history", action='store_true') +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=TestHelper.DEFAULT_WALLET_PORT -totalProducerNodes=2 +totalProducerNodes=4 totalNonProducerNodes=1 totalNodes=totalProducerNodes+totalNonProducerNodes maxActiveProducers=21 @@ -65,91 +67,37 @@ def getLatestSnapshot(nodeId): # *** setup topogrophy *** - # "bridge" shape connects defprocera through defproducerc (3 in node0) to each other and defproduceru (1 in node1) - # and the only connection between those 2 groups is through the bridge node + # "bridge" shape connects defproducera (node0) defproducerb (node1) defproducerc (node2) to each other and defproducerd (node3) + # and the only connection between those 2 groups is through the bridge (node4) - shipNodeNum = 1 + shipNodeNum = 3 specificExtraNodeosArgs={} specificExtraNodeosArgs[shipNodeNum]="--plugin eosio::state_history_plugin --trace-history --chain-state-history --state-history-stride 200 --plugin eosio::net_api_plugin --plugin eosio::producer_api_plugin " + if args.finality_data_history: + specificExtraNodeosArgs[shipNodeNum]+=" --finality-data-history" # producer nodes will be mapped to 0 through totalProducerNodes-1, so the number totalProducerNodes will be the non-producing node specificExtraNodeosArgs[totalProducerNodes]="--plugin eosio::test_control_api_plugin " - if cluster.launch(topo="bridge", pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + if cluster.launch(topo="./tests/bridge_for_fork_test_shape.json", pnodes=totalProducerNodes, loadSystemContract=False, + totalNodes=totalNodes, totalProducers=totalProducerNodes, activateIF=activateIF, biosFinalizer=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") - Utils.errorExit("Failed to stand up eos cluster.") + Utils.errorExit("Failed to stand up cluster.") # *** identify each node (producers and non-producing node) *** - #verify nodes are in sync and advancing + # verify nodes are in sync and advancing cluster.waitOnClusterSync(blockAdvancing=5) Print("Cluster in Sync") - prodNode = cluster.getNode(0) - prodNode0 = prodNode - prodNode1 = cluster.getNode(1) - nonProdNode = cluster.getNode(2) + prodNode0 = cluster.getNode(0) + prodNode3 = cluster.getNode(3) + nonProdNode = cluster.getNode(4) shipNode = cluster.getNode(shipNodeNum) - - accounts=createAccountKeys(6) - if accounts is None: - Utils.errorExit("FAILURE - create keys") - - accounts[0].name="testeraaaaaa" - accounts[1].name="tester111111" # needed for voting - accounts[2].name="tester222222" # needed for voting - accounts[3].name="tester333333" # needed for voting - accounts[4].name="tester444444" # needed for voting - accounts[5].name="tester555555" # needed for voting - - testWalletName="test" - - Print(f"Creating wallet {testWalletName}.") - testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount,accounts[0],accounts[1],accounts[2],accounts[3],accounts[4],accounts[5]]) - - for _, account in cluster.defProducerAccounts.items(): - walletMgr.importKey(account, testWallet, ignoreDupKeyWarning=True) - - for i in range(0, totalNodes): - node=cluster.getNode(i) - node.producers=Cluster.parseProducers(i) - for prod in node.producers: - prodName = cluster.defProducerAccounts[prod].name - if prodName == "defproducera" or prodName == "defproducerb" or prodName == "defproducerc" or prodName == "defproduceru": - Print(f"Register producer {prodName}") - trans=node.regproducer(cluster.defProducerAccounts[prod], "http://mysite.com", 0, waitForTransBlock=False, exitOnError=True) - - # create accounts via eosio as otherwise a bid is needed - transferAmount="100000000.0000 {0}".format(CORE_SYMBOL) - for account in accounts: - Print(f"Create new account {account.name} via {cluster.eosioAccount.name} with private key: {account.activePrivateKey}") - trans=nonProdNode.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, stakeNet=10000, stakeCPU=10000, buyRAM=10000000, exitOnError=True) - nonProdNode.waitForTransBlockIfNeeded(trans, True, exitOnError=True) - for account in accounts: - Print(f"Transfer funds {transferAmount} from account {cluster.eosioAccount.name} to {account.name}") - trans=nonProdNode.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer", waitForTransBlock=False) - nonProdNode.waitForTransBlockIfNeeded(trans, True, exitOnError=True) - for account in accounts: - trans=nonProdNode.delegatebw(account, 20000000.0000, 20000000.0000, waitForTransBlock=False, exitOnError=True) - nonProdNode.waitForTransBlockIfNeeded(trans, True, exitOnError=True) - - # *** vote using accounts *** - - cluster.waitOnClusterSync(blockAdvancing=3) + # cluster.waitOnClusterSync(blockAdvancing=3) start_block_num = shipNode.getBlockNum() - # vote a,b,c (node0) u (node1) - voteProducers=[] - voteProducers.append("defproducera") - voteProducers.append("defproducerb") - voteProducers.append("defproducerc") - voteProducers.append("defproduceru") - for account in accounts: - Print(f"Account {account.name} vote for producers={voteProducers}") - trans=prodNode.vote(account, voteProducers, exitOnError=True, waitForTransBlock=False) - #verify nodes are in sync and advancing cluster.waitOnClusterSync(blockAdvancing=3) Print("Shutdown unneeded bios node") @@ -161,11 +109,11 @@ def getLatestSnapshot(nodeId): wasmFile = "%s.wasm" % (contract) abiFile = "%s.abi" % (contract) - nonProdNode.publishContract(accounts[0], contractDir, wasmFile, abiFile) + nonProdNode.publishContract(cluster.defproducerbAccount, contractDir, wasmFile, abiFile) jumbotxn = { - "actions": [{"account": "testeraaaaaa","name": "jumbotime", - "authorization": [{"actor": "testeraaaaaa","permission": "active"}], + "actions": [{"account": "defproducerb","name": "jumbotime", + "authorization": [{"actor": "defproducerb","permission": "active"}], "data": "", "compression": "none"}] } @@ -175,21 +123,23 @@ def getLatestSnapshot(nodeId): targetTpsPerGenerator = 10 testTrxGenDurationSec=60*60 numTrxGenerators=2 - cluster.launchTrxGenerators(contractOwnerAcctName=cluster.eosioAccount.name, acctNamesList=[accounts[0].name, accounts[1].name], - acctPrivKeysList=[accounts[0].activePrivateKey,accounts[1].activePrivateKey], nodeId=prodNode1.nodeId, + cluster.launchTrxGenerators(contractOwnerAcctName=cluster.eosioAccount.name, acctNamesList=[cluster.defproduceraAccount.name, cluster.defproducerbAccount.name], + acctPrivKeysList=[cluster.defproduceraAccount.activePrivateKey,cluster.defproducerbAccount.activePrivateKey], nodeId=prodNode3.nodeId, tpsPerGenerator=targetTpsPerGenerator, numGenerators=numTrxGenerators, durationSec=testTrxGenDurationSec, waitToComplete=False) - status = cluster.waitForTrxGeneratorsSpinup(nodeId=prodNode1.nodeId, numGenerators=numTrxGenerators) + status = cluster.waitForTrxGeneratorsSpinup(nodeId=prodNode3.nodeId, numGenerators=numTrxGenerators) assert status is not None and status is not False, "ERROR: Failed to spinup Transaction Generators" prodNode0.waitForProducer("defproducerc") - block_range = 350 + block_range = 250 end_block_num = start_block_num + block_range shipClient = "tests/ship_streamer" cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + if args.finality_data_history: + cmd += " --fetch-finality-data" if Utils.Debug: Utils.Print(f"cmd: {cmd}") clients = [] files = [] @@ -210,23 +160,31 @@ def getLatestSnapshot(nodeId): Print(f"Client {i} started, Ship node head is: {shipNode.getBlockNum()}") # Generate a fork - prodNode1Prod="defproduceru" + prodNode3Prod= "defproducerd" preKillBlockNum=nonProdNode.getBlockNum() preKillBlockProducer=nonProdNode.getBlockProducerByNum(preKillBlockNum) - forkAtProducer="defproducer" + chr(ord(preKillBlockProducer[-1])+2) + forkAtProducer="defproducerb" nonProdNode.killNodeOnProducer(producer=forkAtProducer, whereInSequence=1) Print(f"Current block producer {preKillBlockProducer} fork will be at producer {forkAtProducer}") - prodNode0.waitForProducer(forkAtProducer) - prodNode1.waitForProducer(prodNode1Prod) - if nonProdNode.verifyAlive(): # if on defproducera, need to wait again - prodNode0.waitForProducer(forkAtProducer) - prodNode1.waitForProducer(prodNode1Prod) + prodNode0.waitForProducer("defproducera") + prodNode3.waitForProducer(prodNode3Prod) + if nonProdNode.verifyAlive(): + prodNode0.waitForProducer("defproducera") + prodNode3.waitForProducer(prodNode3Prod) if nonProdNode.verifyAlive(): Utils.errorExit("Bridge did not shutdown") Print("Fork started") - forkProgress="defproducer" + chr(ord(forkAtProducer[-1])+3) - prodNode0.waitForProducer(forkProgress) # wait for fork to progress a bit + prodNode0.waitForProducer("defproducerc") # wait for fork to progress a bit + restore0BlockNum = prodNode0.getBlockNum() + restore1BlockNum = prodNode3.getBlockNum() + restoreBlockNum = max(int(restore0BlockNum), int(restore1BlockNum)) + restore0LIB = prodNode0.getIrreversibleBlockNum() + restore1LIB = prodNode3.getIrreversibleBlockNum() + restoreLIB = max(int(restore0LIB), int(restore1LIB)) + + if int(restoreBlockNum) > int(end_block_num): + Utils.errorExit(f"Did not stream long enough {end_block_num} to cover the fork {restoreBlockNum}, increase block_range {block_range}") Print("Restore fork") Print("Relaunching the non-producing bridge node to connect the producing nodes again") @@ -236,10 +194,11 @@ def getLatestSnapshot(nodeId): Utils.errorExit(f"Failure - (non-production) node {nonProdNode.nodeNum} should have restarted") nonProdNode.waitForProducer(forkAtProducer) - nonProdNode.waitForProducer(prodNode1Prod) + nonProdNode.waitForProducer(prodNode3Prod) + nonProdNode.waitForIrreversibleBlock(restoreLIB+1) afterForkBlockNum = nonProdNode.getBlockNum() - if int(afterForkBlockNum) < int(end_block_num): - Utils.errorExit(f"Did not stream long enough {end_block_num} to cover the fork {afterForkBlockNum}, increase block_range {block_range}") + + assert shipNode.findInLog(f"successfully switched fork to new head"), f"No fork found in log {shipNode}" Print(f"Stopping all {args.num_clients} clients") for index, (popen, _), (out, err), start in zip(range(len(clients)), clients, files, starts): @@ -252,11 +211,11 @@ def getLatestSnapshot(nodeId): block_num = start_block_num for i in data: # fork can cause block numbers to be repeated - this_block_num = i['get_blocks_result_v0']['this_block']['block_num'] + this_block_num = i['get_blocks_result_v1']['this_block']['block_num'] if this_block_num < block_num: block_num = this_block_num assert block_num == this_block_num, f"{block_num} != {this_block_num}" - assert isinstance(i['get_blocks_result_v0']['block'], str) # verify block in result + assert isinstance(i['get_blocks_result_v1']['block'], str) # verify block in result block_num += 1 assert block_num-1 == end_block_num, f"{block_num-1} != {end_block_num}" @@ -281,6 +240,8 @@ def getLatestSnapshot(nodeId): block_range = 0 end_block_num = start_block_num + block_range cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + if args.finality_data_history: + cmd += " --fetch-finality-data" if Utils.Debug: Utils.Print(f"cmd: {cmd}") clients = [] files = [] @@ -307,11 +268,11 @@ def getLatestSnapshot(nodeId): block_num = start_block_num for i in data: # fork can cause block numbers to be repeated - this_block_num = i['get_blocks_result_v0']['this_block']['block_num'] + this_block_num = i['get_blocks_result_v1']['this_block']['block_num'] if this_block_num < block_num: block_num = this_block_num assert block_num == this_block_num, f"{block_num} != {this_block_num}" - assert isinstance(i['get_blocks_result_v0']['deltas'], str) # verify deltas in result + assert isinstance(i['get_blocks_result_v1']['deltas'], str) # verify deltas in result block_num += 1 assert block_num-1 == end_block_num, f"{block_num-1} != {end_block_num}" diff --git a/tests/ship_test.py b/tests/ship_test.py index 08ead9be2c..6770e8d183 100755 --- a/tests/ship_test.py +++ b/tests/ship_test.py @@ -30,7 +30,7 @@ extraArgs = appArgs.add(flag="--num-requests", type=int, help="How many requests that each ship_client requests", default=1) extraArgs = appArgs.add(flag="--num-clients", type=int, help="How many ship_clients should be started", default=1) extraArgs = appArgs.add_bool(flag="--unix-socket", help="Run ship over unix socket") -args = TestHelper.parse_args({"-p", "-n","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) +args = TestHelper.parse_args({"-p", "-n","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}, applicationSpecificArgs=appArgs) Utils.Debug=args.v totalProducerNodes=args.p @@ -40,6 +40,7 @@ totalNonProducerNodes=totalNodes-totalProducerNodes totalProducers=totalProducerNodes cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=TestHelper.DEFAULT_WALLET_PORT @@ -63,7 +64,7 @@ specificExtraNodeosArgs[shipNodeNum] += "--state-history-unix-socket-path ship.sock" if cluster.launch(pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + totalNodes=totalNodes, totalProducers=totalProducers, activateIF=activateIF, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: Utils.cmdError("launcher") Utils.errorExit("Failed to stand up eos cluster.") diff --git a/tests/subjective_billing_test.py b/tests/subjective_billing_test.py index 57416307df..c6685808a2 100755 --- a/tests/subjective_billing_test.py +++ b/tests/subjective_billing_test.py @@ -58,7 +58,7 @@ "3": "--subjective-account-decay-time-minutes=1" } Print("Stand up cluster") - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, activateIF=True, extraNodeosArgs=" --http-max-response-time-ms 990000 --disable-subjective-api-billing false ", specificExtraNodeosArgs=specificArgs ) is False: errorExit("Failed to stand up eos cluster.") diff --git a/tests/terminate-scenarios-test.py b/tests/terminate-scenarios-test.py index 561a8529f2..319db32fc1 100755 --- a/tests/terminate-scenarios-test.py +++ b/tests/terminate-scenarios-test.py @@ -18,7 +18,7 @@ errorExit=Utils.errorExit args=TestHelper.parse_args({"-d","-s","-c","--kill-sig","--keep-logs" - ,"--dump-error-details","-v","--leave-running" + ,"--activate-if","--dump-error-details","-v","--leave-running" ,"--terminate-at-block","--unshared"}) pnodes=1 topo=args.s @@ -27,6 +27,7 @@ debug=args.v total_nodes = pnodes killSignal=args.kill_sig +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details terminate=args.terminate_at_block @@ -49,7 +50,7 @@ pnodes, topo, delay, chainSyncStrategyStr)) Print("Stand up cluster") - if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay) is False: + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay, activateIF=activateIF) is False: errorExit("Failed to stand up eos cluster.") Print ("Wait for Cluster stabilization") @@ -57,6 +58,9 @@ if not cluster.waitOnClusterBlockNumSync(3): errorExit("Cluster never stabilized") + # make sure enough blocks produced to verify truncate works on restart + cluster.getNode(0).waitForBlock(terminate+5) + Print("Kill cluster node instance.") if cluster.killSomeEosInstances(1, killSignal) is False: errorExit("Failed to kill Eos instances") diff --git a/tests/test_chain_plugin.cpp b/tests/test_chain_plugin.cpp index 4d365e8a02..2bb1e99aff 100644 --- a/tests/test_chain_plugin.cpp +++ b/tests/test_chain_plugin.cpp @@ -10,9 +10,9 @@ #include #include #include -#include -#include #include +#include +#include using namespace eosio; using namespace eosio::chain; @@ -344,7 +344,7 @@ class chain_plugin_tester : public validating_tester { } produce_blocks( 250 ); - auto producer_keys = control->head_block_state()->active_schedule.producers; + auto producer_keys = control->active_producers().producers; BOOST_CHECK_EQUAL( 21u, producer_keys.size() ); BOOST_CHECK_EQUAL( name("defproducera"), producer_keys[0].producer_name ); diff --git a/tests/test_snapshot_scheduler.cpp b/tests/test_snapshot_scheduler.cpp index 1a560ca079..a03add72bb 100644 --- a/tests/test_snapshot_scheduler.cpp +++ b/tests/test_snapshot_scheduler.cpp @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(snapshot_scheduler_test) { chain_plugin* chain_plug = app->find_plugin(); plugin_promise.set_value({prod_plug, chain_plug}); - auto bs = chain_plug->chain().block_start.connect([&prod_plug, &at_block_20_promise](uint32_t bn) { + auto bs = chain_plug->chain().block_start().connect([&prod_plug, &at_block_20_promise](uint32_t bn) { if(bn == 20u) at_block_20_promise.set_value(); // catching pending snapshot diff --git a/tests/trace_plugin_test.py b/tests/trace_plugin_test.py index 995bca74eb..60093b48c2 100755 --- a/tests/trace_plugin_test.py +++ b/tests/trace_plugin_test.py @@ -22,7 +22,7 @@ def startEnv(self) : account_names = ["alice", "bob", "charlie"] abs_path = os.path.abspath(os.getcwd() + '/unittests/contracts/eosio.token/eosio.token.abi') traceNodeosArgs = " --verbose-http-errors --trace-rpc-abi eosio.token=" + abs_path - self.cluster.launch(totalNodes=2, extraNodeosArgs=traceNodeosArgs) + self.cluster.launch(totalNodes=2, activateIF=True, extraNodeosArgs=traceNodeosArgs) self.walletMgr.launch() testWalletName="testwallet" testWallet=self.walletMgr.create(testWalletName, [self.cluster.eosioAccount, self.cluster.defproduceraAccount]) diff --git a/tests/transition_to_if.py b/tests/transition_to_if.py new file mode 100755 index 0000000000..249248d3c7 --- /dev/null +++ b/tests/transition_to_if.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +from TestHarness import Cluster, TestHelper, Utils, WalletMgr +from TestHarness.TestHelper import AppArgs + +############################################################### +# transition_to_if +# +# Transition to instant-finality with multiple producers (at least 4). +# +############################################################### + + +Print=Utils.Print +errorExit=Utils.errorExit + +appArgs = AppArgs() +args=TestHelper.parse_args({"-p","-d","-s","--keep-logs","--dump-error-details","-v","--leave-running","--unshared"}, + applicationSpecificArgs=appArgs) +pnodes=args.p if args.p > 4 else 4 +delay=args.d +topo=args.s +debug=args.v +prod_count = 1 # per node prod count +total_nodes=pnodes+1 +irreversibleNodeId=pnodes +dumpErrorDetails=args.dump_error_details + +Utils.Debug=debug +testSuccessful=False + +cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +walletMgr=WalletMgr(True) + +try: + TestHelper.printSystemInfo("BEGIN") + + cluster.setWalletMgr(walletMgr) + + Print(f'producing nodes: {pnodes}, topology: {topo}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}') + + numTrxGenerators=2 + Print("Stand up cluster") + # For now do not load system contract as it does not support setfinalizer + specificExtraNodeosArgs = { irreversibleNodeId: "--read-mode irreversible"} + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, prodCount=prod_count, maximumP2pPerHost=total_nodes+numTrxGenerators, topo=topo, delay=delay, loadSystemContract=False, + activateIF=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: + errorExit("Failed to stand up eos cluster.") + + assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio", "launch should have waited for production to change" + + Print("Configure and launch txn generators") + targetTpsPerGenerator = 10 + testTrxGenDurationSec=60*60 + cluster.launchTrxGenerators(contractOwnerAcctName=cluster.eosioAccount.name, acctNamesList=[cluster.defproduceraAccount.name, cluster.defproducerbAccount.name], + acctPrivKeysList=[cluster.defproduceraAccount.activePrivateKey,cluster.defproducerbAccount.activePrivateKey], nodeId=cluster.getNode(0).nodeId, + tpsPerGenerator=targetTpsPerGenerator, numGenerators=numTrxGenerators, durationSec=testTrxGenDurationSec, + waitToComplete=False) + + status = cluster.waitForTrxGeneratorsSpinup(nodeId=cluster.getNode(0).nodeId, numGenerators=numTrxGenerators) + assert status is not None and status is not False, "ERROR: Failed to spinup Transaction Generators" + + assert cluster.activateInstantFinality(biosFinalizer=False), "Activate instant finality failed" + + assert cluster.biosNode.waitForLibToAdvance(), "Lib should advance after instant finality activated" + assert cluster.biosNode.waitForProducer("defproducera"), "Did not see defproducera" + assert cluster.biosNode.waitForHeadToAdvance(blocksToAdvance=13), "Head did not advance 13 blocks to next producer" + assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing on biosNode" + assert cluster.getNode(1).waitForLibToAdvance(), "Lib stopped advancing on Node 1" + assert cluster.getNode(irreversibleNodeId).waitForLibToAdvance(), f"Lib stopped advancing on Node {irreversibleNodeId}, irreversible node" + + info = cluster.biosNode.getInfo(exitOnError=True) + assert (info["head_block_num"] - info["last_irreversible_block_num"]) < 9, "Instant finality enabled LIB diff should be small" + + # launch setup node_00 (defproducera - defproducerf), node_01 (defproducerg - defproducerk), + # node_02 (defproducerl - defproducerp), node_03 (defproducerq - defproduceru) + # with setprods of (defproducera, defproducerg, defproducerl, defproducerq) + assert cluster.biosNode.waitForProducer("defproducerq"), "defproducerq did not produce" + + # should take effect in first block of defproducerg slot (so defproducerh) + assert cluster.setProds(["defproducerb", "defproducerh", "defproducerm", "defproducerr"]), "setprods failed" + setProdsBlockNum = cluster.biosNode.getBlockNum() + assert cluster.biosNode.waitForBlock(setProdsBlockNum+12+12+1), "Block of new producers not reached" + assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] == "defproducerh", "setprods should have taken effect" + assert cluster.getNode(4).waitForBlock(setProdsBlockNum + 12 + 12 + 1), "Block of new producers not reached on irreversible node" + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful=testSuccessful, dumpErrorDetails=dumpErrorDetails) + +exitCode = 0 if testSuccessful else 1 +exit(exitCode) diff --git a/tests/trx_finality_status_forked_test.py b/tests/trx_finality_status_forked_test.py index 8ee00b49ab..89e5bf735a 100755 --- a/tests/trx_finality_status_forked_test.py +++ b/tests/trx_finality_status_forked_test.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 - +import sys import time import decimal import json @@ -14,34 +14,32 @@ # trx_finality_status_forked_test # # Test to verify that transaction finality status feature is -# working appropriately when forks occur +# working appropriately when forks occur. +# Note this test does not use transaction retry as forked out +# transactions should always make it into a block unless they +# expire. # ############################################################### Print=Utils.Print errorExit=Utils.errorExit -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running", +args = TestHelper.parse_args({"--activate-if","--dump-error-details","--keep-logs","-v","--leave-running", "--wallet-port","--unshared"}) Utils.Debug=args.v -totalProducerNodes=2 +totalProducerNodes=4 totalNonProducerNodes=1 totalNodes=totalProducerNodes+totalNonProducerNodes maxActiveProducers=3 totalProducers=maxActiveProducers cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details walletPort=args.wallet_port walletMgr=WalletMgr(True, port=walletPort) testSuccessful=False -WalletdName=Utils.EosWalletName -ClientName="cleos" - -EOSIO_ACCT_PRIVATE_DEFAULT_KEY = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" -EOSIO_ACCT_PUBLIC_DEFAULT_KEY = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" - try: TestHelper.printSystemInfo("BEGIN") @@ -61,10 +59,11 @@ # *** setup topogrophy *** - # "bridge" shape connects defprocera through defproducerb (in node0) to each other and defproducerc is alone (in node01) - # and the only connection between those 2 groups is through the bridge node - if cluster.launch(prodCount=2, topo="bridge", pnodes=totalProducerNodes, - totalNodes=totalNodes, totalProducers=totalProducers, + # "bridge" shape connects defproducera (node0) defproducerb (node1) defproducerc (node2) to each other and defproducerd (node3) + # and the only connection between those 2 groups is through the bridge (node4) + if cluster.launch(topo="./tests/bridge_for_fork_test_shape.json", pnodes=totalProducerNodes, + totalNodes=totalNodes, totalProducers=totalProducerNodes, loadSystemContract=False, + activateIF=activateIF, biosFinalizer=False, specificExtraNodeosArgs=specificExtraNodeosArgs, extraNodeosArgs=extraNodeosArgs) is False: Utils.cmdError("launcher") @@ -74,24 +73,16 @@ # *** identify each node (producers and non-producing node) *** - nonProdNode=None - prodNodes=[] - producers=[] - for i, node in enumerate(cluster.getNodes()): - node.producers=Cluster.parseProducers(node.nodeId) - numProducers=len(node.producers) - Print(f"node {i} has producers={node.producers}") - if numProducers==0: - if nonProdNode is None: - nonProdNode=node - else: - Utils.errorExit("More than one non-producing nodes") - else: - prodNodes.append(node) - producers.extend(node.producers) - - prodAB=prodNodes[0] # defproducera, defproducerb - prodC=prodNodes[1] # defproducerc + prodNode0 = cluster.getNode(0) + prodNode1 = cluster.getNode(1) + prodNode2 = cluster.getNode(2) + prodNode3 = cluster.getNode(3) # other side of bridge + nonProdNode = cluster.getNode(4) + + prodNodes=[ prodNode0, prodNode1, prodNode2, prodNode3 ] + + prodA=prodNode0 # defproducera + prodD=prodNode3 # defproducerd # *** Identify a block where production is stable *** @@ -99,15 +90,6 @@ cluster.biosNode.kill(signal.SIGTERM) cluster.waitOnClusterSync(blockAdvancing=5) - Print("Creating account1") - account1 = Account('account1') - account1.ownerPublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY - account1.activePublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY - cluster.createAccountAndVerify(account1, cluster.eosioAccount, stakedDeposit=1000) - - Print("Validating accounts after bootstrap") - cluster.validateAccounts([account1]) - # *** Killing the "bridge" node *** Print('Sending command to kill "bridge" node to separate the 2 producer groups.') # kill at the beginning of the production window for defproducera, so there is time for the fork for @@ -124,22 +106,41 @@ while nonProdNode.verifyAlive() and count > 0: # wait on prodNode 0 since it will continue to advance, since defproducera and defproducerb are its producers Print("Wait for next block") - assert prodAB.waitForNextBlock(timeout=6), "Production node AB should continue to advance, even after bridge node is killed" + assert prodA.waitForNextBlock(timeout=6), "Production node A should continue to advance, even after bridge node is killed" count -= 1 assert not nonProdNode.verifyAlive(), "Bridge node should have been killed if test was functioning correctly." + assert prodD.waitForNextBlock(), "Prod node D should continue to advance, even after bridge node is killed" + def getState(status): assert status is not None, "ERROR: getTransactionStatus failed to return any status" assert "state" in status, \ - f"ERROR: getTransactionStatus returned a status object that didn't have a \"state\" field. state: {json.dumps(status, indent=1)}" + f"ERROR: getTransactionStatus returned a status object that didn't have a \"state\" field. status: {json.dumps(status, indent=1)}" return status["state"] + def getBlockNum(status): + assert status is not None, "ERROR: getTransactionStatus failed to return any status" + if "block_number" in status: + return status["block_number"] + assert "head_number" in status, \ + f"ERROR: getTransactionStatus returned a status object that didn't have a \"head_number\" field. status: {json.dumps(status, indent=1)}" + return status["head_number"] + + def getBlockID(status): + assert status is not None, "ERROR: getTransactionStatus failed to return any status" + if "block_id" in status: + return status["block_id"] + assert "head_id" in status, \ + f"ERROR: getTransactionStatus returned a status object that didn't have a \"head_id\" field. status: {json.dumps(status, indent=1)}" + return status["head_id"] + transferAmount = 10 - transfer = prodC.transferFunds(cluster.eosioAccount, account1, f"{transferAmount}.0000 {CORE_SYMBOL}", "fund account") + # Does not use transaction retry (not needed) + transfer = prodD.transferFunds(cluster.eosioAccount, cluster.defproduceraAccount, f"{transferAmount}.0000 {CORE_SYMBOL}", "fund account") transBlockNum = transfer['processed']['block_num'] - transId = prodC.getLastTrackedTransactionId() - retStatus = prodC.getTransactionStatus(transId) + transId = prodD.getLastTrackedTransactionId() + retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) localState = "LOCALLY_APPLIED" @@ -148,21 +149,21 @@ def getState(status): forkedOutState = "FORKED_OUT" unknownState = "UNKNOWN" - assert state == localState, \ - f"ERROR: getTransactionStatus didn't return \"{localState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + assert state == localState or state == inBlockState, \ + f"ERROR: getTransactionStatus didn't return \"{localState}\" or \"{inBlockState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" - assert prodC.waitForNextBlock(), "Production node C should continue to advance, even after bridge node is killed" + assert prodD.waitForNextBlock(), "Production node D should continue to advance, even after bridge node is killed" # since the Bridge node is killed when this producer is producing its last block in its window, there is plenty of time for the transfer to be # sent before the first block is created, but adding this to ensure it is in one of these blocks numTries = 2 while numTries > 0: - retStatus = prodC.getTransactionStatus(transId) + retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) if state == inBlockState: break numTries -= 1 - assert prodC.waitForNextBlock(), "Production node C should continue to advance, even after bridge node is killed" + assert prodD.waitForNextBlock(), "Production node D should continue to advance, even after bridge node is killed" Print(f"getTransactionStatus returned status: {json.dumps(retStatus, indent=1)}") assert state == inBlockState, \ @@ -174,57 +175,81 @@ def getState(status): if not nonProdNode.relaunch(): errorExit(f"Failure - (non-production) node {nonProdNode.nodeNum} should have restarted") - while prodC.getInfo()['last_irreversible_block_num'] < transBlockNum: - Print("Wait for LIB to move, which indicates prodC may have forked out the branch") - assert prodC.waitForLibToAdvance(60), \ - "ERROR: Network did not reach consensus after bridge node was restarted." - if prodC.getTransactionStatus(transId)['state'] == forkedOutState: + Print("Repeatedly check status looking for forked out state until after LIB moves and defproducerd") + while True: + info = prodD.getInfo() + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + blockNum = getBlockNum(retStatus) + if state == forkedOutState or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): break - retStatus = prodC.getTransactionStatus(transId) - state = getState(retStatus) + if state == irreversibleState: + Print(f"Transaction became irreversible before it could be found forked out: {json.dumps(retStatus, indent=1)}") + testSuccessful = True + sys.exit(0) assert state == forkedOutState, \ f"ERROR: getTransactionStatus didn't return \"{forkedOutState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ - f"\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" for prodNode in prodNodes: info=prodNode.getInfo() Print(f"node info: {json.dumps(info, indent=1)}") - assert prodC.waitForProducer("defproducerc"), \ - f"Waiting for prodC to produce, but it never happened" + \ - f"\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + assert prodD.waitForProducer("defproducerd"), \ + f"Waiting for prodD to produce, but it never happened" + \ + f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" - retStatus = prodC.getTransactionStatus(transId) + retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) - assert state == inBlockState, \ - f"ERROR: getTransactionStatus didn't return \"{inBlockState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ - f"\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + # it is possible for another fork switch to cause the trx to be forked out again + if state == forkedOutState or state == localState: + while True: + info = prodD.getInfo() + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + blockNum = getBlockNum(retStatus) + 2 # Add 2 to give time to move from locally applied to in-block + if (state == inBlockState or state == irreversibleState) or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): + break + + assert state == inBlockState or state == irreversibleState, \ + f"ERROR: getTransactionStatus didn't return \"{inBlockState}\" or \"{irreversibleState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ + f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" afterForkInBlockState = retStatus - afterForkBlockId = retStatus["block_id"] - assert afterForkInBlockState["block_number"] > originalInBlockState["block_number"], \ + afterForkBlockId = getBlockID(retStatus) + assert getBlockNum(afterForkInBlockState) > getBlockNum(originalInBlockState), \ "ERROR: The way the test is designed, the transaction should be added to a block that has a higher number than it was in originally before it was forked out." + \ f"\n\noriginal in block state: {json.dumps(originalInBlockState, indent=1)}\n\nafter fork in block state: {json.dumps(afterForkInBlockState, indent=1)}" - assert prodC.waitForBlock(afterForkInBlockState["block_number"], timeout=120, blockType=BlockType.lib), \ - f"ERROR: Block never finalized.\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + \ + assert prodD.waitForBlock(getBlockNum(afterForkInBlockState), timeout=120, blockType=BlockType.lib), \ + f"ERROR: Block never finalized.\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodD.getInfo(), indent=1)}" + \ f"\n\nafter fork in block state: {json.dumps(afterForkInBlockState, indent=1)}" - retStatus = prodC.getTransactionStatus(transId) - if afterForkBlockId != retStatus["block_id"]: # might have been forked out, if so wait for new block to become LIB - assert prodC.waitForBlock(retStatus["block_number"], timeout=120, blockType=BlockType.lib), \ - f"ERROR: Block never finalized.\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + \ + retStatus = prodD.getTransactionStatus(transId) + if afterForkBlockId != getBlockID(retStatus): # might have been forked out, if so wait for new block to become LIB + assert prodD.waitForBlock(getBlockNum(retStatus), timeout=120, blockType=BlockType.lib), \ + f"ERROR: Block never finalized.\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodD.getInfo(), indent=1)}" + \ f"\n\nafter fork in block state: {json.dumps(afterForkInBlockState, indent=1)}" - retStatus = prodC.getTransactionStatus(transId) + retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) + # it is possible for another fork switch to cause the trx to be forked out again + if state == forkedOutState: + while True: + info = prodD.getInfo() + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + blockNum = getBlockNum(retStatus) + if state == irreversibleState or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): + break + assert state == irreversibleState, \ f"ERROR: getTransactionStatus didn't return \"{irreversibleState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ - f"\n\nprod AB info: {json.dumps(prodAB.getInfo(), indent=1)}\n\nprod C info: {json.dumps(prodC.getInfo(), indent=1)}" + f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" testSuccessful=True finally: diff --git a/tests/trx_finality_status_test.py b/tests/trx_finality_status_test.py index 0ec660518f..86643bab67 100755 --- a/tests/trx_finality_status_test.py +++ b/tests/trx_finality_status_test.py @@ -28,13 +28,14 @@ errorExit=Utils.errorExit appArgs=AppArgs() -args = TestHelper.parse_args({"-n", "--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}) +args = TestHelper.parse_args({"-n","--activate-if","--dump-error-details","--keep-logs","-v","--leave-running","--unshared"}) Utils.Debug=args.v pnodes=3 totalNodes=args.n if totalNodes<=pnodes+2: totalNodes=pnodes+2 cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) +activateIF=args.activate_if dumpErrorDetails=args.dump_error_details prodCount=1 walletPort=TestHelper.DEFAULT_WALLET_PORT @@ -58,7 +59,7 @@ extraNodeosArgs=" --transaction-finality-status-max-storage-size-gb 1 " + \ f"--transaction-finality-status-success-duration-sec {successDuration} --transaction-finality-status-failure-duration-sec {failure_duration}" extraNodeosArgs+=" --http-max-response-time-ms 990000" - if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, + if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount, activateIF=activateIF, topo="line", extraNodeosArgs=extraNodeosArgs) is False: Utils.errorExit("Failed to stand up eos cluster.") @@ -170,7 +171,7 @@ def validate(status, knownTrx=True): status.append(testNode.getTransactionStatus(transId)) state = getState(status[1]) - assert state == inBlockState, f"ERROR: getTransactionStatus never returned a \"{inBlockState}\" state" + assert state == inBlockState or state == irreversibleState, f"ERROR: getTransactionStatus never returned a \"{inBlockState}\" state or \"{irreversibleState}\"" validateTrxState(status[1], present=True) diff --git a/tests/trx_generator/main.cpp b/tests/trx_generator/main.cpp index fab8fba31c..2ca977e14e 100644 --- a/tests/trx_generator/main.cpp +++ b/tests/trx_generator/main.cpp @@ -203,6 +203,7 @@ int main(int argc, char** argv) { et::trx_tps_tester tester{generator, monitor, tester_config}; if (!tester.run()) { + wlog("Exiting main with OTHER_FAIL"); return OTHER_FAIL; } } else { @@ -212,14 +213,16 @@ int main(int argc, char** argv) { et::trx_tps_tester tester{generator, monitor, tester_config}; if (!tester.run()) { + wlog("Exiting main with OTHER_FAIL"); return OTHER_FAIL; } } if (monitor->terminated_early()) { + wlog("Exiting main with TERMINATED_EARLY"); return TERMINATED_EARLY; } - - return SUCCESS; + ilog("Exiting main SUCCESS"); + return SUCCESS; } diff --git a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py index e042e60487..8822546307 100755 --- a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py +++ b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py @@ -356,6 +356,9 @@ def stepSetSystemContract(): # DISABLE_DEFERRED_TRXS_STAGE_2 - PREVENT PREVIOUSLY SCHEDULED DEFERRED TRANSACTIONS FROM REACHING OTHER NODE # THIS DEPENDS ON DISABLE_DEFERRED_TRXS_STAGE_1 retry(args.cleos + 'push action eosio activate \'["09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16"]\' -p eosio@active') + # INSTANT_FINALITY + # Depends on WTMSIG_BLOCK_SIGNATURES , BLS_PRIMITIVES2 , DISALLOW_EMPTY_PRODUCER_SCHEDULE , ACTION_RETURN_VALUE + retry(args.cleos + 'push action eosio activate \'["18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390"]\' -p eosio@active') sleep(1) # install eosio.system latest version diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index c1e3eed7a2..65c3a66d28 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -88,20 +88,23 @@ target_include_directories( unit_test PUBLIC add_test(NAME protocol_feature_digest_unit_test COMMAND unit_test --run_test=protocol_feature_digest_tests --report_level=detailed --color_output) set(ctest_tests "protocol_feature_digest_tests") foreach(TEST_SUITE ${UNIT_TESTS}) # create an independent target for each test suite - execute_process(COMMAND sh -c "grep -E 'BOOST_AUTO_TEST_SUITE\\s*[(]' '${TEST_SUITE}' | grep -vE '//.*BOOST_AUTO_TEST_SUITE\\s*[(]' | cut -d ')' -f 1 | cut -d '(' -f 2" OUTPUT_VARIABLE SUITE_NAME OUTPUT_STRIP_TRAILING_WHITESPACE) # get the test suite name from the *.cpp file - if (NOT "" STREQUAL "${SUITE_NAME}") # ignore empty lines - execute_process(COMMAND sh -c "echo ${SUITE_NAME} | sed -e 's/s$//' | sed -e 's/_test$//'" OUTPUT_VARIABLE TRIMMED_SUITE_NAME OUTPUT_STRIP_TRAILING_WHITESPACE) # trim "_test" or "_tests" from the end of ${SUITE_NAME} - # to run unit_test with all log from blockchain displayed, put "--verbose" after "--", i.e. "unit_test -- --verbose" - foreach(RUNTIME ${EOSIO_WASM_RUNTIMES}) - add_test(NAME ${TRIMMED_SUITE_NAME}_unit_test_${RUNTIME} COMMAND unit_test --run_test=${SUITE_NAME} --report_level=detailed --color_output -- --${RUNTIME}) - # build list of tests to run during coverage testing - if(ctest_tests) - string(APPEND ctest_tests "|") - endif() - string(APPEND ctest_tests ${TRIMMED_SUITE_NAME}_unit_test_$RUNTIME) - endforeach() - endif() -endforeach(TEST_SUITE) + execute_process(COMMAND sh -c "grep -E 'BOOST_AUTO_TEST_SUITE\\s*[(]' '${TEST_SUITE}' | grep -vE '//.*BOOST_AUTO_TEST_SUITE\\s*[(]' | cut -d ')' -f 1 | cut -d '(' -f 2" OUTPUT_VARIABLE SUITE_NAMES OUTPUT_STRIP_TRAILING_WHITESPACE) # get the test suite name from the *.cpp file + string(REPLACE "\n" ";" SUITE_NAMES "${SUITE_NAMES}") + foreach(SUITE_NAME IN LISTS SUITE_NAMES) + if (NOT "" STREQUAL "${SUITE_NAME}") # ignore empty lines + execute_process(COMMAND sh -c "echo ${SUITE_NAME} | sed -e 's/s$//' | sed -e 's/_test$//'" OUTPUT_VARIABLE TRIMMED_SUITE_NAME OUTPUT_STRIP_TRAILING_WHITESPACE) # trim "_test" or "_tests" from the end of ${SUITE_NAME} + # to run unit_test with all log from blockchain displayed, put "--verbose" after "--", i.e. "unit_test -- --verbose" + foreach(RUNTIME ${EOSIO_WASM_RUNTIMES}) + add_test(NAME ${TRIMMED_SUITE_NAME}_unit_test_${RUNTIME} COMMAND unit_test --run_test=${SUITE_NAME} --report_level=detailed --color_output -- --${RUNTIME}) + # build list of tests to run during coverage testing + if(ctest_tests) + string(APPEND ctest_tests "|") + endif() + string(APPEND ctest_tests ${TRIMMED_SUITE_NAME}_unit_test_$RUNTIME) + endforeach() + endif() + endforeach() +endforeach() set(ctest_tests "'${ctest_tests}' -j8") # surround test list string in apostrophies # The following tests are known to take the longest, bump up their cost (priority) so that they'll run first diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 598f230bdd..f96fe14ec3 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -888,7 +889,7 @@ BOOST_AUTO_TEST_CASE(light_validation_skip_cfa) try { other.execute_setup_policy( setup_policy::full ); transaction_trace_ptr other_trace; - auto cc = other.control->applied_transaction.connect( [&](std::tuple x) { + auto cc = other.control->applied_transaction().connect( [&](std::tuple x) { auto& t = std::get<0>(x); if( t && t->id == trace->id ) { other_trace = t; @@ -906,16 +907,17 @@ BOOST_AUTO_TEST_CASE(light_validation_skip_cfa) try { BOOST_CHECK(*trace->receipt == *other_trace->receipt); BOOST_CHECK_EQUAL(2, other_trace->action_traces.size()); + auto check_action_traces = [](const auto& t, const auto& ot) { + BOOST_CHECK_EQUAL("", ot.console); // cfa not executed for light validation (trusted producer) + BOOST_CHECK_EQUAL(t.receipt->global_sequence, ot.receipt->global_sequence); + BOOST_CHECK_EQUAL(t.digest_legacy(), ot.digest_legacy()); // digest_legacy because test doesn't switch to Savanna + }; + BOOST_CHECK(other_trace->action_traces.at(0).context_free); // cfa - BOOST_CHECK_EQUAL("", other_trace->action_traces.at(0).console); // cfa not executed for light validation (trusted producer) - BOOST_CHECK_EQUAL(trace->action_traces.at(0).receipt->global_sequence, other_trace->action_traces.at(0).receipt->global_sequence); - BOOST_CHECK_EQUAL(trace->action_traces.at(0).receipt->digest(), other_trace->action_traces.at(0).receipt->digest()); + check_action_traces(trace->action_traces.at(0), other_trace->action_traces.at(0)); BOOST_CHECK(!other_trace->action_traces.at(1).context_free); // non-cfa - BOOST_CHECK_EQUAL("", other_trace->action_traces.at(1).console); - BOOST_CHECK_EQUAL(trace->action_traces.at(1).receipt->global_sequence, other_trace->action_traces.at(1).receipt->global_sequence); - BOOST_CHECK_EQUAL(trace->action_traces.at(1).receipt->digest(), other_trace->action_traces.at(1).receipt->digest()); - + check_action_traces(trace->action_traces.at(1), other_trace->action_traces.at(1)); other.close(); @@ -1466,12 +1468,12 @@ void transaction_tests(T& chain) { { chain.produce_blocks(10); transaction_trace_ptr trace; - auto c = chain.control->applied_transaction.connect([&](std::tuple x) { + auto c = chain.control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t && t->receipt && t->receipt->status != transaction_receipt::executed) { trace = t; } } ); signed_block_ptr block; - auto c2 = chain.control->accepted_block.connect([&](block_signal_params t) { + auto c2 = chain.control->accepted_block().connect([&](block_signal_params t) { const auto& [ b, id ] = t; block = b; }); @@ -1651,7 +1653,7 @@ BOOST_AUTO_TEST_CASE(deferred_inline_action_limit) { try { chain2.push_block(block); transaction_trace_ptr trace; - auto c = chain.control->applied_transaction.connect([&](std::tuple x) { + auto c = chain.control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t->scheduled) { trace = t; } } ); @@ -1686,7 +1688,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable //schedule { transaction_trace_ptr trace; - auto c = control->applied_transaction.connect([&](std::tuple x) { + auto c = control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t->scheduled) { trace = t; } } ); @@ -1709,7 +1711,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable { transaction_trace_ptr trace; uint32_t count = 0; - auto c = control->applied_transaction.connect([&](std::tuple x) { + auto c = control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t && t->scheduled) { trace = t; ++count; } } ); @@ -1735,7 +1737,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable { transaction_trace_ptr trace; uint32_t count = 0; - auto c = control->applied_transaction.connect([&](std::tuple x) { + auto c = control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t && t->scheduled) { trace = t; ++count; } } ); @@ -1761,7 +1763,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable //schedule and cancel { transaction_trace_ptr trace; - auto c = control->applied_transaction.connect([&](std::tuple x) { + auto c = control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t && t->scheduled) { trace = t; } } ); @@ -1784,7 +1786,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable //repeated deferred transactions { vector traces; - auto c = control->applied_transaction.connect([&](std::tuple x) { + auto c = control->applied_transaction().connect([&](std::tuple x) { auto& t = std::get<0>(x); if (t && t->scheduled) { traces.push_back( t ); @@ -3857,4 +3859,189 @@ BOOST_AUTO_TEST_CASE(get_code_hash_tests) { try { check("test"_n, 3); } FC_LOG_AND_RETHROW() } +// test set_finalizer host function serialization and tester set_finalizers +BOOST_AUTO_TEST_CASE(set_finalizer_test) { try { + validating_tester t; + + uint32_t lib = 0; + signed_block_ptr lib_block; + t.control->irreversible_block().connect([&](const block_signal_params& t) { + const auto& [ block, id ] = t; + lib = block->block_num(); + lib_block = block; + }); + + t.produce_block(); + + // Create finalizer accounts + vector finalizers = { + "inita"_n, "initb"_n, "initc"_n, "initd"_n, "inite"_n, "initf"_n, "initg"_n, + "inith"_n, "initi"_n, "initj"_n, "initk"_n, "initl"_n, "initm"_n, "initn"_n, + "inito"_n, "initp"_n, "initq"_n, "initr"_n, "inits"_n, "initt"_n, "initu"_n + }; + + t.create_accounts(finalizers); + t.produce_block(); + + // activate savanna + t.set_finalizers(finalizers); + // this block contains the header extension for the instant finality, savanna activated when it is LIB + auto block = t.produce_block(); + + std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional fin_policy = std::get(*ext).new_finalizer_policy; + BOOST_TEST(!!fin_policy); + BOOST_TEST(fin_policy->finalizers.size() == finalizers.size()); + BOOST_TEST(fin_policy->generation == 1); + BOOST_TEST(fin_policy->threshold == finalizers.size() / 3 * 2 + 1); + block_id_type if_genesis_block_id = block->calculate_id(); + + for (block_num_type active_block_num = block->block_num(); active_block_num > lib; t.produce_block()) { + (void)active_block_num; // avoid warning + }; + + // lib_block is IF Genesis Block + // block is IF Critical Block + auto fb = t.control->fetch_block_by_id(lib_block->calculate_id()); + BOOST_REQUIRE(!!fb); + BOOST_TEST(fb->calculate_id() == lib_block->calculate_id()); + ext = fb->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE(!!ext); + BOOST_TEST(if_genesis_block_id == fb->calculate_id()); + + auto lib_after_transition = lib; + // block after IF Critical Block is IF Proper Block + block = t.produce_block(); + + // lib must advance after 3 blocks + t.produce_blocks(3); + BOOST_CHECK_GT(lib, lib_after_transition); +} FC_LOG_AND_RETHROW() } + +void test_finality_transition(const vector& accounts, const base_tester::finalizer_policy_input& input, bool lib_advancing_expected) { + validating_tester t; + + uint32_t lib = 0; + signed_block_ptr lib_block; + t.control->irreversible_block().connect([&](const block_signal_params& t) { + const auto& [ block, id ] = t; + lib = block->block_num(); + lib_block = block; + }); + + t.produce_block(); + + // Create finalizer accounts + t.create_accounts(accounts); + t.produce_block(); + + // activate savanna + t.set_finalizers(input); + // this block contains the header extension for the instant finality, savanna activated when it is LIB + auto block = t.produce_block(); + + std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional fin_policy = std::get(*ext).new_finalizer_policy; + BOOST_TEST(!!fin_policy); + BOOST_TEST(fin_policy->finalizers.size() == accounts.size()); + BOOST_TEST(fin_policy->generation == 1); + block_id_type if_genesis_block_id = block->calculate_id(); + + block_num_type active_block_num = block->block_num(); + while (active_block_num > lib) { + block = t.produce_block(); + } + // lib_block is IF Genesis Block + // block is IF Critical Block + auto fb = t.control->fetch_block_by_id(lib_block->calculate_id()); + BOOST_REQUIRE(!!fb); + BOOST_TEST(fb->calculate_id() == lib_block->calculate_id()); + ext = fb->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE(!!ext); + BOOST_TEST(if_genesis_block_id == fb->calculate_id()); + + auto lib_after_transition = lib; + // block after IF Critical Block is IF Proper Block + block = t.produce_block(); + + t.produce_blocks(4); + if( lib_advancing_expected ) { + BOOST_CHECK_GT(lib, lib_after_transition); + } else { + BOOST_CHECK_EQUAL(lib, lib_after_transition); + } +} + +BOOST_AUTO_TEST_CASE(threshold_equal_to_half_weight_sum_test) { try { + vector account_names = { + "alice"_n, "bob"_n, "carol"_n + }; + + // threshold set to half of the weight sum of finalizers + base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "alice"_n, .weight = 1}, + {.name = "bob"_n, .weight = 2}, + {.name = "carol"_n, .weight = 3} }, + .threshold = 3, + .local_finalizers = {"alice"_n, "bob"_n} + }; + + // threshold must be greater than half of the sum of the weights + BOOST_REQUIRE_THROW( test_finality_transition(account_names, policy_input, false), eosio_assert_message_exception ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(votes_equal_to_threshold_test) { try { + vector account_names = { + "alice"_n, "bob"_n, "carol"_n + }; + + base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "alice"_n, .weight = 1}, + {.name = "bob"_n, .weight = 3}, + {.name = "carol"_n, .weight = 5} }, + .threshold = 5, + .local_finalizers = {"carol"_n} + }; + + // Carol votes with weight 5 and threshold 5 + test_finality_transition(account_names, policy_input, true); // lib_advancing_expected +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(votes_greater_than_threshold_test) { try { + vector account_names = { + "alice"_n, "bob"_n, "carol"_n + }; + + base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "alice"_n, .weight = 1}, + {.name = "bob"_n, .weight = 4}, + {.name = "carol"_n, .weight = 2} }, + .threshold = 4, + .local_finalizers = {"alice"_n, "bob"_n} + }; + + // alice and bob vote with weight 5 and threshold 4 + test_finality_transition(account_names, policy_input, true); // lib_advancing_expected +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(votes_less_than_threshold_test) { try { + vector account_names = { + "alice"_n, "bob"_n, "carol"_n + }; + + base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "alice"_n, .weight = 1}, + {.name = "bob"_n, .weight = 3}, + {.name = "carol"_n, .weight = 10} }, + .threshold = 8, + .local_finalizers = {"alice"_n, "bob"_n} + }; + + // alice and bob vote with weight 4 but threshold 8. LIB cannot advance + test_finality_transition(account_names, policy_input, false); // not expecting lib advancing +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/block_header_tests.cpp b/unittests/block_header_tests.cpp new file mode 100644 index 0000000000..22b68345c9 --- /dev/null +++ b/unittests/block_header_tests.cpp @@ -0,0 +1,111 @@ +#include +#include + +using namespace eosio::chain; + +BOOST_AUTO_TEST_SUITE(block_header_tests) + +// test for block header without extension +BOOST_AUTO_TEST_CASE(block_header_without_extension_test) +{ + block_header header; + std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE(!ext); +} + +// test for empty instant_finality_extension +BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) +{ + block_header header; + constexpr uint32_t last_qc_block_num {0}; + constexpr bool is_last_strong_qc {false}; + + emplace_extension( + header.header_extensions, + instant_finality_extension::extension_id(), + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_last_strong_qc}, + std::optional{}, std::shared_ptr{}} ) + ); + + std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE( !!ext ); + + const auto& if_extension = std::get(*ext); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_strong_qc, is_last_strong_qc ); + BOOST_REQUIRE( !if_extension.new_finalizer_policy ); + BOOST_REQUIRE( !if_extension.new_proposer_policy ); +} + +// test for instant_finality_extension uniqueness +BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) +{ + block_header header; + + emplace_extension( + header.header_extensions, + instant_finality_extension::extension_id(), + fc::raw::pack( instant_finality_extension{qc_claim_t{0, false}, {std::nullopt}, + std::shared_ptr{}} ) + ); + + std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q" }} }; + finalizer_policy new_finalizer_policy; + new_finalizer_policy.generation = 1; + new_finalizer_policy.threshold = 100; + new_finalizer_policy.finalizers = finalizers; + + proposer_policy_ptr new_proposer_policy = std::make_shared(1, block_timestamp_type{200}, producer_authority_schedule{} ); + + emplace_extension( + header.header_extensions, + instant_finality_extension::extension_id(), + fc::raw::pack( instant_finality_extension{qc_claim_t{100, true}, new_finalizer_policy, new_proposer_policy} ) + ); + + BOOST_CHECK_THROW(header.validate_and_extract_header_extensions(), invalid_block_header_extension); +} + +// test for instant_finality_extension with values +BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) +{ + block_header header; + constexpr uint32_t last_qc_block_num {10}; + constexpr bool is_strong_qc {true}; + + std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q" }} }; + finalizer_policy new_finalizer_policy; + new_finalizer_policy.generation = 1; + new_finalizer_policy.threshold = 100; + new_finalizer_policy.finalizers = finalizers; + + proposer_policy_ptr new_proposer_policy = std::make_shared(1, block_timestamp_type{200}, producer_authority_schedule{} ); + + emplace_extension( + header.header_extensions, + instant_finality_extension::extension_id(), + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_strong_qc}, new_finalizer_policy, new_proposer_policy} ) + ); + + std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE( !!ext ); + + const auto& if_extension = std::get(*ext); + + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_strong_qc, is_strong_qc ); + + BOOST_REQUIRE( !!if_extension.new_finalizer_policy ); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->generation, 1u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->threshold, 100u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].description, "test description"); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].weight, 50u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].public_key.to_string(), "PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q"); + + BOOST_REQUIRE( !!if_extension.new_proposer_policy ); + BOOST_REQUIRE_EQUAL(if_extension.new_proposer_policy->schema_version, 1u); + fc::time_point t = (fc::time_point)(if_extension.new_proposer_policy->active_time); + BOOST_REQUIRE_EQUAL(t.time_since_epoch().to_seconds(), 946684900ll); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp new file mode 100644 index 0000000000..434dace32b --- /dev/null +++ b/unittests/block_state_tests.cpp @@ -0,0 +1,380 @@ +#include +#include + +#include +#include +#include + +#include + +BOOST_AUTO_TEST_SUITE(block_state_tests) + +BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { + using namespace eosio::chain; + using namespace fc::crypto::blslib; + + digest_type block_id(fc::sha256("0000000000000000000000000000001")); + + digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); + + weak_digest_t weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + + const size_t num_finalizers = 3; + + // initialize a set of private keys + std::vector private_key { + bls_private_key("PVT_BLS_foNjZTu0k6qM5ftIrqC5G_sim1Rg7wq3cRUaJGvNtm2rM89K"), + bls_private_key("PVT_BLS_FWK1sk_DJnoxNvUNhwvJAYJFcQAFtt_mCtdQCUPQ4jN1K7eT"), + bls_private_key("PVT_BLS_tNAkC5MnI-fjHWSX7la1CPC2GIYgzW5TBfuKFPagmwVVsOeW"), + }; + + // construct finalizers + std::vector public_key(num_finalizers); + std::vector finalizers(num_finalizers); + for (size_t i = 0; i < num_finalizers; ++i) { + public_key[i] = private_key[i].get_public_key(); + finalizers[i] = finalizer_authority{ "test", 1, public_key[i] }; + } + + { // all finalizers can aggregate votes + block_state_ptr bsp = std::make_shared(); + bsp->active_finalizer_policy = std::make_shared( 10, 15, finalizers ); + bsp->strong_digest = strong_digest; + bsp->weak_digest = weak_digest; + bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; + + for (size_t i = 0; i < num_finalizers; ++i) { + bool strong = (i % 2 == 0); // alternate strong and weak + auto sig = strong ? private_key[i].sign(strong_digest.to_uint8_span()) : private_key[i].sign(weak_digest); + vote_message vote{ block_id, strong, public_key[i], sig }; + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); + } + } + + { // public and private keys mismatched + block_state_ptr bsp = std::make_shared(); + bsp->active_finalizer_policy = std::make_shared( 10, 15, finalizers ); + bsp->strong_digest = strong_digest; + bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; + + vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest.to_uint8_span()) }; + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); + } + + { // duplicate votes + block_state_ptr bsp = std::make_shared(); + bsp->active_finalizer_policy = std::make_shared( 10, 15, finalizers ); + bsp->strong_digest = strong_digest; + bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; + + vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest.to_uint8_span()) }; + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); + } + + { // public key does not exit in finalizer set + block_state_ptr bsp = std::make_shared(); + bsp->active_finalizer_policy = std::make_shared( 10, 15, finalizers ); + bsp->strong_digest = strong_digest; + bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; + + bls_private_key new_private_key{ "PVT_BLS_Wfs3KzfTI2P5F85PnoHXLnmYgSbp-XpebIdS6BUCHXOKmKXK" }; + bls_public_key new_public_key{ new_private_key.get_public_key() }; + + vote_message vote {block_id, true, new_public_key, private_key[0].sign(strong_digest.to_uint8_span()) }; + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); + } +} FC_LOG_AND_RETHROW(); + +void do_quorum_test(const std::vector& weights, + uint64_t threshold, + bool strong, + const std::vector& to_vote, + bool expected_quorum) { + using namespace eosio::chain; + using namespace fc::crypto::blslib; + + digest_type block_id(fc::sha256("0000000000000000000000000000001")); + digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); + auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + + // initialize a set of private keys + std::vector private_key { + bls_private_key("PVT_BLS_foNjZTu0k6qM5ftIrqC5G_sim1Rg7wq3cRUaJGvNtm2rM89K"), + bls_private_key("PVT_BLS_FWK1sk_DJnoxNvUNhwvJAYJFcQAFtt_mCtdQCUPQ4jN1K7eT"), + bls_private_key("PVT_BLS_tNAkC5MnI-fjHWSX7la1CPC2GIYgzW5TBfuKFPagmwVVsOeW"), + }; + const size_t num_finalizers = private_key.size(); + + // construct finalizers + std::vector public_key(num_finalizers); + std::vector finalizers(num_finalizers); + for (size_t i = 0; i < num_finalizers; ++i) { + public_key[i] = private_key[i].get_public_key(); + finalizers[i] = finalizer_authority{ "test", weights[i], public_key[i] }; + } + + block_state_ptr bsp = std::make_shared(); + constexpr uint32_t generation = 1; + bsp->active_finalizer_policy = std::make_shared( generation, threshold, finalizers ); + bsp->strong_digest = strong_digest; + bsp->weak_digest = weak_digest; + bsp->pending_qc = pending_quorum_certificate{ num_finalizers, threshold, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; + + for (size_t i = 0; i < num_finalizers; ++i) { + if( to_vote[i] ) { + auto sig = strong ? private_key[i].sign(strong_digest.to_uint8_span()) : private_key[i].sign(weak_digest); + vote_message vote{ block_id, strong, public_key[i], sig }; + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); + } + } + + BOOST_REQUIRE_EQUAL(bsp->pending_qc.is_quorum_met(), expected_quorum); +} + +BOOST_AUTO_TEST_CASE(quorum_test) try { + std::vector weights{1, 3, 5}; + constexpr uint64_t threshold = 4; + + { // 1 strong vote, quorum not met + constexpr bool strong = true; + std::vector to_vote{true, false, false}; // finalizer 0 voting + constexpr bool expected_quorum_met = false; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } + + { // 2 strong votes, quorum met + constexpr bool strong = true; + std::vector to_vote{true, true, false}; // finalizers 0 and 1 voting + constexpr bool expected_quorum_met = true; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } + + { // 1 strong vote, quorum met + constexpr bool strong = true; + std::vector to_vote{false, false, true}; // finalizer 2 voting + constexpr bool expected_quorum_met = true; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } + + { // 1 weak vote, quorum not met + constexpr bool strong = false; + std::vector to_vote{true, false, false}; // finalizer 0 voting + constexpr bool expected_quorum_met = false; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } + + { // 2 weak votes, quorum met + constexpr bool strong = false; + std::vector to_vote{true, true, false}; // finalizers 0 and 1 voting + constexpr bool expected_quorum_met = true; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } + + { // 1 weak vote, quorum met + constexpr bool strong = false; + std::vector to_vote{false, false, true}; // finalizer 2 voting + constexpr bool expected_quorum_met = true; + do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met ); + } +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(verify_qc_test) try { + using namespace eosio::chain; + using namespace fc::crypto::blslib; + + // prepare digests + digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); + auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + + // initialize a set of private keys + std::vector private_key { + bls_private_key("PVT_BLS_foNjZTu0k6qM5ftIrqC5G_sim1Rg7wq3cRUaJGvNtm2rM89K"), + bls_private_key("PVT_BLS_FWK1sk_DJnoxNvUNhwvJAYJFcQAFtt_mCtdQCUPQ4jN1K7eT"), + bls_private_key("PVT_BLS_tNAkC5MnI-fjHWSX7la1CPC2GIYgzW5TBfuKFPagmwVVsOeW"), + }; + auto num_finalizers = private_key.size(); + + // construct finalizers, with weight 1, 2, 3 respectively + std::vector public_key(num_finalizers); + std::vector finalizers(num_finalizers); + for (size_t i = 0; i < num_finalizers; ++i) { + public_key[i] = private_key[i].get_public_key(); + uint64_t weight = i + 1; + finalizers[i] = finalizer_authority{ "test", weight, public_key[i] }; + } + + // consturct a test bsp + block_state_ptr bsp = std::make_shared(); + constexpr uint32_t generation = 1; + constexpr uint64_t threshold = 4; // 2/3 of total weights of 6 + bsp->active_finalizer_policy = std::make_shared( generation, threshold, finalizers ); + bsp->strong_digest = strong_digest; + bsp->weak_digest = weak_digest; + + { // valid strong QC + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + strong_votes[2] = 1; // finalizer 2 voted with weight 3 + + bls_signature sig_0 = private_key[0].sign(strong_digest.to_uint8_span()); + bls_signature sig_2 = private_key[2].sign(strong_digest.to_uint8_span()); + bls_aggregate_signature agg_sig; + agg_sig.aggregate(sig_0); + agg_sig.aggregate(sig_2); + + // create a valid_quorum_certificate + valid_quorum_certificate qc{strong_votes, {}, agg_sig}; + + BOOST_REQUIRE_NO_THROW( bsp->verify_qc(qc) ); + } + + { // valid weak QC + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + bls_signature strong_sig = private_key[0].sign(strong_digest.to_uint8_span()); + + hs_bitset weak_votes(num_finalizers); + weak_votes[2] = 1; // finalizer 2 voted with weight 3 + bls_signature weak_sig = private_key[2].sign(weak_digest); + + bls_aggregate_signature agg_sig; + agg_sig.aggregate(strong_sig); + agg_sig.aggregate(weak_sig); + + valid_quorum_certificate qc(strong_votes, weak_votes, agg_sig); + BOOST_REQUIRE_NO_THROW( bsp->verify_qc(qc) ); + } + + { // valid strong QC signed by all finalizers + hs_bitset strong_votes(num_finalizers); + std::vector sigs(num_finalizers); + bls_aggregate_signature agg_sig; + + for (auto i = 0u; i < num_finalizers; ++i) { + strong_votes[i] = 1; + sigs[i] = private_key[i].sign(strong_digest.to_uint8_span()); + agg_sig.aggregate(sigs[i]); + } + + // create a valid_quorum_certificate + valid_quorum_certificate qc(strong_votes, {}, agg_sig); + + BOOST_REQUIRE_NO_THROW( bsp->verify_qc(qc) ); + } + + { // valid weak QC signed by all finalizers + hs_bitset weak_votes(num_finalizers); + std::vector sigs(num_finalizers); + bls_aggregate_signature agg_sig; + + for (auto i = 0u; i < num_finalizers; ++i) { + weak_votes[i] = 1; + sigs[i] = private_key[i].sign(weak_digest); + agg_sig.aggregate(sigs[i]); + } + + // create a valid_quorum_certificate + valid_quorum_certificate qc({}, weak_votes, agg_sig); + + BOOST_REQUIRE_NO_THROW( bsp->verify_qc(qc) ); + } + + { // strong QC quorem not met + hs_bitset strong_votes(num_finalizers); + strong_votes[2] = 1; // finalizer 2 voted with weight 3 (threshold is 4) + + bls_aggregate_signature agg_sig; + bls_signature sig_2 = private_key[2].sign(strong_digest.to_uint8_span()); + agg_sig.aggregate(sig_2); + + // create a valid_quorum_certificate + valid_quorum_certificate qc(strong_votes, {}, agg_sig); + + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_starts_with("strong quorum is not met") ); + } + + { // weak QC quorem not met + hs_bitset weak_votes(num_finalizers); + weak_votes[2] = 1; // finalizer 2 voted with weight 3 (threshold is 4) + + bls_aggregate_signature agg_sig; + bls_signature sig_2 = private_key[2].sign(weak_digest); + agg_sig.aggregate(sig_2); + + // create a valid_quorum_certificate + valid_quorum_certificate qc({}, weak_votes, agg_sig); + + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_starts_with("weak quorum is not met") ); + } + + { // strong QC with a wrong signing private key + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + strong_votes[2] = 1; // finalizer 2 voted with weight 3 + + bls_signature sig_0 = private_key[0].sign(strong_digest.to_uint8_span()); + bls_signature sig_2 = private_key[1].sign(strong_digest.to_uint8_span()); // signed by finalizer 1 which is not set in strong_votes + bls_aggregate_signature sig; + sig.aggregate(sig_0); + sig.aggregate(sig_2); + + // create a valid_quorum_certificate + valid_quorum_certificate qc(strong_votes, {}, sig); + + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_is("signature validation failed") ); + } + + { // strong QC with a wrong digest + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + strong_votes[2] = 1; // finalizer 2 voted with weight 3 + + bls_signature sig_0 = private_key[0].sign(weak_digest); // should have used strong digest + bls_signature sig_2 = private_key[2].sign(strong_digest.to_uint8_span()); + bls_aggregate_signature sig; + sig.aggregate(sig_0); + sig.aggregate(sig_2); + + // create a valid_quorum_certificate + valid_quorum_certificate qc(strong_votes, {}, sig); + + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_is("signature validation failed") ); + } + + { // weak QC with a wrong signing private key + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + bls_signature strong_sig = private_key[0].sign(strong_digest.to_uint8_span()); + + hs_bitset weak_votes(num_finalizers); + weak_votes[2] = 1; // finalizer 2 voted with weight 3 + bls_signature weak_sig = private_key[1].sign(weak_digest); // wrong key + + bls_aggregate_signature sig; + sig.aggregate(strong_sig); + sig.aggregate(weak_sig); + + valid_quorum_certificate qc(strong_votes, weak_votes, sig); + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_is("signature validation failed") ); + } + + { // weak QC with a wrong digest + hs_bitset strong_votes(num_finalizers); + strong_votes[0] = 1; // finalizer 0 voted with weight 1 + bls_signature strong_sig = private_key[0].sign(weak_digest); // wrong digest + + hs_bitset weak_votes(num_finalizers); + weak_votes[2] = 1; // finalizer 2 voted with weight 3 + bls_signature weak_sig = private_key[2].sign(weak_digest); + + bls_aggregate_signature sig; + sig.aggregate(strong_sig); + sig.aggregate(weak_sig); + + valid_quorum_certificate qc(strong_votes, weak_votes, sig); + BOOST_CHECK_EXCEPTION( bsp->verify_qc(qc), block_validate_exception, eosio::testing::fc_exception_message_is("signature validation failed") ); + } +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/block_tests.cpp b/unittests/block_tests.cpp index d75832ac09..aa9733a15c 100644 --- a/unittests/block_tests.cpp +++ b/unittests/block_tests.cpp @@ -35,19 +35,19 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_test) const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = calculate_merkle_legacy( std::move(trx_digests) ); // Re-sign the block - auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state_legacy()->blockroot_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); copy_b->producer_signature = main.get_private_key(config::system_account_name, "active").sign(sig_digest); // Push block with invalid transaction to other chain tester validator; - auto bsf = validator.control->create_block_state_future( copy_b->calculate_id(), copy_b ); + auto btf = validator.control->create_block_handle_future( copy_b->calculate_id(), copy_b ); validator.control->abort_block(); controller::block_report br; - BOOST_REQUIRE_EXCEPTION(validator.control->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ), fc::exception , + BOOST_REQUIRE_EXCEPTION(validator.control->push_block( br, btf.get(), {}, trx_meta_cache_lookup{} ), fc::exception , [] (const fc::exception &e)->bool { return e.code() == account_name_exists_exception::code_value ; }) ; @@ -77,16 +77,16 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_mroot_test) copy_b->transactions.back().trx = std::move(invalid_packed_tx); // Re-sign the block - auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state_legacy()->blockroot_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); copy_b->producer_signature = main.get_private_key(config::system_account_name, "active").sign(sig_digest); // Push block with invalid transaction to other chain tester validator; - auto bsf = validator.control->create_block_state_future( copy_b->calculate_id(), copy_b ); + auto btf = validator.control->create_block_handle_future( copy_b->calculate_id(), copy_b ); validator.control->abort_block(); controller::block_report br; - BOOST_REQUIRE_EXCEPTION(validator.control->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ), fc::exception, + BOOST_REQUIRE_EXCEPTION(validator.control->push_block( br, btf.get(), {}, trx_meta_cache_lookup{} ), fc::exception, [] (const fc::exception &e)->bool { return e.code() == block_validate_exception::code_value && e.to_detail_string().find("invalid block transaction merkle root") != std::string::npos; @@ -115,11 +115,11 @@ std::pair corrupt_trx_in_block(validating_te const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = calculate_merkle_legacy( std::move(trx_digests) ); // Re-sign the block - auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state_legacy()->blockroot_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); copy_b->producer_signature = main.get_private_key(b->producer, "active").sign(sig_digest); return std::pair(b, copy_b); } @@ -217,11 +217,11 @@ BOOST_AUTO_TEST_CASE(broadcasted_block_test) signed_block_ptr bcasted_blk_by_prod_node; signed_block_ptr bcasted_blk_by_recv_node; - producer_node.control->accepted_block.connect( [&](block_signal_params t) { + producer_node.control->accepted_block().connect( [&](block_signal_params t) { const auto& [ block, id ] = t; bcasted_blk_by_prod_node = block; }); - receiving_node.control->accepted_block.connect( [&](block_signal_params t) { + receiving_node.control->accepted_block().connect( [&](block_signal_params t) { const auto& [ block, id ] = t; bcasted_blk_by_recv_node = block; }); diff --git a/unittests/bootseq_tests.cpp b/unittests/bootseq_tests.cpp index e5d2a5a344..eae5f8eee4 100644 --- a/unittests/bootseq_tests.cpp +++ b/unittests/bootseq_tests.cpp @@ -272,7 +272,7 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { // No producers will be set, since the total activated stake is less than 150,000,000 produce_blocks_for_n_rounds(2); // 2 rounds since new producer schedule is set when the first block of next round is irreversible - auto active_schedule = control->head_block_state()->active_schedule; + auto active_schedule = control->active_producers(); BOOST_TEST(active_schedule.producers.size() == 1u); BOOST_TEST(active_schedule.producers.front().producer_name == name("eosio")); @@ -287,7 +287,7 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { // Since the total vote stake is more than 150,000,000, the new producer set will be set produce_blocks_for_n_rounds(2); // 2 rounds since new producer schedule is set when the first block of next round is irreversible - active_schedule = control->head_block_state()->active_schedule; + active_schedule = control->active_producers(); BOOST_REQUIRE(active_schedule.producers.size() == 21); BOOST_TEST(active_schedule.producers.at( 0).producer_name == name("proda")); BOOST_TEST(active_schedule.producers.at( 1).producer_name == name("prodb")); diff --git a/unittests/chain_tests.cpp b/unittests/chain_tests.cpp index 05fb688e3a..dc6a63914c 100644 --- a/unittests/chain_tests.cpp +++ b/unittests/chain_tests.cpp @@ -18,23 +18,27 @@ BOOST_AUTO_TEST_SUITE(chain_tests) BOOST_AUTO_TEST_CASE( replace_producer_keys ) try { validating_tester tester; - const auto head_ptr = tester.control->head_block_state(); - BOOST_REQUIRE(head_ptr); - const auto new_key = get_public_key(name("newkey"), config::active_name.to_string()); // make sure new keys is not used - for(const auto& prod : head_ptr->active_schedule.producers) { + for(const auto& prod : tester.control->active_producers().producers) { for(const auto& key : std::get(prod.authority).keys){ BOOST_REQUIRE(key.key != new_key); } } - const auto old_version = head_ptr->pending_schedule.schedule.version; + // TODO: Add test with instant-finality enabled + BOOST_REQUIRE(tester.control->pending_producers_legacy()); + const auto old_pending_version = tester.control->pending_producers_legacy()->version; + const auto old_version = tester.control->active_producers().version; BOOST_REQUIRE_NO_THROW(tester.control->replace_producer_keys(new_key)); - const auto new_version = head_ptr->pending_schedule.schedule.version; + const auto new_version = tester.control->active_producers().version; + BOOST_REQUIRE(tester.control->pending_producers_legacy()); + const auto pending_version = tester.control->pending_producers_legacy()->version; // make sure version not been changed BOOST_REQUIRE(old_version == new_version); + BOOST_REQUIRE(old_version == pending_version); + BOOST_REQUIRE(pending_version == old_pending_version); const auto& gpo = tester.control->db().get(); BOOST_REQUIRE(!gpo.proposed_schedule_block_num); @@ -43,7 +47,8 @@ BOOST_AUTO_TEST_CASE( replace_producer_keys ) try { const uint32_t expected_threshold = 1; const weight_type expected_key_weight = 1; - for(const auto& prod : head_ptr->active_schedule.producers) { + BOOST_REQUIRE(tester.control->pending_producers_legacy()); + for(const auto& prod : tester.control->pending_producers_legacy()->producers) { BOOST_REQUIRE_EQUAL(std::get(prod.authority).threshold, expected_threshold); for(const auto& key : std::get(prod.authority).keys){ BOOST_REQUIRE_EQUAL(key.key, new_key); @@ -151,15 +156,12 @@ BOOST_AUTO_TEST_CASE( signal_validated_blocks ) try { signed_block_ptr accepted_block; block_id_type accepted_id; - auto c = chain.control->accepted_block.connect([&](block_signal_params t) { + auto c = chain.control->accepted_block().connect([&](block_signal_params t) { const auto& [ block, id ] = t; auto block_num = block->block_num(); BOOST_CHECK(block); - const auto& bsp_by_id = chain.control->fetch_block_state_by_id(id); - BOOST_CHECK(bsp_by_id->block_num == block_num); - const auto& bsp_by_number = chain.control->fetch_block_state_by_number(block_num); // verify it can be found (has to be validated) - BOOST_CHECK(bsp_by_number->id == id); BOOST_CHECK(chain.control->fetch_block_by_id(id) == block); + BOOST_CHECK(chain.control->block_exists(id)); BOOST_CHECK(chain.control->fetch_block_by_number(block_num) == block); BOOST_REQUIRE(chain.control->fetch_block_header_by_number(block_num)); BOOST_CHECK(chain.control->fetch_block_header_by_number(block_num)->calculate_id() == id); @@ -170,15 +172,12 @@ BOOST_AUTO_TEST_CASE( signal_validated_blocks ) try { }); signed_block_ptr validated_block; block_id_type validated_id; - auto c2 = validator.control->accepted_block.connect([&](block_signal_params t) { + auto c2 = validator.control->accepted_block().connect([&](block_signal_params t) { const auto& [ block, id ] = t; auto block_num = block->block_num(); BOOST_CHECK(block); - const auto& bsp_by_id = validator.control->fetch_block_state_by_id(id); - BOOST_CHECK(bsp_by_id->block_num == block_num); - const auto& bsp_by_number = validator.control->fetch_block_state_by_number(block_num); // verify it can be found (has to be validated) - BOOST_CHECK(bsp_by_number->id == id); BOOST_CHECK(validator.control->fetch_block_by_id(id) == block); + BOOST_CHECK(validator.control->block_exists(id)); BOOST_CHECK(validator.control->fetch_block_by_number(block_num) == block); BOOST_REQUIRE(validator.control->fetch_block_header_by_number(block_num)); BOOST_CHECK(validator.control->fetch_block_header_by_number(block_num)->calculate_id() == id); diff --git a/unittests/contracts/CMakeLists.txt b/unittests/contracts/CMakeLists.txt index ae0c483d71..60b1f6cc60 100644 --- a/unittests/contracts/CMakeLists.txt +++ b/unittests/contracts/CMakeLists.txt @@ -21,6 +21,7 @@ elseif( USE_EOSIO_CDT_1_8_X ) add_definitions(-DUSE_EOSIO_CDT_1_8_X=true) endif() +add_subdirectory(eosio.boot) add_subdirectory(eosio.msig) add_subdirectory(eosio.system) add_subdirectory(eosio.token) diff --git a/unittests/contracts/eosio.boot/CMakeLists.txt b/unittests/contracts/eosio.boot/CMakeLists.txt new file mode 100644 index 0000000000..3b37a86c76 --- /dev/null +++ b/unittests/contracts/eosio.boot/CMakeLists.txt @@ -0,0 +1,6 @@ +if( EOSIO_COMPILE_TEST_CONTRACTS ) + add_contract( eosio.boot eosio.boot eosio.boot.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/eosio.boot.wasm ${CMAKE_CURRENT_BINARY_DIR}/eosio.boot.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/eosio.boot.abi ${CMAKE_CURRENT_BINARY_DIR}/eosio.boot.abi COPYONLY ) +endif() diff --git a/unittests/contracts/eosio.boot/eosio.boot.abi b/unittests/contracts/eosio.boot/eosio.boot.abi new file mode 100644 index 0000000000..fa76e88486 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.abi @@ -0,0 +1,328 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "activate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "authority", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + }, + { + "name": "accounts", + "type": "permission_level_weight[]" + }, + { + "name": "waits", + "type": "wait_weight[]" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "key_weight", + "base": "", + "fields": [ + { + "name": "key", + "type": "public_key" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "linkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + }, + { + "name": "requirement", + "type": "name" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "onerror", + "base": "", + "fields": [ + { + "name": "sender_id", + "type": "uint128" + }, + { + "name": "sent_trx", + "type": "bytes" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "permission_level_weight", + "base": "", + "fields": [ + { + "name": "permission", + "type": "permission_level" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "reqactivated", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setcode", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "vmtype", + "type": "uint8" + }, + { + "name": "vmversion", + "type": "uint8" + }, + { + "name": "code", + "type": "bytes" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + } + ], + "actions": [ + { + "name": "activate", + "type": "activate", + "ricardian_contract": "" + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + }, + { + "name": "reqactivated", + "type": "reqactivated", + "ricardian_contract": "" + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/unittests/contracts/eosio.boot/eosio.boot.cpp b/unittests/contracts/eosio.boot/eosio.boot.cpp new file mode 100644 index 0000000000..33c5f91ea2 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.cpp @@ -0,0 +1,19 @@ +#include "eosio.boot.hpp" +#include + +namespace eosioboot { + +void boot::onerror( ignore, ignore> ) { + check( false, "the onerror action cannot be called directly" ); +} + +void boot::activate( const eosio::checksum256& feature_digest ) { + require_auth( get_self() ); + eosio::preactivate_feature( feature_digest ); +} + +void boot::reqactivated( const eosio::checksum256& feature_digest ) { + check( eosio::is_feature_activated( feature_digest ), "protocol feature is not activated" ); +} + +} diff --git a/unittests/contracts/eosio.boot/eosio.boot.hpp b/unittests/contracts/eosio.boot/eosio.boot.hpp new file mode 100644 index 0000000000..422c2e2df6 --- /dev/null +++ b/unittests/contracts/eosio.boot/eosio.boot.hpp @@ -0,0 +1,260 @@ +#pragma once + +#include +#include + +namespace eosioboot { + + using eosio::action_wrapper; + using eosio::check; + using eosio::checksum256; + using eosio::ignore; + using eosio::name; + using eosio::permission_level; + using eosio::public_key; + + /** + * A weighted permission. + * + * @details Defines a weighted permission, that is a permission which has a weight associated. + * A permission is defined by an account name plus a permission name. The weight is going to be + * used against a threshold, if the weight is equal or greater than the threshold set then authorization + * will pass. + */ + struct permission_level_weight { + permission_level permission; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) ) + }; + + /** + * Weighted key. + * + * @details A weighted key is defined by a public key and an associated weight. + */ + struct key_weight { + eosio::public_key key; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( key_weight, (key)(weight) ) + }; + + /** + * Wait weight. + * + * @details A wait weight is defined by a number of seconds to wait for and a weight. + */ + struct wait_weight { + uint32_t wait_sec; + uint16_t weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( wait_weight, (wait_sec)(weight) ) + }; + + /** + * Blockchain authority. + * + * @details An authority is defined by: + * - a vector of key_weights (a key_weight is a public key plus a weight), + * - a vector of permission_level_weights, (a permission_level is an account name plus a permission name) + * - a vector of wait_weights (a wait_weight is defined by a number of seconds to wait and a weight) + * - a threshold value + */ + struct authority { + uint32_t threshold = 0; + std::vector keys; + std::vector accounts; + std::vector waits; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts)(waits) ) + }; + + /** + * @defgroup eosioboot eosio.boot + * @ingroup eosiocontracts + * + * eosio.boot is a extremely minimalistic system contract that only supports the native actions and an + * activate action that allows activating desired protocol features prior to deploying a system contract + * with more features such as eosio.bios or eosio.system. + * + * @{ + */ + class [[eosio::contract("eosio.boot")]] boot : public eosio::contract { + public: + using contract::contract; + /** + * @{ + * These actions map one-on-one with the ones defined in + * [Native Action Handlers](@ref native_action_handlers) section. + * They are present here so they can show up in the abi file and thus user can send them + * to this contract, but they have no specific implementation at this contract level, + * they will execute the implementation at the core level and nothing else. + */ + /** + * New account action + * + * @details Creates a new account. + * + * @param creator - the creator of the account + * @param name - the name of the new account + * @param owner - the authority for the owner permission of the new account + * @param active - the authority for the active permission of the new account + */ + [[eosio::action]] + void newaccount( name creator, + name name, + ignore owner, + ignore active) {} + /** + * Update authorization action. + * + * @details Updates pemission for an account. + * + * @param account - the account for which the permission is updated, + * @param pemission - the permission name which is updated, + * @param parem - the parent of the permission which is updated, + * @param aut - the json describing the permission authorization. + */ + [[eosio::action]] + void updateauth( ignore account, + ignore permission, + ignore parent, + ignore auth ) {} + + /** + * Delete authorization action. + * + * @details Deletes the authorization for an account's permision. + * + * @param account - the account for which the permission authorization is deleted, + * @param permission - the permission name been deleted. + */ + [[eosio::action]] + void deleteauth( ignore account, + ignore permission ) {} + + /** + * Link authorization action. + * + * @details Assigns a specific action from a contract to a permission you have created. Five system + * actions can not be linked `updateauth`, `deleteauth`, `linkauth`, `unlinkauth`, and `canceldelay`. + * This is useful because when doing authorization checks, the EOSIO based blockchain starts with the + * action needed to be authorized (and the contract belonging to), and looks up which permission + * is needed to pass authorization validation. If a link is set, that permission is used for authoraization + * validation otherwise then active is the default, with the exception of `eosio.any`. + * `eosio.any` is an implicit permission which exists on every account; you can link actions to `eosio.any` + * and that will make it so linked actions are accessible to any permissions defined for the account. + * + * @param account - the permission's owner to be linked and the payer of the RAM needed to store this link, + * @param code - the owner of the action to be linked, + * @param type - the action to be linked, + * @param requirement - the permission to be linked. + */ + [[eosio::action]] + void linkauth( ignore account, + ignore code, + ignore type, + ignore requirement ) {} + + /** + * Unlink authorization action. + * + * @details It's doing the reverse of linkauth action, by unlinking the given action. + * + * @param account - the owner of the permission to be unlinked and the receiver of the freed RAM, + * @param code - the owner of the action to be unlinked, + * @param type - the action to be unlinked. + */ + [[eosio::action]] + void unlinkauth( ignore account, + ignore code, + ignore type ) {} + + /** + * Cancel delay action. + * + * @details Cancels a deferred transaction. + * + * @param canceling_auth - the permission that authorizes this action, + * @param trx_id - the deferred transaction id to be cancelled. + */ + [[eosio::action]] + void canceldelay( ignore canceling_auth, ignore trx_id ) {} + + /** + * Set code action. + * + * @details Sets the contract code for an account. + * + * @param account - the account for which to set the contract code. + * @param vmtype - reserved, set it to zero. + * @param vmversion - reserved, set it to zero. + * @param code - the code content to be set, in the form of a blob binary.. + */ + [[eosio::action]] + void setcode( name account, uint8_t vmtype, uint8_t vmversion, const std::vector& code ) {} + + /** + * Set abi for contract. + * + * @details Set the abi for contract identified by `account` name. + * + * @param account - the name of the account to set the abi for + * @param abi - the abi hash represented as a vector of characters + */ + [[eosio::action]] + void setabi( name account, const std::vector& abi ) {} + + /** @}*/ + + /** + * On error action. + * + * @details Notification of this action is delivered to the sender of a deferred transaction + * when an objective error occurs while executing the deferred transaction. + * This action is not meant to be called directly. + * + * @param sender_id - the id for the deferred transaction chosen by the sender, + * @param sent_trx - the deferred transaction that failed. + */ + [[eosio::action]] + void onerror( ignore sender_id, ignore> sent_trx ); + + /** + * Activates a protocol feature. + * + * @details Activates a protocol feature + * + * @param feature_digest - hash of the protocol feature to activate. + */ + [[eosio::action]] + void activate( const eosio::checksum256& feature_digest ); + + /** + * Asserts that a protocol feature has been activated. + * + * @details Asserts that a protocol feature has been activated + * + * @param feature_digest - hash of the protocol feature to check for activation. + */ + [[eosio::action]] + void reqactivated( const eosio::checksum256& feature_digest ); + + using newaccount_action = action_wrapper<"newaccount"_n, &boot::newaccount>; + using updateauth_action = action_wrapper<"updateauth"_n, &boot::updateauth>; + using deleteauth_action = action_wrapper<"deleteauth"_n, &boot::deleteauth>; + using linkauth_action = action_wrapper<"linkauth"_n, &boot::linkauth>; + using unlinkauth_action = action_wrapper<"unlinkauth"_n, &boot::unlinkauth>; + using canceldelay_action = action_wrapper<"canceldelay"_n, &boot::canceldelay>; + using setcode_action = action_wrapper<"setcode"_n, &boot::setcode>; + using setabi_action = action_wrapper<"setabi"_n, &boot::setabi>; + using activate_action = action_wrapper<"activate"_n, &boot::activate>; + using reqactivated_action = action_wrapper<"reqactivated"_n, &boot::reqactivated>; + }; + /** @}*/ // end of @defgroup eosioboot eosio.boot +} /// namespace eosioboot diff --git a/unittests/contracts/eosio.boot/eosio.boot.wasm b/unittests/contracts/eosio.boot/eosio.boot.wasm new file mode 100755 index 0000000000..0da4b8c983 Binary files /dev/null and b/unittests/contracts/eosio.boot/eosio.boot.wasm differ diff --git a/unittests/database_tests.cpp b/unittests/database_tests.cpp index 1ddd56e64e..1f0ba01ca7 100644 --- a/unittests/database_tests.cpp +++ b/unittests/database_tests.cpp @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_SUITE(database_tests) // Check the last irreversible block number is set correctly, with one producer, irreversibility should only just 1 block before const auto expected_last_irreversible_block_number = test.control->head_block_num() - 1; - BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == expected_last_irreversible_block_number); + BOOST_TEST(test.control->head_block_state_legacy()->dpos_irreversible_blocknum == expected_last_irreversible_block_number); // Ensure that future block doesn't exist const auto nonexisting_future_block_num = test.control->head_block_num() + 1; BOOST_TEST(test.control->fetch_block_by_number(nonexisting_future_block_num) == nullptr); @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_SUITE(database_tests) const auto next_expected_last_irreversible_block_number = test.control->head_block_num() - 1; // Check the last irreversible block number is updated correctly - BOOST_TEST(test.control->head_block_state()->dpos_irreversible_blocknum == next_expected_last_irreversible_block_number); + BOOST_TEST(test.control->head_block_state_legacy()->dpos_irreversible_blocknum == next_expected_last_irreversible_block_number); // Previous nonexisting future block should exist by now BOOST_CHECK_NO_THROW(test.control->fetch_block_by_number(nonexisting_future_block_num)); // Check the latest head block match diff --git a/unittests/deep-mind/deep-mind.log b/unittests/deep-mind/deep-mind.log index 83af237cad..ae0819ecaa 100644 --- a/unittests/deep-mind/deep-mind.log +++ b/unittests/deep-mind/deep-mind.log @@ -29,16 +29,16 @@ DMLOG TRX_OP CREATE onblock ef240e45433c433de4061120632aa06e32ec3e77048abf55c62e DMLOG APPLIED_TRANSACTION 2 ef240e45433c433de4061120632aa06e32ec3e77048abf55c62e0612c22548ed02000000013b3d4b010000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e801006400000000000000000000000000000000000000000001010000010000000000ea305506d4766d9dbedb630ad9546f583a9809539cf09d38fd1554b4216503113ff4e501000000000000000100000000000000010000000000ea3055010000000000000000000000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed323274003b3d4b000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044423079ed372a4dda0bf89c3a594df409eaa8c1535451b7d5ca6a3d7a37691200000000000000000000000000000000ef240e45433c433de4061120632aa06e32ec3e77048abf55c62e0612c22548ed02000000013b3d4b010000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e80000000000000000 DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":0,"value_ex":0,"consumed":0},"average_block_cpu_usage":{"last_ordinal":0,"value_ex":0,"consumed":0},"pending_net_usage":0,"pending_cpu_usage":100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1048576,"virtual_cpu_limit":200000} DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":2,"value_ex":0,"consumed":0},"average_block_cpu_usage":{"last_ordinal":2,"value_ex":833334,"consumed":100},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1049625,"virtual_cpu_limit":200200} -DMLOG ACCEPTED_BLOCK 2 02000000020000000000000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010001000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba10100000000000000010000000000ea305502000000010000000000ea305500000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add80100000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e8013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0020701fd1d2d6fbca71ad1df5bd09a987d6863f301b93acfc1c34857e4b2f53821a0b4ca8483cf594f845f3f4fc155dbbc98009cb9c7b7b60d449f922dc00abcb0f0000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef26198000000000001010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0001013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0020701fd1d2d6fbca71ad1df5bd09a987d6863f301b93acfc1c34857e4b2f53821a0b4ca8483cf594f845f3f4fc155dbbc98009cb9c7b7b60d449f922dc00abcb0f000001 +DMLOG ACCEPTED_BLOCK 2 02000000020000000000000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010001000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba10100000000000000010000000000ea305502000000010000000000ea305500000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add80100000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e8013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0020701fd1d2d6fbca71ad1df5bd09a987d6863f301b93acfc1c34857e4b2f53821a0b4ca8483cf594f845f3f4fc155dbbc98009cb9c7b7b60d449f922dc00abcb0f0000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef26198000000000001010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0001013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0020701fd1d2d6fbca71ad1df5bd09a987d6863f301b93acfc1c34857e4b2f53821a0b4ca8483cf594f845f3f4fc155dbbc98009cb9c7b7b60d449f922dc00abcb0f0000010110d8a6645b8237d61a3afd21b78548f9ba8d319c021dc836487afb96a92676c1 DMLOG START_BLOCK 3 DMLOG CREATION_OP ROOT 0 DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":0,"consumed":0},"cpu_usage":{"last_ordinal":1262304002,"value_ex":1157,"consumed":101},"ram_usage":2724} DMLOG TRX_OP CREATE onblock da9fbe9042e1bc9bd64d7a4506534d492107a29f79ad671c1fea19ae3fb70eb4 01e10b5e02005132b41600000000010000000000ea305500000000221acfa4010000000000ea305500000000a8ed32329801013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd000000 -DMLOG APPLIED_TRANSACTION 3 da9fbe9042e1bc9bd64d7a4506534d492107a29f79ad671c1fea19ae3fb70eb403000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b4401006400000000000000000000000000000000000000000001010000010000000000ea3055ccfe3b56076237b0b6da2f580652ee1420231b96d3d96b28183769ac932c9e5902000000000000000200000000000000010000000000ea3055020000000000000000000000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed32329801013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd00000000000000000000da9fbe9042e1bc9bd64d7a4506534d492107a29f79ad671c1fea19ae3fb70eb403000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 da9fbe9042e1bc9bd64d7a4506534d492107a29f79ad671c1fea19ae3fb70eb403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c01006400000000000000000000000000000000000000000001010000010000000000ea3055ccfe3b56076237b0b6da2f580652ee1420231b96d3d96b28183769ac932c9e5902000000000000000200000000000000010000000000ea3055020000000000000000000000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed32329801013b3d4b0000000000ea30550000000000015ab65a885a31e441ac485ebd2aeba87bf7ee6e7bcc40bf3a24506ba1000000000000000000000000000000000000000000000000000000000000000062267e8b11d7d8f28e1f991a4de2b08cf92500861af2795765bdc9263cd6f4cd000000000001000021010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd00000000000000000000da9fbe9042e1bc9bd64d7a4506534d492107a29f79ad671c1fea19ae3fb70eb403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG RAM_OP 0 eosio code add setcode eosio 180494 177770 DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":35325,"consumed":6104},"cpu_usage":{"last_ordinal":1262304002,"value_ex":12732,"consumed":2101},"ram_usage":180494} -DMLOG APPLIED_TRANSACTION 3 03917c562680b415b93db73416ff29230dfbe7ab1ba4d208b46029d01333cd3a03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d0070000fb050000000000000000d8170000000000000001010000010000000000ea30559a90c525172f87bbac0a6378610727f0fe1d7ebe908df973923d29a1606f9a5703000000000000000300000000000000010000000000ea3055030000000000000001000000000000ea30550000000000ea305500000040258ab2c2010000000000ea305500000000a8ed3232fe8a010000000000ea30550000f18a010061736d01000000019d011a60000060037f7e7f0060027f7e0060027f7f0060057f7e7e7e7e0060047f7e7e7e0060017f017f60017f0060037f7f7f017f6000017f60027f7f017f60017e0060027e7f0060047e7e7e7e0060027f7f017e6000017e60047e7e7e7e017f60047f7e7e7f0060037f7f7f0060067e7e7e7e7f7f017f60047f7e7f7f0060037e7e7e0060037e7e7f017f60047f7f7e7f0060027e7e0060047f7f7f7f00028e041803656e761469735f666561747572655f616374697661746564000603656e761370726561637469766174655f66656174757265000703656e760c656f73696f5f617373657274000303656e76066d656d736574000803656e7610616374696f6e5f646174615f73697a65000903656e7610726561645f616374696f6e5f64617461000a03656e76066d656d637079000803656e760c726571756972655f61757468000b03656e760e7365745f70726976696c65676564000c03656e76137365745f7265736f757263655f6c696d697473000d03656e760561626f7274000003656e76167365745f70726f706f7365645f70726f647563657273000e03656e76207365745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000303656e76206765745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000a03656e760c63757272656e745f74696d65000f03656e76146765745f6163746976655f70726f647563657273000a03656e760b64625f66696e645f693634001003656e76095f5f6173686c746933001103656e7611656f73696f5f6173736572745f636f6465000203656e761063757272656e745f7265636569766572000f03656e760a64625f6765745f693634000803656e7606736861323536001203656e760c64625f73746f72655f693634001303656e760d64625f7570646174655f69363400140347460006070007090a08060607070a0a030307070a060715011602160316041603160316030516011603030a0a0a030a17170318181818181818180318181818031818181818081904050170010a0a05030100010616037f014180c0000b7f0041abc3000b7f0041abc3000b070901056170706c79002d090f010041010b092e30323436383a3b3d0ac98001460400101b0b800101037f02400240024002402000450d004100410028028c40200041107622016a220236028c404100410028028440220320006a41076a417871220036028440200241107420004d0d0120014000417f460d020c030b41000f0b4100200241016a36028c40200141016a4000417f470d010b4100419cc000100220030f0b20030b02000b3601017f230041106b2200410036020c4100200028020c28020041076a417871220036028440410020003602804041003f0036028c400b02000b06004190c0000bf50101067f4100210202400240410020006b22032000712000470d00200041104b0d01200110190f0b101d411636020041000f0b0240024002402000417f6a220420016a10192200450d002000200420006a2003712202460d012000417c6a220328020022044107712201450d02200020044178716a220441786a2205280200210620032001200220006b2207723602002002417c6a200420026b2203200172360200200241786a20064107712201200772360200200520012003723602002000101a0b20020f0b20000f0b200241786a200041786a280200200220006b22006a3602002002417c6a200328020020006b36020020020b3301017f411621030240024020014104490d0020012002101e2201450d0120002001360200410021030b20030f0b101d2802000b3801027f02402000410120001b2201101922000d000340410021004100280298402202450d012002110000200110192200450d000b0b20000b0600200010200b0e0002402000450d002000101a0b0b0600200010220b6b01027f230041106b2202240002402002410c6a20014104200141044b1b22012000410120001b2203101f450d00024003404100280298402200450d0120001100002002410c6a20012003101f0d000c020b0b2002410036020c0b200228020c2100200241106a240020000b08002000200110240b0e0002402000450d002000101a0b0b08002000200110260b0500100a000b4e01017f230041e0006b220124002001200141d8006a3602082001200141106a3602042001200141106a36020020012000102a1a200141106a200128020420012802006b100c200141e0006a24000b920901047f02402000280208200028020422026b41074a0d00410041e8c0001002200041046a28020021020b20022001410810061a200041046a2202200228020041086a2203360200200141086a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001410c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141106a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141146a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141186a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001411c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141206a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141246a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141286a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001412c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141306a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141346a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141386a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001413c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141c0006a21040240200041086a220528020020036b41014a0d00410041e8c0001002200228020021030b20032004410210061a2002200228020041026a2203360200200141c2006a21010240200528020020036b41014a0d00410041e8c0001002200041046a28020021030b20032001410210061a200041046a2201200128020041026a36020020000bfa0203017f027e017f230041206b220124002001200029030022024220883c000b200120024228883c000a200120024230883c0009200120024238883c00082001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c000020012002a722043a000f200120044108763a000e200120044110763a000d200120044118763a000c20012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a00042001200041186a29030022023c00172001200029031022034220883c001b200120034228883c001a200120034230883c0019200120034238883c0018200120024220883c0013200120024228883c0012200120024230883c0011200120024238883c001020012002a722004108763a0016200120004110763a0015200120004118763a001420012003a722003a001f200120004108763a001e200120004110763a001d200120004118763a001c200110002100200141206a240020000bf60203017f027e017f230041206b220124002001200029030022024220883c000b200120024228883c000a200120024230883c0009200120024238883c00082001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c000020012002a722043a000f200120044108763a000e200120044110763a000d200120044118763a000c20012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a00042001200041186a29030022023c00172001200029031022034220883c001b200120034228883c001a200120034230883c0019200120034238883c0018200120024220883c0013200120024228883c0012200120024230883c0011200120024238883c001020012002a722004108763a0016200120004110763a0015200120004118763a001420012003a722003a001f200120004108763a001e200120004110763a001d200120004118763a001c20011001200141206a24000bcc0401017f23004190016b220324001018024020012000520d0002400240024002400240024002400240200242ffffb7f6a497b2d942570d00200242ffffffffb5f7d6d942570d01200242808080d0b2b3bb9932510d03200242808080c093fad6d942510d0420024280808080b6f7d6d942520d082003410036028c0120034101360288012003200329038801370300200120012003102f1a0c080b200242fffffffffff698d942550d0120024290a9d9d9dd8c99d6ba7f510d0420024280808080daac9bd6ba7f520d0720034100360264200341023602602003200329036037032820012001200341286a10311a0c070b2002428080b8f6a497b2d942510d0420024280808096cdebd4d942520d062003410036026c200341033602682003200329036837032020012001200341206a10331a0c060b2002428080808080f798d942510d042002428080b8f6a4979ad942520d0520034100360284012003410436028001200320032903800137030820012001200341086a10351a0c050b20034100360254200341053602502003200329035037033820012001200341386a10371a0c040b20034100360274200341063602702003200329037037031820012001200341186a10391a0c030b2003410036024c200341073602482003200329034837034020012001200341c0006a10371a0c020b2003410036027c200341083602782003200329037837031020012001200341106a103c1a0c010b2003410036025c200341093602582003200329035837033020012001200341306a103e1a0b4100101c20034190016a24000b1200200029030010072001200241004710080bd30201077f230041306b2203210420032400200228020421052002280200210641002102024010042207450d00024002402007418104490d002007101921020c010b20032007410f6a4170716b220224000b2002200710051a0b200441003a002820044200370320200220076a2103200441206a41086a210802400240200741074b0d0041004185c1001002200441206a2002410810061a200241086a21090c010b200441206a2002410810061a200241086a210920074108470d0041004185c10010020b20082009410110061a200441186a200336020020042002360210200441146a200241096a3602002004200137030820042000370300200420054101756a2103200441286a2d000021082004290320210002402005410171450d00200328020020066a28020021060b20032000200841ff0171200611010002402007418104490d002002101a0b200441306a240041010b0600200110070b830201057f230041306b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b20044200370328200220076a21030240200741074b0d0041004185c10010020b200441286a2002410810061a2004411c6a200241086a360200200441206a2003360200200420013703102004200037030820042002360218200441086a20054101756a21032004290328210002402005410171450d00200328020020066a28020021060b20032000200611020002402007418104490d002002101a0b200441306a240041010b0d0020002903001007200110290bf70201067f230041a0026b2203210420032400200228020421052002280200210641002102024010042207450d00024002402007418104490d002007101921020c010b20032007410f6a4170716b220224000b2002200710051a0b200441c8006a410041c80010031a2004200236023c200420023602382004200220076a360240200441386a200441c8006a10421a200441086a41086a220320042802403602002004200429033837030820044190016a41086a220820032802003602002004200429030837039001200441d8016a41086a20082802002203360200200441306a2003360200200420003703182004200137032020042004290390012200370328200420003703d80120044190016a200441c8006a41c80010061a200441d8016a20044190016a41c80010061a200441186a20054101756a210302402005410171450d00200328020020066a28020021060b2003200441d8016a200611030002402007418104490d002002101a0b200441a0026a240041010b130020002903001007200120022003200410090b940302067f027e23004180016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b2004420037034820044200370340200442003703502004420037035820042002360234200420023602302004200220076a3602382004200441306a3602702004200441c0006a360210200441106a200441f0006a103f200441086a2203200428023836020020042004290330370300200441e0006a41086a2208200328020036020020042004290300370360200441f0006a41086a20082802002203360200200441286a2003360200200420003703102004200137031820042004290360220037032020042000370370200441106a20054101756a21032004290358210020042903502101200429034821092004290340210a02402005410171450d00200328020020066a28020021060b2003200a200920012000200611040002402007418104490d002002101a0b20044180016a240041010b0d00200029030010072001102c0bfe0301087f230041a0016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b200441c0006a41186a22034200370300200441c0006a41106a22084200370300200442003703482004420037034020042002360234200420023602302004200220076a3602382004200441306a3602602004200441c0006a3602800120044180016a200441e0006a1048200441086a2209200428023836020020042004290330370300200441e0006a41086a220a20092802003602002004200429030037036020044180016a41086a200a2802002209360200200441106a41186a200936020020042000370310200420013703182004200429036022003703202004200037038001200441e0006a41186a22092003290300370300200441e0006a41106a22032008290300370300200420042903483703682004200429034037036020044180016a41186a200929030037030020044180016a41106a200329030037030020042004290368370388012004200429036037038001200441106a20054101756a210302402005410171450d00200328020020066a28020021060b200320044180016a200611030002402007418104490d002002101a0b200441a0016a240041010b5601027f23002202210320002903001007024010042200418104490d00200010192202200010051a20022000100b1a200324000f0b20022000410f6a4170716b220224002002200010051a20022000100b1a200324000bb80501077f230041f0006b220321042003240020022802042105200228020021064100210741002102024010042208450d00024002402008418104490d002008101921020c010b20032008410f6a4170716b220224000b2002200810051a0b200441003602482004420037034020042002360234200420023602302004200220086a360238200441306a200441c0006a10411a200441086a2203200428023836020020042004290330370300200441d0006a41086a2209200328020036020020042004290300370350200441e0006a41086a20092802002203360200200441286a20033602002004200037031020042001370318200420042903502200370320200420003703602004410036025820044200370350200428024420042802406b220341306d21090240024002402003450d00200941d6aad52a4f0d01200441d8006a200310202207200941306c6a36020020042007360250200420073602542004280244200428024022096b22034101480d0020072009200310061a20042004280254200341306e41306c6a22073602540b200441106a20054101756a210302402005410171450d00200328020020066a28020021060b2004410036026820044200370360200720042802506b220741306d210502402007450d00200541d6aad52a4f0d02200441e8006a200710202207200541306c6a36020020042007360260200420073602642004280254200428025022096b22054101480d0020072009200510061a20042007200541306e41306c6a3602640b2003200441e0006a2006110300024020042802602207450d0020042007360264200710220b024020042802502207450d0020042007360254200710220b02402008418104490d002002101a0b024020042802402202450d0020042002360244200210220b200441f0006a240041010f0b200441d0006a1028000b200441e0006a1028000b130002402001102b0d00410041d9c20010020b0b0900200029030010070b870302067f017e23004180016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b2004420037035020044200370348200442003703582004200236023c200420023602382004200220076a3602402004200441386a3602702004200441c8006a360218200441186a200441f0006a1040200441086a41086a2203200428024036020020042004290338370308200441e0006a41086a2208200328020036020020042004290308370360200441f0006a41086a20082802002203360200200441306a2003360200200420003703182004200137032020042004290360220037032820042000370370200441186a20054101756a210320042903582100200429035021012004290348210902402005410171450d00200328020020066a28020021060b2003200920012000200611050002402007418104490d002002101a0b20044180016a240041010bc00203017f017e027f230041c0006b2203240020032001370338200341306a41003602002003427f37032020034200370328200320002903002204370310200320043703180240024002402004200442808080809aecb4ee312001101022004100480d000240200341106a200010452200280230200341106a460d00410041b5c00010020b20032002360208200341106a20004200200341086a1046200328022822050d010c020b2003200236020c2003200341386a3602082003200341106a2001200341086a104720032802282205450d010b024002402003412c6a220628020022002005460d000340200041686a220028020021022000410036020002402002450d00200210220b20052000470d000b200341286a28020021000c010b200521000b2006200536020020001022200341c0006a24000f0b200341c0006a24000b9e0301057f23004180016b2203240020032204200229020037035841002102024010042205450d00024002402005418104490d002005101921020c010b20032005410f6a4170716b220224000b2002200510051a0b200441d0006a4100360200200442003703402004420037034820042002360234200420023602302004200220056a360238200221030240200541074b0d0041004185c1001002200428023421030b200441c0006a2003410810061a2004200341086a360234200441306a200441c0006a41086a220310431a200441086a2206200441306a41086a28020036020020042004290330370300200441e0006a41086a2207200628020036020020042004290300370360200441f0006a41086a20072802002206360200200441286a20063602002004200037031020042001370318200420042903602200370320200420003703702004200441d8006a3602742004200441106a360270200441f0006a200441c0006a104402402005418104490d002002101a0b024020032802002202450d00200441cc006a2002360200200210220b20044180016a240041010bc10201037f20002802002102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a3602002000280200220041086a2102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a360200200041106a2102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a360200200041186a2100024020012802002201280208200128020422036b41074b0d0041004185c1001002200141046a28020021030b20002003410810061a200141046a2201200128020041086a3602000bf30101037f20002802002102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a3602002000280200220441086a2102024020012802002203280208200328020422006b41074b0d0041004185c1001002200341046a28020021000b20022000410810061a200341046a2203200328020041086a360200200441106a2100024020012802002201280208200128020422036b41074b0d0041004185c1001002200141046a28020021030b20002003410810061a200141046a2201200128020041086a3602000be80303017f017e067f2000280204210242002103200041086a2104200041046a2105410021060340024020022004280200490d00410041fbc2001002200528020021020b20022d000021072005200241016a22023602002003200741ff0071200641ff0171220674ad842103200641076a2106200221022007418001710d000b02400240024020012802042208200128020022096b41306d22072003a722024f0d002001200220076b105620012802002209200141046a2802002208470d010c020b0240200720024d0d00200141046a2009200241306c6a22083602000b20092008460d010b200041046a22042802002102200041086a210103400240200128020020026b41074b0d0041004185c1001002200428020021020b20092002410810061a2004200428020041086a220236020041002105420021030340024020022001280200490d00410041fbc2001002200428020021020b20022d000021072004200241016a22063602002003200741ff0071200541ff0171220274ad842103200241076a2105200621022007418001710d000b200920033e02082009410c6a21020240200128020020066b41204b0d0041004185c1001002200428020021060b20022006412110061a2004200428020041216a2202360200200941306a22092008470d000b0b20000b920901047f02402000280208200028020422026b41074b0d0041004185c1001002200041046a28020021020b20012002410810061a200041046a2202200228020041086a2203360200200141086a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001410c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141106a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141146a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141186a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001411c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141206a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141246a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141286a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001412c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141306a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141346a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141386a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001413c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141c0006a21040240200041086a220528020020036b41014b0d0041004185c1001002200228020021030b20042003410210061a2002200228020041026a2203360200200141c2006a21010240200528020020036b41014b0d0041004185c1001002200041046a28020021030b20012003410210061a200041046a2201200128020041026a36020020000ba10203017f017e057f2000280204210242002103200041086a2104200041046a2105410021060340024020022004280200490d00410041fbc2001002200528020021020b20022d000021072005200241016a22083602002003200741ff0071200641ff0171220274ad842103200241076a2106200821022007418001710d000b0240024020012802042207200128020022026b22052003a722064f0d002001200620056b1051200041046a2802002108200141046a2802002107200128020021020c010b200520064d0d00200141046a200220066a22073602000b0240200041086a28020020086b200720026b22074f0d0041004185c1001002200041046a28020021080b20022008200710061a200041046a2202200228020020076a36020020000bf80103017f017e027f230041106b22022400200242003703002002410036020820012903002103024002402001410c6a28020020012802086b2204450d002004417f4c0d01200241086a20041020220520046a36020020022005360200200220053602042001410c6a280200200141086a28020022046b22014101480d0020052004200110061a2002200520016a3602040b20002802002000280204220128020422044101756a21002001280200210102402004410171450d00200028020020016a28020021010b2000200320022001110100024020022802002201450d0020022001360204200110220b200241106a24000f0b20021028000bbf0302077f017e230041206b22022103200224000240200028021822042000411c6a2802002205460d0002400340200541786a2802002001460d012004200541686a2205470d000c020b0b20042005460d00200541686a2802002105200341206a240020050f0b02400240024020014100410010142204417f4c0d0020044181044f0d0120022004410f6a4170716b22022400410021060c020b410041eec00010020b200410192102410121060b20012002200410141a41c000102022052000360230200542003703000240200441074b0d0041004185c10010020b20052002410810061a200541106a2107200241086a21080240200441786a411f4b0d0041004185c10010020b20072008412010061a20052001360234200320053602182003200529030022093703102003200136020c0240024002402000411c6a22072802002204200041206a2802004f0d00200420093703082004200136021020034100360218200420053602002007200441186a36020020060d010c020b200041186a200341186a200341106a2003410c6a105d2006450d010b2002101a0b200328021821012003410036021802402001450d00200110220b200341206a240020050bc40103027f017e017f230022042105024020012802302000460d00410041bdc10010020b024020002903001013510d00410041ebc10010020b20012903002106200328020022032802002207200328020420076b200141106a22071015024020062001290300510d004100419ec20010020b2004220441506a2203240020032001410810061a200441586a2007412010061a20012802342002200341281017024020062000290310540d00200041106a427e200642017c2006427d561b3703000b200524000bfb0101047f230041306b2204240020042002370328024020012903001013510d004100418ac10010020b20042003360214200420013602102004200441286a36021841c000102022032001200441106a105c1a2004200336022020042003290300220237031020042003280234220536020c024002402001411c6a22062802002207200141206a2802004f0d00200720023703082007200536021020044100360220200720033602002006200741186a3602000c010b200141186a200441206a200441106a2004410c6a105d0b2000200336020420002001360200200428022021012004410036022002402001450d00200110220b200441306a24000b960305027f017e017f017e017f230041d0006b2202240020002802002103024020012802002201280208200128020422006b411f4b0d0041004185c1001002200141046a28020021000b200241306a2000412010061a200141046a2201200128020041206a3602004200210441102101200241106a2105410021004200210602400340200241306a20006a2107024020014102490d002006420886200420073100008422044238888421062001417f6a210120044208862104200041016a22004120470d010c020b024020014101460d00410041ffc20010020b200520063703082005200420073100008437030041102101200541106a21054200210442002106200041016a22004120470d000b0b024020014110460d00024020014102490d00200220042006200141037441786a1011200241086a2903002106200229030021040b20052004370300200520063703080b20032002290310370300200341086a2002290318370300200341186a200241106a41186a290300370300200341106a200241106a41106a290300370300200241d0006a24000bba0101047f230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a20034200370300200241086a2102024020044178714108470d0041004185c10010020b20032002410810061a200341106a24000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000bd30201047f230041306b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360224200320023602202003200220046a2205360228200342003703180240200441074b0d0041004185c1001002200341286a2802002105200328022421020b200341186a2002410810061a2003200241086a2202360224024020052002470d0041004185c1001002200341206a41086a2802002105200328022421020b200341176a2002410110061a2003200241016a2202360224024020052002470d0041004185c1001002200328022421020b200341166a2002410110061a2003200241016a3602242003410036021020034200370308200341206a200341086a10431a024020032802082202450d002003200236020c200210220b200341306a24000bbe0201067f0240024002400240024020002802082202200028020422036b20014f0d002003200028020022046b220520016a2206417f4c0d0241ffffffff0721070240200220046b220241feffffff034b0d0020062002410174220220022006491b2207450d020b2007102021020c030b200041046a21000340200341003a00002000200028020041016a22033602002001417f6a22010d000c040b0b41002107410021020c010b20001028000b200220076a2107200320016a20046b2104200220056a220521030340200341003a0000200341016a21032001417f6a22010d000b200220046a21042005200041046a2206280200200028020022016b22036b2102024020034101480d0020022001200310061a200028020021010b2000200236020020062004360200200041086a20073602002001450d00200110220f0b0bd00102047f017e230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a2102024020044108470d0041004185c10010020b200341076a2002410110061a2003290308210620032d0007210420001007200620044100471008200341106a24000bab0202047f047e230041206b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037031841002102200341186a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370318200341186a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a21050240200441787122044108470d0041004185c10010020b200341106a2005410810061a200241106a2105024020044110470d0041004185c10010020b200341086a2005410810061a200241186a2102024020044118470d0041004185c10010020b20032002410810061a200329030021062003290308210720032903102108200329031821092000100720092008200720061009200341206a24000bd30101047f230041206b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b41002102200341186a21050c020b20022004410f6a4170716b220224000b2002200410051a200341186a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a21050240200441787122044108470d0041004185c10010020b200341106a2005410810061a200241106a2102024020044110470d0041004185c10010020b200341086a2002410810061a20001007200341206a24000bc60301047f23004180016b220221032002240041002104024010042205450d00024002402005418004490d002005101921040c010b20022005410f6a4170716b220424000b2004200510051a0b20032004360254200320043602502003200420056a3602582003410036024820034200370340200341d0006a200341c0006a10411a200341106a41086a2204200328025836020020032003290350370310200341e0006a41086a2205200428020036020020032003290310370360200341f0006a41086a20052802002204360200200341386a20043602002003200037032020032001370328200320032903602200370330200320003703702003410036020820034200370300200328024420032802406b220441306d2105024002402004450d00200541d6aad52a4f0d01200341086a200410202204200541306c6a36020020032004360200200320043602042003280244200328024022026b22054101480d0020042002200510061a20032003280204200541306e41306c6a3602040b200341206a20031038024020032802002204450d0020032004360204200410220b024020032802402204450d0020032004360244200410220b20034180016a24000f0b20031028000bc60301067f0240024002400240024020002802082202200028020422036b41306d20014f0d002003200028020022046b41306d220520016a220641d6aad52a4f0d0241d5aad52a21030240200220046b41306d220241a9d5aa154b0d0020062002410174220320032006491b2203450d020b200341306c102021040c030b200041046a21020340200341086a2200420037030020034200370300200341286a4200370300200341206a4200370300200341186a4200370300200341106a4200370300200041003602002002200228020041306a22033602002001417f6a22010d000c040b0b41002103410021040c010b20001028000b2004200341306c6a21072004200541306c6a220521030340200341086a2202420037030020034200370300200341286a4200370300200341206a4200370300200341186a4200370300200341106a420037030020024100360200200341306a21032001417f6a22010d000b2004200641306c6a21042005200041046a2206280200200028020022036b220141506d41306c6a2102024020014101480d0020022003200110061a200028020021030b2000200236020020062004360200200041086a20073602002003450d00200310220f0b0b8a0101037f230041e0006b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360254200320023602502003200220046a360258200341d0006a200341086a10421a20001007200341086a1029200341e0006a24000b950101047f230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a20032903081007200341106a24000bd70303047f027e017f230041f0006b2202210320022400024002400240024010042204450d002004418004490d012004101921050c020b410021050c020b20022004410f6a4170716b220524000b2005200410051a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004185c10010020b200341d0006a2005412010061a200341306a2105410021044200210702400340200341d0006a20046a2108024020024102490d002007420886200620083100008422064238888421072002417f6a210220064208862106200441016a22044120470d010c020b024020024101460d00410041ffc20010020b200520073703082005200620083100008437030041102102200541106a21054200210642002107200441016a22044120470d000b0b024020024110460d00024020024102490d00200320062007200241037441786a1011200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a290300370300200320032903383703182003200329033037031020001007200341106a102c200341f0006a24000be00303047f027e017f230041f0006b2202210320022400024002400240024010042204450d002004418004490d012004101921050c020b410021050c020b20022004410f6a4170716b220524000b2005200410051a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004185c10010020b200341d0006a2005412010061a200341306a2105410021044200210702400340200341d0006a20046a2108024020024102490d002007420886200620083100008422064238888421072002417f6a210220064208862106200441016a22044120470d010c020b024020024101460d00410041ffc20010020b200520073703082005200620083100008437030041102102200541106a21054200210642002107200441016a22044120470d000b0b024020024110460d00024020024102490d00200320062007200241037441786a1011200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a29030037030020032003290338370318200320032903303703100240200341106a102b0d00410041d9c20010020b200341f0006a24000beb0201037f23004180016b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360254200320023602502003200220046a360258200342003703480240200441074b0d0041004185c1001002200328025421020b200341c8006a2002410810061a2003200241086a3602542003410036024020034200370338200341d0006a200341386a10431a200341086a41086a2202200341d0006a41086a28020036020020032003290350370308200341e0006a41086a2204200228020036020020032003290308370360200341f0006a41086a20042802002202360200200341306a2002360200200320003703182003200137032020032003290360220037032820032000370370200341186a2003290348200341386a103d024020032802382202450d002003200236023c200210220b20034180016a24000bbc0102037f017e230041306b22032400200020013602302000420037030020002002280204220428020029030037030020022802002101200428020422042802002205200428020420056b200041106a2204101520032000410810061a20034108722004412010061a2000200129030842808080809aecb4ee31200228020829030020002903002206200341281016360234024020062001290310540d00200141106a427e200642017c2006427d561b3703000b200341306a240020000baa0301057f024002402000280204200028020022046b41186d220541016a220641abd5aad5004f0d0041aad5aad500210702400240200028020820046b41186d220441d4aad52a4b0d0020062004410174220720072006491b2207450d010b200741186c102021040c020b41002107410021040c010b20001028000b20012802002106200141003602002004200541186c22086a2201200636020020012002290300370308200120032802003602102004200741186c6a2105200141186a210602400240200041046a280200220220002802002207460d00200420086a41686a21010340200241686a220428020021032004410036020020012003360200200141106a200241786a280200360200200141086a200241706a290300370300200141686a21012004210220072004470d000b200141186a2101200041046a2802002107200028020021020c010b200721020b20002001360200200041046a2006360200200041086a2005360200024020072002460d000340200741686a220728020021012007410036020002402001450d00200110220b20022007470d000b0b02402002450d00200210220b0b0bdf030b00419cc0000b4c6661696c656420746f20616c6c6f63617465207061676573006f626a6563742070617373656420746f206974657261746f725f746f206973206e6f7420696e206d756c74695f696e646578000041e8c0000b1d7772697465006572726f722072656164696e67206974657261746f7200004185c1000b05726561640000418ac1000b3363616e6e6f7420637265617465206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e7472616374000041bdc1000b2e6f626a6563742070617373656420746f206d6f64696679206973206e6f7420696e206d756c74695f696e646578000041ebc1000b3363616e6e6f74206d6f64696679206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e74726163740000419ec2000b3b757064617465722063616e6e6f74206368616e6765207072696d617279206b6579207768656e206d6f64696679696e6720616e206f626a656374000041d9c2000b2270726f746f636f6c2066656174757265206973206e6f7420616374697661746564000041fbc2000b04676574000041ffc2000b2c756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f72000041000b04b02100000000000000000000000003917c562680b415b93db73416ff29230dfbe7ab1ba4d208b46029d01333cd3a03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b44010000000000ea30556ab602000000000000000000000000 +DMLOG APPLIED_TRANSACTION 3 03917c562680b415b93db73416ff29230dfbe7ab1ba4d208b46029d01333cd3a03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d0070000fb050000000000000000d8170000000000000001010000010000000000ea30559a90c525172f87bbac0a6378610727f0fe1d7ebe908df973923d29a1606f9a5703000000000000000300000000000000010000000000ea3055030000000000000001000000000000ea30550000000000ea305500000040258ab2c2010000000000ea305500000000a8ed3232fe8a010000000000ea30550000f18a010061736d01000000019d011a60000060037f7e7f0060027f7e0060027f7f0060057f7e7e7e7e0060047f7e7e7e0060017f017f60017f0060037f7f7f017f6000017f60027f7f017f60017e0060027e7f0060047e7e7e7e0060027f7f017e6000017e60047e7e7e7e017f60047f7e7e7f0060037f7f7f0060067e7e7e7e7f7f017f60047f7e7f7f0060037e7e7e0060037e7e7f017f60047f7f7e7f0060027e7e0060047f7f7f7f00028e041803656e761469735f666561747572655f616374697661746564000603656e761370726561637469766174655f66656174757265000703656e760c656f73696f5f617373657274000303656e76066d656d736574000803656e7610616374696f6e5f646174615f73697a65000903656e7610726561645f616374696f6e5f64617461000a03656e76066d656d637079000803656e760c726571756972655f61757468000b03656e760e7365745f70726976696c65676564000c03656e76137365745f7265736f757263655f6c696d697473000d03656e760561626f7274000003656e76167365745f70726f706f7365645f70726f647563657273000e03656e76207365745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000303656e76206765745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000a03656e760c63757272656e745f74696d65000f03656e76146765745f6163746976655f70726f647563657273000a03656e760b64625f66696e645f693634001003656e76095f5f6173686c746933001103656e7611656f73696f5f6173736572745f636f6465000203656e761063757272656e745f7265636569766572000f03656e760a64625f6765745f693634000803656e7606736861323536001203656e760c64625f73746f72655f693634001303656e760d64625f7570646174655f69363400140347460006070007090a08060607070a0a030307070a060715011602160316041603160316030516011603030a0a0a030a17170318181818181818180318181818031818181818081904050170010a0a05030100010616037f014180c0000b7f0041abc3000b7f0041abc3000b070901056170706c79002d090f010041010b092e30323436383a3b3d0ac98001460400101b0b800101037f02400240024002402000450d004100410028028c40200041107622016a220236028c404100410028028440220320006a41076a417871220036028440200241107420004d0d0120014000417f460d020c030b41000f0b4100200241016a36028c40200141016a4000417f470d010b4100419cc000100220030f0b20030b02000b3601017f230041106b2200410036020c4100200028020c28020041076a417871220036028440410020003602804041003f0036028c400b02000b06004190c0000bf50101067f4100210202400240410020006b22032000712000470d00200041104b0d01200110190f0b101d411636020041000f0b0240024002402000417f6a220420016a10192200450d002000200420006a2003712202460d012000417c6a220328020022044107712201450d02200020044178716a220441786a2205280200210620032001200220006b2207723602002002417c6a200420026b2203200172360200200241786a20064107712201200772360200200520012003723602002000101a0b20020f0b20000f0b200241786a200041786a280200200220006b22006a3602002002417c6a200328020020006b36020020020b3301017f411621030240024020014104490d0020012002101e2201450d0120002001360200410021030b20030f0b101d2802000b3801027f02402000410120001b2201101922000d000340410021004100280298402202450d012002110000200110192200450d000b0b20000b0600200010200b0e0002402000450d002000101a0b0b0600200010220b6b01027f230041106b2202240002402002410c6a20014104200141044b1b22012000410120001b2203101f450d00024003404100280298402200450d0120001100002002410c6a20012003101f0d000c020b0b2002410036020c0b200228020c2100200241106a240020000b08002000200110240b0e0002402000450d002000101a0b0b08002000200110260b0500100a000b4e01017f230041e0006b220124002001200141d8006a3602082001200141106a3602042001200141106a36020020012000102a1a200141106a200128020420012802006b100c200141e0006a24000b920901047f02402000280208200028020422026b41074a0d00410041e8c0001002200041046a28020021020b20022001410810061a200041046a2202200228020041086a2203360200200141086a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001410c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141106a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141146a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141186a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001411c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141206a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141246a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141286a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001412c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141306a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a2203360200200141346a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141386a21040240200041086a220528020020036b41034a0d00410041e8c0001002200228020021030b20032004410410061a2002200228020041046a22033602002001413c6a21020240200528020020036b41034a0d00410041e8c0001002200041046a28020021030b20032002410410061a200041046a2202200228020041046a2203360200200141c0006a21040240200041086a220528020020036b41014a0d00410041e8c0001002200228020021030b20032004410210061a2002200228020041026a2203360200200141c2006a21010240200528020020036b41014a0d00410041e8c0001002200041046a28020021030b20032001410210061a200041046a2201200128020041026a36020020000bfa0203017f027e017f230041206b220124002001200029030022024220883c000b200120024228883c000a200120024230883c0009200120024238883c00082001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c000020012002a722043a000f200120044108763a000e200120044110763a000d200120044118763a000c20012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a00042001200041186a29030022023c00172001200029031022034220883c001b200120034228883c001a200120034230883c0019200120034238883c0018200120024220883c0013200120024228883c0012200120024230883c0011200120024238883c001020012002a722004108763a0016200120004110763a0015200120004118763a001420012003a722003a001f200120004108763a001e200120004110763a001d200120004118763a001c200110002100200141206a240020000bf60203017f027e017f230041206b220124002001200029030022024220883c000b200120024228883c000a200120024230883c0009200120024238883c00082001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c000020012002a722043a000f200120044108763a000e200120044110763a000d200120044118763a000c20012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a00042001200041186a29030022023c00172001200029031022034220883c001b200120034228883c001a200120034230883c0019200120034238883c0018200120024220883c0013200120024228883c0012200120024230883c0011200120024238883c001020012002a722004108763a0016200120004110763a0015200120004118763a001420012003a722003a001f200120004108763a001e200120004110763a001d200120004118763a001c20011001200141206a24000bcc0401017f23004190016b220324001018024020012000520d0002400240024002400240024002400240200242ffffb7f6a497b2d942570d00200242ffffffffb5f7d6d942570d01200242808080d0b2b3bb9932510d03200242808080c093fad6d942510d0420024280808080b6f7d6d942520d082003410036028c0120034101360288012003200329038801370300200120012003102f1a0c080b200242fffffffffff698d942550d0120024290a9d9d9dd8c99d6ba7f510d0420024280808080daac9bd6ba7f520d0720034100360264200341023602602003200329036037032820012001200341286a10311a0c070b2002428080b8f6a497b2d942510d0420024280808096cdebd4d942520d062003410036026c200341033602682003200329036837032020012001200341206a10331a0c060b2002428080808080f798d942510d042002428080b8f6a4979ad942520d0520034100360284012003410436028001200320032903800137030820012001200341086a10351a0c050b20034100360254200341053602502003200329035037033820012001200341386a10371a0c040b20034100360274200341063602702003200329037037031820012001200341186a10391a0c030b2003410036024c200341073602482003200329034837034020012001200341c0006a10371a0c020b2003410036027c200341083602782003200329037837031020012001200341106a103c1a0c010b2003410036025c200341093602582003200329035837033020012001200341306a103e1a0b4100101c20034190016a24000b1200200029030010072001200241004710080bd30201077f230041306b2203210420032400200228020421052002280200210641002102024010042207450d00024002402007418104490d002007101921020c010b20032007410f6a4170716b220224000b2002200710051a0b200441003a002820044200370320200220076a2103200441206a41086a210802400240200741074b0d0041004185c1001002200441206a2002410810061a200241086a21090c010b200441206a2002410810061a200241086a210920074108470d0041004185c10010020b20082009410110061a200441186a200336020020042002360210200441146a200241096a3602002004200137030820042000370300200420054101756a2103200441286a2d000021082004290320210002402005410171450d00200328020020066a28020021060b20032000200841ff0171200611010002402007418104490d002002101a0b200441306a240041010b0600200110070b830201057f230041306b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b20044200370328200220076a21030240200741074b0d0041004185c10010020b200441286a2002410810061a2004411c6a200241086a360200200441206a2003360200200420013703102004200037030820042002360218200441086a20054101756a21032004290328210002402005410171450d00200328020020066a28020021060b20032000200611020002402007418104490d002002101a0b200441306a240041010b0d0020002903001007200110290bf70201067f230041a0026b2203210420032400200228020421052002280200210641002102024010042207450d00024002402007418104490d002007101921020c010b20032007410f6a4170716b220224000b2002200710051a0b200441c8006a410041c80010031a2004200236023c200420023602382004200220076a360240200441386a200441c8006a10421a200441086a41086a220320042802403602002004200429033837030820044190016a41086a220820032802003602002004200429030837039001200441d8016a41086a20082802002203360200200441306a2003360200200420003703182004200137032020042004290390012200370328200420003703d80120044190016a200441c8006a41c80010061a200441d8016a20044190016a41c80010061a200441186a20054101756a210302402005410171450d00200328020020066a28020021060b2003200441d8016a200611030002402007418104490d002002101a0b200441a0026a240041010b130020002903001007200120022003200410090b940302067f027e23004180016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b2004420037034820044200370340200442003703502004420037035820042002360234200420023602302004200220076a3602382004200441306a3602702004200441c0006a360210200441106a200441f0006a103f200441086a2203200428023836020020042004290330370300200441e0006a41086a2208200328020036020020042004290300370360200441f0006a41086a20082802002203360200200441286a2003360200200420003703102004200137031820042004290360220037032020042000370370200441106a20054101756a21032004290358210020042903502101200429034821092004290340210a02402005410171450d00200328020020066a28020021060b2003200a200920012000200611040002402007418104490d002002101a0b20044180016a240041010b0d00200029030010072001102c0bfe0301087f230041a0016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b200441c0006a41186a22034200370300200441c0006a41106a22084200370300200442003703482004420037034020042002360234200420023602302004200220076a3602382004200441306a3602602004200441c0006a3602800120044180016a200441e0006a1048200441086a2209200428023836020020042004290330370300200441e0006a41086a220a20092802003602002004200429030037036020044180016a41086a200a2802002209360200200441106a41186a200936020020042000370310200420013703182004200429036022003703202004200037038001200441e0006a41186a22092003290300370300200441e0006a41106a22032008290300370300200420042903483703682004200429034037036020044180016a41186a200929030037030020044180016a41106a200329030037030020042004290368370388012004200429036037038001200441106a20054101756a210302402005410171450d00200328020020066a28020021060b200320044180016a200611030002402007418104490d002002101a0b200441a0016a240041010b5601027f23002202210320002903001007024010042200418104490d00200010192202200010051a20022000100b1a200324000f0b20022000410f6a4170716b220224002002200010051a20022000100b1a200324000bb80501077f230041f0006b220321042003240020022802042105200228020021064100210741002102024010042208450d00024002402008418104490d002008101921020c010b20032008410f6a4170716b220224000b2002200810051a0b200441003602482004420037034020042002360234200420023602302004200220086a360238200441306a200441c0006a10411a200441086a2203200428023836020020042004290330370300200441d0006a41086a2209200328020036020020042004290300370350200441e0006a41086a20092802002203360200200441286a20033602002004200037031020042001370318200420042903502200370320200420003703602004410036025820044200370350200428024420042802406b220341306d21090240024002402003450d00200941d6aad52a4f0d01200441d8006a200310202207200941306c6a36020020042007360250200420073602542004280244200428024022096b22034101480d0020072009200310061a20042004280254200341306e41306c6a22073602540b200441106a20054101756a210302402005410171450d00200328020020066a28020021060b2004410036026820044200370360200720042802506b220741306d210502402007450d00200541d6aad52a4f0d02200441e8006a200710202207200541306c6a36020020042007360260200420073602642004280254200428025022096b22054101480d0020072009200510061a20042007200541306e41306c6a3602640b2003200441e0006a2006110300024020042802602207450d0020042007360264200710220b024020042802502207450d0020042007360254200710220b02402008418104490d002002101a0b024020042802402202450d0020042002360244200210220b200441f0006a240041010f0b200441d0006a1028000b200441e0006a1028000b130002402001102b0d00410041d9c20010020b0b0900200029030010070b870302067f017e23004180016b22032104200324002002280204210520022802002106024002400240024010042207450d002007418104490d012007101921020c020b410021020c020b20032007410f6a4170716b220224000b2002200710051a0b2004420037035020044200370348200442003703582004200236023c200420023602382004200220076a3602402004200441386a3602702004200441c8006a360218200441186a200441f0006a1040200441086a41086a2203200428024036020020042004290338370308200441e0006a41086a2208200328020036020020042004290308370360200441f0006a41086a20082802002203360200200441306a2003360200200420003703182004200137032020042004290360220037032820042000370370200441186a20054101756a210320042903582100200429035021012004290348210902402005410171450d00200328020020066a28020021060b2003200920012000200611050002402007418104490d002002101a0b20044180016a240041010bc00203017f017e027f230041c0006b2203240020032001370338200341306a41003602002003427f37032020034200370328200320002903002204370310200320043703180240024002402004200442808080809aecb4ee312001101022004100480d000240200341106a200010452200280230200341106a460d00410041b5c00010020b20032002360208200341106a20004200200341086a1046200328022822050d010c020b2003200236020c2003200341386a3602082003200341106a2001200341086a104720032802282205450d010b024002402003412c6a220628020022002005460d000340200041686a220028020021022000410036020002402002450d00200210220b20052000470d000b200341286a28020021000c010b200521000b2006200536020020001022200341c0006a24000f0b200341c0006a24000b9e0301057f23004180016b2203240020032204200229020037035841002102024010042205450d00024002402005418104490d002005101921020c010b20032005410f6a4170716b220224000b2002200510051a0b200441d0006a4100360200200442003703402004420037034820042002360234200420023602302004200220056a360238200221030240200541074b0d0041004185c1001002200428023421030b200441c0006a2003410810061a2004200341086a360234200441306a200441c0006a41086a220310431a200441086a2206200441306a41086a28020036020020042004290330370300200441e0006a41086a2207200628020036020020042004290300370360200441f0006a41086a20072802002206360200200441286a20063602002004200037031020042001370318200420042903602200370320200420003703702004200441d8006a3602742004200441106a360270200441f0006a200441c0006a104402402005418104490d002002101a0b024020032802002202450d00200441cc006a2002360200200210220b20044180016a240041010bc10201037f20002802002102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a3602002000280200220041086a2102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a360200200041106a2102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a360200200041186a2100024020012802002201280208200128020422036b41074b0d0041004185c1001002200141046a28020021030b20002003410810061a200141046a2201200128020041086a3602000bf30101037f20002802002102024020012802002203280208200328020422046b41074b0d0041004185c1001002200341046a28020021040b20022004410810061a200341046a2203200328020041086a3602002000280200220441086a2102024020012802002203280208200328020422006b41074b0d0041004185c1001002200341046a28020021000b20022000410810061a200341046a2203200328020041086a360200200441106a2100024020012802002201280208200128020422036b41074b0d0041004185c1001002200141046a28020021030b20002003410810061a200141046a2201200128020041086a3602000be80303017f017e067f2000280204210242002103200041086a2104200041046a2105410021060340024020022004280200490d00410041fbc2001002200528020021020b20022d000021072005200241016a22023602002003200741ff0071200641ff0171220674ad842103200641076a2106200221022007418001710d000b02400240024020012802042208200128020022096b41306d22072003a722024f0d002001200220076b105620012802002209200141046a2802002208470d010c020b0240200720024d0d00200141046a2009200241306c6a22083602000b20092008460d010b200041046a22042802002102200041086a210103400240200128020020026b41074b0d0041004185c1001002200428020021020b20092002410810061a2004200428020041086a220236020041002105420021030340024020022001280200490d00410041fbc2001002200428020021020b20022d000021072004200241016a22063602002003200741ff0071200541ff0171220274ad842103200241076a2105200621022007418001710d000b200920033e02082009410c6a21020240200128020020066b41204b0d0041004185c1001002200428020021060b20022006412110061a2004200428020041216a2202360200200941306a22092008470d000b0b20000b920901047f02402000280208200028020422026b41074b0d0041004185c1001002200041046a28020021020b20012002410810061a200041046a2202200228020041086a2203360200200141086a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001410c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141106a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141146a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141186a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001411c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141206a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141246a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141286a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001412c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141306a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a2203360200200141346a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141386a21040240200041086a220528020020036b41034b0d0041004185c1001002200228020021030b20042003410410061a2002200228020041046a22033602002001413c6a21020240200528020020036b41034b0d0041004185c1001002200041046a28020021030b20022003410410061a200041046a2202200228020041046a2203360200200141c0006a21040240200041086a220528020020036b41014b0d0041004185c1001002200228020021030b20042003410210061a2002200228020041026a2203360200200141c2006a21010240200528020020036b41014b0d0041004185c1001002200041046a28020021030b20012003410210061a200041046a2201200128020041026a36020020000ba10203017f017e057f2000280204210242002103200041086a2104200041046a2105410021060340024020022004280200490d00410041fbc2001002200528020021020b20022d000021072005200241016a22083602002003200741ff0071200641ff0171220274ad842103200241076a2106200821022007418001710d000b0240024020012802042207200128020022026b22052003a722064f0d002001200620056b1051200041046a2802002108200141046a2802002107200128020021020c010b200520064d0d00200141046a200220066a22073602000b0240200041086a28020020086b200720026b22074f0d0041004185c1001002200041046a28020021080b20022008200710061a200041046a2202200228020020076a36020020000bf80103017f017e027f230041106b22022400200242003703002002410036020820012903002103024002402001410c6a28020020012802086b2204450d002004417f4c0d01200241086a20041020220520046a36020020022005360200200220053602042001410c6a280200200141086a28020022046b22014101480d0020052004200110061a2002200520016a3602040b20002802002000280204220128020422044101756a21002001280200210102402004410171450d00200028020020016a28020021010b2000200320022001110100024020022802002201450d0020022001360204200110220b200241106a24000f0b20021028000bbf0302077f017e230041206b22022103200224000240200028021822042000411c6a2802002205460d0002400340200541786a2802002001460d012004200541686a2205470d000c020b0b20042005460d00200541686a2802002105200341206a240020050f0b02400240024020014100410010142204417f4c0d0020044181044f0d0120022004410f6a4170716b22022400410021060c020b410041eec00010020b200410192102410121060b20012002200410141a41c000102022052000360230200542003703000240200441074b0d0041004185c10010020b20052002410810061a200541106a2107200241086a21080240200441786a411f4b0d0041004185c10010020b20072008412010061a20052001360234200320053602182003200529030022093703102003200136020c0240024002402000411c6a22072802002204200041206a2802004f0d00200420093703082004200136021020034100360218200420053602002007200441186a36020020060d010c020b200041186a200341186a200341106a2003410c6a105d2006450d010b2002101a0b200328021821012003410036021802402001450d00200110220b200341206a240020050bc40103027f017e017f230022042105024020012802302000460d00410041bdc10010020b024020002903001013510d00410041ebc10010020b20012903002106200328020022032802002207200328020420076b200141106a22071015024020062001290300510d004100419ec20010020b2004220441506a2203240020032001410810061a200441586a2007412010061a20012802342002200341281017024020062000290310540d00200041106a427e200642017c2006427d561b3703000b200524000bfb0101047f230041306b2204240020042002370328024020012903001013510d004100418ac10010020b20042003360214200420013602102004200441286a36021841c000102022032001200441106a105c1a2004200336022020042003290300220237031020042003280234220536020c024002402001411c6a22062802002207200141206a2802004f0d00200720023703082007200536021020044100360220200720033602002006200741186a3602000c010b200141186a200441206a200441106a2004410c6a105d0b2000200336020420002001360200200428022021012004410036022002402001450d00200110220b200441306a24000b960305027f017e017f017e017f230041d0006b2202240020002802002103024020012802002201280208200128020422006b411f4b0d0041004185c1001002200141046a28020021000b200241306a2000412010061a200141046a2201200128020041206a3602004200210441102101200241106a2105410021004200210602400340200241306a20006a2107024020014102490d002006420886200420073100008422044238888421062001417f6a210120044208862104200041016a22004120470d010c020b024020014101460d00410041ffc20010020b200520063703082005200420073100008437030041102101200541106a21054200210442002106200041016a22004120470d000b0b024020014110460d00024020014102490d00200220042006200141037441786a1011200241086a2903002106200229030021040b20052004370300200520063703080b20032002290310370300200341086a2002290318370300200341186a200241106a41186a290300370300200341106a200241106a41106a290300370300200241d0006a24000bba0101047f230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a20034200370300200241086a2102024020044178714108470d0041004185c10010020b20032002410810061a200341106a24000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000b4401037f230022022103024010042204450d00024002402004418004490d002004101921020c010b20022004410f6a4170716b220224000b2002200410051a0b200324000bd30201047f230041306b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360224200320023602202003200220046a2205360228200342003703180240200441074b0d0041004185c1001002200341286a2802002105200328022421020b200341186a2002410810061a2003200241086a2202360224024020052002470d0041004185c1001002200341206a41086a2802002105200328022421020b200341176a2002410110061a2003200241016a2202360224024020052002470d0041004185c1001002200328022421020b200341166a2002410110061a2003200241016a3602242003410036021020034200370308200341206a200341086a10431a024020032802082202450d002003200236020c200210220b200341306a24000bbe0201067f0240024002400240024020002802082202200028020422036b20014f0d002003200028020022046b220520016a2206417f4c0d0241ffffffff0721070240200220046b220241feffffff034b0d0020062002410174220220022006491b2207450d020b2007102021020c030b200041046a21000340200341003a00002000200028020041016a22033602002001417f6a22010d000c040b0b41002107410021020c010b20001028000b200220076a2107200320016a20046b2104200220056a220521030340200341003a0000200341016a21032001417f6a22010d000b200220046a21042005200041046a2206280200200028020022016b22036b2102024020034101480d0020022001200310061a200028020021010b2000200236020020062004360200200041086a20073602002001450d00200110220f0b0bd00102047f017e230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a2102024020044108470d0041004185c10010020b200341076a2002410110061a2003290308210620032d0007210420001007200620044100471008200341106a24000bab0202047f047e230041206b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037031841002102200341186a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370318200341186a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a21050240200441787122044108470d0041004185c10010020b200341106a2005410810061a200241106a2105024020044110470d0041004185c10010020b200341086a2005410810061a200241186a2102024020044118470d0041004185c10010020b20032002410810061a200329030021062003290308210720032903102108200329031821092000100720092008200720061009200341206a24000bd30101047f230041206b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b41002102200341186a21050c020b20022004410f6a4170716b220224000b2002200410051a200341186a2105200441074b0d010b41004185c10010020b20052002410810061a200241086a21050240200441787122044108470d0041004185c10010020b200341106a2005410810061a200241106a2102024020044110470d0041004185c10010020b200341086a2002410810061a20001007200341206a24000bc60301047f23004180016b220221032002240041002104024010042205450d00024002402005418004490d002005101921040c010b20022005410f6a4170716b220424000b2004200510051a0b20032004360254200320043602502003200420056a3602582003410036024820034200370340200341d0006a200341c0006a10411a200341106a41086a2204200328025836020020032003290350370310200341e0006a41086a2205200428020036020020032003290310370360200341f0006a41086a20052802002204360200200341386a20043602002003200037032020032001370328200320032903602200370330200320003703702003410036020820034200370300200328024420032802406b220441306d2105024002402004450d00200541d6aad52a4f0d01200341086a200410202204200541306c6a36020020032004360200200320043602042003280244200328024022026b22054101480d0020042002200510061a20032003280204200541306e41306c6a3602040b200341206a20031038024020032802002204450d0020032004360204200410220b024020032802402204450d0020032004360244200410220b20034180016a24000f0b20031028000bc60301067f0240024002400240024020002802082202200028020422036b41306d20014f0d002003200028020022046b41306d220520016a220641d6aad52a4f0d0241d5aad52a21030240200220046b41306d220241a9d5aa154b0d0020062002410174220320032006491b2203450d020b200341306c102021040c030b200041046a21020340200341086a2200420037030020034200370300200341286a4200370300200341206a4200370300200341186a4200370300200341106a4200370300200041003602002002200228020041306a22033602002001417f6a22010d000c040b0b41002103410021040c010b20001028000b2004200341306c6a21072004200541306c6a220521030340200341086a2202420037030020034200370300200341286a4200370300200341206a4200370300200341186a4200370300200341106a420037030020024100360200200341306a21032001417f6a22010d000b2004200641306c6a21042005200041046a2206280200200028020022036b220141506d41306c6a2102024020014101480d0020022003200110061a200028020021030b2000200236020020062004360200200041086a20073602002003450d00200310220f0b0b8a0101037f230041e0006b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360254200320023602502003200220046a360258200341d0006a200341086a10421a20001007200341086a1029200341e0006a24000b950101047f230041106b22022103200224000240024002400240024010042204450d002004418004490d012004101921020c020b2003420037030841002102200341086a21050c020b20022004410f6a4170716b220224000b2002200410051a20034200370308200341086a2105200441074b0d010b41004185c10010020b20052002410810061a20032903081007200341106a24000bd70303047f027e017f230041f0006b2202210320022400024002400240024010042204450d002004418004490d012004101921050c020b410021050c020b20022004410f6a4170716b220524000b2005200410051a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004185c10010020b200341d0006a2005412010061a200341306a2105410021044200210702400340200341d0006a20046a2108024020024102490d002007420886200620083100008422064238888421072002417f6a210220064208862106200441016a22044120470d010c020b024020024101460d00410041ffc20010020b200520073703082005200620083100008437030041102102200541106a21054200210642002107200441016a22044120470d000b0b024020024110460d00024020024102490d00200320062007200241037441786a1011200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a290300370300200320032903383703182003200329033037031020001007200341106a102c200341f0006a24000be00303047f027e017f230041f0006b2202210320022400024002400240024010042204450d002004418004490d012004101921050c020b410021050c020b20022004410f6a4170716b220524000b2005200410051a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004185c10010020b200341d0006a2005412010061a200341306a2105410021044200210702400340200341d0006a20046a2108024020024102490d002007420886200620083100008422064238888421072002417f6a210220064208862106200441016a22044120470d010c020b024020024101460d00410041ffc20010020b200520073703082005200620083100008437030041102102200541106a21054200210642002107200441016a22044120470d000b0b024020024110460d00024020024102490d00200320062007200241037441786a1011200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a29030037030020032003290338370318200320032903303703100240200341106a102b0d00410041d9c20010020b200341f0006a24000beb0201037f23004180016b2202210320022400024002400240024010042204450d002004418004490d012004101921020c020b410021020c020b20022004410f6a4170716b220224000b2002200410051a0b20032002360254200320023602502003200220046a360258200342003703480240200441074b0d0041004185c1001002200328025421020b200341c8006a2002410810061a2003200241086a3602542003410036024020034200370338200341d0006a200341386a10431a200341086a41086a2202200341d0006a41086a28020036020020032003290350370308200341e0006a41086a2204200228020036020020032003290308370360200341f0006a41086a20042802002202360200200341306a2002360200200320003703182003200137032020032003290360220037032820032000370370200341186a2003290348200341386a103d024020032802382202450d002003200236023c200210220b20034180016a24000bbc0102037f017e230041306b22032400200020013602302000420037030020002002280204220428020029030037030020022802002101200428020422042802002205200428020420056b200041106a2204101520032000410810061a20034108722004412010061a2000200129030842808080809aecb4ee31200228020829030020002903002206200341281016360234024020062001290310540d00200141106a427e200642017c2006427d561b3703000b200341306a240020000baa0301057f024002402000280204200028020022046b41186d220541016a220641abd5aad5004f0d0041aad5aad500210702400240200028020820046b41186d220441d4aad52a4b0d0020062004410174220720072006491b2207450d010b200741186c102021040c020b41002107410021040c010b20001028000b20012802002106200141003602002004200541186c22086a2201200636020020012002290300370308200120032802003602102004200741186c6a2105200141186a210602400240200041046a280200220220002802002207460d00200420086a41686a21010340200241686a220428020021032004410036020020012003360200200141106a200241786a280200360200200141086a200241706a290300370300200141686a21012004210220072004470d000b200141186a2101200041046a2802002107200028020021020c010b200721020b20002001360200200041046a2006360200200041086a2005360200024020072002460d000340200741686a220728020021012007410036020002402001450d00200110220b20022007470d000b0b02402002450d00200210220b0b0bdf030b00419cc0000b4c6661696c656420746f20616c6c6f63617465207061676573006f626a6563742070617373656420746f206974657261746f725f746f206973206e6f7420696e206d756c74695f696e646578000041e8c0000b1d7772697465006572726f722072656164696e67206974657261746f7200004185c1000b05726561640000418ac1000b3363616e6e6f7420637265617465206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e7472616374000041bdc1000b2e6f626a6563742070617373656420746f206d6f64696679206973206e6f7420696e206d756c74695f696e646578000041ebc1000b3363616e6e6f74206d6f64696679206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e74726163740000419ec2000b3b757064617465722063616e6e6f74206368616e6765207072696d617279206b6579207768656e206d6f64696679696e6720616e206f626a656374000041d9c2000b2270726f746f636f6c2066656174757265206973206e6f7420616374697661746564000041fbc2000b04676574000041ffc2000b2c756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f72000041000b04b02100000000000000000000000003917c562680b415b93db73416ff29230dfbe7ab1ba4d208b46029d01333cd3a03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c010000000000ea30556ab602000000000000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG RAM_OP 0 eosio abi update setabi eosio 180538 44 DMLOG RAM_OP 0 eosio:eosio:abihash table add create_table eosio 180650 112 @@ -46,94 +46,98 @@ DMLOG TBL_OP INS 0 eosio eosio abihash eosio DMLOG RAM_OP 0 eosio:eosio:abihash:eosio table_row add primary_index_add eosio 180802 152 DMLOG DB_OP INS 0 eosio eosio eosio abihash eosio 0000000000ea3055d7abd75d188060de8a01ab2672d1cc2cd768fddc56203181b43685cc11f5ce46 DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":41298,"consumed":7136},"cpu_usage":{"last_ordinal":1262304002,"value_ex":24307,"consumed":4101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 78216184577675cf681592f18c754116fdf63576c1fa05b7566dd6ae6fe2ed8003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d00700008101000000000000000008040000000000000001010000010000000000ea3055e7de58a9939c6e694d3235202685f51b7fab8e82b1f9f96a637dafd9be0998a204000000000000000400000000000000010000000000ea3055040000000000000001010000000000ea30550000000000ea305500000000b863b2c2010000000000ea305500000000a8ed32328a110000000000ea305580110e656f73696f3a3a6162692f312e310019086162695f686173680002056f776e6572046e616d6504686173680b636865636b73756d32353608616374697661746500010e666561747572655f6469676573740b636865636b73756d32353609617574686f726974790004097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d086163636f756e7473197065726d697373696f6e5f6c6576656c5f7765696768745b5d0577616974730d776169745f7765696768745b5d15626c6f636b636861696e5f706172616d65746572730011136d61785f626c6f636b5f6e65745f75736167650675696e7436341a7461726765745f626c6f636b5f6e65745f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6e65745f75736167650675696e7433321e626173655f7065725f7472616e73616374696f6e5f6e65745f75736167650675696e743332106e65745f75736167655f6c65657761790675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f6e756d0675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f64656e0675696e743332136d61785f626c6f636b5f6370755f75736167650675696e7433321a7461726765745f626c6f636b5f6370755f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6370755f75736167650675696e743332196d696e5f7472616e73616374696f6e5f6370755f75736167650675696e743332186d61785f7472616e73616374696f6e5f6c69666574696d650675696e7433321e64656665727265645f7472785f65787069726174696f6e5f77696e646f770675696e743332156d61785f7472616e73616374696f6e5f64656c61790675696e743332166d61785f696e6c696e655f616374696f6e5f73697a650675696e743332176d61785f696e6c696e655f616374696f6e5f64657074680675696e743136136d61785f617574686f726974795f64657074680675696e7431360b63616e63656c64656c617900020e63616e63656c696e675f61757468107065726d697373696f6e5f6c6576656c067472785f69640b636865636b73756d3235360a64656c657465617574680002076163636f756e74046e616d650a7065726d697373696f6e046e616d650a6b65795f7765696768740002036b65790a7075626c69635f6b6579067765696768740675696e743136086c696e6b617574680004076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650b726571756972656d656e74046e616d650a6e65776163636f756e7400040763726561746f72046e616d65046e616d65046e616d65056f776e657209617574686f726974790661637469766509617574686f72697479076f6e6572726f7200020973656e6465725f69640775696e743132380873656e745f747278056279746573107065726d697373696f6e5f6c6576656c0002056163746f72046e616d650a7065726d697373696f6e046e616d65177065726d697373696f6e5f6c6576656c5f77656967687400020a7065726d697373696f6e107065726d697373696f6e5f6c6576656c067765696768740675696e7431360c70726f64756365725f6b657900020d70726f64756365725f6e616d65046e616d6511626c6f636b5f7369676e696e675f6b65790a7075626c69635f6b65790c72657161637469766174656400010e666561747572655f6469676573740b636865636b73756d323536077265716175746800010466726f6d046e616d65067365746162690002076163636f756e74046e616d65036162690562797465730a736574616c696d6974730004076163636f756e74046e616d650972616d5f627974657305696e7436340a6e65745f77656967687405696e7436340a6370755f77656967687405696e74363407736574636f64650004076163636f756e74046e616d6506766d747970650575696e743809766d76657273696f6e0575696e743804636f64650562797465730a736574676c696d69747300030372616d0675696e743634036e65740675696e743634036370750675696e74363409736574706172616d73000106706172616d7315626c6f636b636861696e5f706172616d657465727307736574707269760002076163636f756e74046e616d650769735f707269760575696e74380873657470726f64730001087363686564756c650e70726f64756365725f6b65795b5d0a756e6c696e6b617574680003076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650a757064617465617574680004076163636f756e74046e616d650a7065726d697373696f6e046e616d6506706172656e74046e616d65046175746809617574686f726974790b776169745f776569676874000208776169745f7365630675696e743332067765696768740675696e743136110000002a9bed32320861637469766174650000bc892a4585a6410b63616e63656c64656c6179000040cbdaa8aca24a0a64656c65746561757468000000002d6b03a78b086c696e6b617574680000409e9a2264b89a0a6e65776163636f756e7400000000e0d27bd5a4076f6e6572726f7200905436db6564acba0c72657161637469766174656400000000a0656dacba07726571617574680000000000b863b2c206736574616269000000ce4eba68b2c20a736574616c696d6974730000000040258ab2c207736574636f6465000000ce4ebac8b2c20a736574676c696d697473000000c0d25c53b3c209736574706172616d730000000060bb5bb3c207736574707269760000000038d15bb3c20873657470726f6473000040cbdac0e9e2d40a756e6c696e6b61757468000040cbdaa86c52d50a757064617465617574680001000000a061d3dc31036936340000086162695f6861736800000000000000000000000000000078216184577675cf681592f18c754116fdf63576c1fa05b7566dd6ae6fe2ed8003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b44010000000000ea3055340100000000000000000000000000 +DMLOG APPLIED_TRANSACTION 3 78216184577675cf681592f18c754116fdf63576c1fa05b7566dd6ae6fe2ed8003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d00700008101000000000000000008040000000000000001010000010000000000ea3055e7de58a9939c6e694d3235202685f51b7fab8e82b1f9f96a637dafd9be0998a204000000000000000400000000000000010000000000ea3055040000000000000001010000000000ea30550000000000ea305500000000b863b2c2010000000000ea305500000000a8ed32328a110000000000ea305580110e656f73696f3a3a6162692f312e310019086162695f686173680002056f776e6572046e616d6504686173680b636865636b73756d32353608616374697661746500010e666561747572655f6469676573740b636865636b73756d32353609617574686f726974790004097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d086163636f756e7473197065726d697373696f6e5f6c6576656c5f7765696768745b5d0577616974730d776169745f7765696768745b5d15626c6f636b636861696e5f706172616d65746572730011136d61785f626c6f636b5f6e65745f75736167650675696e7436341a7461726765745f626c6f636b5f6e65745f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6e65745f75736167650675696e7433321e626173655f7065725f7472616e73616374696f6e5f6e65745f75736167650675696e743332106e65745f75736167655f6c65657761790675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f6e756d0675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f64656e0675696e743332136d61785f626c6f636b5f6370755f75736167650675696e7433321a7461726765745f626c6f636b5f6370755f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6370755f75736167650675696e743332196d696e5f7472616e73616374696f6e5f6370755f75736167650675696e743332186d61785f7472616e73616374696f6e5f6c69666574696d650675696e7433321e64656665727265645f7472785f65787069726174696f6e5f77696e646f770675696e743332156d61785f7472616e73616374696f6e5f64656c61790675696e743332166d61785f696e6c696e655f616374696f6e5f73697a650675696e743332176d61785f696e6c696e655f616374696f6e5f64657074680675696e743136136d61785f617574686f726974795f64657074680675696e7431360b63616e63656c64656c617900020e63616e63656c696e675f61757468107065726d697373696f6e5f6c6576656c067472785f69640b636865636b73756d3235360a64656c657465617574680002076163636f756e74046e616d650a7065726d697373696f6e046e616d650a6b65795f7765696768740002036b65790a7075626c69635f6b6579067765696768740675696e743136086c696e6b617574680004076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650b726571756972656d656e74046e616d650a6e65776163636f756e7400040763726561746f72046e616d65046e616d65046e616d65056f776e657209617574686f726974790661637469766509617574686f72697479076f6e6572726f7200020973656e6465725f69640775696e743132380873656e745f747278056279746573107065726d697373696f6e5f6c6576656c0002056163746f72046e616d650a7065726d697373696f6e046e616d65177065726d697373696f6e5f6c6576656c5f77656967687400020a7065726d697373696f6e107065726d697373696f6e5f6c6576656c067765696768740675696e7431360c70726f64756365725f6b657900020d70726f64756365725f6e616d65046e616d6511626c6f636b5f7369676e696e675f6b65790a7075626c69635f6b65790c72657161637469766174656400010e666561747572655f6469676573740b636865636b73756d323536077265716175746800010466726f6d046e616d65067365746162690002076163636f756e74046e616d65036162690562797465730a736574616c696d6974730004076163636f756e74046e616d650972616d5f627974657305696e7436340a6e65745f77656967687405696e7436340a6370755f77656967687405696e74363407736574636f64650004076163636f756e74046e616d6506766d747970650575696e743809766d76657273696f6e0575696e743804636f64650562797465730a736574676c696d69747300030372616d0675696e743634036e65740675696e743634036370750675696e74363409736574706172616d73000106706172616d7315626c6f636b636861696e5f706172616d657465727307736574707269760002076163636f756e74046e616d650769735f707269760575696e74380873657470726f64730001087363686564756c650e70726f64756365725f6b65795b5d0a756e6c696e6b617574680003076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650a757064617465617574680004076163636f756e74046e616d650a7065726d697373696f6e046e616d6506706172656e74046e616d65046175746809617574686f726974790b776169745f776569676874000208776169745f7365630675696e743332067765696768740675696e743136110000002a9bed32320861637469766174650000bc892a4585a6410b63616e63656c64656c6179000040cbdaa8aca24a0a64656c65746561757468000000002d6b03a78b086c696e6b617574680000409e9a2264b89a0a6e65776163636f756e7400000000e0d27bd5a4076f6e6572726f7200905436db6564acba0c72657161637469766174656400000000a0656dacba07726571617574680000000000b863b2c206736574616269000000ce4eba68b2c20a736574616c696d6974730000000040258ab2c207736574636f6465000000ce4ebac8b2c20a736574676c696d697473000000c0d25c53b3c209736574706172616d730000000060bb5bb3c207736574707269760000000038d15bb3c20873657470726f6473000040cbdac0e9e2d40a756e6c696e6b61757468000040cbdaa86c52d50a757064617465617574680001000000a061d3dc31036936340000086162695f6861736800000000000000000000000000000078216184577675cf681592f18c754116fdf63576c1fa05b7566dd6ae6fe2ed8003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c010000000000ea3055340100000000000000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241 {"feature_digest":"1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"f3c3d91c4603cde2397268bfed4e662465293aab10cd9416db0d442b8cec2949","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"ONLY_LINK_TO_EXISTING_PERMISSION"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":42039,"consumed":7264},"cpu_usage":{"last_ordinal":1262304002,"value_ex":35882,"consumed":6101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 aa30bc93a59737ce708fd4d691b61d7858bfb309c4cf883e77a6a161b5a4abe503000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055218268a92acd1b24eeaeff3b51b569de14ee151eea2132d748be984aa9535d1405000000000000000500000000000000010000000000ea3055050000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232201a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b724100000000000000000000aa30bc93a59737ce708fd4d691b61d7858bfb309c4cf883e77a6a161b5a4abe503000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 aa30bc93a59737ce708fd4d691b61d7858bfb309c4cf883e77a6a161b5a4abe503000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055218268a92acd1b24eeaeff3b51b569de14ee151eea2132d748be984aa9535d1405000000000000000500000000000000010000000000ea3055050000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232201a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b724100000000000000000000aa30bc93a59737ce708fd4d691b61d7858bfb309c4cf883e77a6a161b5a4abe503000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99 {"feature_digest":"ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"9908b3f8413c8474ab2a6be149d3f4f6d0421d37886033f27d4759c47a26d944","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"REPLACE_DEFERRED"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":42780,"consumed":7392},"cpu_usage":{"last_ordinal":1262304002,"value_ex":47457,"consumed":8101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 3f12eecaafb41ec5142c6c6d69df767fb8f5183e1e5468aa418bef38a2bdf2bb03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea305513ab6d113ba5b180d6f68e1b67bdea99847550d673a1785e40dfe4faee8ec7c706000000000000000600000000000000010000000000ea3055060000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99000000000000000000003f12eecaafb41ec5142c6c6d69df767fb8f5183e1e5468aa418bef38a2bdf2bb03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 3f12eecaafb41ec5142c6c6d69df767fb8f5183e1e5468aa418bef38a2bdf2bb03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea305513ab6d113ba5b180d6f68e1b67bdea99847550d673a1785e40dfe4faee8ec7c706000000000000000600000000000000010000000000ea3055060000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99000000000000000000003f12eecaafb41ec5142c6c6d69df767fb8f5183e1e5468aa418bef38a2bdf2bb03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f {"feature_digest":"4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"45967387ee92da70171efd9fefd1ca8061b5efe6f124d269cd2468b47f1575a0","dependencies":["ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99"],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"NO_DUPLICATE_DEFERRED_ID"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":43521,"consumed":7520},"cpu_usage":{"last_ordinal":1262304002,"value_ex":59032,"consumed":10101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 39ec55367e4e4d0d6063a5e5aa2aa15d4a1aa1fbe0abe42c9081713ee04e55b103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea30552267bc3ee69f217c4f0bdbff84c23074f1780839b8adfb17537db55da4a0dc7607000000000000000700000000000000010000000000ea3055070000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f0000000000000000000039ec55367e4e4d0d6063a5e5aa2aa15d4a1aa1fbe0abe42c9081713ee04e55b103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 39ec55367e4e4d0d6063a5e5aa2aa15d4a1aa1fbe0abe42c9081713ee04e55b103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea30552267bc3ee69f217c4f0bdbff84c23074f1780839b8adfb17537db55da4a0dc7607000000000000000700000000000000010000000000ea3055070000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f0000000000000000000039ec55367e4e4d0d6063a5e5aa2aa15d4a1aa1fbe0abe42c9081713ee04e55b103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526 {"feature_digest":"e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"a98241c83511dc86c857221b9372b4aa7cea3aaebc567a48604e1d3db3557050","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"FIX_LINKAUTH_RESTRICTION"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":44262,"consumed":7648},"cpu_usage":{"last_ordinal":1262304002,"value_ex":70607,"consumed":12101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 72c5e78f690d5d20ec8c8e12ace2a3b34929099b93f621a8671ae43df821bc5b03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea30550f86c0418ffb919c58d37997594e446d2d98fd38b1ff3849da2c5da410aa331a08000000000000000800000000000000010000000000ea3055080000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff5260000000000000000000072c5e78f690d5d20ec8c8e12ace2a3b34929099b93f621a8671ae43df821bc5b03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 72c5e78f690d5d20ec8c8e12ace2a3b34929099b93f621a8671ae43df821bc5b03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea30550f86c0418ffb919c58d37997594e446d2d98fd38b1ff3849da2c5da410aa331a08000000000000000800000000000000010000000000ea3055080000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff5260000000000000000000072c5e78f690d5d20ec8c8e12ace2a3b34929099b93f621a8671ae43df821bc5b03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428 {"feature_digest":"68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"2853617cec3eabd41881eb48882e6fc5e81a0db917d375057864b3befbe29acd","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"DISALLOW_EMPTY_PRODUCER_SCHEDULE"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":45003,"consumed":7776},"cpu_usage":{"last_ordinal":1262304002,"value_ex":82182,"consumed":14101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 e358ede0d30a5ac5fa03a484a5142b0a38f658e0fb57644adb5b60c94206f9e003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055659dd999c0cb81c2eea85d3eda39898997e4a9bd57bcebcac06cc25db35e000b09000000000000000900000000000000010000000000ea3055090000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322068dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a29742800000000000000000000e358ede0d30a5ac5fa03a484a5142b0a38f658e0fb57644adb5b60c94206f9e003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 e358ede0d30a5ac5fa03a484a5142b0a38f658e0fb57644adb5b60c94206f9e003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055659dd999c0cb81c2eea85d3eda39898997e4a9bd57bcebcac06cc25db35e000b09000000000000000900000000000000010000000000ea3055090000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322068dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a29742800000000000000000000e358ede0d30a5ac5fa03a484a5142b0a38f658e0fb57644adb5b60c94206f9e003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43 {"feature_digest":"ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"e71b6712188391994c78d8c722c1d42c477cf091e5601b5cf1befd05721a57f3","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"RESTRICT_ACTION_TO_SELF"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":45744,"consumed":7904},"cpu_usage":{"last_ordinal":1262304002,"value_ex":93757,"consumed":16101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 60b8a605178774eed85eb65b3ae743e5f3dc9b11d4672e1d00be33a0d21c8dae03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055d209fd21b66b7e1f62b25302fd208120700fb20e0a9a0151d3909e1ca7a98f460a000000000000000a00000000000000010000000000ea30550a0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c430000000000000000000060b8a605178774eed85eb65b3ae743e5f3dc9b11d4672e1d00be33a0d21c8dae03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 60b8a605178774eed85eb65b3ae743e5f3dc9b11d4672e1d00be33a0d21c8dae03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055d209fd21b66b7e1f62b25302fd208120700fb20e0a9a0151d3909e1ca7a98f460a000000000000000a00000000000000010000000000ea30550a0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c430000000000000000000060b8a605178774eed85eb65b3ae743e5f3dc9b11d4672e1d00be33a0d21c8dae03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405 {"feature_digest":"8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"2f1f13e291c79da5a2bbad259ed7c1f2d34f697ea460b14b565ac33b063b73e2","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"ONLY_BILL_FIRST_AUTHORIZER"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":46485,"consumed":8032},"cpu_usage":{"last_ordinal":1262304002,"value_ex":105332,"consumed":18101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 689db7ff0751fd6025dbc997d9a7ca1fe4e525ee48e55e5fb2aee8403077dd3e03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055fd71f42952743b790fcaa82dabd6a843676b9bd5b91c891fc050f9c41374a35e0b000000000000000b00000000000000010000000000ea30550b0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232208ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a40500000000000000000000689db7ff0751fd6025dbc997d9a7ca1fe4e525ee48e55e5fb2aee8403077dd3e03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 689db7ff0751fd6025dbc997d9a7ca1fe4e525ee48e55e5fb2aee8403077dd3e03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055fd71f42952743b790fcaa82dabd6a843676b9bd5b91c891fc050f9c41374a35e0b000000000000000b00000000000000010000000000ea30550b0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232208ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a40500000000000000000000689db7ff0751fd6025dbc997d9a7ca1fe4e525ee48e55e5fb2aee8403077dd3e03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25 {"feature_digest":"2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"898082c59f921d0042e581f00a59d5ceb8be6f1d9c7a45b6f07c0e26eaee0222","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"FORWARD_SETCODE"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":47226,"consumed":8160},"cpu_usage":{"last_ordinal":1262304002,"value_ex":116907,"consumed":20101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 48ed94d5a6fa7dd478278b29bbff0a72bd9d9a5431423ed3f0b1ce393643108303000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea305512250767854476ab3904c7f604b0322bfa91821d01ddb20ecfaaff1beef8e04b0c000000000000000c00000000000000010000000000ea30550c0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232202652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed250000000000000000000048ed94d5a6fa7dd478278b29bbff0a72bd9d9a5431423ed3f0b1ce393643108303000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 48ed94d5a6fa7dd478278b29bbff0a72bd9d9a5431423ed3f0b1ce393643108303000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea305512250767854476ab3904c7f604b0322bfa91821d01ddb20ecfaaff1beef8e04b0c000000000000000c00000000000000010000000000ea30550c0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232202652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed250000000000000000000048ed94d5a6fa7dd478278b29bbff0a72bd9d9a5431423ed3f0b1ce393643108303000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d {"feature_digest":"f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"1eab748b95a2e6f4d7cb42065bdee5566af8efddf01a55a0a8d831b823f8828a","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"GET_SENDER"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":47967,"consumed":8288},"cpu_usage":{"last_ordinal":1262304002,"value_ex":128482,"consumed":22101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 aa192243a78a9d8954a3af3f044207536068d3ad3f7ffb3b7de53b959de190b003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055063f8bf038af0888c33fcfdd66c2f91fd6b060df73aaa32a1e905b143ceb9ac00d000000000000000d00000000000000010000000000ea30550d0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d00000000000000000000aa192243a78a9d8954a3af3f044207536068d3ad3f7ffb3b7de53b959de190b003000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 aa192243a78a9d8954a3af3f044207536068d3ad3f7ffb3b7de53b959de190b003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055063f8bf038af0888c33fcfdd66c2f91fd6b060df73aaa32a1e905b143ceb9ac00d000000000000000d00000000000000010000000000ea30550d0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d00000000000000000000aa192243a78a9d8954a3af3f044207536068d3ad3f7ffb3b7de53b959de190b003000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67 {"feature_digest":"4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"1812fdb5096fd854a4958eb9d53b43219d114de0e858ce00255bd46569ad2c68","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"RAM_RESTRICTIONS"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":48708,"consumed":8416},"cpu_usage":{"last_ordinal":1262304002,"value_ex":140057,"consumed":24101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 a9e581a81302c707c14f5985458d2ef53faf24afacb03115f5cbc17271d7504803000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055f279231a0740adb280f58749e984c932e17897073e9aedc1c33a102df52498430e000000000000000e00000000000000010000000000ea30550e0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d6700000000000000000000a9e581a81302c707c14f5985458d2ef53faf24afacb03115f5cbc17271d7504803000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 a9e581a81302c707c14f5985458d2ef53faf24afacb03115f5cbc17271d7504803000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055f279231a0740adb280f58749e984c932e17897073e9aedc1c33a102df52498430e000000000000000e00000000000000010000000000ea30550e0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d6700000000000000000000a9e581a81302c707c14f5985458d2ef53faf24afacb03115f5cbc17271d7504803000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2 {"feature_digest":"4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"927fdf78c51e77a899f2db938249fb1f8bb38f4e43d9c1f75b190492080cbc34","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"WEBAUTHN_KEY"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":49449,"consumed":8544},"cpu_usage":{"last_ordinal":1262304002,"value_ex":151632,"consumed":26101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 4185b6265a360d2bf774af7d82bd837333cfb6b976390dac78c284207b6bbce103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea305578e423734b3bacaadd9c1864e7a7c612255a9c0d9fcdeba49708ee6b147e13170f000000000000000f00000000000000010000000000ea30550f0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2000000000000000000004185b6265a360d2bf774af7d82bd837333cfb6b976390dac78c284207b6bbce103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 4185b6265a360d2bf774af7d82bd837333cfb6b976390dac78c284207b6bbce103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea305578e423734b3bacaadd9c1864e7a7c612255a9c0d9fcdeba49708ee6b147e13170f000000000000000f00000000000000010000000000ea30550f0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2000000000000000000004185b6265a360d2bf774af7d82bd837333cfb6b976390dac78c284207b6bbce103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707 {"feature_digest":"299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"ab76031cad7a457f4fd5f5fca97a3f03b8a635278e0416f77dcc91eb99a48e10","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"WTMSIG_BLOCK_SIGNATURES"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":50190,"consumed":8672},"cpu_usage":{"last_ordinal":1262304002,"value_ex":163207,"consumed":28101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 f6025d888ddcfb8fdfeee18204122f8b7a71908a96ac4e52bf9542ff398b0d4403000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055368a5df8e81472fb54f3424401fba4956a6e0737806b4f642b2d7014cf66fc2c10000000000000001000000000000000010000000000ea3055100000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670700000000000000000000f6025d888ddcfb8fdfeee18204122f8b7a71908a96ac4e52bf9542ff398b0d4403000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 f6025d888ddcfb8fdfeee18204122f8b7a71908a96ac4e52bf9542ff398b0d4403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055368a5df8e81472fb54f3424401fba4956a6e0737806b4f642b2d7014cf66fc2c10000000000000001000000000000000010000000000ea3055100000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670700000000000000000000f6025d888ddcfb8fdfeee18204122f8b7a71908a96ac4e52bf9542ff398b0d4403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071 {"feature_digest":"c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"69b064c5178e2738e144ed6caa9349a3995370d78db29e494b3126ebd9111966","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"ACTION_RETURN_VALUE"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":50931,"consumed":8800},"cpu_usage":{"last_ordinal":1262304002,"value_ex":174782,"consumed":30101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 116b232e8995b25d7bab8c5134bc993bcd84e72bc35d0b27fe723d7d25e98ac703000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea30552acd5ab1218225e0cc0a013d8e86b58cfc4d998058708fb1eb0116c1124f7c7f11000000000000001100000000000000010000000000ea3055110000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead4507100000000000000000000116b232e8995b25d7bab8c5134bc993bcd84e72bc35d0b27fe723d7d25e98ac703000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 116b232e8995b25d7bab8c5134bc993bcd84e72bc35d0b27fe723d7d25e98ac703000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea30552acd5ab1218225e0cc0a013d8e86b58cfc4d998058708fb1eb0116c1124f7c7f11000000000000001100000000000000010000000000ea3055110000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead4507100000000000000000000116b232e8995b25d7bab8c5134bc993bcd84e72bc35d0b27fe723d7d25e98ac703000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4 {"feature_digest":"5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"70787548dcea1a2c52c913a37f74ce99e6caae79110d7ca7b859936a0075b314","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"BLOCKCHAIN_PARAMETERS"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":51672,"consumed":8928},"cpu_usage":{"last_ordinal":1262304002,"value_ex":186357,"consumed":32101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 11a09bc0cc023daf656af6dadf37577a9d4c0cea8020c1d007a2c3d6dc1e52c103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055db17f5e8a451e3814885ec6d61c420ac422f1e0de77043c9024e592b64f8bd1412000000000000001200000000000000010000000000ea3055120000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232205443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b40000000000000000000011a09bc0cc023daf656af6dadf37577a9d4c0cea8020c1d007a2c3d6dc1e52c103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 11a09bc0cc023daf656af6dadf37577a9d4c0cea8020c1d007a2c3d6dc1e52c103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055db17f5e8a451e3814885ec6d61c420ac422f1e0de77043c9024e592b64f8bd1412000000000000001200000000000000010000000000ea3055120000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232205443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b40000000000000000000011a09bc0cc023daf656af6dadf37577a9d4c0cea8020c1d007a2c3d6dc1e52c103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99 {"feature_digest":"bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"d2596697fed14a0840013647b99045022ae6a885089f35a7e78da7a43ad76ed4","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"GET_CODE_HASH"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":52413,"consumed":9056},"cpu_usage":{"last_ordinal":1262304002,"value_ex":197932,"consumed":34101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 76bcbbd871a26403befd2ebf5491d6b84ded9f29cb95bfd54ca6ec46b1dfad5903000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055693240e7063adb7478594592f8a6e6cb76e33cabc605272575b687e3a0fa5f5e13000000000000001300000000000000010000000000ea3055130000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e990000000000000000000076bcbbd871a26403befd2ebf5491d6b84ded9f29cb95bfd54ca6ec46b1dfad5903000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 76bcbbd871a26403befd2ebf5491d6b84ded9f29cb95bfd54ca6ec46b1dfad5903000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055693240e7063adb7478594592f8a6e6cb76e33cabc605272575b687e3a0fa5f5e13000000000000001300000000000000010000000000ea3055130000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e990000000000000000000076bcbbd871a26403befd2ebf5491d6b84ded9f29cb95bfd54ca6ec46b1dfad5903000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40 {"feature_digest":"d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"8139e99247b87f18ef7eae99f07f00ea3adf39ed53f4d2da3f44e6aa0bfd7c62","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"CONFIGURABLE_WASM_LIMITS2"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":53154,"consumed":9184},"cpu_usage":{"last_ordinal":1262304002,"value_ex":209507,"consumed":36101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 1948411767455fe23b05b44fe5fb737422ce3831a41f2c68064990fd6f52fdaf03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055a40aa97866a6e0814065142f7d1038aaccb2e8a73661f6554c415c331ab8ec8b14000000000000001400000000000000010000000000ea3055140000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40000000000000000000001948411767455fe23b05b44fe5fb737422ce3831a41f2c68064990fd6f52fdaf03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 1948411767455fe23b05b44fe5fb737422ce3831a41f2c68064990fd6f52fdaf03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055a40aa97866a6e0814065142f7d1038aaccb2e8a73661f6554c415c331ab8ec8b14000000000000001400000000000000010000000000ea3055140000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40000000000000000000001948411767455fe23b05b44fe5fb737422ce3831a41f2c68064990fd6f52fdaf03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 6bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc {"feature_digest":"6bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"68d6405cb8df3de95bd834ebb408196578500a9f818ff62ccc68f60b932f7d82","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"CRYPTO_PRIMITIVES"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":53895,"consumed":9312},"cpu_usage":{"last_ordinal":1262304002,"value_ex":221082,"consumed":38101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 3cea935e0deaa090b14d4ee01f3fee31a1c426779f1c32840aefaa99cb83ec5f03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea30555705a61c2ae1877963ee8e857abb78d2975071d25ce32f1235b4d4803967a9fa15000000000000001500000000000000010000000000ea3055150000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232206bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc000000000000000000003cea935e0deaa090b14d4ee01f3fee31a1c426779f1c32840aefaa99cb83ec5f03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 3cea935e0deaa090b14d4ee01f3fee31a1c426779f1c32840aefaa99cb83ec5f03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea30555705a61c2ae1877963ee8e857abb78d2975071d25ce32f1235b4d4803967a9fa15000000000000001500000000000000010000000000ea3055150000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232206bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc000000000000000000003cea935e0deaa090b14d4ee01f3fee31a1c426779f1c32840aefaa99cb83ec5f03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b {"feature_digest":"35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"e5d7992006e628a38c5e6c28dd55ff5e57ea682079bf41fef9b3cced0f46b491","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"GET_BLOCK_NUM"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":54636,"consumed":9440},"cpu_usage":{"last_ordinal":1262304002,"value_ex":232657,"consumed":40101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 04ba316cf9ddd86690833edc0f4548f8c07f0d66c09dca029b0a1fb96f16c62803000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055302a2f1713925c939a997367c967b457bfc2c580304f9686b1de22fc5946e40616000000000000001600000000000000010000000000ea3055160000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322035c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b0000000000000000000004ba316cf9ddd86690833edc0f4548f8c07f0d66c09dca029b0a1fb96f16c62803000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 04ba316cf9ddd86690833edc0f4548f8c07f0d66c09dca029b0a1fb96f16c62803000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055302a2f1713925c939a997367c967b457bfc2c580304f9686b1de22fc5946e40616000000000000001600000000000000010000000000ea3055160000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322035c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b0000000000000000000004ba316cf9ddd86690833edc0f4548f8c07f0d66c09dca029b0a1fb96f16c62803000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a {"feature_digest":"63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"c0cce5bcd8ea19a28d9e12eafda65ebe6d0e0177e280d4f20c7ad66dcd9e011b","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"BLS_PRIMITIVES2"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":55377,"consumed":9568},"cpu_usage":{"last_ordinal":1262304002,"value_ex":244232,"consumed":42101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 450cd1132e85279e093be4ae967a313b793f7fe0bf579e0f0852e003a04ad39d03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055ddd516213adb142966c5365d88fe333b8e244cb90fe77627ff51a2901becc46d17000000000000001700000000000000010000000000ea3055170000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322063320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a00000000000000000000450cd1132e85279e093be4ae967a313b793f7fe0bf579e0f0852e003a04ad39d03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 450cd1132e85279e093be4ae967a313b793f7fe0bf579e0f0852e003a04ad39d03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055ddd516213adb142966c5365d88fe333b8e244cb90fe77627ff51a2901becc46d17000000000000001700000000000000010000000000ea3055170000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322063320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a00000000000000000000450cd1132e85279e093be4ae967a313b793f7fe0bf579e0f0852e003a04ad39d03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4 {"feature_digest":"fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"440c3efaaab212c387ce967c574dc813851cf8332d041beb418dfaf55facd5a9","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"DISABLE_DEFERRED_TRXS_STAGE_1"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":56118,"consumed":9696},"cpu_usage":{"last_ordinal":1262304002,"value_ex":255807,"consumed":44101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 163cea51d12265063bf77437db57c2e9c1ef93dcb7205808665ab4cfc9bc7be103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea30559ce3cf675d2f9ecbf427930685680d9117ba72ed64d5d7474fb50c8768a921d218000000000000001800000000000000010000000000ea3055180000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb400000000000000000000163cea51d12265063bf77437db57c2e9c1ef93dcb7205808665ab4cfc9bc7be103000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 +DMLOG APPLIED_TRANSACTION 3 163cea51d12265063bf77437db57c2e9c1ef93dcb7205808665ab4cfc9bc7be103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea30559ce3cf675d2f9ecbf427930685680d9117ba72ed64d5d7474fb50c8768a921d218000000000000001800000000000000010000000000ea3055180000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb400000000000000000000163cea51d12265063bf77437db57c2e9c1ef93dcb7205808665ab4cfc9bc7be103000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG FEATURE_OP PRE_ACTIVATE 0 09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16 {"feature_digest":"09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"a857eeb932774c511a40efb30346ec01bfb7796916b54c3c69fe7e5fb70d5cba","dependencies":["fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4"],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"DISABLE_DEFERRED_TRXS_STAGE_2"}]} DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":56859,"consumed":9824},"cpu_usage":{"last_ordinal":1262304002,"value_ex":267382,"consumed":46101},"ram_usage":180802} -DMLOG APPLIED_TRANSACTION 3 0ba60f7118b04f4981554d97fcd15865c4ad6633f4e78f216d034a9ef6394e7f03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440100d007000010000000000000000080000000000000000001010000010000000000ea3055b76acc0a0bc58aae737e94451f7f38e72ff2e66e45b1838f558f7266783bf69719000000000000001900000000000000010000000000ea3055190000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322009e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16000000000000000000000ba60f7118b04f4981554d97fcd15865c4ad6633f4e78f216d034a9ef6394e7f03000000023b3d4b01000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440000000000000000 -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":2,"value_ex":0,"consumed":0},"average_block_cpu_usage":{"last_ordinal":2,"value_ex":833334,"consumed":100},"pending_net_usage":9824,"pending_cpu_usage":46100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1049625,"virtual_cpu_limit":200200} -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":3,"value_ex":81866667,"consumed":9824},"average_block_cpu_usage":{"last_ordinal":3,"value_ex":384993056,"consumed":46101},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1050675,"virtual_cpu_limit":200400} -DMLOG ACCEPTED_BLOCK 3 03000000030000000200000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add80100012d5b1b639d6ae94fcdd0536b224644931573d1ccb2a0c548613cd1feea18888b0200000000000000010000000000ea305503000000010000000000ea305502000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010000000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b44023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e82f7d596eef522faabf96b279df52ed6aca4107c87b6d789038d6f3ab4cce7311075993067e6f396bd6073f497bfeb8da8ad7850ab7a28e75602a6ca92df12a2a000000000000001f4efab24108b62d2edcccf3ffec0ab6382ee0cfef4d5653f37e07734c76a3535d0a131a9f342707a49d910a4521a16815a28c8c0dcf017ef30ded2fa875e894d10000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef26198000000000001010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0001023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e82f7d596eef522faabf96b279df52ed6aca4107c87b6d789038d6f3ab4cce7311075993067e6f396bd6073f497bfeb8da8ad7850ab7a28e75602a6ca92df12a2a000000000000001f4efab24108b62d2edcccf3ffec0ab6382ee0cfef4d5653f37e07734c76a3535d0a131a9f342707a49d910a4521a16815a28c8c0dcf017ef30ded2fa875e894d11700d0070000fb05010100203b7de491b51d3d74624078bc2c5dc4420985f0350afb6923a5585b5621750c9f126d7cff0efeade2068c7b618fc754b2abb5bff8cdb9bd0ecb4432b72ae1ed380100a82f78daed5c7b8c5ce755ff1ef7357b67e3ebc6d94c3609f9e662d0b8a4659bb8eb2575dbbddbc476694b9cca2dfea3b0bbd99d647776bdbb9e1da70e0adead081045158a7894b6405524a4d21424545aa8cacb0d0815a94891fa20414284ff2a025511a245ad54737ee77cf7ceeccb71f09a87545b9e7be77b9cef7ce79cef3cbf71f44fe94f1bf5d03d9f1951f447e343fdf3d87be873f2879efef473830dea77fff59e7bbef7f440d3bfd197d9f57368d1bfa54767949ab11b9736d48cd9b8840f7a0b372ed11f35136cf0436fe80dfac0b80dbc2afa67f84d6306e6063201ad97a8ff9234d00880f033d54c84469e48cd68b03c8b3ea54dd0909531c1fc52d0b0ed95c70e2dae4f3fd29eed5de8b6a767e77a8b8fcdf6daf32a42d7cd6bdd76d9548e51317aeaedd5f5c5d5e9d9f5f576b7a72c9aa273ed73ebed9e4af025c3b4d595e9f9d9deecf4fae2cfb4558d9b09defcf4409f1a2aa7cead3d2e53ebddf6f90b8b40e6426f41a568ba89e04eaf75171f5b5c6e3f4ac8d519393476dbebab17ba73ede9e5c5738bbd75358c9e70f6e155c24ae17d44a6aeaeadaeb7e7f1327f61aedd5d5737a1d3a1f3e1e5d5b9a5b985d9c595e9b5d9eeecb9768ffae9756e8956e29db9475f6918efa23e77a1db6daff4a67b8be7daea00d316339982ed81b579743afff0f4238b2bf3d38be347558696da34d17361b9b778af3a88ef0707693c3db73adf56868958aed36dcfb5097257d61a2280580ef09890d1fac2ec3d6f1c57af61e4a877bdb74a6445ffcd681aa6a60b6bf3e02dda0ed993275414abb8369444511c0f0d594b9f517c8b1e31237624a07ff4371cd123d60e51efd0adb7da86ff63ab8f46725b10ea353d34145aad7434623774b17959a51baaf8d45f568fb8a6c3d9b5b5e5c7d5eb6a07b42a745a7bfdd83d47c727ee7bd39b87fe66539f0854767bbaa9b5dd3093f2d7a9078655417f5be683f4a5c81ecb752737e3f44d5a9f9cccad539d22ee1417cfe76a9c1a9c29b29e53ef1ad64e4faa62e3c4b0a9dbb45007e81ff5e90e663b4d2fe83d39aca9bdf8cdcb2a33ce1e489d4d8d4ac7b5def8415a6e29a755c64d9d66d262f59651832ba175dc6cd2f3ad0a40313352c533b4f3ffd03ada2854d3601718b7043ccf3b757258611fef0076d96d07d2ecce62649cc0127ae5968b8d4e1e38ddc96ecbb17da75c405b74f67c6e4ed034553cd1c92da19207457c3ed70f0c1b0c21ac685a71b19387d4d78c9c75da192c1c776901daf9131d02648088f62d173b2e62184ec68434c5f29bca465367881c84970c54f4d1c22c80549d0a2430a126fe9ede4b742b469a9637a28be0ed843e6191fd00d024d49de6bd366d0a5a6777d2dc74429b0dde36f5df9e6bec7a5859225a9339fce1c9dc60ae39a894d39e26292146a426345d7a93f272c2484b6b9e2e1154e1a0398c01a6a8778011febd839629d7b3d95d34d54c62415e4c31a2584ca6381a31acea26051d200bf4245168a23feb1ca6d5d2043cd2d9e1eda8f8f61f4e43950da9f42744a85e22fae9c3a08b2e5e0021137ecde82da8ded0adb2d78ef257a75be822622d65756a7949d1bae92fd774c0846b1104fa0872b354c43fcee7e5eb2cceaa08c0b2a62194695a9245a3dc961b6c411509c9112f456fcd80799088f838bb54d8415018cf5c23410b00c783082a10f50e84dded3abb44840118013088481f4a76fd881cda17441ad78fc81dfb8288bb7e440eef0b22adeb47e4ee7d4164ecfa1139ba2f884c5c3f22c7f70591cb6a174cf45e9898014c4c05e33982a10750d17ba2a2050223a0592d1118361ae9778cd51be612eb3957aa3975c4aadc4cb9a78eab14d660aa456f43fc36466f357e9ba03728426c01e32d8f870db33cdef01bc66b7ec378b62d9fc883fbd4017a0b8ae4b1fbd44dfc96d1db30bf35e8ad8e193c2eaec645d5b8b01a17f0fa0d5edf1c57b70aee99c7e5f60a97d10a97db2a5c1abc0b8cbbb9dae36baa3d1eacf69809ce8a9118e10581c42db234bd1d1264d57dea2e2107b5fd4035eece6adc1d6459c844b286602bf4adefd3fe7f92f6da533efd522076fd194daed5619535e0fa38f56e78155bff121a57aefcf1b77ee7d73ffde2d44f929380af57ae7cf6db5fc35720b9b9b9f9fca7fff04f3e72cf43c356be5efe95ef50ef43c3817cddfc230c7ef770e22c7c910f12ba05b9544fd1d3d923f6297dccb263414ecb8f8ed693d42f71e55b1f7e71ea3dbcc4339f7cf1c57ff8e047bef6f98d3ed0bfffbddfa0efef1e8e05ea3c3dc8c59e119833c76c4b409205c8de305a8f539ef639d94705e5437ffbf257805a244096e9419a6541802c1cb3ce03719decded17a94fab537bffde13e10c0fc28808402e4494c08c8c5f6fbdba4fd251e4ed2c9de385a0f531979861ee1b8392de34e1fb3137ed844273b365a0ffcb01e3da271b326c3d68ed9861fd6e8643f365ab77ed83be9118f9b5332ecd4313be98791a20538e3c73d013cc6cd451977f198cdfcb8ac931d1fad6b3fec7df4a88d9bb332ecec313be6878d75b2b78c52f891dd415f9ed190a6d7283eb3194e0bf99b27b324fdb2d131046c8ce4ab19389231e8eea0198a568f24ccc8823c7e4064cec5c507d8f58eb3db9a86d1a0a6039d62ed3cbbc37007e32c240f3f2848d65b2e98526010b5769ab010ae038f30f1b0e277b025f8f92fc012a09310635fd260540df077b6d2bce4647f5eea12572b34fae9bc53d4007b414c1f3719351cc2e45a47da98c714f14094031716fa8220d5eabc4ea926751db1ae09479bbacec3d7e6082462fb1461abca25c5157dde4507b51a2086c978c36344650a3d2378e671fa73468757a36d79743d753d30ed296b52d09ec5612f0283b22d4fd91dd44c795b25e102f218997a4c0750d45614c9842289d0ac0145dae9d3e6886dbd0245a283666f5a0cf7652e3b927edb50e84a24f9b8b911f2f6450ad6157d667654f6725c1e13781095c6095c40a756866653a3bc550e555cd032934211daf1045303a7069d09efb9ea4c8ed96760595ee05e97205a1662d29e4bb22a1c7fa6ae9359cfe89cb9c55d2f6881ee71268c99452f700b562d5b1a1523aec20199181db4bb70e1e346d870f3e0d1c79cac96feaa3511197562c7a6be91227a4a1e93f2382d8fb3c29aa3f218ab38045e819050a478bb8c2816e738036dbe496c7b2b734d58365171658c8f34c2d75d5846ebcdc8eced1c6b0d722c138e3564d24cae847bf4581304060ec559728fe871baa9f138454a891e93cda1abf069c8c125c2790976e1d4a6de7960ee4ebf6775c207e6867108142639236748b4227fcf8884fefb560ebe02cf66fa3cdbd4b229614a764ab856bb1ad78840bb706d53ced910b85613ae65c0d8d5ae81718cc54bb2c31a2ca4eaaf98418892b289d978cc2ec8db647f6dac54cd430309821d9c450e083949b2b45f31bbb673bbb9f7b9f5d2f05e4e35e586844ea48239adfc6095dd46019b2246227596a5a3900f24d5c897ec33dbed18927e2e14b3ff4db5b71e8e2b5d9c94ba38f1eb267d5d9c6c93aaa4b4fd7071f6949a44a4060a93c5252b46af76aa9f17f9a8ed38d5a72be161d1b986537d7a40386604cfb395626a99fbd91010518ab173cd9a77ad2db8572bbef6ec575ffbe030ab7ea44c3397c7d43ab6ec7d8b182e223fcef421e535c0d2a77032e9f85b56ebe8815339b682d93966a4d726348cef82e03b431009d0e9a53c06b221840833428f28fca9af13a231231a6e4174461ef38209a000d1b08f682888f2bc15993a2f324be42e6596e6cd88d6f1d0e22c4fa5fdf440fb99b23d19907119c6f957efacdd4fed792a6a1ab27f2015ce672d957a25426f3763619dfd083b3a2f3e074727ad952a33fd4598347de34ddae92d7af1ecdede06fb1ba52dfb22f46243ccbad8b2c957f040763767c99ee6ec2a0ec8cc80ffb1b6c5b5d8d59c5d456f95562cbc8a15bb8c8481bec479f2cb8a83576477103b2134297833766a03e859f16345c3e5014e2ce144f8fbe347e87338f7d17ff9cc37de40bccf5038390595c4d11069b50772d522cd826f2758303e7b993d600b7e247ed49492c8ee0436d4cac3615d2f87d4113d31a3127ecb3a651878d20f7e6058a7a20b8abb3b790492d3493b816202e9da850e1020c1715cd2e19ac0034c1412e8900b3329c7b818a4a038c326b5442e947a482ee11feb6eff967ecc4af4b0a93df57212ab2306e25629e6b054cca1e742d857cce136e90dbd62862e15511a70ca4eeda2a343d6d1c66ba3ad815acb1c45be8e75370825dac2727c717440afb364676ff3ca3de21e7a1b14e6ad2e40eca2bd1db718648f2a151f5d9be326fa1af179c04a964f23407ad373ff00fdbc66e20a9868a6e24b34d070054ab45329e15f30da6e38613b54129f42944b2cca25c1d2568a599fe40cc08a40086639cbca8bf9c04cb15c21c6dd3f90287bec23b44687a34186a6010df5a3dc6e83a6fb395d55ca871ec8e932b4f4dff50d2261b00709d51e2095b84c7b8084d0ecdfa6bf6e593346bcf1a069a6147c3bae9271dabb19d2f18e2ca7f470d0d4db7989efc2d471029d4b6e48579071e69a73cee2097b75459d7711f21379d4fbfd27096e54c49d664487980c1249ee79d2435ea9f20e12d9526d891c083a7af613b97950aaaa2e5ecadeeb7bcb8de5c949d699d0facebc0b03a983cc81613726c1eee85b728274a564f0835229d2eeb4f5cbd2495adaa14e7857b52a5bc14dd007466aba21a8e469a2b7d124d84a934068120dd224649a18a189014d42170dd0049ed95b0cb248f5bedcb868a9703bd0447291c8da1c40b3e93940be207c54a4a6b886bc7b117510e2401155977b7f1545d441506511065af8da8aa8bb2162b13bfbaa8ba8af0e9143fb8248e3fa11b9635f1071d78fc8e17d41a475fd88dcbd2f888c5d3f2247f7059189eb47e4f8be20b27b11752f4caeb188ba072aba84b05b11f5b7c52f0ff7d1fa243badcfa0a68d5cb2cdfa88ed89c5ba180a3b617822313ce4122f650f55db492aa32ac3c5b925e55d591f52c61c4103346f04d4499660a128307e701712259ca6a0686e2bb738620389fe53f74397cc27502417c677740825f24bab6b48755e104ec1521e88c7b8f1ce61d6e6e46052e81dba402e3489b3cf8fa03f5130266727d7127d87f065450042870b65e4efa896783641cea40b386e534211cd496d89d4789ce65d6a7642602ea55261d877e1a00417a5b0469efa6b46c81821b6fe0b6b62899edd12a79ce47a13416de4108f3b1855443db8d34456556e6d69dc1c433585c2a0f0a4bfcf147074c48d4027e4ea1c9132aceea269dcb2cb0ee54c30d0ed0301b22bf0edfa910ba49183f2e21b12d20588700a0d3bcc63b343a374ba98ce0a914bc8ac629a6cad8684a5810d61c3622925253cf062a7b86bcbd8d82585e3b1a0d551445308dce98108b526112af5d4ab6b75779010321fe9dd61c70f725aa32665158d143697eb10a2b01cc41c82e32d92405471e94a3e90612401c97eca45083c25b8268fb4d1d41e0ce8076632174bd2a67fa5ad2106a2649c079c11d2888b9504c57fc69b03ba4896dcfc1037be2c3b66998e24f0e18f983d667203d9e6e771760b4d8c789c4cfcd873c20fe2dfe94e19df97c5a6b314ac09050981a3ac1d5bd9ad0c0195f7337251b13375c94553fa09faf8d9f7de4e6c232e51b0fa5d4d7e93d4cd82c39c1c3a46b84cf2da25da4ffb1217d21d874a0a071c1712754422ac5c05e864ef1b958188092d5f02909091a01ecd43cf46f60724b28fd9aa7b26c6583e41264cea100a706249b344b44b6622b49296b48eeb94c50a30904f218e9b5c4f844a75c8b130982d4c948a59fa211b0a0b858d14ae8b0ae228c9ee0c4228a4b96bb72004210dc270e5d930600b1c3026c54f683635ab00d6fa688af860cb443a244c1583c0389a4a7e01d9bc3728f5641e4c4d3cf524498b2e363ad80cf5b1f9206340d0ab2081149a08de95e7fc098c40c9b084430c670cf840c2c30f80c1001c72a3194cc61aa744850e3d04b1b03d3ab8d9413ec822bd068f000b0550d7b21ea77848e6d0820405be34e44ba3c3bb979b21d294f9a6ac6c324898105f3eef85321bd08c03a944affa37399518f854a264b612a46b78e9665837e93605c7df919d97b17e9c682fbe3dbc5d7dd9d216f910179773b795c36d3596d57b7a3f85d95244a87095c41ae3ab3cbe7a2fd4522e197c1fc80d02f26553a9bb6d92b5975c9529ea3da1226175581e8e9d003afca4be5a223c8d1dd6b1ca4d86d089879b7c07a5515d1e6079e220f730fc4f674e6e99ea7c4a6fcbec5b315b97b3f59eb3ab0923db26f00ea026b3fed1701dc9cabe6d5492748924e97c0ed7882d6435fae7b86830703b4af160f1a12cd9b407799af2ae171cad3c821f620a5c698a59f511d988b0c5f7a8016e3f291dc2ab0777d1456fbf1dd503b80a996be23700e23d231d6c71ef05b7b3011d3bf7fefb062960728e82342d8b6b900cc5e50dbec311c38292e1586a4afa350f91f328e15902d5b4151ce636bcf6509cd8a85526bf902f5e62d5e00b4f7cc58ebdddca313462bd02c9e921b5ca387a6374204d9fd7261057f07f5de10d68ba6d6a8ec28b4a668ed804fecbeb540c5394c5d81d5f712a95e0a70ced28d8eedc5edb8e1a7e478d6bd851c38f7ba51d855e77e73bb7c585403f322b4766db062503831a25811a7bd801efdd8148311e194556f468346b4cab1ae221176535ef4aa65ff6d6eed590ea1a69b4cfc4317b11a74ca76571b9a9bfb6b2295454fcae08e7607b2565b3aaa404a2baab4a4a807d04be9262717acec8035703032e989c159d754a640147f079ae90f81a37d0872a65dff3ac04ce72a710f181af81841c78579d196a20b6ac8184acb2b8936f32c9302e78707dade56f56a20632263d6b825352ba0e16c569cb65eec0578e41c4c1dab154bf387e0dfaa5635b2e17c0a3adc0700c2faa861597e8700e1ffad5e320f5fa3b9b280b2c81e86e0616488598c1f5dbefe7769ac8451714c7a02d898f57d1edb4a36dea1dc96dafe17d65bcf82a3dd99b868e47bf293ef9d5676f19d0f2b401d6f296b53c59956552f441a5e80df39698a53c4dfd83ec68f9e6aab746f596f937291396399eb1dd6d848574f66d44c0587438c5cd2ca9ec036cf37f0b0de3ebb0c8d80d9a1672b079a95dac8b45a2e2f439ee36e2e48b8db192b550550564771bc377292cdb98a735bb4ffca3a5fdf47ccec8e3b4f77ce450ca314cf8d69fe8047a3f22878e20fcdaff19f79e7434a3c746ebefac0dca7bf7dfbc36328542a6edb820b046600432719855c908c5604614532916a51dc32363fdba353d22d40c25b264e141fc88e82de6f851fa0349af1889da620490914b38808c3880440e860248c3c16513f65ae35786fd00d2ec08206309203d9c12f92a808ca6b80254c19100d29401a447c5226ea72f6500697d00197b3be92355e5d713a3238999b16dc1a2646ac606e245d6be134c3ebc8d41b32bcfd0ec6ed1e3c48a97becfd8ffff8cf51750b65c46aa38fcb211ed36e06ddc30edc657387689ea5ae68c04575f54db8239f95583c21d259e3d51a9c80984574c3ab62bd2debfb351fa2b49df5f09d88a559dc9167f25e0247f69659ca9fc9586f82b6ec05f69f5fd9506dfb13c25f8bc593c83898168ef7819edb16790fea93656c29531b92dc3e9b631e7adb35c01e3727499d6e15008d849b3385d64ef9638319907d92dcef6af04245d64f6d8be210d990cdc472248b8432a9797f8f46523e3e668992de55ca7de35d729a1aa53e9b3b8ea53ba3241e5b634cec1ad82dbf229f257908c2c9ec50b0e635956966141f1157268c47b09e0bdc470e7254625ff212e1ae2bd9832f41c702bb4fca25bfb4b4174e61acb79826461243f15364c32fc34462ea121730a88b0635c868d7c0e5c2e0918c13f3ec1ee2049d102d7fe49ea16fc85002be94fc0ae8acafc3b702f455adcf7b5f2e46906e10294915cc077a9785d5d9574627f8904bb8a21f13edb8a7ed9063b20a15ccd22152117b762a0148b24c4e5c5ad7e469696ab344d799b2b4dffd1a6fc93fef49d8fcc2e2eb7e75d6fd5cd2e2fafcecdf6da6e6df6d1f6ba5a7db8d39eebd197f575e95fecb5bbb3bdd5ee34ded7ddca6acf2daeb87317967b8bd38b2bf3ed8b8a7f0c99def9fe2e0d55ed6e77b5ebf07f5b2cae3c5a4d567cacd310ed8a33e0e9bd73b32b0036476db4baacbb0ed8bdd98797a9e111374bfd0bedae9b5b5de97567e77a8aeb00e9eb77e0786e757ef191c7f744efe581e5fcd06b5cee63cfa9f44df21f4350bb47786176e551225777f1dc6cf771b7d47edcbd7fa1bde22163d7b32b1ebe62cd9ae66bddd5deeadceab2f3ff71488969ffff18e132651a3cdac61cb22ce9dd1756da17d70806ed50684aa83eb278b13d3ffdf0e3bdf63ab05cef752fcc097569ee1f349552ff05ee7357f400d00700008101010100204b21f3cba072cc493e70861540df4677b498b0505a8b8e2a346b85a0c2dd2fc4263c4a7d8629026c4eb594ad96fac2bfe5f8ffebb9c841c353920b7b8ec11abc0100d90778da8d563b8f1c4510eedbf7e37cf209d9e60808402496c0dcdaac4e8ece01090112afe83043ef74ed4e6b677a86ee9edd5b3b2121b049888d842c84c0c1456702eb20b036424242c2e00408800c24fe03d53db3f33a58e860b6bbeaebeaeaaaafaab7f55bff9d1a796df0e5798263c37cc89f2fbe657e1eb8c7cb92e0de5f83c1eded95e4fded2d08150faf5ea5237e69f7855db2d3c199e351e5915a339c0b900d4103681849dff5c09daa3818bc34ec5057f319d54036b6c640752cc1617c024a17515d1a6b2f945c2f48a3ab3d09ca0b7dd68ab9d097078d292cd4267e9c39f089a70faea351378c85563b11c8802bf44c383eccc0cf20cd39e55a9d31df4c766ee487eed4f528174e4425baab412ab2fd44400f1dab73046827567402f6ece195a73495139455b44ee4ead4bb1db3594b2a94b929fa51367179f0f4882adc00722dea6c6edb0798d3452a7fd60d858643ed8c2598c8297bf18227220efe2f948148a1851bbb515c72a47ce34cbbeec655133b0106781de0c9aa059f8f41f3200b19833148090c41870e1c465c528b9b73c1c2798a3a57b5c2c0cfe276de28b9f0b90027552b7e6375c085d35a0691f6ac7a7768c39351b2a4eabb54b8e0dba3486d2b597131b1f0b3553ab68cff9c15a9dec3adc83b0327b5764a645b3bbd7c77b2ce294f6a755cf4a278e473d7c1692b91a74e75d083a9b5d828596cb8218364a6175132eb4b782fe61202581d2b906ec926dcee4a2cd2302de6ec9354785ea52d5bd5900bda21ea652849adab4030243b676debdc60af83126d32d91c2d34a85341c20682e6d233ab41b8f02f154e6a05e4e9b897c2b319c990c52e3a859123b533d932bbdf76c276c527c2e4b21ceb4d8cd8aa8bb1b56dac6d90260d1b8db10c036bbaa54063abace4ba8ea2241c3da3f77980ddaa92bd2e7628c7629ab617f54c2527174b05a6ae8a8236da3229af186acd0293fea689c65e7716ccb0eb61a892b5e548eeca2475a55ec7d3d32658c78357533c329d62a2b5eda28a6cb492c93f3758e35524f9ac128236578e11276e742c286468aca330a42cf661ab98b783ebbd58643cafff27cf7b71c4685a678db575669c5f1543c3e0735af70bef07a975ec4a819b769132cbcc6379f1637c36f3278f7c7debe2cb1f7c7eadd434c8feb73fdd3bfaf4956223c0f1fcb4fec587792193fd4fee3cc31edc2956278e5f1fdd7cfc59566c1fbd39fc19d8d14999a138ee42707492b171f5c0afa848c877af9e78c7cb22f570ec3f77fb789951c882be4940930cf4f0d1db6fdc5f16528fe3ddaf0eee2fb324e3d8fb1e057942cd851ffef1fb8fc5fcd920f8af3f2e66c9fcffb84b7ff865b7ce875708c9ff60d8f137aa5a1fa900d00700001001010020742877c36a520b152b1337ea1ecd37b0c98ad07289c32fec392e7eebab9f0ac71f7bc8c718cfa75317b2e15702372a9222c4616783ee7b3f0ec6358f8c328eea00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232201a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72410000d00700001001010020058e23368b919493d6ac61d27f66b829a53893e88ddde857d3b82d913960960d22fa36f397752b98c295e3b31927f740127c0a99e76f8bfeea88f44466b8fbfd00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea990000d0070000100101001f43fe868e263d8134cf705aa85e26ce78ebb058edd558865fa3d240f5cb9e50c2389e9c8276eac800b7233a552045b2e79124c97e5156a0649849cc7f5d09eee600005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f0000d0070000100101001f29e82b08ccf15e2187f29fea11ee3f4974f41b51e45b19f353348d8848b86fb71cadd88630456b7a1c60803c7b402487d41fbf18f0b0a13b4cca1f740447938300005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff5260000d0070000100101002047a8b784c3765b5c63ac52e3d8461b80bc2d3e3f62434f8accb277d9f2487cfd3c0728fcd26b5119a11288e5db46bc5b547877e220971609d1cef8cba443340800005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322068dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974280000d007000010010100203e701fbafd4149bc95b55a6bfc3b78246f5c2668ccc05ed4059a36ceb38f140b31e3b69e15f2579571e5bde39e034947271599c200e540b3949112bef163074c00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c430000d0070000100101001f0cc7352e60f4f8476783d6d1b48766a111c56fee2c1a552e76a75c92bc17de172f994ffc854c09717c904054819ca7a17379ddecaf531c439b35337ba099b81300005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232208ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4050000d0070000100101002040965063a83be2d53b36c8d7e0775f503c2caa1407e586314562aace52c272fe60659e196413a6c9db4168470bcabb9a5851121c10c7b665f363f6cd4d1e4bda00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232202652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed250000d0070000100101002074ea7468b2a031c4cd53bf10ec3ac66b0c4b5c8779e045f1ef8d9c7b116be649217ff340107d0163397b99918ee2ce822b66cd6fce7b385af97a04671136e2ee00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d0000d007000010010100204dfb21ca5140582379bc026792c16b4cf97827143a4a9cd99ae70b3e6016cd6316bcbb9f1cb1233f12a0bbcd9debafa64724d0459b5c8d3cb67ceddfb2e3962500005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d670000d0070000100101002033446a3a94ade71dff3edb786259679487ab701bbc147490b1d4159fecf545fa22fee0698db16bf616465e5cebb985bfc4d9ed1ec4a55e38997dd4b4bbc427eb00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c20000d0070000100101001f3f67edd35bf731a07f40c638e8812112cd7d1baa39ec7dac4a1b2f0c83ac8bd53689b56dba69a7386e3860a6f8976695ac0bc2b5dacae91080f1d54df2dac0c000005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b44767070000d0070000100101001f1e030564013603d54f9e983b63cd940f8ff09ae038b14813f4021bb0c09ebb640d90cb4f8d57be2809f492a51737b671a5f549d4efa8e7efdaeaa9663c09d1ad00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450710000d007000010010100205cea642eecf05568ce8c5564e63349eea3b816108914ba2ab5efffbb8ea467265f0b6d474f03ed02a3bf529fd6e55a595cbf8dd1adf4311cb9c51e862f8a535400005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232205443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b40000d0070000100101001f4556076cc86e0840bf69664f1ef8fcd4d91abda313d08e7840d24ba45cb429cf12b7d3a1f64250c19d1b975e7b107853beff70ebfc4c27c44f825dc05cdc9cd600005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e990000d0070000100101001f354d903ad0f2c6cc9d9a377d681ffaa00475d1e559e48074b4c8cce3111d5c172903b2f179ad4d736dda4e7d1b6a859baeab9dde5e5e495ce09733ec4650634400005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb400000d0070000100101001f1766fa716a828da244c9ce52919b7a19acb38dbd110d1bb0039bb2477c17e4465dceecb8330ed5ee9de1330930dfcfa1a5e8149ce8536a82c0093642adf7328200005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232206bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc0000d00700001001010020488923db1c78fa430a3a9eab75f4ee467c7b9a3d3b4eb3bd08e183c82ef79b9102a4d2a7d1ec79c96b404911ae1b10f579bd82a660011c1ca2b872b30ef7dcac00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322035c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b0000d0070000100101001f61ee60b366c2f3623012648000835e6089f9e9594a113acad200ae8a87bd05274acede23160e2e187d9921ea2ff6f37e3bd10ffd624ffceb511455c42f1c9ee200005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322063320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a0000d0070000100101001f184aad2b65730f7485957642fa1688c66e8ece7827ee2e8e01f8bc904cedd8ec5462c12a1e3c6cd41f4a15a350ec8575bb05e9597f4316ff73a4e1066aeab3d500005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40000d0070000100101002041c996b52c4bdbbc4fbdaf707dd01e74c46c51ce2c8e10e174e12502cb6be3f23e2d44e8e8802e970bc5ccfc4d056e400c92a56667183c37e0f79fbe77540a0000005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322009e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16000001 +DMLOG APPLIED_TRANSACTION 3 0ba60f7118b04f4981554d97fcd15865c4ad6633f4e78f216d034a9ef6394e7f03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea3055b76acc0a0bc58aae737e94451f7f38e72ff2e66e45b1838f558f7266783bf69719000000000000001900000000000000010000000000ea3055190000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322009e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16000000000000000000000ba60f7118b04f4981554d97fcd15865c4ad6633f4e78f216d034a9ef6394e7f03000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 +DMLOG CREATION_OP ROOT 0 +DMLOG FEATURE_OP PRE_ACTIVATE 0 18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390 {"feature_digest":"18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"bd496b9e85ce61dcddeee4576ea185add87844238da992a9ee6df2a2bdb357c2","dependencies":["299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707","63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a","68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428","c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"INSTANT_FINALITY"}]} +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304002,"value_ex":57600,"consumed":9952},"cpu_usage":{"last_ordinal":1262304002,"value_ex":278957,"consumed":48101},"ram_usage":180802} +DMLOG APPLIED_TRANSACTION 3 280cc3aadfeaefd2d0684756bc38781ef59daf38a1d6243f34ac6c615b3dc05403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0100d007000010000000000000000080000000000000000001010000010000000000ea305515e0016f47aca153485160c1ed66d8e7e0cc611789e3b37c81ac9c9679aca0ee1a000000000000001a00000000000000010000000000ea30551a0000000000000001010000000000ea30550000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322018b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d639000000000000000000000280cc3aadfeaefd2d0684756bc38781ef59daf38a1d6243f34ac6c615b3dc05403000000023b3d4b0100000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c0000000000000000 +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":2,"value_ex":0,"consumed":0},"average_block_cpu_usage":{"last_ordinal":2,"value_ex":833334,"consumed":100},"pending_net_usage":9952,"pending_cpu_usage":48100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1049625,"virtual_cpu_limit":200200} +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":3,"value_ex":82933334,"consumed":9952},"average_block_cpu_usage":{"last_ordinal":3,"value_ex":401659723,"consumed":48101},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1050675,"virtual_cpu_limit":200400} +DMLOG ACCEPTED_BLOCK 3 03000000030000000200000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add80100012d5b1b639d6ae94fcdd0536b224644931573d1ccb2a0c548613cd1feea18888b0200000000000000010000000000ea305503000000010000000000ea305502000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add801000000000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e80f70f21e77b7c902392950756201912a2d1f719663cae19edcb4cc1b12fe7bdd82aeb196b752844f91a8052ae8f5e5ff87c0af0c6f0b7b2ebf387fc81062e95f00000000000000204cb0fb597bad84486310c5cd290d8c20b23b1b8c968404eabcc6c8b381cc6ed47da83963564b00fb712ee06740861ba3d4615809027275066cf05bbff785fa150000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef26198000000000001010ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd0001023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e80f70f21e77b7c902392950756201912a2d1f719663cae19edcb4cc1b12fe7bdd82aeb196b752844f91a8052ae8f5e5ff87c0af0c6f0b7b2ebf387fc81062e95f00000000000000204cb0fb597bad84486310c5cd290d8c20b23b1b8c968404eabcc6c8b381cc6ed47da83963564b00fb712ee06740861ba3d4615809027275066cf05bbff785fa151800d0070000fb05010100203b7de491b51d3d74624078bc2c5dc4420985f0350afb6923a5585b5621750c9f126d7cff0efeade2068c7b618fc754b2abb5bff8cdb9bd0ecb4432b72ae1ed380100a82f78daed5c7b8c5ce755ff1ef7357b67e3ebc6d94c3609f9e662d0b8a4659bb8eb2575dbbddbc476694b9cca2dfea3b0bbd99d647776bdbb9e1da70e0adead081045158a7894b6405524a4d21424545aa8cacb0d0815a94891fa20414284ff2a025511a245ad54737ee77cf7ceeccb71f09a87545b9e7be77b9cef7ce79cef3cbf71f44fe94f1bf5d03d9f1951f447e343fdf3d87be873f2879efef473830dea77fff59e7bbef7f440d3bfd197d9f57368d1bfa54767949ab11b9736d48cd9b8840f7a0b372ed11f35136cf0436fe80dfac0b80dbc2afa67f84d6306e6063201ad97a8ff9234d00880f033d54c84469e48cd68b03c8b3ea54dd0909531c1fc52d0b0ed95c70e2dae4f3fd29eed5de8b6a767e77a8b8fcdf6daf32a42d7cd6bdd76d9548e51317aeaedd5f5c5d5e9d9f5f576b7a72c9aa273ed73ebed9e4af025c3b4d595e9f9d9deecf4fae2cfb4558d9b09defcf4409f1a2aa7cead3d2e53ebddf6f90b8b40e6426f41a568ba89e04eaf75171f5b5c6e3f4ac8d519393476dbebab17ba73ede9e5c5738bbd75358c9e70f6e155c24ae17d44a6aeaeadaeb7e7f1327f61aedd5d5737a1d3a1f3e1e5d5b9a5b985d9c595e9b5d9eeecb9768ffae9756e8956e29db9475f6918efa23e77a1db6daff4a67b8be7daea00d316339982ed81b579743afff0f4238b2bf3d38be347558696da34d17361b9b778af3a88ef0707693c3db73adf56868958aed36dcfb5097257d61a2280580ef09890d1fac2ec3d6f1c57af61e4a877bdb74a6445ffcd681aa6a60b6bf3e02dda0ed993275414abb8369444511c0f0d594b9f517c8b1e31237624a07ff4371cd123d60e51efd0adb7da86ff63ab8f46725b10ea353d34145aad7434623774b17959a51baaf8d45f568fb8a6c3d9b5b5e5c7d5eb6a07b42a745a7bfdd83d47c727ee7bd39b87fe66539f0854767bbaa9b5dd3093f2d7a9078655417f5be683f4a5c81ecb752737e3f44d5a9f9cccad539d22ee1417cfe76a9c1a9c29b29e53ef1ad64e4faa62e3c4b0a9dbb45007e81ff5e90e663b4d2fe83d39aca9bdf8cdcb2a33ce1e489d4d8d4ac7b5def8415a6e29a755c64d9d66d262f59651832ba175dc6cd2f3ad0a40313352c533b4f3ffd03ada2854d3601718b7043ccf3b757258611fef0076d96d07d2ecce62649cc0127ae5968b8d4e1e38ddc96ecbb17da75c405b74f67c6e4ed034553cd1c92da19207457c3ed70f0c1b0c21ac685a71b19387d4d78c9c75da192c1c776901daf9131d02648088f62d173b2e62184ec68434c5f29bca465367881c84970c54f4d1c22c80549d0a2430a126fe9ede4b742b469a9637a28be0ed843e6191fd00d024d49de6bd366d0a5a6777d2dc74429b0dde36f5df9e6bec7a5859225a9339fce1c9dc60ae39a894d39e26292146a426345d7a93f272c2484b6b9e2e1154e1a0398c01a6a8778011febd839629d7b3d95d34d54c62415e4c31a2584ca6381a31acea26051d200bf4245168a23feb1ca6d5d2043cd2d9e1eda8f8f61f4e43950da9f42744a85e22fae9c3a08b2e5e0021137ecde82da8ded0adb2d78ef257a75be822622d65756a7949d1bae92fd774c0846b1104fa0872b354c43fcee7e5eb2cceaa08c0b2a62194695a9245a3dc961b6c411509c9112f456fcd80799088f838bb54d8415018cf5c23410b00c783082a10f50e84dded3abb44840118013088481f4a76fd881cda17441ad78fc81dfb8288bb7e440eef0b22adeb47e4ee7d4164ecfa1139ba2f884c5c3f22c7f70591cb6a174cf45e9898014c4c05e33982a10750d17ba2a2050223a0592d1118361ae9778cd51be612eb3957aa3975c4aadc4cb9a78eab14d660aa456f43fc36466f357e9ba03728426c01e32d8f870db33cdef01bc66b7ec378b62d9fc883fbd4017a0b8ae4b1fbd44dfc96d1db30bf35e8ad8e193c2eaec645d5b8b01a17f0fa0d5edf1c57b70aee99c7e5f60a97d10a97db2a5c1abc0b8cbbb9dae36baa3d1eacf69809ce8a9118e10581c42db234bd1d1264d57dea2e2107b5fd4035eece6adc1d6459c844b286602bf4adefd3fe7f92f6da533efd522076fd194daed5619535e0fa38f56e78155bff121a57aefcf1b77ee7d73ffde2d44f929380af57ae7cf6db5fc35720b9b9b9f9fca7fff04f3e72cf43c356be5efe95ef50ef43c3817cddfc230c7ef770e22c7c910f12ba05b9544fd1d3d923f6297dccb263414ecb8f8ed693d42f71e55b1f7e71ea3dbcc4339f7cf1c57ff8e047bef6f98d3ed0bfffbddfa0efef1e8e05ea3c3dc8c59e119833c76c4b409205c8de305a8f539ef639d94705e5437ffbf257805a244096e9419a6541802c1cb3ce03719decded17a94fab537bffde13e10c0fc28808402e4494c08c8c5f6fbdba4fd251e4ed2c9de385a0f531979861ee1b8392de34e1fb3137ed844273b365a0ffcb01e3da271b326c3d68ed9861fd6e8643f365ab77ed83be9118f9b5332ecd4313be98791a20538e3c73d013cc6cd451977f198cdfcb8ac931d1fad6b3fec7df4a88d9bb332ecec313be6878d75b2b78c52f891dd415f9ed190a6d7283eb3194e0bf99b27b324fdb2d131046c8ce4ab19389231e8eea0198a568f24ccc8823c7e4064cec5c507d8f58eb3db9a86d1a0a6039d62ed3cbbc37007e32c240f3f2848d65b2e98526010b5769ab010ae038f30f1b0e277b025f8f92fc012a09310635fd260540df077b6d2bce4647f5eea12572b34fae9bc53d4007b414c1f3719351cc2e45a47da98c714f14094031716fa8220d5eabc4ea926751db1ae09479bbacec3d7e6082462fb1461abca25c5157dde4507b51a2086c978c36344650a3d2378e671fa73468757a36d79743d753d30ed296b52d09ec5612f0283b22d4fd91dd44c795b25e102f218997a4c0750d45614c9842289d0ac0145dae9d3e6886dbd0245a283666f5a0cf7652e3b927edb50e84a24f9b8b911f2f6450ad6157d667654f6725c1e13781095c6095c40a756866653a3bc550e555cd032934211daf1045303a7069d09efb9ea4c8ed96760595ee05e97205a1662d29e4bb22a1c7fa6ae9359cfe89cb9c55d2f6881ee71268c99452f700b562d5b1a1523aec20199181db4bb70e1e346d870f3e0d1c79cac96feaa3511197562c7a6be91227a4a1e93f2382d8fb3c29aa3f218ab38045e819050a478bb8c2816e738036dbe496c7b2b734d58365171658c8f34c2d75d5846ebcdc8eced1c6b0d722c138e3564d24cae847bf4581304060ec559728fe871baa9f138454a891e93cda1abf069c8c125c2790976e1d4a6de7960ee4ebf6775c207e6867108142639236748b4227fcf8884fefb560ebe02cf66fa3cdbd4b229614a764ab856bb1ad78840bb706d53ced910b85613ae65c0d8d5ae81718cc54bb2c31a2ca4eaaf98418892b289d978cc2ec8db647f6dac54cd430309821d9c450e083949b2b45f31bbb673bbb9f7b9f5d2f05e4e35e586844ea48239adfc6095dd46019b2246227596a5a3900f24d5c897ec33dbed18927e2e14b3ff4db5b71e8e2b5d9c94ba38f1eb267d5d9c6c93aaa4b4fd7071f6949a44a4060a93c5252b46af76aa9f17f9a8ed38d5a72be161d1b986537d7a40386604cfb395626a99fbd91010518ab173cd9a77ad2db8572bbef6ec575ffbe030ab7ea44c3397c7d43ab6ec7d8b182e223fcef421e535c0d2a77032e9f85b56ebe8815339b682d93966a4d726348cef82e03b431009d0e9a53c06b221840833428f28fca9af13a231231a6e4174461ef38209a000d1b08f682888f2bc15993a2f324be42e6596e6cd88d6f1d0e22c4fa5fdf440fb99b23d19907119c6f957efacdd4fed792a6a1ab27f2015ce672d957a25426f3763619dfd083b3a2f3e074727ad952a33fd4598347de34ddae92d7af1ecdede06fb1ba52dfb22f46243ccbad8b2c957f040763767c99ee6ec2a0ec8cc80ffb1b6c5b5d8d59c5d456f95562cbc8a15bb8c8481bec479f2cb8a83576477103b2134297833766a03e859f16345c3e5014e2ce144f8fbe347e87338f7d17ff9cc37de40bccf5038390595c4d11069b50772d522cd826f2758303e7b993d600b7e247ed49492c8ee0436d4cac3615d2f87d4113d31a3127ecb3a651878d20f7e6058a7a20b8abb3b790492d3493b816202e9da850e1020c1715cd2e19ac0034c1412e8900b3329c7b818a4a038c326b5442e947a482ee11feb6eff967ecc4af4b0a93df57212ab2306e25629e6b054cca1e742d857cce136e90dbd62862e15511a70ca4eeda2a343d6d1c66ba3ad815acb1c45be8e75370825dac2727c717440afb364676ff3ca3de21e7a1b14e6ad2e40eca2bd1db718648f2a151f5d9be326fa1af179c04a964f23407ad373ff00fdbc66e20a9868a6e24b34d070054ab45329e15f30da6e38613b54129f42944b2cca25c1d2568a599fe40cc08a40086639cbca8bf9c04cb15c21c6dd3f90287bec23b44687a34186a6010df5a3dc6e83a6fb395d55ca871ec8e932b4f4dff50d2261b00709d51e2095b84c7b8084d0ecdfa6bf6e593346bcf1a069a6147c3bae9271dabb19d2f18e2ca7f470d0d4db7989efc2d471029d4b6e48579071e69a73cee2097b75459d7711f21379d4fbfd27096e54c49d664487980c1249ee79d2435ea9f20e12d9526d891c083a7af613b97950aaaa2e5ecadeeb7bcb8de5c949d699d0facebc0b03a983cc81613726c1eee85b728274a564f0835229d2eeb4f5cbd2495adaa14e7857b52a5bc14dd007466aba21a8e469a2b7d124d84a934068120dd224649a18a189014d42170dd0049ed95b0cb248f5bedcb868a9703bd0447291c8da1c40b3e93940be207c54a4a6b886bc7b117510e2401155977b7f1545d441506511065af8da8aa8bb2162b13bfbaa8ba8af0e9143fb8248e3fa11b9635f1071d78fc8e17d41a475fd88dcbd2f888c5d3f2247f7059189eb47e4f8be20b27b11752f4caeb188ba072aba84b05b11f5b7c52f0ff7d1fa243badcfa0a68d5cb2cdfa88ed89c5ba180a3b617822313ce4122f650f55db492aa32ac3c5b925e55d591f52c61c4103346f04d4499660a128307e701712259ca6a0686e2bb738620389fe53f74397cc27502417c677740825f24bab6b48755e104ec1521e88c7b8f1ce61d6e6e46052e81dba402e3489b3cf8fa03f5130266727d7127d87f065450042870b65e4efa896783641cea40b386e534211cd496d89d4789ce65d6a7642602ea55261d877e1a00417a5b0469efa6b46c81821b6fe0b6b62899edd12a79ce47a13416de4108f3b1855443db8d34456556e6d69dc1c433585c2a0f0a4bfcf147074c48d4027e4ea1c9132aceea269dcb2cb0ee54c30d0ed0301b22bf0edfa910ba49183f2e21b12d20588700a0d3bcc63b343a374ba98ce0a914bc8ac629a6cad8684a5810d61c3622925253cf062a7b86bcbd8d82585e3b1a0d551445308dce98108b526112af5d4ab6b75779010321fe9dd61c70f725aa32665158d143697eb10a2b01cc41c82e32d92405471e94a3e90612401c97eca45083c25b8268fb4d1d41e0ce8076632174bd2a67fa5ad2106a2649c079c11d2888b9504c57fc69b03ba4896dcfc1037be2c3b66998e24f0e18f983d667203d9e6e771760b4d8c789c4cfcd873c20fe2dfe94e19df97c5a6b314ac09050981a3ac1d5bd9ad0c0195f7337251b13375c94553fa09faf8d9f7de4e6c232e51b0fa5d4d7e93d4cd82c39c1c3a46b84cf2da25da4ffb1217d21d874a0a071c1712754422ac5c05e864ef1b958188092d5f02909091a01ecd43cf46f60724b28fd9aa7b26c6583e41264cea100a706249b344b44b6622b49296b48eeb94c50a30904f218e9b5c4f844a75c8b130982d4c948a59fa211b0a0b858d14ae8b0ae228c9ee0c4228a4b96bb72004210dc270e5d930600b1c3026c54f683635ab00d6fa688af860cb443a244c1583c0389a4a7e01d9bc3728f5641e4c4d3cf524498b2e363ad80cf5b1f9206340d0ab2081149a08de95e7fc098c40c9b084430c670cf840c2c30f80c1001c72a3194cc61aa744850e3d04b1b03d3ab8d9413ec822bd068f000b0550d7b21ea77848e6d0820405be34e44ba3c3bb979b21d294f9a6ac6c324898105f3eef85321bd08c03a944affa37399518f854a264b612a46b78e9665837e93605c7df919d97b17e9c682fbe3dbc5d7dd9d216f910179773b795c36d3596d57b7a3f85d95244a87095c41ae3ab3cbe7a2fd4522e197c1fc80d02f26553a9bb6d92b5975c9529ea3da1226175581e8e9d003afca4be5a223c8d1dd6b1ca4d86d089879b7c07a5515d1e6079e220f730fc4f674e6e99ea7c4a6fcbec5b315b97b3f59eb3ab0923db26f00ea026b3fed1701dc9cabe6d5492748924e97c0ed7882d6435fae7b86830703b4af160f1a12cd9b407799af2ae171cad3c821f620a5c698a59f511d988b0c5f7a8016e3f291dc2ab0777d1456fbf1dd503b80a996be23700e23d231d6c71ef05b7b3011d3bf7fefb062960728e82342d8b6b900cc5e50dbec311c38292e1586a4afa350f91f328e15902d5b4151ce636bcf6509cd8a85526bf902f5e62d5e00b4f7cc58ebdddca313462bd02c9e921b5ca387a6374204d9fd7261057f07f5de10d68ba6d6a8ec28b4a668ed804fecbeb540c5394c5d81d5f712a95e0a70ced28d8eedc5edb8e1a7e478d6bd851c38f7ba51d855e77e73bb7c585403f322b4766db062503831a25811a7bd801efdd8148311e194556f468346b4cab1ae221176535ef4aa65ff6d6eed590ea1a69b4cfc4317b11a74ca76571b9a9bfb6b2295454fcae08e7607b2565b3aaa404a2baab4a4a807d04be9262717acec8035703032e989c159d754a640147f079ae90f81a37d0872a65dff3ac04ce72a710f181af81841c78579d196a20b6ac8184acb2b8936f32c9302e78707dade56f56a20632263d6b825352ba0e16c569cb65eec0578e41c4c1dab154bf387e0dfaa5635b2e17c0a3adc0700c2faa861597e8700e1ffad5e320f5fa3b9b280b2c81e86e0616488598c1f5dbefe7769ac8451714c7a02d898f57d1edb4a36dea1dc96dafe17d65bcf82a3dd99b868e47bf293ef9d5676f19d0f2b401d6f296b53c59956552f441a5e80df39698a53c4dfd83ec68f9e6aab746f596f937291396399eb1dd6d848574f66d44c0587438c5cd2ca9ec036cf37f0b0de3ebb0c8d80d9a1672b079a95dac8b45a2e2f439ee36e2e48b8db192b550550564771bc377292cdb98a735bb4ffca3a5fdf47ccec8e3b4f77ce450ca314cf8d69fe8047a3f22878e20fcdaff19f79e7434a3c746ebefac0dca7bf7dfbc36328542a6edb820b046600432719855c908c5604614532916a51dc32363fdba353d22d40c25b264e141fc88e82de6f851fa0349af1889da620490914b38808c3880440e860248c3c16513f65ae35786fd00d2ec08206309203d9c12f92a808ca6b80254c19100d29401a447c5226ea72f6500697d00197b3be92355e5d713a3238999b16dc1a2646ac606e245d6be134c3ebc8d41b32bcfd0ec6ed1e3c48a97becfd8ffff8cf51750b65c46aa38fcb211ed36e06ddc30edc657387689ea5ae68c04575f54db8239f95583c21d259e3d51a9c80984574c3ab62bd2debfb351fa2b49df5f09d88a559dc9167f25e0247f69659ca9fc9586f82b6ec05f69f5fd9506dfb13c25f8bc593c83898168ef7819edb16790fea93656c29531b92dc3e9b631e7adb35c01e3727499d6e15008d849b3385d64ef9638319907d92dcef6af04245d64f6d8be210d990cdc472248b8432a9797f8f46523e3e668992de55ca7de35d729a1aa53e9b3b8ea53ba3241e5b634cec1ad82dbf229f257908c2c9ec50b0e635956966141f1157268c47b09e0bdc470e7254625ff212e1ae2bd9832f41c702bb4fca25bfb4b4174e61acb79826461243f15364c32fc34462ea121730a88b0635c868d7c0e5c2e0918c13f3ec1ee2049d102d7fe49ea16fc85002be94fc0ae8acafc3b702f455adcf7b5f2e46906e10294915cc077a9785d5d9574627f8904bb8a21f13edb8a7ed9063b20a15ccd22152117b762a0148b24c4e5c5ad7e469696ab344d799b2b4dffd1a6fc93fef49d8fcc2e2eb7e75d6fd5cd2e2fafcecdf6da6e6df6d1f6ba5a7db8d39eebd197f575e95fecb5bbb3bdd5ee34ded7ddca6acf2daeb87317967b8bd38b2bf3ed8b8a7f0c99def9fe2e0d55ed6e77b5ebf07f5b2cae3c5a4d567cacd310ed8a33e0e9bd73b32b0036476db4baacbb0ed8bdd98797a9e111374bfd0bedae9b5b5de97567e77a8aeb00e9eb77e0786e757ef191c7f744efe581e5fcd06b5cee63cfa9f44df21f4350bb47786176e551225777f1dc6cf771b7d47edcbd7fa1bde22163d7b32b1ebe62cd9ae66bddd5deeadceab2f3ff71488969ffff18e132651a3cdac61cb22ce9dd1756da17d70806ed50684aa83eb278b13d3ffdf0e3bdf63ab05cef752fcc097569ee1f349552ff05ee7357f400d00700008101010100204b21f3cba072cc493e70861540df4677b498b0505a8b8e2a346b85a0c2dd2fc4263c4a7d8629026c4eb594ad96fac2bfe5f8ffebb9c841c353920b7b8ec11abc0100d90778da8d563b8f1c4510eedbf7e37cf209d9e60808402496c0dcdaac4e8ece01090112afe83043ef74ed4e6b677a86ee9edd5b3b2121b049888d842c84c0c1456702eb20b036424242c2e00408800c24fe03d53db3f33a58e860b6bbeaebeaeaaaafaab7f55bff9d1a796df0e5798263c37cc89f2fbe657e1eb8c7cb92e0de5f83c1eded95e4fded2d08150faf5ea5237e69f7855db2d3c199e351e5915a339c0b900d4103681849dff5c09daa3818bc34ec5057f319d54036b6c640752cc1617c024a17515d1a6b2f945c2f48a3ab3d09ca0b7dd68ab9d097078d292cd4267e9c39f089a70faea351378c85563b11c8802bf44c383eccc0cf20cd39e55a9d31df4c766ee487eed4f528174e4425baab412ab2fd44400f1dab73046827567402f6ece195a73495139455b44ee4ead4bb1db3594b2a94b929fa51367179f0f4882adc00722dea6c6edb0798d3452a7fd60d858643ed8c2598c8297bf18227220efe2f948148a1851bbb515c72a47ce34cbbeec655133b0106781de0c9aa059f8f41f3200b19833148090c41870e1c465c528b9b73c1c2798a3a57b5c2c0cfe276de28b9f0b90027552b7e6375c085d35a0691f6ac7a7768c39351b2a4eabb54b8e0dba3486d2b597131b1f0b3553ab68cff9c15a9dec3adc83b0327b5764a645b3bbd7c77b2ce294f6a755cf4a278e473d7c1692b91a74e75d083a9b5d828596cb8218364a6175132eb4b782fe61202581d2b906ec926dcee4a2cd2302de6ec9354785ea52d5bd5900bda21ea652849adab4030243b676debdc60af83126d32d91c2d34a85341c20682e6d233ab41b8f02f154e6a05e4e9b897c2b319c990c52e3a859123b533d932bbdf76c276c527c2e4b21ceb4d8cd8aa8bb1b56dac6d90260d1b8db10c036bbaa54063abace4ba8ea2241c3da3f77980ddaa92bd2e7628c7629ab617f54c2527174b05a6ae8a8236da3229af186acd0293fea689c65e7716ccb0eb61a892b5e548eeca2475a55ec7d3d32658c78357533c329d62a2b5eda28a6cb492c93f3758e35524f9ac128236578e11276e742c286468aca330a42cf661ab98b783ebbd58643cafff27cf7b71c4685a678db575669c5f1543c3e0735af70bef07a975ec4a819b769132cbcc6379f1637c36f3278f7c7debe2cb1f7c7eadd434c8feb73fdd3bfaf4956223c0f1fcb4fec587792193fd4fee3cc31edc2956278e5f1fdd7cfc59566c1fbd39fc19d8d14999a138ee42707492b171f5c0afa848c877af9e78c7cb22f570ec3f77fb789951c882be4940930cf4f0d1db6fdc5f16528fe3ddaf0eee2fb324e3d8fb1e057942cd851ffef1fb8fc5fcd920f8af3f2e66c9fcffb84b7ff865b7ce875708c9ff60d8f137aa5a1fa900d00700001001010020742877c36a520b152b1337ea1ecd37b0c98ad07289c32fec392e7eebab9f0ac71f7bc8c718cfa75317b2e15702372a9222c4616783ee7b3f0ec6358f8c328eea00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232201a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72410000d00700001001010020058e23368b919493d6ac61d27f66b829a53893e88ddde857d3b82d913960960d22fa36f397752b98c295e3b31927f740127c0a99e76f8bfeea88f44466b8fbfd00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea990000d0070000100101001f43fe868e263d8134cf705aa85e26ce78ebb058edd558865fa3d240f5cb9e50c2389e9c8276eac800b7233a552045b2e79124c97e5156a0649849cc7f5d09eee600005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f0000d0070000100101001f29e82b08ccf15e2187f29fea11ee3f4974f41b51e45b19f353348d8848b86fb71cadd88630456b7a1c60803c7b402487d41fbf18f0b0a13b4cca1f740447938300005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff5260000d0070000100101002047a8b784c3765b5c63ac52e3d8461b80bc2d3e3f62434f8accb277d9f2487cfd3c0728fcd26b5119a11288e5db46bc5b547877e220971609d1cef8cba443340800005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322068dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974280000d007000010010100203e701fbafd4149bc95b55a6bfc3b78246f5c2668ccc05ed4059a36ceb38f140b31e3b69e15f2579571e5bde39e034947271599c200e540b3949112bef163074c00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c430000d0070000100101001f0cc7352e60f4f8476783d6d1b48766a111c56fee2c1a552e76a75c92bc17de172f994ffc854c09717c904054819ca7a17379ddecaf531c439b35337ba099b81300005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232208ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4050000d0070000100101002040965063a83be2d53b36c8d7e0775f503c2caa1407e586314562aace52c272fe60659e196413a6c9db4168470bcabb9a5851121c10c7b665f363f6cd4d1e4bda00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232202652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed250000d0070000100101002074ea7468b2a031c4cd53bf10ec3ac66b0c4b5c8779e045f1ef8d9c7b116be649217ff340107d0163397b99918ee2ce822b66cd6fce7b385af97a04671136e2ee00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d0000d007000010010100204dfb21ca5140582379bc026792c16b4cf97827143a4a9cd99ae70b3e6016cd6316bcbb9f1cb1233f12a0bbcd9debafa64724d0459b5c8d3cb67ceddfb2e3962500005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d670000d0070000100101002033446a3a94ade71dff3edb786259679487ab701bbc147490b1d4159fecf545fa22fee0698db16bf616465e5cebb985bfc4d9ed1ec4a55e38997dd4b4bbc427eb00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232204fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c20000d0070000100101001f3f67edd35bf731a07f40c638e8812112cd7d1baa39ec7dac4a1b2f0c83ac8bd53689b56dba69a7386e3860a6f8976695ac0bc2b5dacae91080f1d54df2dac0c000005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b44767070000d0070000100101001f1e030564013603d54f9e983b63cd940f8ff09ae038b14813f4021bb0c09ebb640d90cb4f8d57be2809f492a51737b671a5f549d4efa8e7efdaeaa9663c09d1ad00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450710000d007000010010100205cea642eecf05568ce8c5564e63349eea3b816108914ba2ab5efffbb8ea467265f0b6d474f03ed02a3bf529fd6e55a595cbf8dd1adf4311cb9c51e862f8a535400005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232205443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b40000d0070000100101001f4556076cc86e0840bf69664f1ef8fcd4d91abda313d08e7840d24ba45cb429cf12b7d3a1f64250c19d1b975e7b107853beff70ebfc4c27c44f825dc05cdc9cd600005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e990000d0070000100101001f354d903ad0f2c6cc9d9a377d681ffaa00475d1e559e48074b4c8cce3111d5c172903b2f179ad4d736dda4e7d1b6a859baeab9dde5e5e495ce09733ec4650634400005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb400000d0070000100101001f1766fa716a828da244c9ce52919b7a19acb38dbd110d1bb0039bb2477c17e4465dceecb8330ed5ee9de1330930dfcfa1a5e8149ce8536a82c0093642adf7328200005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed3232206bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc0000d00700001001010020488923db1c78fa430a3a9eab75f4ee467c7b9a3d3b4eb3bd08e183c82ef79b9102a4d2a7d1ec79c96b404911ae1b10f579bd82a660011c1ca2b872b30ef7dcac00005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322035c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b0000d0070000100101001f61ee60b366c2f3623012648000835e6089f9e9594a113acad200ae8a87bd05274acede23160e2e187d9921ea2ff6f37e3bd10ffd624ffceb511455c42f1c9ee200005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322063320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a0000d0070000100101001f184aad2b65730f7485957642fa1688c66e8ece7827ee2e8e01f8bc904cedd8ec5462c12a1e3c6cd41f4a15a350ec8575bb05e9597f4316ff73a4e1066aeab3d500005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed323220fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40000d0070000100101002041c996b52c4bdbbc4fbdaf707dd01e74c46c51ce2c8e10e174e12502cb6be3f23e2d44e8e8802e970bc5ccfc4d056e400c92a56667183c37e0f79fbe77540a0000005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322009e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc160000d0070000100101001f559c038cf4880a42f0e9fe85898105f1fe00e13f9d332448a6dfc2012ae6f3ee2f868de75894443c82b89a2d1ed3ea14ed8da702d92e4e4c633a40d3dfb5e59400005206e10b5e02005132b41600000000010000000000ea30550000002a9bed3232010000000000ea305500000000a8ed32322018b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390000001016f5987d65b93798fed86dbb47909c3ee914651cbf35da5c65ec57032b69c6de5 DMLOG START_BLOCK 4 DMLOG FEATURE_OP ACTIVATE 1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241 {"feature_digest":"1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"f3c3d91c4603cde2397268bfed4e662465293aab10cd9416db0d442b8cec2949","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"ONLY_LINK_TO_EXISTING_PERMISSION"}]} DMLOG FEATURE_OP ACTIVATE ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99 {"feature_digest":"ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"9908b3f8413c8474ab2a6be149d3f4f6d0421d37886033f27d4759c47a26d944","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"REPLACE_DEFERRED"}]} @@ -156,40 +160,41 @@ DMLOG FEATURE_OP ACTIVATE 35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569 DMLOG FEATURE_OP ACTIVATE 63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a {"feature_digest":"63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"c0cce5bcd8ea19a28d9e12eafda65ebe6d0e0177e280d4f20c7ad66dcd9e011b","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"BLS_PRIMITIVES2"}]} DMLOG FEATURE_OP ACTIVATE fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4 {"feature_digest":"fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"440c3efaaab212c387ce967c574dc813851cf8332d041beb418dfaf55facd5a9","dependencies":[],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"DISABLE_DEFERRED_TRXS_STAGE_1"}]} DMLOG FEATURE_OP ACTIVATE 09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16 {"feature_digest":"09e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"a857eeb932774c511a40efb30346ec01bfb7796916b54c3c69fe7e5fb70d5cba","dependencies":["fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4"],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"DISABLE_DEFERRED_TRXS_STAGE_2"}]} -DMLOG CREATION_OP ROOT 0 -DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":56858,"consumed":1},"cpu_usage":{"last_ordinal":1262304003,"value_ex":267959,"consumed":101},"ram_usage":180802} -DMLOG TRX_OP CREATE onblock 1b966cce9b736607ae395e8c0ff4bd21c4ce3566b2ddc1883ce69dbc28883666 0000000000000000000000000000010000000000ea305500000000221acfa4010000000000ea305500000000a8ed323274023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e82f7d596eef522faabf96b279df52ed6aca4107c87b6d789038d6f3ab4cce7311075993067e6f396bd6073f497bfeb8da8ad7850ab7a28e75602a6ca92df12a2a000000000000000000 -DMLOG APPLIED_TRANSACTION 4 1b966cce9b736607ae395e8c0ff4bd21c4ce3566b2ddc1883ce69dbc2888366604000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e01006400000000000000000000000000000000000000000001010000010000000000ea3055f332f7b7263781f123b5e98b21d4a1684f7ba6a93f8b592b59be063b98f2e4dd1a000000000000001a00000000000000010000000000ea30551a0000000000000001010000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed323274023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e82f7d596eef522faabf96b279df52ed6aca4107c87b6d789038d6f3ab4cce7311075993067e6f396bd6073f497bfeb8da8ad7850ab7a28e75602a6ca92df12a2a000000000000000000000000000000001b966cce9b736607ae395e8c0ff4bd21c4ce3566b2ddc1883ce69dbc2888366604000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e0000000000000000 -DMLOG CREATION_OP ROOT 0 -DMLOG RAM_OP 0 eosio code update setcode eosio 199492 18690 -DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":96673,"consumed":6881},"cpu_usage":{"last_ordinal":1262304003,"value_ex":279534,"consumed":2101},"ram_usage":199492} -DMLOG APPLIED_TRANSACTION 4 8a3984f522d3f6667cf3263234f9b8f5b02c3dd27fc4e5b811c3dcabdd7b30ac04000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e0100d0070000dc060000000000000000e01a0000000000000001010000010000000000ea3055378d583c2888f3564b4db37bfb52d74d038e54fcc63aeb57cf27b03c001e97a51b000000000000001b00000000000000010000000000ea30551b0000000000000002010000000000ea30550000000000ea305500000040258ab2c2010000000000ea305500000000a8ed3232cb99010000000000ea30550000be99010061736d010000000198011960000060027f7f0060037f7f7f0060047e7e7e7e017f6000017e60047f7e7e7f0060057f7f7f7f7f017f60037f7f7f017f60027f7f017f60027f7f017e60057f7f7f7f7f0060067e7e7e7e7f7f017f60017e0060027e7f0060047e7e7e7e0060037e7f7f017e60017f0060017f017f6000017f60027f7e0060047f7e7f7f0060037e7e7e0060037f7e7f0060047f7f7f7f0060027e7e0002f0052403656e760b64625f66696e645f693634000303656e760c656f73696f5f617373657274000103656e761063757272656e745f7265636569766572000403656e760561626f7274000003656e760d6173736572745f736861323536000203656e760b6173736572745f73686131000203656e760d6173736572745f736861353132000203656e76106173736572745f726970656d64313630000203656e7606736861323536000203656e76095f5f6173686c746933000503656e760473686131000203656e7606736861353132000203656e7609726970656d64313630000203656e760b7265636f7665725f6b6579000603656e76207365745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000103656e76066d656d637079000703656e76206765745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000803656e76167365745f70726f706f7365645f70726f647563657273000903656e760c63757272656e745f74696d65000403656e76146765745f6163746976655f70726f647563657273000803656e76087072696e74735f6c000103656e76126173736572745f7265636f7665725f6b6579000a03656e760c64625f73746f72655f693634000b03656e760c726571756972655f61757468000c03656e760e7365745f70726976696c65676564000d03656e76137365745f7265736f757263655f6c696d697473000e03656e76197365745f70726f706f7365645f70726f6475636572735f6578000f03656e761370726561637469766174655f66656174757265001003656e76067072696e7473001003656e761469735f666561747572655f616374697661746564001103656e7610616374696f6e5f646174615f73697a65001203656e7610726561645f616374696f6e5f64617461000803656e7611656f73696f5f6173736572745f636f6465001303656e760a64625f6765745f693634000703656e760d64625f7570646174655f693634001403656e76087072696e7468657800010346450015111000111010100c100802101608020817010110011818181818181808011818181818080101180818181808000808010101080101010801010102080108020202020804050170010d0d05030100010616037f014180c0000b7f0041e2c5000b7f0041e2c5000b070901056170706c7900250912010041010b0c555657595a5b5d5e5f6465660aab8b0145040010280bdd03002000102d102420002001510440428080f9d4a98499dc9a7f200251044020002001103b05428080add68d959ba955200251044020002001103c05428080add68d95abd1ca00200251044020002001103d0542808080e8b2edc0d38b7f200251044020002001103e05428080add68db8baf154200251044020002001103f054280f8a6d4d2a8a1d3c1002002510440200020011040054280808080d4c4a2d942200251044020002001104105428080808080f798d9422002510440200020011044054280808080aefadeeaa47f2002510440200020011045054280808080b6f7d6d942200251044020002001104605428080b8f6a4979ad94220025104402000200110470542808080c093fad6d9422002510440200020011048054280808096cdebd4d942200251044020002001104c054280808080daac9bd6ba7f200251044020002001104e0542808080d0b2b3bb9932200251044020002001104f054290a9d9d9dd8c99d6ba7f2002510440200020011050052000428080808080c0ba98d500520440410042808080d9d3b3ed82ef0010200b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b05428080808080c0ba98d50020015104404280808080aefadeeaa47f2002510440410042818080d9d3b3ed82ef0010200b0b0b410010310b7201037f024020000d0041000f0b4100410028028c40200041107622016a220236028c404100410028028440220320006a410f6a4170712200360284400240200241107420004b0d004100200241016a36028c40200141016a21010b024020014000417f470d0041004190c00010010b20030b02000b3601017f230041106b2200410036020c4100200028020c280200410f6a417071220036028040410020003602844041003f0036028c400b3301027f2000410120001b2101024003402001102622000d01410021004100280284412202450d0120021100000c000b0b20000b0600200010270b05001003000b05001003000b0a0041002000370388410b4e01017f230041e0006b220124002001200141d8006a3602082001200141106a3602042001200141106a36020020012000102f1a200141106a200128020420012802006b100e200141e0006a24000ba20801027f02402000280208200028020422026b41074a0d0041004190c1001001200028020421020b200220014108100f1a2000200028020441086a2202360204200141086a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a22023602042001410c6a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141106a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141146a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141186a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a22023602042001411c6a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141206a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141246a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141286a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a22023602042001412c6a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141306a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141346a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141386a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a22023602042001413c6a21030240200028020820026b41034a0d0041004190c1001001200028020421020b200220034104100f1a2000200028020441046a2202360204200141c0006a21030240200028020820026b41014a0d0041004190c1001001200028020421020b200220034102100f1a2000200028020441026a2202360204200141c2006a21010240200028020820026b41014a0d0041004190c1001001200028020421020b200220014102100f1a2000200028020441026a36020420000bfa0103017f027e017f230041306b2203240020012002200341106a1008420021044110210141002102420021050340200341106a20026a21060240024020014102490d002005420886200420063100008422044238888421052001417f6a2101200442088621040c010b024020014101460d00410041a9c00010010b200020053703082000200420063100008437030041102101200041106a210042002104420021050b200241016a22024120470d000b024020014110460d00024020014102490d00200320042005200141037441786a1009200341086a2903002105200329030021040b20002004370300200020053703080b200341306a24000b02000b970503017f017e047f230041f0006b22032400200341206a4100360200200342003703182003427f37031020032000290300220437030820032004370300024002402004200442808080809aecb4ee312001100022004100480d00024020032000103322002802302003460d00410041c0c20010010b2003200236023020032000200341306a10340c010b024020041002510d004100418ac30010010b41c000102922004200370310200041286a22054200370300200041206a22064200370300200041186a220742003703002000200336023020002001370300200341306a20022802002208200228020420086b10302005200341306a41186a2903003703002006200341306a41106a29030037030020072003290338370300200020032903303703102003200341306a41286a3602682003200341306a360260200341306a20004108100f1a2003200341306a410872360264200341e0006a200041106a10351a2000200329030842808080809aecb4ee31200120002903002204200341306a412810162205360234024020042003290310540d002003427e200442017c2004427d561b3703100b200320003602602003200029030022043703302003200536022c02400240200328021c220220032802204f0d00200220053602102002200437030820034100360260200220003602002003200241186a36021c0c010b200341186a200341e0006a200341306a2003412c6a10360b20032802602100200341003602602000450d002000102a0b024020032802182205450d0002400240200328021c22002005470d00200521000c010b0340200041686a220028020021022000410036020002402002450d002002102a0b20052000470d000b200328021821000b2003200536021c2000102a0b200341f0006a24000b840603097f027e017f230041e0006b220221032002240002400240200028021822042000411c6a2802002205460d0002400340200541786a2802002001460d012004200541686a2205470d000c020b0b20042005460d00200541686a28020021060c010b024002400240024020014100410010212205417f4a0d00410041f3c20010010c010b2005418104490d010b200510262107410121080c010b20022005410f6a4170716b22072400410021080b20012007200510211a41c0001029220642003703102006420037030020062000360230200641186a4200370300200641206a4200370300200641286a42003703000240200541074b0d00410041d9c40010010b200620074108100f1a200741086a21040240200541786a411f4b0d00410041d9c40010010b200041186a2109200641106a210a200341c0006a20044120100f1a4200210b41102105200341206a2102410021044200210c0340200341c0006a20046a210d0240024020054102490d00200c420886200b200d31000084220b42388884210c2005417f6a2105200b420886210b0c010b024020054101460d00410041b6c50010010b2002200c3703082002200b200d3100008437030041102105200241106a21024200210b4200210c0b200441016a22044120470d000b024020054110460d00024020054102490d00200341086a200b200c200541037441786a1009200341106a290300210c2003290308210b0b2002200b3703002002200c3703080b200a2003290320370300200a41086a2003290328370300200a41186a200341206a41186a290300370300200a41106a200341206a41106a290300370300200620013602342003200636022020032006290300220b3703402003200136021c02400240200028021c2205200041206a2802004f0d00200520013602102005200b37030820034100360220200520063602002000200541186a36021c0c010b2009200341206a200341c0006a2003411c6a10360b02402008450d00200710270b20032802202105200341003602202005450d002005102a0b200341e0006a240020060b980203027f017e017f230041206b2203210420032400024020012802302000460d00410041bdc30010010b024010022000290300510d00410041ebc30010010b200129030021052004200228020022022802002206200228020420066b1030200141286a200441186a290300370300200141206a200441106a290300370300200141186a200429030837030020012004290300370310200141106a2102024020052001290300510d004100419ec40010010b200341506a220324002004200341286a36020820042003360200200320014108100f1a2004200341086a3602042004200210351a20012802344200200341281022024020052000290310540d002000427e200542017c2005427d561b3703100b200441206a24000bd20303017f027e017f230041206b220224002002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722054108763a0016200220054110763a0015200220054118763a001420022004a722053a0007200220054108763a0006200220054110763a0005200220054118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c001002402000280208200028020422016b411f4a0d0041004188c2001001200028020421010b200120024120100f1a2000200028020441206a360204200241206a240020000b9a0301057f0240024002402000280204200028020022046b41186d220541016a220641abd5aad5004f0d0041aad5aad500210702400240200028020820046b41186d220441d4aad52a4b0d0020062004410174220720072006491b22070d0041002107410021040c010b200741186c102921040b20012802002106200141003602002004200541186c22086a2201200328020036021020012002290300370308200120063602002004200741186c6a2105200141186a21062000280204220220002802002207460d01200420086a41686a21010340200241686a220428020021032004410036020020012003360200200141086a200241706a2202290300370300200141106a200241086a280200360200200141686a21012004210220072004470d000b200141186a210120002802042107200028020021040c020b2000102c000b200721040b200020053602082000200636020420002001360200024020072004460d000340200741686a220728020021012007410036020002402001450d002001102a0b20042007470d000b0b02402004450d002004102a0b0bd00203047f017e017f230041106b220224004100210320004100360208200042003702002002410036020020012802042204200128020022056b410575ad21060340200341016a2103200642078822064200520d000b2002200336020002400240024020052004460d0003402002200341086a3602002003410c6a2103200541186a2802002207ad21060340200341016a2103200642078822064200520d000b20022003417c6a3602002007417f460d022002200336020020022005410c6a10511a20022802002103200541206a22052004470d000b20002802002105200028020421070c020b41002105410021070c010b1052000b024002402003200720056b22074d0d002000200320076b1043200028020021050c010b200320074f0d002000200520036a3602040b2002200536020420022005360200200220002802043602082002200110531a200241106a24000baf0302017f027e230041206b22022400200029030010172002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722004108763a0016200220004110763a0015200220004118763a001420022004a722003a0007200220004108763a0006200220004110763a0005200220004118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c00102002101b41bdc100101c2001103941bbc100101c200241206a24000b9c0303017f027e017f230041206b220124002001200041186a29030022023c00172001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c0000200120024220883c001320012002a722044108763a0016200120044110763a0015200120044118763a001420012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a0004200120002903002203423886200342288642808080808080c0ff0083842003421886428080808080e03f8320034208864280808080f01f838484200342088842808080f80f832003421888428080fc07838420034228884280fe03832003423888848484370308200120002903102203423886200342288642808080808080c0ff0083842003421886428080808080e03f8320034208864280808080f01f838484200342088842808080f80f832003421888428080fc07838420034228884280fe03832003423888848484370318200120024230883c0011200120024228883c0012200120024238883c0010200141201023200141206a24000ba70303017f027e017f230041206b220224002002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722054108763a0016200220054110763a0015200220054118763a001420022004a722053a0007200220054108763a0006200220054110763a0005200220054118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c001002402002101d0d00410041d8c10010010b200241206a24000bb90101047f230041106b2202210320022400024002400240101e22040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a20034200370308200341086a2105200441074b0d010b410041d9c40010010b200520024108100f1a20034200370300200241086a2102024020044178714108470d00410041d9c40010010b200320024108100f1a200341106a24000b4401037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b200324000b4401037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b200324000b4401037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b200324000b4401037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b200324000b4401037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b200324000bc90201047f230041306b220221032002240002400240101e22040d00410021020c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b20032002360224200320023602202003200220046a2205360228200342003703180240200441074b0d00410041d9c400100120032802282105200328022421020b200341186a20024108100f1a2003200241086a2202360224024020052002470d00410041d9c400100120032802282105200328022421020b200341176a20024101100f1a2003200241016a2202360224024020052002470d00410041d9c4001001200328022421020b200341166a20024101100f1a2003200241016a3602242003410036021020034200370308200341206a200341086a10421a024020032802082202450d002003200236020c2002102a0b200341306a24000bff0103017f017e047f2000280204210242002103410021040340024020022000280208490d0041004183c5001001200028020421020b20022d000021052000200241016a22063602042003200541ff0071200441ff0171220274ad842103200241076a2104200621022005418001710d000b0240024020012802042205200128020022026b22072003a722044f0d002001200420076b10432000280204210620012802042105200128020021020c010b200720044d0d002001200220046a22053602040b0240200028020820066b200520026b22054f0d00410041d9c4001001200028020421060b200220062005100f1a2000200028020420056a36020420000b980201057f02400240024020002802082202200028020422036b2001490d000340200341003a00002000200028020441016a22033602042001417f6a22010d000c020b0b2003200028020022046b220520016a2206417f4c0d0141ffffffff07210302400240200220046b220241feffffff034b0d0020062002410174220320032006491b22030d0041002103410021020c010b2003102921020b200220036a2106200220056a220421030340200341003a0000200341016a21032001417f6a22010d000b20042000280204200028020022016b22026b2104024020024101480d00200420012002100f1a200028020021010b2000200636020820002003360204200020043602002001450d002001102a0b0f0b2000102c000bb20202037f017e23004180016b220221032002240002400240101e22040d00410021020c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b20032002360254200320023602502003200220046a360258200342003703480240200441074b0d00410041d9c4001001200328025421020b200341c8006a20024108100f1a2003200241086a3602542003410036024020034200370338200341d0006a200341386a10421a200341086a41086a200341d0006a41086a2802002202360200200341306a2002360200200320032903502205370308200320013703202003200037031820032005370328200341186a2003290348200341386a1032024020032802382202450d002003200236023c2002102a0b20034180016a24000b4c01037f2300220221030240101e2204450d00024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b410041d5c0001001200324000bcf0102047f017e230041106b2202210320022400024002400240101e22040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a20034200370308200341086a2105200441074b0d010b410041d9c40010010b200520024108100f1a200241086a2102024020044108470d00410041d9c40010010b200341076a20024101100f1a2003290308210620032d0007210420001017200620044100471018200341106a24000baa0202047f047e230041206b2202210320022400024002400240101e22040d002003420037031841002102200341186a21050c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a20034200370318200341186a2105200441074b0d010b410041d9c40010010b200520024108100f1a200241086a21050240200441787122044108470d00410041d9c40010010b200341106a20054108100f1a200241106a2105024020044110470d00410041d9c40010010b200341086a20054108100f1a200241186a2102024020044118470d00410041d9c40010010b200320024108100f1a200329030021062003290308210720032903102108200329031821092000101720092008200720061019200341206a24000ba103010b7f230041306b2202210320022400410021040240101e2205450d00024002402005418004490d002005102621040c010b20022005410f6a4170716b220424000b20042005101f1a0b20032004360214200320043602102003200420056a3602182003410036020820034200370300200341106a200310491a20001017200341206a20031037420120032802202204200328022420046b101a1a024020032802202204450d00200320043602242004102a0b024020032802002206450d0002400240200328020422072006470d00200621040c010b03402007220441606a21070240200441786a2208280200417f460d002004416c6a2209280200220a450d00200a21050240200441706a220b2802002204200a460d000340200441486a21050240200441786a2202280200220c417f460d00200341206a200441486a200c4102744188c5006a2802001101000b2002417f36020020052104200a2005470d000b200928020021050b200b200a3602002005102a0b2008417f36020020072006470d000b200328020021040b200320063602042004102a0b200341306a24000bcc0303027f017e097f230041206b220224002000280204210342002104410021050340024020032000280208490d0041004183c5001001200028020421030b20032d000021062000200341016a22033602042004200641ff0071200541ff0171220574ad842104200541076a2105200321032006418001710d000b0240024020012802042207200128020022056b41057522062004a722034f0d002001200320066b104a200128020421070c010b200620034d0d000240200520034105746a22082007460d0003402007220341606a21070240200341786a2209280200417f460d002003416c6a220a280200220b450d00200b21060240200341706a220c2802002203200b460d000340200341486a21060240200341786a2205280200220d417f460d00200241186a200341486a200d4102744188c5006a2802001101000b2005417f36020020062103200b2006470d000b200a28020021060b200c200b3602002006102a0b2009417f36020020072008470d000b0b20012008360204200821070b0240200128020022032007460d00034020022000360208200220033602102002200341086a360214200241106a200241086a104b200341206a22032007470d000b0b200241206a240020000b9f06030a7f017e037f230041106b220224000240024020002802082203200028020422046b4105752001490d000340200441186a2203420037030020044200370300200441106a4200370300200441086a4200370300200341003602002000200028020441206a22043602042001417f6a22010d000c020b0b02400240024002402004200028020022056b410575220620016a220741808080c0004f0d0041ffffff3f210402400240200320056b220341057541feffff1f4b0d00024020072003410475220420042007491b22040d0041002104410021030c020b200441808080c0004f0d030b2004410574102921030b200320044105746a2108200320064105746a22092104034020044200370300200441186a4200370300200441106a4200370300200441086a4200370300200441206a21042001417f6a22010d000b2000280204220a20002802002206460d022006200a6b210b410021050340200920056a220141786a2206417f360200200a20056a220341606a290300210c200141686a220741003a0000200141606a200c3703000240200341786a280200220d417f460d00200141706a220e42003702002001416c6a220f4100360200200e200341706a280200360200200f2003416c6a220e280200360200200141746a200341746a22012802003602002007200341686a2802003602002006200d36020020014100360200200e42003702000b200b200541606a2205470d000b200920056a2109200028020421062000280200210d0c030b2000102c000b1003000b2006210d0b20002008360208200020043602042000200936020002402006200d460d0003402006220441606a21060240200441786a2207280200417f460d002004416c6a220e2802002200450d00200021010240200441706a220f28020022042000460d000340200441486a21010240200441786a22032802002205417f460d00200241086a200441486a20054102744188c5006a2802001101000b2003417f3602002001210420002001470d000b200e28020021010b200f20003602002001102a0b2007417f3602002006200d470d000b0b200d450d00200d102a0b200241106a24000bca0102037f017e20002802002102024020012802002203280208200328020422046b41074b0d00410041d9c4001001200328020421040b200220044108100f1a2003200328020441086a3602042000280204210220012802002201280204210342002105410021040340024020032001280208490d0041004183c5001001200128020421030b20032d000021002001200341016a22033602042005200041ff0071200441ff0171220474ad842105200441076a2104200321032000418001710d000b200120022005a710600b890101037f230041e0006b220221032002240002400240101e22040d00410021020c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a0b20032002360254200320023602502003200220046a360258200341d0006a200341086a104d1a20001017200341086a102e200341e0006a24000ba20801027f02402000280208200028020422026b41074b0d00410041d9c4001001200028020421020b200120024108100f1a2000200028020441086a2202360204200141086a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a22023602042001410c6a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141106a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141146a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141186a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a22023602042001411c6a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141206a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141246a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141286a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a22023602042001412c6a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141306a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141346a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141386a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a22023602042001413c6a21030240200028020820026b41034b0d00410041d9c4001001200028020421020b200320024104100f1a2000200028020441046a2202360204200141c0006a21030240200028020820026b41014b0d00410041d9c4001001200028020421020b200320024102100f1a2000200028020441026a2202360204200141c2006a21010240200028020820026b41014b0d00410041d9c4001001200028020421020b200120024102100f1a2000200028020441026a36020420000b940101047f230041106b2202210320022400024002400240101e22040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102621020c010b20022004410f6a4170716b220224000b20022004101f1a20034200370308200341086a2105200441074b0d010b410041d9c40010010b200520024108100f1a20032903081017200341106a24000b8c0405047f017e037f017e017f230041f0006b220221032002240002400240101e22040d00410021050c010b024002402004418004490d002004102621050c010b20022004410f6a4170716b220524000b20052004101f1a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d00410041d9c40010010b200520046a2107200341d0006a20054120100f1a200541206a2108200341306a2109410021044200210a0340200341d0006a20046a210b0240024020024102490d00200a4208862006200b31000084220642388884210a2002417f6a2102200642088621060c010b024020024101460d00410041b6c50010010b2009200a37030820092006200b3100008437030041102102200941106a2109420021064200210a0b200441016a22044120470d000b024020024110460d00024020024102490d0020032006200a200241037441786a1009200341086a290300210a200329030021060b200920063703002009200a3703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a2903003703002003200329033837031820032003290330370310200341d0006a41186a2007360200200341e4006a2008360200200320053602602003200137035820032000370350200341d0006a200341106a1038200341f0006a24000bc80303047f027e017f230041f0006b220221032002240002400240101e22040d00410021050c010b024002402004418004490d002004102621050c010b20022004410f6a4170716b220524000b20052004101f1a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d00410041d9c40010010b200341d0006a20054120100f1a200341306a210541002104420021070340200341d0006a20046a21080240024020024102490d002007420886200620083100008422064238888421072002417f6a2102200642088621060c010b024020024101460d00410041b6c50010010b200520073703082005200620083100008437030041102102200541106a210542002106420021070b200441016a22044120470d000b024020024110460d00024020024102490d00200320062007200241037441786a1009200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a29030037030020032003290338370318200320032903303703102002200341106a103a200341f0006a24000bd50103037f017e017f230041106b2202240020012802042203200128020022046b41386dad2105200028020021010340200141016a2101200542078822054200520d000b200020013602000240024020042003460d00034020042802302206ad21050340200141016a2101200542078822054200520d000b20002001360200200220003602002006417f460d0220022002360208200241086a2004200641027441fcc1006a2802001101002000200028020041026a2201360200200441386a22042003470d000b0b200241106a240020000f0b1052000b05001003000bfe0103017f017e037f230041106b22022400200128020420012802006b410575ad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d0041004188c2001001200028020421040b20042002410f6a4101100f1a2000200028020441016a220436020420060d000b02402001280200220520012802042206460d0003400240200028020820046b41074a0d0041004188c2001001200028020421040b200420054108100f1a2000200028020441086a3602042000200541086a10541a200541206a22052006460d01200028020421040c000b0b200241106a240020000bdd0103027f017e027f230041106b22022400200028020421032001350210210403402004a721052002200442078822044200522206410774200541ff0071723a000f0240200028020820036b41004a0d0041004188c2001001200028020421030b20032002410f6a4101100f1a2000200028020441016a220336020420060d000b02402001280210417f460d00200141046a21050240200028020820036b41034a0d0041004188c2001001200028020421030b200320014104100f1a2000200028020441046a3602042000200510581a200241106a240020000f0b1052000b170020002802002802002200200028020041216a3602000b170020002802002802002200200028020041216a3602000b7602017f017e20002802002802002202200228020041226a2200360200200141286a350200420020012d00244101711b21030340200041016a2100200342078822034200520d000b200220003602000240200128022820012d0024220141017620014101711b2201450d002002200120006a3602000b0b990303017f017e047f230041106b22022400200128020420012802006b41386dad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d0041004188c2001001200028020421040b20042002410f6a4101100f1a2000200028020441016a220436020420060d000b024002402001280200220720012802042201460d0003402007350230210303402003a721052002200342078822034200522206410774200541ff0071723a000e0240200028020820046b41004a0d0041004188c2001001200028020421040b20042002410e6a4101100f1a2000200028020441016a220436020420060d000b2002200036020020072802302204417f460d0220022002360208200241086a2007200441027441b4c2006a280200110100200741346a210502402000280208200028020422046b41014a0d0041004188c2001001200028020421040b200420054102100f1a2000200028020441026a2204360204200741386a22072001470d000b0b200241106a240020000f0b1052000b6401037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d0041004188c2001001200028020421020b200220044101100f1a2000200028020441016a2202360204200341016a22034121470d000b0b6401037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d0041004188c2001001200028020421020b200220044101100f1a2000200028020441016a2202360204200341016a22034121470d000b0baa0101037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d0041004188c2001001200028020421020b200220044101100f1a2000200028020441016a2202360204200341016a22034121470d000b200141216a21030240200028020820026b41004a0d0041004188c2001001200028020421020b200220034101100f1a2000200028020441016a3602042000200141246a105c1a0bfd0103027f017e027f230041106b22022400200128020420012d0000220341017620034101711bad21042000280204210303402004a721052002200442078822044200522206410774200541ff0071723a000f0240200028020820036b41004a0d0041004188c2001001200028020421030b20032002410f6a4101100f1a2000200028020441016a220336020420060d000b0240200128020420012d00002205410176200541017122061b2205450d002001280208200141016a20061b21060240200028020820036b20054e0d0041004188c2001001200028020421030b200320062005100f1a2000200028020420056a3602040b200241106a240020000b02000b02000b1a00024020012d0024410171450d002001412c6a280200102a0b0bae0201047f230041206b220324000240024020020d00200341146a41003602002003420037020c200341086a410472210402402000280208200028020422026b41034b0d00410041d9c4001001200028020421020b200341086a20024104100f1a2000200028020441046a3602042000200410611a02402001280210417f460d0020012802042205450d00200521020240200141086a28020022002005460d000340200041486a21020240200041786a22042802002206417f460d00200341186a200041486a20064102744188c5006a2802001101000b2004417f3602002002210020052002470d000b200128020421020b200120053602082002102a0b2001200329030837020020014100360210200141086a20032903103702000c010b410041a0c50010010b200341206a24000b870303027f017e047f230041106b220224002000280204210342002104410021050340024020032000280208490d0041004183c5001001200028020421030b20032d000021062000200341016a22033602042004200641ff0071200541ff0171220574ad842104200541076a2105200321032006418001710d000b0240024020012802042205200128020022076b41386d22062004a722034f0d002001200320066b1062200128020421050c010b200620034d0d0002402007200341386c6a22082005460d000340200541486a21030240200541786a22062802002207417f460d00200241086a200541486a20074102744188c5006a2802001101000b2006417f3602002003210520082003470d000b0b20012008360204200821050b0240200128020022032005460d0003402000200310631a02402000280208200028020422066b41014b0d00410041d9c4001001200028020421060b200341346a20064102100f1a2000200028020441026a360204200341386a22032005470d000b0b200241106a240020000ba105010c7f230041106b2202240002400240024020002802082203200028020422046b41386d2001490d000340200441306a2203420037020020044200370200200441286a4200370200200441186a4200370200200441106a4200370200200441086a4200370200200441206a4200370200200341003602002000200028020441386a22043602042001417f6a22010d000c020b0b2004200028020022056b41386d220620016a220741a592c9244f0d0141a492c924210402400240200320056b41386d22034191c9a4124b0d0020072003410174220420042007491b22040d0041002104410021030c010b200441386c102921030b2003200441386c6a21082003200641386c6a22092104034020044200370200200441286a4200370200200441186a4200370200200441106a4200370200200441086a4200370200200441206a4200370200200441306a4200370200200441386a21042001417f6a22010d000b024002402000280204220a20002802002205470d002000200836020820002004360204200020093602000c010b2005200a6b210b410021010340200920016a220341786a2206417f360200200341486a220741003a00000240200a20016a220541786a220c280200220d417f460d00200241086a2007200541486a200d4102744194c5006a2802001102002006200c2802003602000b2003417c6a2005417c6a2f01003b0100200b200141486a2201470d000b200020083602082000280204210320002004360204200028020021052000200920016a36020020032005460d000340200341486a21040240200341786a22012802002200417f460d002002200341486a20004102744188c5006a2802001101000b2001417f3602002004210320052004470d000b0b2005450d002005102a0b200241106a24000f0b2000102c000bdf0203027f017e037f230041306b220224002000280204210342002104410021050340024020032000280208490d0041004183c5001001200028020421030b20032d000021062000200341016a22073602042004200641ff0071200541ff0171220374ad842104200341076a2105200721032006418001710d000b024002402004a722030d00410021030340200220036a2106024020002802082007470d00410041d9c4001001200028020421070b200620074101100f1a2000200028020441016a2207360204200341016a22034121470d000b024020012802302203417f460d00200241286a200120034102744188c5006a2802001101000b2001200229030037000020014100360230200141206a200241206a2d00003a0000200141186a200241186a290300370000200141106a200241106a290300370000200141086a200241086a2903003700000c010b20002001200310670b200241306a240020000b4c0020012002290000370000200141206a200241206a2d00003a0000200141186a200241186a290000370000200141106a200241106a290000370000200141086a200241086a2900003700000b4c0020012002290000370000200141206a200241206a2d00003a0000200141186a200241186a290000370000200141106a200241106a290000370000200141086a200241086a2900003700000b7801017f20012002290200370200200141206a200241206a2f01003b0100200141186a200241186a290200370200200141106a200241106a290200370200200141086a200241086a2902003702002001412c6a2002412c6a22032802003602002001200229022437022420024200370224200341003602000be70401037f230041c0006b22032400024002402002417f6a220241014b0d000240024020020e020001000b20002802042102410021040340200341086a20046a2105024020002802082002470d00410041d9c4001001200028020421020b200520024101100f1a2000200028020441016a2202360204200441016a22044121470d000b024020012802302200417f460d00200341386a200120004102744188c5006a2802001101000b2001200329030837000020014101360230200141206a200341086a41206a2d00003a0000200141186a200341086a41186a290300370000200141106a200341086a41106a290300370000200141086a200341086a41086a2903003700000c020b200341346a41003602002003420037022c20002802042102410021040340200341086a20046a2105024020002802082002470d00410041d9c4001001200028020421020b200520024101100f1a2000200028020441016a2202360204200441016a22044121470d000b200341296a2104024020002802082002470d00410041d9c4001001200028020421020b200420024101100f1a2000200028020441016a36020420002003412c6a220210681a024020012802302200417f460d00200341386a200120004102744188c5006a2802001101000b200120032903083702002001410236023020012002290200370224200141206a200341086a41206a2f01003b0100200141186a200341086a41186a290300370200200141106a200341086a41106a290300370200200141086a200341086a41086a2903003702002001412c6a200241086a2802003602000c010b410041a0c50010010b200341c0006a24000ba00301057f230041206b2202240020024100360218200242003703102000200241106a10421a0240024002402002280214200228021022036b2204450d00200241086a410036020020024200370300200441704f0d02024002402004410a4b0d00200220044101743a0000200241017221050c010b200441106a4170712206102921052002200436020420022006410172360200200220053602080b0340200520032d00003a0000200541016a2105200341016a21032004417f6a22040d000b200541003a00000240024020012d00004101710d00200141003b01000c010b200128020841003a00002001410036020420012d0000410171450d002001280208102a200141003602000b20012002290300370200200141086a200241086a2802003602000c010b0240024020012d00004101710d00200141003b01000c010b200128020841003a00002001410036020420012d0000410171450d002001280208102a200141003602000b20014100360208200142003702000b024020022802102205450d00200220053602142005102a0b200241206a240020000f0b2002102b000b0beb0503004190c0000b796661696c656420746f20616c6c6f6361746520706167657300756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f7200746865206f6e6572726f7220616374696f6e2063616e6e6f742062652063616c6c6564206469726563746c790000000000000000004189c1000bd904000000000000006461746173747265616d20617474656d7074656420746f20777269746520706173742074686520656e64000a006665617475726520646967657374206163746976617465643a200070726f746f636f6c2066656174757265206973206e6f74206163746976617465640000000100000002000000030000006461746173747265616d20617474656d7074656420746f20777269746520706173742074686520656e6400000400000005000000060000006f626a6563742070617373656420746f206974657261746f725f746f206973206e6f7420696e206d756c74695f696e646578006572726f722072656164696e67206974657261746f720063616e6e6f7420637265617465206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e7472616374006f626a6563742070617373656420746f206d6f64696679206973206e6f7420696e206d756c74695f696e6465780063616e6e6f74206d6f64696679206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e747261637400757064617465722063616e6e6f74206368616e6765207072696d617279206b6579207768656e206d6f64696679696e6720616e206f626a656374006461746173747265616d20617474656d7074656420746f207265616420706173742074686520656e640067657400000700000008000000090000000a0000000b0000000c000000696e76616c69642076617269616e7420696e64657800756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f72000041000b04e8220000000000000000000000008a3984f522d3f6667cf3263234f9b8f5b02c3dd27fc4e5b811c3dcabdd7b30ac04000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e010000000000ea3055024900000000000000000000000000 -DMLOG CREATION_OP ROOT 0 -DMLOG RAM_OP 0 eosio abi update setabi eosio 199629 137 -DMLOG DB_OP UPD 0 eosio:eosio eosio eosio abihash eosio 0000000000ea3055d7abd75d188060de8a01ab2672d1cc2cd768fddc56203181b43685cc11f5ce46:0000000000ea3055fc470c7761cfe2530d91ab199fc6326b456e254a57fcc882544eb4c0e488fd39 -DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":102738,"consumed":7929},"cpu_usage":{"last_ordinal":1262304003,"value_ex":291109,"consumed":4101},"ram_usage":199629} -DMLOG APPLIED_TRANSACTION 4 5ecfddd3ef98258eaf45e9bbb378bacd091dfaf25891b15b984a4229d765b53504000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e0100d00700008301000000000000000018040000000000000001010000010000000000ea30552deb8b0eef2f2bfd027d20727a96e4b30eb6ccdc27488670d57bf488395c48fc1c000000000000001c00000000000000010000000000ea30551c0000000000000002020000000000ea30550000000000ea305500000000b863b2c2010000000000ea305500000000a8ed323293120000000000ea305589120e656f73696f3a3a6162692f312e320117626c6f636b5f7369676e696e675f617574686f726974792276617269616e745f626c6f636b5f7369676e696e675f617574686f726974795f763019086162695f686173680002056f776e6572046e616d6504686173680b636865636b73756d32353608616374697661746500010e666561747572655f6469676573740b636865636b73756d32353609617574686f726974790004097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d086163636f756e7473197065726d697373696f6e5f6c6576656c5f7765696768745b5d0577616974730d776169745f7765696768745b5d1a626c6f636b5f7369676e696e675f617574686f726974795f76300002097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d15626c6f636b636861696e5f706172616d65746572730011136d61785f626c6f636b5f6e65745f75736167650675696e7436341a7461726765745f626c6f636b5f6e65745f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6e65745f75736167650675696e7433321e626173655f7065725f7472616e73616374696f6e5f6e65745f75736167650675696e743332106e65745f75736167655f6c65657761790675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f6e756d0675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f64656e0675696e743332136d61785f626c6f636b5f6370755f75736167650675696e7433321a7461726765745f626c6f636b5f6370755f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6370755f75736167650675696e743332196d696e5f7472616e73616374696f6e5f6370755f75736167650675696e743332186d61785f7472616e73616374696f6e5f6c69666574696d650675696e7433321e64656665727265645f7472785f65787069726174696f6e5f77696e646f770675696e743332156d61785f7472616e73616374696f6e5f64656c61790675696e743332166d61785f696e6c696e655f616374696f6e5f73697a650675696e743332176d61785f696e6c696e655f616374696f6e5f64657074680675696e743136136d61785f617574686f726974795f64657074680675696e7431360b63616e63656c64656c617900020e63616e63656c696e675f61757468107065726d697373696f6e5f6c6576656c067472785f69640b636865636b73756d3235360a64656c657465617574680002076163636f756e74046e616d650a7065726d697373696f6e046e616d650a6b65795f7765696768740002036b65790a7075626c69635f6b6579067765696768740675696e743136086c696e6b617574680004076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650b726571756972656d656e74046e616d650a6e65776163636f756e7400040763726561746f72046e616d65046e616d65046e616d65056f776e657209617574686f726974790661637469766509617574686f72697479076f6e6572726f7200020973656e6465725f69640775696e743132380873656e745f747278056279746573107065726d697373696f6e5f6c6576656c0002056163746f72046e616d650a7065726d697373696f6e046e616d65177065726d697373696f6e5f6c6576656c5f77656967687400020a7065726d697373696f6e107065726d697373696f6e5f6c6576656c067765696768740675696e7431361270726f64756365725f617574686f7269747900020d70726f64756365725f6e616d65046e616d6509617574686f7269747917626c6f636b5f7369676e696e675f617574686f726974790c72657161637469766174656400010e666561747572655f6469676573740b636865636b73756d323536077265716175746800010466726f6d046e616d65067365746162690002076163636f756e74046e616d65036162690562797465730a736574616c696d6974730004076163636f756e74046e616d650972616d5f627974657305696e7436340a6e65745f77656967687405696e7436340a6370755f77656967687405696e74363407736574636f64650004076163636f756e74046e616d6506766d747970650575696e743809766d76657273696f6e0575696e743804636f646505627974657309736574706172616d73000106706172616d7315626c6f636b636861696e5f706172616d657465727307736574707269760002076163636f756e74046e616d650769735f707269760575696e74380873657470726f64730001087363686564756c651470726f64756365725f617574686f726974795b5d0a756e6c696e6b617574680003076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650a757064617465617574680004076163636f756e74046e616d650a7065726d697373696f6e046e616d6506706172656e74046e616d65046175746809617574686f726974790b776169745f776569676874000208776169745f7365630675696e743332067765696768740675696e743136100000002a9bed32320861637469766174650000bc892a4585a6410b63616e63656c64656c6179000040cbdaa8aca24a0a64656c65746561757468000000002d6b03a78b086c696e6b617574680000409e9a2264b89a0a6e65776163636f756e7400000000e0d27bd5a4076f6e6572726f7200905436db6564acba0c72657161637469766174656400000000a0656dacba07726571617574680000000000b863b2c206736574616269000000ce4eba68b2c20a736574616c696d6974730000000040258ab2c207736574636f6465000000c0d25c53b3c209736574706172616d730000000060bb5bb3c207736574707269760000000038d15bb3c20873657470726f6473000040cbdac0e9e2d40a756e6c696e6b61757468000040cbdaa86c52d50a757064617465617574680001000000a061d3dc31036936340000086162695f68617368000000012276617269616e745f626c6f636b5f7369676e696e675f617574686f726974795f7630011a626c6f636b5f7369676e696e675f617574686f726974795f763000000000000000000000005ecfddd3ef98258eaf45e9bbb378bacd091dfaf25891b15b984a4229d765b53504000000033b3d4b0100000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e010000000000ea3055890000000000000000000000000000 -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":3,"value_ex":81866667,"consumed":9824},"average_block_cpu_usage":{"last_ordinal":3,"value_ex":384993056,"consumed":46101},"pending_net_usage":7928,"pending_cpu_usage":4100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1050675,"virtual_cpu_limit":200400} -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":4,"value_ex":147251111,"consumed":8010},"average_block_cpu_usage":{"last_ordinal":4,"value_ex":415951447,"consumed":4482},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1051726,"virtual_cpu_limit":200600} -DMLOG ACCEPTED_BLOCK 4 04000000040000000300000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010003000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b442d5b1b639d6ae94fcdd0536b224644931573d1ccb2a0c548613cd1feea18888b87ba6ee368252e66e0c9b4d234cb5031b8963705c169568543012ac702e7c7ca0300000000000000010000000000ea305504000000010000000000ea305503000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add801000000000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e033b3d4b0000000000ea30550000000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440fb1731b78038b7fd1e418d83062417a2c12a515dde5d4d8bbd2610bb46ce01b8888fd15e295bb3585742c5737bf6bd6deb93ece7f776af86767a2bc20834cff0000000000010000a105151a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1600203f08f2be4e59753b3ce01482e4023e51b0971a7d71fab20cadbdeb96890e8ce66dbdee15bfa731a2353bfbfc345485ba643aea2d3accee6e6f426b8382661ae30000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef261980000000000011609e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc160ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72412652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670735c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c25443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b463320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974286bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1dfce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40001033b3d4b0000000000ea30550000000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440fb1731b78038b7fd1e418d83062417a2c12a515dde5d4d8bbd2610bb46ce01b8888fd15e295bb3585742c5737bf6bd6deb93ece7f776af86767a2bc20834cff0000000000010000a105151a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1600203f08f2be4e59753b3ce01482e4023e51b0971a7d71fab20cadbdeb96890e8ce66dbdee15bfa731a2353bfbfc345485ba643aea2d3accee6e6f426b8382661ae30200d0070000dc060101002073f10e2efaca3f684759d38276323ab5b0d66cb39d52db8dca6f4fc7108b1352749fa40dbfad03e1eb209291d43125433ab1bede2575ef11919ede3ef81c6aae0100af3578daed3c0b90645755f7f33e3dfdba77df862569665773fb31602f6e641236b36358cdbe91ec877c8901f1c76cef4cef4ef77cb7a767d9b5707b315b71f958a2a224c15242454382c1aa40510885b589a40a50cb0809452c51b4ac92029158560952483c9f7b5fbfee99091b3a8954c9243b7ddf7df79c7beff99f736f4ff84fd19bb4f8cd7dd56f08f891f84b7c75fcf5f07bffcbdefed023f90ef181af5f75d55fde95ebfa7378a8af2e628fbc53bee488104754b72b8ee82efef6cec08fec1e11f2cc11af7be60c74f95dfc813ecd1f2afb7dc6be13470204a37e7906f09d719800ed191a28a147760931c2c20bc04ed3f2a82e813032053dea297f4c37964e46b347a78f359766a79b137b84c69e526379b5b93c5d5f5d6db43b4262573cb3d66e37963ad3edc64ca379b2d1161e76fbf5a3cb304460bbcce3a757e7ea575d3d2114e1eef55dc93db951575f7915f7c5b6afdd5c692cce5e3931cedd411ed3c834ac676ea1d37c95f0f1d9eba10cf2b846069044b0e06558eff47ce3b408b0c7ac363ad347179667e667e6eacda5e9957abbbed8e834daabd09c996fccf28e83c5c6e2ccca691112ccf1ef0553c0619722ea95f6f2caf26a63161bb36b3330468c10551d093bcdc506936f3ba2adcf7480a0b9d184aab0d26e2e7556a7177835973812e5765324acc0bdd5ce72bb41ec8ba8abdd38b1d6849efa5a674e94b06b0b2fac79b2b9d0380eab2d63e78bb0b3dd585d5e6bcf34a6179a8bcdceaad8826f5eb2f13ea61ba7c456825c693768d9f54e63fa58a3de596b37444c54e365f3c3f6e6aa7b3bed86cf8a6dcc72785e5e9a9ead77ead3abcd5f69884ba81bf0ce4ee7de3131b6e505727a6679b6215e84fd45d83c9210b74e7c2a43c7daca2c2e0bfbb6f72839076b97fac075e2c5db62b12d8ee3525c50f1a50555b84cca5856f8a720ed87ac14f05314a029dd3f559005053f05cf972bb25cf6b51432b85477657af68288ba22fde747b38f7044faf5959585d3e26523974891caa8f4fa37fcdccfffc22ffef29ba6671bc78a1f7c87bcce13712dfa921646c457c4634618f93a6fffd4d9b3fffdf8fde7eefafbbbbb46c13376c7aff6a1fbc12ffcc6efbef7fed7f77af7b9de0ffeed6745affba7b0fbec571efafa85cfbf2387e3a7ede88f7dfc3f6eebf55e0bbddffae3c73ff7817b3eff700ec77ec271f6f14fbdffc9a95e6fcabd67bf7967bef735dcfba16fffc357efcdcd771d777ff49b5fc80f3e80bd1ffbaf7bdf7377bef7208dbdf03bdfee1b7b887a7fefafbff678bef706c6fb777ff2de2f7c3c37dd4dd4fdd8431ffec45d57f57a6ff6a7de75ff934f7ee99d77f50dbec53782b772e1e3773e216ef5f6a7f4fce4e73ffcf55ffb86884d34f8e3e7863b3ef5ed1910bcb51f412ae22ba3b6d45d059396452ab642572a6aea9df09cc62713d94ad4043c71efb9fd8936a2956e6da52b271231011d00a8d2b863c4f5080e6dd9c2f14642a32a23782df78bb47b10dfa6efba206219191d29114d48d97d29cc319fc09b0955026098a15453a287feec7eecc5695271ad40bcd1aba4eac2d2a4113baa52edd7803f7e7922ca3215555e639aa8ebcad2a86d4294441419110528be3f16f9a0f3c2fe2ee262c55e7d3e8d6ee2857c59cc27720c08074bffa2804d14a81943cbcb5af85ac4af1ca547236bf80a563c1f6f819e2f8bd69888de5f801522396b80017e79899a4fc3d7f2fe1f86fd5367554546014c21de3a6a68e75e5a205a7bd8dbaa6a8702a0f5c6d03af572d05e065d1a0a3a1e0a7afb50d095a1a0770e056d86821e1b0aba3614f4eea1a0c78782de3314f4e450d0fb8682be20fac1e566e02a07ae32f047005c7e6f7039008ec022fab6d4b2abce90dd199f4f34991d9c2b6ec5852951f5d2b88a064d41db0713872f8c6a550385f616701e2e0be34f15ee309e09ae14e25ce24d4d9e3f57f5e15d17d6653c7857f54a6c7f537980d6763f5b5f80dcabc13439d8bd5ae07468ef5b5581b3e3ac1199f244a5064c37a389014d6f7a0df0389feea4a75af108acb1d0daa501d268fcf070226f2f460e341f587d1035b08f60fadf036109e4031e6eff29c1fbd7a080e807a0350576b9021fddbd3a866904a04b3c5cb2268444030fb688deedee7ffbc8bf5f092e40a0173944eb0388f85509d07bdc68def88547d8ed18e0dd38bea7b5c47b983e5eac5e47c3defe17380c0423de95085c440c34a9b5121fdbb00f586112b876a59584b66d34a20516e21362360a7c4252c00fcf14e6e3712014f60310ec054705fc1cbbe710893669b1edd2e3bc731a53039999b30f13ea084f20d871d82185f6849a85263820e2627cf528e329e469640969b1c69726fe84da43a48491f16dc8d2a9334056f916f8f5ab6fd8016b409a099a34e302d20f00771317744ded4c147e989b015ee19b183e985b29812ac35c0579aac00676969013d0b4eba5ed68b061f14484888e54858314d795d171bf22e2992a897f5d59f4e605b93a886a5015805263bc32d742b683da180a29501814e2508003582050920953153f71b13b093f4cf814f9ee73811e717a89f180aac2b2c7784e812b00e5053783dcf54919605a1fc4bf8681c101083a40257015b8b09282d8039e0fe01aa113171690c4f17fa03ab0cab89af869976dc77fa2982279fcf4addee13236e29757c354560bd80bd4e5c8683e09c7607c15744a82e4f8717594853660a1b5321a20e1c74d00b4b61d20c0ae55e396c2f58714baa54f7e8ab42430210b57886188a77887e9e57d8350feab2380074c4611c807c6d478a90130b01d1198139f14baaa52362725326234aa552dd3f67d362325b462912993158bc88a95e01d5831df4468c522d652df59b18f3e4a2b50a68442a61c2c5b311f83d116d9cd08a7050ea015f3322be63b2b66a747ab052870ca9e2123b58465a00655239c2b42aaf1949129e20b833d458486871a3f90509b9c9a17d16e5357dcd37c096a07d2174c28831fa857801eed16bcd9e9646d67e293c501b141d5f2f1650c1f51a65a069e02542d94af4cb546683e4b6c0dc20a7a05180ba8072104c1a4acc41e4681fd3eeb00c5af2688ee545a8179262d30609dab6021c6c8f2a34d15cc864f92b154fb6345a6812de8d7a8d348f6031edb41fa15b0310cd01862bc037cc9a884b1173cc7bd6720a407b4a7076c91585368aa90753401cff8072c8f3abda5456e04cd5b8d82770fcd32da1d1b647bc46bf4bfb02eb490b09b3d532801b53821acc29a410166d04733e86766100408edc3e774cf6f0365c034908ba79d247a9fb88c637a7af4a6ccf97d42a3359caa414b516b1c5a925a93d042704de35e84adfbc016144e5e232e255d8fa1f5626a55a0b51d8160c03522a43e1c1764e3fc6c9c874be0f927efc0a9efe0acf0ec85a7c5ede7a0a3623bbe7ceded1426d0d35397df7e0e5f16cee3e3b7b6e2ab0ab6bf1312500ddadfd5b7d3b2cf9d3bc79a07d3c4cff334152210126d1bb5909097500bc917f7a559723ebd9c0de9f947b2200ca5116c82c9056186844011472116bb5b4bbf6b4d32bec75f40bdf9b4b208ec00e311a41f7ce28127500bd307b0510ddd5810321ae6a58f3ff0c42bc040827683bde9242198e5e0f08e24c4e5800517148b8145ad2cc4bb303492e40dd005a08ca2d7a82c2490ff49544f819a0eab26b9c72c34e04108dea2180f8c6f60734b5e6ec8cea7d0024753951a737270431ece024113cf225921503ee1f50ac47699be617409dd34353ecf510c097e34341e394d9ad29234a429614710e08a7837bc0d39daf351ed04aed6c368881d30600083012b0a7145214282d372ee59a2f991687e607b38130564d8e94167f498d29eb34431e95b8a1b22ef0e53810b535872e0552339381d4ffcf9d45f7bb01a90d79110e7834f0ccfa37fbc1577a3881296e7be5ba0b206024d02a6d0645589b8cf88297d0bc18469f7405959d4a48da556fcba513281840b8247df5293e8e75b6a021961533eca09c8487cab8838c441cf0e9efec632c584269c8f7fc602520465c29be90d587294e788832f8f3f7001881da9047a1affec28f945b0607faa155ab09efd42e31d5ff61c9b3191993191993191376382cd98c8cc98c8cc98f8ff67c6c01fed483f0999e34ee0d54fa69fa0161ba8e8f7fb5d0e57a884e595425ee123f34a5b5e493b85a216f34adac928d3b4bc82d67d60bc9857125d30f10a5bcc2b89ccf49057d8c7bc72e3fc6c9ce7720b8d44d48344d47d44d4fd44d43922ea1c11754644dde3154d133fcfd3548840cc2b6c31afb0c5bc8268257e29c52cc09dfb7e1810fca005042afe110a02bef830e709cca83f93d2735ea497d441f8faa38957b6358702565cc809908de53a437ad63b4ceee8e555c55998e7b2304042cff1e5a316810586219053c9a8972f615ec2193b2760e86d2996f5d25327d2c2c17c6ea5b3b16cb25f2335ac1c97cdcb7549f0452e2dc2e0fd87483640f219c53231de27132c11280acf52066c81698c3f30b783fe1656596ab6a645b8f2d936467b354cc66a6a0ceb85541771dc2721c172e3184529eae033c15d8670d2c1c96780b300970e00d0c2530a3d3359a63cb2d08aa746b9e65248a89c42fb2b7155c516f59e96b6a2674b9f907b6b8c7a359d0f51ac7c989671fba37df5d12b040542bc640a1c31547a5a9c00423d2d4f24aaf3e039644d1ab6200b0daa68d4ceca13651b2451cce773cca72058227785711106c32e62f230d0c60f1ec82c84b0f4c632575d99475ee462fa601ea9369ff837e708c77890d501a4cb5932617c5bd1bd53f56511050eccbd44cf1b7998c24ba0c73542b82404b6abb990dc85a8df568bb4cb3d704f9878746f28cbf469f80951d08994f052a5df852ecdf986c27c43632101f20d4d82abadf01a0dc90617b1315f00da414a50d5bdb5d888d6ad0183f05e062461a279aaffc0148750fc915ab4750ae2230ef50b5cffc488dfa330dc45f45b6d76f090521a4403f4ecac7c4e15ed36feb8c52ada847a234beea10dd5ec3616fb4f8b0105233c28f7fb197a129e1ea3d2c9240a3e29005579b0977324d42d57e8e51283dea56f497c2e51cbbd1a759f6bd858faaed972e72e7d88b15ec5da3499d3a67d4e9bce4a54a71b86346fb0f1272ed0c6a1f36fa4f29801ffd7ce2fe7f5065c1ee877668eb0e816187d8580b4d2606e8249b53818579c437c40c1863c97c36cb6a18add50e5fbdd50c5027faf0df9d68d27eb770579b5ef46c66e643c30a8901b5471f4a96c121260652d601ad199415c2de047a53a42a41a31052c3dc42f21eb0d3bba47cba8dfc7a159264a65d574df5205abcdde40a5d91b23b3e05bb50315dfce1f311f0081fe54587f0a2eb6a16debf8f028ad88bd48bc778ab4d0e0f903f8203063f1a8f52a86e49bb18e51decffd220972f57e0fac7b80f5fe80d688050520f89116566390fcada4805707ba586ef7d285563282188a88b96809bfd24a22b2afa6c819bf971ecad8d7e23a658931682e491e6a9952aa3ae9f947052afe368922927651e97d10ce229d40446684d2f2c844a648af50950b3c8c174d55573a13d35c1ef172bef3af34955b47723904791b4d07817400c8a75a1b39511da1a6a04cb03967af02ae929ca84f4ed447278a35a610a559630d639d130d73859324c0c442b31fd558b37d2d3b502c50e0d1d58d5c418709fd0ed0dd84b6c29368c7108d041dc918a29121459c204286447890099dc010bcef01ee2b3a607de4a1967d07f03ebe2b3306c5c57560487990213e533a807d4596da452aaf61593fa25748ed11c79002d599606b052415e851c444c07584ae1424b874a2f93c8b0b43db498b49e9e3eb49441082b165e5c43f0c7411b8a97b25ab5e4ca03926f090c82e2cc03a786235c7b31f308b6b155a4ea90497fa6d0933f106a387ec5cc9fa6fc74a908930c53c8c2a9810385c8bfa4f12e5cf03c7fc350c27f07887aa753af5d61294a010e209f6cf28841aab7d9ec3a3b1ed7720bcd06415500ed0108160a1448c60e0d7db4da5b5f9be702f552f178210858abc85002b6a8129ce5723d603b06f10c348148e80d959c40e143b3eb1915464e4e846422f9ed8082b4f396992287a5bb87c285136b73279b79050da32e85616db2d5955b4831208db93b6078935e74607a6cca31811e1468be0c32a126b2660ad68a73924a528aa5cd2364cc24b4920c3650cab0a1c56791c568d70a9142660e10cd8ee05ce6e85fd760fd79bf0d1295d5020bbb795ed9ec8d93de9e0c9d4fa4ecd0ad6eef9836aa699e0b24ac55ddad0161b086ee5235eaae386561b4d9915a38c2b29e31b5b81fcaca47890f7af32d5c3c85b5bed188cddbcaac7aeda1d7af36da9090e55b14e4d38a4b39a7e967a687cd3673565de6a9281cbac261eb8f5520f0fad26397f14504d6567b69a9435f8f7c547a2b7490ad406cf8a9f8fc8d606a5607a6e748e15da3fe18eed36b97676fd600aa3f83ce4e2ae9d6d048da1c8c55d3b7b76d0f150d0db8782ae0c05bd7328683314f4d850d0b5a1a0770f053d3e14f49ea1a0278782de3714f4ba6b679b815fe4b5b3cd347ca36b67effe41298242fa42168c1cc23b3ddfa388c91ef43db58931dd7039fec0727c5c8ecfc6748a522577d5258dab7cc32e8b42b28b6678bd80f0f65f72f1f1b24a684dafcf67c8788e87910ecaef88bddc52a4c8f531bedcc24b547cbba488975b0213d1e596802eb7143181802ccf0478b9852f0461eadb77b965c4149190230ed62d7f04cde408ee0ba75d77b945b9cb2dca5dd183e0091e06afe8156d1e89130548099e8f33560eb4f397d52cd106aeac697b65adc22dbeb246451240107285e45f8026052e91f87c8d4ceed56fe4eac82d994f8b5bf1647605ebd35a7bee44e5074612fa448078ef5bde8779de17f2bc0f99f7851cefc38be1bd6f42e4bdef60dd827daa2058de8717c5fb7003de8719ef7de63dcf373cefedf5d56b324e3e21b5ee3fbce7545373b40671dee4e2837c0c2ee8ce027f65c0f878c0eedb0376778f80ef29daf0b5a6c693e0414a08368771170f037730af28a5a300173a21bafdcec32eba654329d0ce12a8072e022b152eb78b29b7db4aa7f3fcfd81ef72c15b0f6c8ebf0d40970f38d9a34c48df8717d340bc60912063b742f21276b8ceddbe466ccd5d2611eb2eb078587e5128d43267d1a54d00037b37974278266f4044cae10c37c6e90f04819c6cf81855de36eaee2b0478af84614a7952445f9254b450b9fd733dc2c8ab55ccf91fefdac35d7b9bef5a6fb46bcd3e70fdae75ffae6397cf795cc87118f5c618e58053b67b8edf383ac0e4cb5820289562d1a8e22d8f4dfb4f2ae9721a2a10d3b58f3469253675acb5ae56620a5a5788b1549ed841557881b22b7a62c1174adccd989aaad1704877e54994f3133b122aaee3a9bea059a3bb74ef22f57a2944f57a01a5d0c961c872286d51e86a35ce670ecf38fd968b997ecbe6d33b6d0fd13678eb553e049d0695ffc82399ca871844fa7d1993975de35faf2c03e1184d1ca2990839191e3413b39019f6648502332ca3a0c9c2e3182f17c3addfaf624f36b0590a01395d4dab34e70b32c903f205980504bcda1f186f824a0fa0b297cd20138a7f6934fa9ff566891502b27d980a1449a3223d58b59503fd02d929bb021f5700bf4e24c10eaabf5379829c9809765049c7ce65fc9b369c6b83d3c79c5556f4ff28d90fb6343407a46ac03abc50f7217be46de84b171cae507d767beecb17aac4a75e5ebbc75b5751d838dbe12b851bd9562fae8ff69b6a3ab2a55bc75cfdb1676a826eca93553cc48720028b531ed5015d15b9d2a2d7ecbef3c5298fab4eaa2aec8977c4851e4a86e85e229daf494a3df6ba3a5dcc9721f11805fa4a94abbcef511bf1d1f1c9af736dde5be7e65ec8dabc3de00ed1a2afafcd1fb587db7db579cc5a2617b0366fa9ea2355b5bdcc9f04842f57f2c3d760ce06a86a6bac1a565670c1505636f7736573c73aa3e399d13e9109364a5103a4ef1e62e3ba2c154f481163bf4d8deef165a9bfa4bebea83eb9d82baa8f73515d71519d423a8afe5556905659415a650569c505696ef597dc273728b9e74aedc4192ab5ffd16f7f66ece6b24cef85cf5ca51d47e8f4b73e73ef25786e8ffc919d0debec12a57972a1575b073edada3a72b4575bffbe36859471adc9beca7b7601da55dfe9db351b15a3e92b2aae2a2fa92a4f86bc5795c7831a5b8607a4457c4d7257ea3bd121b7ec67673aefb69247d5e31297d523ba5f0b83ded27aa514af06b78d9e02907301daad8e234fb7487bbb96966593cfdcf192e78e9748766de55cf1d99218d400c9fb41ecf6f26e94fbca044ba7bbe8f08ffcb509ed8e5b87b315e17a5ba1d95668b615e1a0ad40bbc0f73ff83273abe74fc283832a18f2177d06bc5638e896ed973ee0d1f2acd6a292f92099f8b2bab076759cbfd2816762b0273e8ba18b4d9c568aecca79dc7b2ef0c11a3f97e876094c141f472a8fb30db881eeebec121662b319c4c00c6260067a7e6e919d92b24be8943d52b2e8acccf6d0b9f70e9d7b76e8dcf36e7cdecd67337c771f708fed556346a1f6daab5a22fa578f0f202e889c43478d566476f9798b12d29eacf117b248150a54b2c80260757003c7eebb4b1903919bad3df4894876d43bd9a22bc9eb44845c2f114f66224271463fd9a9ab5f50a8ab5f56ecb51c2b2e8a1d4a2e84d9fd7c6d1750edcac7bc1be1f1d6e161e3895f734c543c37faec684612415f5d752236d647bdbc94e5a8a7d6534fada75e4fdcdcf73f06e2a00b54cc791f7e4b2677c919495d2161c46fc99230d3354196b81a9e9543c897e879bedc81d8993bca9dfbaedc5c56b68857bc9e4c307e7506c50088d7e678067d19fe298800bc21c5e8fc0d870006d86f5980f9c7bb203e594f04c6afecd0d7dbe86a9b87bae095e996007b230e8d21f53e419131520e67c238dc1d1b8b091bafbbf819dec5afb047bb99b1cbeb6d46b8e7043fdfa6917c84cce48c3964a60d6f77cec7d8741362db1f07c7f4355fd35fd8884e1fab37171ab3a6b36cea0b0bcb33f54ec3acd48f3756c5da52e3d44a63a6032f1bedf672db3497ccb1e6a9c6ecf4d1d39dc6aa99595e5aedb4d7663acb6dd1996b98e5251ec67feec6ccd49796963be668035a0b38c36cb30dd8164e0bf793beed61113de9d927fcf33880af515f34f54ea7b1b8d2e155bdb9dda425ad760cced2589a154561ff0c0fe08495764cf6d778ae3162a5bddc599e595e306e4c73d5e0427a7fb187ff0096827ffad9cd2b70ad3efc0be0dff2d1166c0707ac32000c6fd78118d3d8e63981628b6b0b9de6747369b6714a307df06f0235978e67e385a5d40cbc801919ef2ac276ea4717a0e398a9c3fbb9461b49de69c346d64fbeb83cdb3c767a9379ed0476cc454dc07f75a8edb83833575f3a0ed4683717ebedd366be71dabc79aeb16451e26eea4b16f1a604c57df7d3f378a3234408b42cc0bf11f857847f91c03fc5224473e9647da1396b4ed6dbcdfa12ee09b7f26c84126c6de47d25015cff0b0d35746300d00700008301010100202726ccb75fbe7efc448c24c0eba863d9358e0f49b7fb26a3e3652dcd3d9486892394b47ebd8648bf66200b820be0bbd29fc0357dc376a934531fa11a965d1e590100e70778da8d56bd8f1b45141f7ffbece4741c84e3522011441309c839c13a451447414341c147758465bcf3ec1d7977769999b5cf5053040a90e88284228420c55597263a2822b7484804d20005d08184f8177833bbde5def71e6a6d88ff7debc99f7deeffd665abf77dfae918f5f7cea1f82a3621ee4af2b6f9ad73df768be24b8f377aff7e9e6427273731d42c5c3ebd7e9803fbff35cafb235f04377ec283e125c8c1c1a6b2f945ccf2e4da8e45468e714bd33b9b2dd46278e479547aa8d702a40d6050da06e245dd70377ace2a0f742bf4d5dcd275403a9ac0f81ea5882c3f808942e5aad65ae497d4d7b129417faac1973a1aff6ea6398a973f870a6c0479edebf814edd30165a6d472003ae3028e1f830013f33694c29d7eabc7966b28ba78743aa6758f6829def7a940b27a212c3d5201579e4d1801ea4a912a09d58d1115827fd6b17359523d065ad13b93a5d66db4cd6920a653285712cbbb8da7b7240154e00b9d26a23f7ed034ce92c953fed8642c3817686124ce6954d5c6127220ece6aca40a4a68588dd285edac872c4997655c46517db01267895c113650f3e1f82e641963206439012181a1d3870107149addd940b164e53ab0b652f0cfc2c6f8f1b25173e17e0a46ac5df5b2cb07552cb20d29e55eff46d7a727815555d970a177cbb14a9ae277f0b346e94e1dc34fbe7acd82a1d9c8ab833e6a4da4a1bc1f65e279f9dfce7d825d51afe74a278e073d7c1cf66224f37d5c61d8cadc7fa92c7ba1b3248bef42c4abeba12de8db9840016cb0a845b3209a7bb129b3c4cc9207b240c917779d3b202e48256887a194aec43058221d8396bd9cdf576db28d1a6928dc14c833a912424207497ae594ec2d6290c41aa05cb93795f4acf66244316bbb8a99ca6aae7336116651ece69c47a0e93b72044b692115bc6d254a4521fca30b0fe9b0a34b26ea9ec35142599e918bdcf0324be5221d790ac1c6bd3b0b4d4314d9dc4980a4c8b15052df465aa5f72d49c0406090d9398ddb549304102c4ac25ff162ec956b08ada52a4229566f2f1dfec69d689249f94826a71e51871e2b86d8d4286ceda0a93c4621f1e3b5994fd1b9d586450aefd2f943b71c468da4af595ad644258e0bd6eecf352770b470ca9b6ed9f0237258a65206de0417cf9333c94f353917c7bf3f2cb1f7cf5d2122f90bdef7ebe73f8c52bc55ec7f1ecb8f6f54779af92bdcf6f5d62f76e151b10c76f0fde7ff865d64f9fbcd1ff05d8e1f132f270dc86e0f03843d9e2fab0801821dfbf7aec1dcd8b90c2b1f7cc8747f30c1a84dc7ff0d6eb77e78562e378e79bfdbbf3acac38767f40415e4213dcfd3ffff8a9582b1bb0ffdac36245cc4de636fdf1d79d1aef5f2324bf6fe0ade70c1794caaad39efc0b56a6590f0001 +DMLOG FEATURE_OP ACTIVATE 18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390 {"feature_digest":"18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390","subjective_restrictions":{"enabled":true,"preactivation_required":true,"earliest_allowed_activation_time":"1970-01-01T00:00:00.000"},"description_digest":"bd496b9e85ce61dcddeee4576ea185add87844238da992a9ee6df2a2bdb357c2","dependencies":["299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707","63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a","68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428","c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"],"protocol_feature_type":"builtin","specification":[{"name":"builtin_feature_codename","value":"INSTANT_FINALITY"}]} +DMLOG CREATION_OP ROOT 0 +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":57599,"consumed":1},"cpu_usage":{"last_ordinal":1262304003,"value_ex":279534,"consumed":101},"ram_usage":180802} +DMLOG TRX_OP CREATE onblock c07a5b477b20eff04cb92a9c03a0d27ce7f972cdfd8f8a0fd3feb47b5e657600 0000000000000000000000000000010000000000ea305500000000221acfa4010000000000ea305500000000a8ed323274023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e80f70f21e77b7c902392950756201912a2d1f719663cae19edcb4cc1b12fe7bdd82aeb196b752844f91a8052ae8f5e5ff87c0af0c6f0b7b2ebf387fc81062e95f000000000000000000 +DMLOG APPLIED_TRANSACTION 4 c07a5b477b20eff04cb92a9c03a0d27ce7f972cdfd8f8a0fd3feb47b5e65760004000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b01006400000000000000000000000000000000000000000001010000010000000000ea305508e89aec91f1c856b4422cfa0de8a86078a690825cb5dbf3e51276c24be13d591b000000000000001b00000000000000010000000000ea30551b0000000000000001010000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed323274023b3d4b0000000000ea305500000000000213588be25132b4167ced6df22b5439e376d5a20284190bb94a43e3e80f70f21e77b7c902392950756201912a2d1f719663cae19edcb4cc1b12fe7bdd82aeb196b752844f91a8052ae8f5e5ff87c0af0c6f0b7b2ebf387fc81062e95f00000000000000000000000000000000c07a5b477b20eff04cb92a9c03a0d27ce7f972cdfd8f8a0fd3feb47b5e65760004000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b0000000000000000 +DMLOG CREATION_OP ROOT 0 +DMLOG RAM_OP 0 eosio code update setcode eosio 452902 272100 +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":154127,"consumed":16681},"cpu_usage":{"last_ordinal":1262304003,"value_ex":291109,"consumed":2101},"ram_usage":452902} +DMLOG APPLIED_TRANSACTION 4 1f5db111b030c2b86ec1d0f7de9234cbb55e88518781e1cf20919489cd2457b804000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b0100d0070000a510000000000000000028410000000000000001010000010000000000ea3055f0d4bd6bf1b62dc0158b37a63d486b34ba1162c7a1e1b612d8d51c675d91e53a1c000000000000001c00000000000000010000000000ea30551c0000000000000002010000000000ea30550000000000ea305500000040258ab2c2010000000000ea305500000000a8ed3232c8df020000000000ea30550000bbdf020061736d0100000001d8012060000060027f7f0060037f7f7f0060037f7f7f017f60047f7f7f7f017f60067f7f7f7f7f7f017f60077f7f7f7f7f7f7f017f60047e7e7e7e017f6000017e60047f7e7e7f0060057f7f7f7f7f017f60027f7f017f60027f7f017e60057f7f7f7f7f0060067e7e7e7e7f7f017f60017e0060027e7f0060047e7e7e7e0060037e7f7f017e60017f0060017f017f6000017f60027f7e0060047f7e7f7f0060037e7e7e0060087f7f7f7f7f7f7f7f0060077f7f7f7f7f7f7f0060017d017d60067f7f7f7f7f7f0060037f7e7f0060047f7f7f7f0060027e7e000280072c03656e760561626f7274000003656e760c656f73696f5f617373657274000103656e76066d656d736574000303656e76076d656d6d6f7665000303656e76066d656d637079000303656e76087072696e74735f6c000103656e760a626c735f66705f6d6f64000403656e760a626c735f67325f6d6170000403656e760a626c735f67325f616464000503656e760b626c735f70616972696e67000603656e760b64625f66696e645f693634000703656e761063757272656e745f7265636569766572000803656e760d6173736572745f736861323536000203656e760b6173736572745f73686131000203656e760d6173736572745f736861353132000203656e76106173736572745f726970656d64313630000203656e7606736861323536000203656e76095f5f6173686c746933000903656e760473686131000203656e7606736861353132000203656e7609726970656d64313630000203656e760b7265636f7665725f6b6579000a03656e76207365745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000103656e76206765745f626c6f636b636861696e5f706172616d65746572735f7061636b6564000b03656e76167365745f70726f706f7365645f70726f647563657273000c03656e760c63757272656e745f74696d65000803656e76146765745f6163746976655f70726f647563657273000b03656e76126173736572745f7265636f7665725f6b6579000d03656e760c64625f73746f72655f693634000e03656e760c726571756972655f61757468000f03656e760e7365745f66696e616c697a657273000103656e760e7365745f70726976696c65676564001003656e76137365745f7265736f757263655f6c696d697473001103656e76197365745f70726f706f7365645f70726f6475636572735f6578001203656e761370726561637469766174655f66656174757265001303656e76067072696e7473001303656e761469735f666561747572655f616374697661746564001403656e7610616374696f6e5f646174615f73697a65001503656e7610726561645f616374696f6e5f64617461000b03656e7611656f73696f5f6173736572745f636f6465001603656e7614656f73696f5f6173736572745f6d657373616765000203656e760a64625f6765745f693634000303656e760d64625f7570646174655f693634001703656e76087072696e746865780001037a79001814140b1300141313131303030b0b130b0a191a01030b0104030301130f130b02021b14020013001300130013001300131c1313021d0b020b1e01010201020101010113011f1f1f1f1f1f1f0b011f1f1f1f1f0b01011f0b1f0b1f1f1f0b0b0b0b000b0b0101010b010101010101020b010b020202020b010405017001131305030100010616037f014180c0000b7f0041a2d8000b7f0041a2d8000b070901056170706c79002d0924010041010b12535557595b5d910192019301950196019701980199019a019f01a001a1010ac1be0279100010321052105410561058105a105c0bf903002000104a102c20002001510440428080f9d4a98499dc9a7f200251044020002001107205428080add68d959ba955200251044020002001107305428080add68d95abd1ca0020025104402000200110740542808080e8b2edc0d38b7f200251044020002001107505428080add68db8baf1542002510440200020011076054280f8a6d4d2a8a1d3c1002002510440200020011077054280808080d4c4a2d942200251044020002001107805428080808080f798d942200251044020002001107b054280808080aefadeeaa47f200251044020002001107c054280808080b6f7d6d942200251044020002001107d05428080b8f6a4979ad942200251044020002001107e0542808080c093fad6d942200251044020002001107f0542f0aadf8bcde9add942200251044020002001108301054280808096cdebd4d942200251044020002001108501054280808080daac9bd6ba7f2002510440200020011087010542808080d0b2b3bb9932200251044020002001108801054290a9d9d9dd8c99d6ba7f200251044020002001108901052000428080808080c0ba98d500520440410042808080d9d3b3ed82ef0010270b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b05428080808080c0ba98d50020015104404280808080aefadeeaa47f2002510440410042818080d9d3b3ed82ef0010270b0b0b410010370bb40101037f200021010240024002402000410371450d00024020002d00000d00200020006b0f0b200041016a210103402001410371450d0120012d00002102200141016a220321012002450d020c000b0b2001417c6a21010340200141046a22012802002202417f73200241fffdfb776a7141808182847871450d000b0240200241ff01710d00200120006b0f0b034020012d00012102200141016a2203210120020d000c020b0b2003417f6a21030b200320006b0b7201037f024020000d0041000f0b4100410028028c40200041107622016a220236028c404100410028028440220320006a410f6a4170712200360284400240200241107420004b0d004100200241016a36028c40200141016a21010b024020014000417f470d0041004190c00010010b20030b8a0101037f0240200120006c22010d0041000f0b4100410028028c40200141107622026a220336028c404100410028028440220020016a410f6a4170712204360284400240200341107420044b0d004100200341016a36028c40200241016a21020b024020024000417f470d0041004190c00010010b024020000d0041000f0b20004100200110021a20000b02000b3601017f230041106b2200410036020c4100200028020c280200410f6a417071220036028040410020003602844041003f0036028c400b3301027f2000410120001b2101024003402001102f22000d01410021004100280280412202450d0120021100000c000b0b20000b0600200010310b0900200041013602000b0900200041003602000b02000ba10101037f4184c10010350240410028028c4122030d004194c100210341004194c10036028c410b0240024041002802904122044120470d0002404184024101103022030d00417f21050c020b410021042003410028028c413602004100200336028c4141004100360290410b410021054100200441016a36029041200320044102746a22034184016a2001360200200341046a20003602000b4184c100103620050b4901037f4100210302402002450d000240034020002d0000220420012d00002205470d01200141016a2101200041016a21002002417f6a22020d000c020b0b200420056b21030b20030bf90101027f0240200041ffc1d72f4b0d0020012000103b0f0b200020004180c2d72f6e22024180c2d72f6c6b210302400240200041ff93ebdc034b0d002001200241306a3a0000410121000c010b410221002001200241017441a0c3006a410210041a0b200120006a220020034190ce006e220141ffff037141e4006e220241017441a0c3006a410210041a200041026a2001200241e4006c6b41017441feff037141a0c3006a410210041a200041046a200320014190ce006c6b220141ffff037141e4006e220341017441a0c3006a410210041a200041066a2001200341e4006c6b41017441feff037141a0c3006a410210041a200041086a0bda0301027f02402001418fce004b0d000240200141e3004b0d000240200141094b0d002000200141306a3a0000200041016a0f0b2000200141017441a0c3006a410210041a200041026a0f0b200141ffff0371220241e4006e21030240200141e7074b0d002000200341306a3a0000200041016a200241e4007041017441a0c3006a410210041a200041036a0f0b2000200341017441a0c3006a410210041a200041026a2001200341e4006c6b41017441feff037141a0c3006a410210041a200041046a0f0b20014190ce006e210302400240200141bf843d4b0d0002402001419f8d064b0d002000200341306a3a0000410121020c020b410221022000200341017441a0c3006a410210041a0c010b0240200141fface2044b0d002000200341ffff037141e4006e220241306a3a0000200041016a2003200241e4006c6b41017441feff037141a0c3006a410210041a410321020c010b2000200141c0843d6e41017441a0c3006a410210041a200041026a200341e4007041017441a0c3006a410210041a410421020b200020026a2200200120034190ce006c6b220141ffff037141e4006e220341017441a0c3006a410210041a200041026a2001200341e4006c6b41017441feff037141a0c3006a410210041a200041046a0b05001000000bbb0101037f20004200370200200041086a22024100360200024020012d00004101710d00200020012902003702002002200141086a28020036020020000f0b02402001280204220241704f0d00200128020821030240024002402002410b490d00200241106a4170712204103321012000200236020420002004410172360200200020013602080c010b200020024101743a0000200041016a21012002450d010b20012003200210041a0b200120026a41003a000020000f0b1000000bc50101047f20004200370200200041086a41003602000240200128020420012d00002205410176200541017122061b22052002490d00200520026b2205200320052003491b220341704f0d00200128020821070240024002402003410b490d00200341106a4170712208103321052000200336020420002008410172360200200020053602080c010b200020034101743a0000200041016a21052003450d010b20052007200141016a20061b20026a200310041a0b200520036a41003a000020000f0b1000000bf80101037f0240416e20016b2002490d000240024020002d0000410171450d00200028020821080c010b200041016a21080b416f21090240200141e6ffffff074b0d00410b21092001410174220a200220016a22022002200a491b2202410b490d00200241106a41707121090b20091033210202402004450d0020022008200410041a0b02402006450d00200220046a2007200610041a0b0240200320056b220320046b2207450d00200220046a20066a200820046a20056a200710041a0b02402001410a460d00200810340b200020023602082000200320066a220436020420002009410172360200200220046a41003a00000f0b1000000bcc0101037f0240416f20016b2002490d000240024020002d0000410171450d00200028020821070c010b200041016a21070b416f21080240200141e6ffffff074b0d00410b210820014101742209200220016a220220022009491b2202410b490d00200241106a41707121080b20081033210202402004450d0020022007200410041a0b0240200320056b20046b2203450d00200220046a20066a200720046a20056a200310041a0b02402001410a460d00200710340b20002002360208200020084101723602000f0b1000000bd80201077f0240200141704f0d000240024020002d00002202410171450d0020002802002202417e71417f6a2103200028020421040c010b20024101762104410a21030b410a2105024020042001200420014b1b2201410b490d00200141106a417071417f6a21050b024020052003460d00024002402005410a470d0041012103200041016a210620002802082107410021080c010b200541016a103321060240200520034b0d002006450d020b024020002d00002202410171450d002000280208210741012103410121080c010b41012108200041016a2107410021030b024002402002410171450d00200028020421010c010b200241fe017141017621010b0240200141016a22022001490d0020062007200210041a0b02402003450d00200710340b02402008450d0020002006360208200020043602042000200541016a4101723602000f0b200020044101743a00000b0f0b1000000bc80101037f0240024020002d000022034101712204450d002000280200417e71417f6a2105200028020421030c010b20034101762103410a21050b02400240200520036b2002490d002002450d01024002402004450d00200028020821050c010b200041016a21050b200520036a2001200210041a200320026a21020240024020002d0000410171450d00200020023602040c010b200020024101743a00000b200520026a41003a000020000f0b20002005200320026a20056b20032003410020022001103f0b20000bce0101047f2001102e21020240024020002d000022034101712204450d002000280200417e71417f6a2105200028020421030c010b20034101762103410a21050b02400240200520036b2002490d002002450d01024002402004450d00200028020821050c010b200041016a21050b200520036a2001200210041a200320026a21020240024020002d0000410171450d00200020023602040c010b200020024101743a00000b200520026a41003a000020000f0b20002005200320026a20056b20032003410020022001103f0b20000ba70101037f0240024020002d0000220241017122030d0020024101762102410a21040c010b2000280200417e71417f6a2104200028020421020b024002400240024020022004470d002000200441012004200441004100104020002d0000410171450d010c020b20030d010b2000200241017441026a3a0000200041016a21000c010b2000200241016a360204200028020821000b200020026a220041003a0001200020013a00000b960201047f0240024020002d000022044101712205450d00200028020421040c010b200441017621040b024020042001490d00410a210602402005450d002000280200417e71417f6a21060b02400240200620046b2003490d002003450d01024002402005450d00200028020821060c010b200041016a21060b0240200420016b2207450d00200620016a220520036a2005200710031a200220036a2002200620046a20024b1b2002200520024d1b21020b200620016a2002200310031a200420036a21040240024020002d0000410171450d00200020043602040c010b200020044101743a00000b200620046a41003a000020000f0b20002006200420036a20066b20042001410020032002103f0b20000f0b1000000b0e002000200120022002102e10450bc20101047f0240024020002d000022034101712204450d00200028020421050c010b200341017621050b024020052001490d0002402002450d00024002402004450d00200028020821060c010b200041016a21060b0240200520016b22042004200220042002491b22026b2204450d00200620016a2201200120026a200410031a20002d000021030b200520026b2102024002402003410171450d00200020023602040c010b200020024101743a00000b200620026a41003a00000b20000f0b1000000bc70101047f230041106b220224002001200241056a103a2103200041086a41003602002000420037020002402003200241056a6b220441704f0d00024002402004410a4b0d00200020044101743a0000200041016a21010c010b200441106a4170712205103321012000200436020420002005410172360200200020013602080b0240200241056a2003460d00200241056a21000340200120002d00003a0000200141016a21012003200041016a2200470d000b0b200141003a0000200241106a24000f0b1000000b05001000000b0a00410020003703e8440b4e01017f230041e0006b220124002001200141d8006a3602082001200141106a3602042001200141106a36020020012000104c1a200141106a200128020420012802006b1016200141e0006a24000ba20801027f02402000280208200028020422026b41074a0d00410041f0c4001001200028020421020b20022001410810041a2000200028020441086a2202360204200141086a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a22023602042001410c6a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141106a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141146a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141186a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a22023602042001411c6a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141206a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141246a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141286a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a22023602042001412c6a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141306a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141346a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141386a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a22023602042001413c6a21030240200028020820026b41034a0d00410041f0c4001001200028020421020b20022003410410041a2000200028020441046a2202360204200141c0006a21030240200028020820026b41014a0d00410041f0c4001001200028020421020b20022003410210041a2000200028020441026a2202360204200141c2006a21010240200028020820026b41014a0d00410041f0c4001001200028020421020b20022001410210041a2000200028020441026a36020420000bfa0103017f027e017f230041306b2203240020012002200341106a1010420021044110210141002102420021050340200341106a20026a21060240024020014102490d002005420886200420063100008422044238888421052001417f6a2101200442088621040c010b024020014101460d00410041a9c00010010b200020053703082000200420063100008437030041102101200041106a210042002104420021050b200241016a22024120470d000b024020014110460d00024020014102490d00200320042005200141037441786a1011200341086a2903002105200329030021040b20002004370300200020053703080b200341306a24000bfa0103017f027e017f230041306b2203240020012002200341106a1014420021044110210141002102420021050340200341106a20026a21060240024020014102490d002005420886200420063100008422044238888421052001417f6a2101200442088621040c010b024020014101460d00410041a9c00010010b200020053703082000200420063100008437030041102101200041106a210042002104420021050b200241016a22024114470d000b024020014110460d00024020014102490d00200320042005200141037441786a1011200341086a2903002105200329030021040b20002004370300200020053703080b200341306a24000ba50101047f230041106b210102402000bc220241177641ff017141817f6a220341164a0d000240024020034100480d0041ffffff032003762204200271450d0220012000430000807b9238020c200441002002417f4a1b20026a418080807c2003757121020c010b20012000430000807b923802080240200241004e0d0041808080807821020c010b41808080fc032002200241ffffffff07711b21020b2002be21000b20000bd60e01067f02400240200041d3014b0d004130210141b0c500210203402002200141017622034102746a220441046a2002200428020020004922041b210220012003417f736a200320041b22010d000b200228020021020c010b02402000417c4f0d002000200041d2016e220541d2016c22066b21004130210141f0c600210203402002200141017622034102746a220441046a2002200428020020004922041b210220012003417f736a200320041b22010d000b200241f0c6006b41027521000340200041027441f0c6006a28020020066a210241142103024003402002200341b0c5006a28020022016e22042001490d042002200420016c460d01200341046a220341bc01470d000b41d30121010340200220016e22032001490d042002200320016c460d0120022001410a6a22036e22042003490d042002200420036c460d0120022001410c6a22046e2206200341026a2203490d042002200620046c460d012002200141106a22046e2206200341046a2203490d042002200620046c460d012002200141126a22046e2206200341026a2203490d042002200620046c460d012002200141166a22046e2206200341046a2203490d042002200620046c460d0120022001411c6a22046e2206200341066a2203490d042002200620046c460d0120022001411e6a22046e2206200341026a2203490d042002200620046c460d012002200141246a22046e2206200341066a2203490d042002200620046c460d012002200141286a22046e2206200341046a2203490d042002200620046c460d0120022001412a6a22046e2206200341026a2203490d042002200620046c460d0120022001412e6a22046e2206200341046a2203490d042002200620046c460d012002200141346a22046e2206200341066a2203490d042002200620046c460d0120022001413a6a22046e2206200341066a2203490d042002200620046c460d0120022001413c6a22046e2206200341026a2203490d042002200620046c460d012002200141c2006a22046e2206200341066a2203490d042002200620046c460d012002200141c6006a22046e2206200341046a2203490d042002200620046c460d012002200141c8006a22046e2206200341026a2203490d042002200620046c460d012002200141ce006a22046e2206200341066a2203490d042002200620046c460d012002200141d2006a22046e2206200341046a2203490d042002200620046c460d012002200141d8006a22046e2206200341066a2203490d042002200620046c460d012002200141e0006a22046e2206200341086a2203490d042002200620046c460d012002200141e4006a22046e2206200341046a2203490d042002200620046c460d012002200141e6006a22046e2206200341026a2203490d042002200620046c460d012002200141ea006a22046e2206200341046a2203490d042002200620046c460d012002200141ec006a22046e2206200341026a2203490d042002200620046c460d012002200141f0006a22046e2206200341046a2203490d042002200620046c460d012002200141f8006a22046e2206200341086a2203490d042002200620046c460d012002200141fe006a22046e2206200341066a2203490d042002200620046c460d01200220014182016a22046e2206200341046a2203490d042002200620046c460d01200220014188016a22046e2206200341066a2203490d042002200620046c460d0120022001418a016a22046e2206200341026a2203490d042002200620046c460d0120022001418e016a22046e2206200341046a2203490d042002200620046c460d01200220014194016a22046e2206200341066a2203490d042002200620046c460d01200220014196016a22046e2206200341026a2203490d042002200620046c460d0120022001419c016a22046e2206200341066a2203490d042002200620046c460d012002200141a2016a22046e2206200341066a2203490d042002200620046c460d012002200141a6016a22046e2206200341046a2203490d042002200620046c460d012002200141a8016a22046e2206200341026a2203490d042002200620046c460d012002200141ac016a22046e2206200341046a2203490d042002200620046c460d012002200141b2016a22046e2206200341066a2203490d042002200620046c460d012002200141b4016a22046e2206200341026a2203490d042002200620046c460d012002200141ba016a22046e2206200341066a2203490d042002200620046c460d012002200141be016a22046e2206200341046a2203490d042002200620046c460d012002200141c0016a22046e2206200341026a2203490d042002200620046c460d012002200141c4016a22046e2206200341046a2203490d042002200620046c460d012002200141c6016a22046e2206200341026a2203490d042002200620046c460d012002200141d0016a22046e22062003410a6a2201490d04200141026a21012002200620046c470d000b0b4100200041016a2202200241304622021b2100200520026a220541d2016c21060c000b0b1000000b20020bb70701067f230041206b220324000240024002400240200128020422040d0020004100360208200042003702000c010b02402002450d00200341186a410036020020034200370310200441704f0d0220012802002102024002402004410b490d00200441106a417071220510332101200320043602142003200541017236021020032001360218200341106a21060c010b200320044101743a0010200341106a4101722101200341106a21060b20012002200410041a200120046a41003a00002003280218200641016a220720032d0010220541017122041b22012003280214200541017620041b22046a2102024002402004450d00034020012d0000410a460d01200141016a21012004417f6a22040d000c020b0b024020012002460d00200141016a22042002460d000340024020042d00002205410a460d00200120053a0000200141016a21010b2002200441016a2204470d000b20032d001021050b200121020b024002402005410171450d002003280218220120032802146a21040c010b2006200541fe01714101766a41016a2104200721010b200341106a200220016b200420026b10471a2003200328021420032d00102201410176200141017122011b36020c20032003280218200720011b36020820032003290308370300200020034100105120032d0010410171450d01200328021810340c010b2003410036021820034200370310200341106a200441027641036c104120012802002107410021010340200141016a20044f0d030240200720016a220241016a2d000041b0c8006a2d0000220541c000470d00410041d5c00010010b024020022d000041b0c8006a2d0000220641c000470d00410041d5c00010010b200341106a200641027420054104764103717241187441187510440240200141026a20044f0d000240200241026a2d0000220841526a2206410f4b0d0020060e1001000000000000000000000000000001010b0240200841b0c8006a2d0000220641c000470d00410041d5c00010010b200341106a2006410276410f712005410474724118744118751044200141036a20044f0d000240200241036a2d0000220541526a2202410f4b0d0020020e1001000000000000000000000000000001010b200641067421020240200541b0c8006a2d0000220541c000470d00410041d5c00010010b200341106a200520026a41187441187510440b200141046a22012004490d000b20002003290310370200200041086a200341106a41086a2802003602000b200341206a24000f0b200341106a103c000b410041b0ca0010011000000bb30101037f0240024041002d00d84a4101710d00410042003702cc4a410041003602d44a41f6c000102e220041704f0d010240024002402000410b490d00200041106a417071220110332102410020003602d04a410020014101723602cc4a410020023602d44a0c010b410020004101743a00cc4a41cdca0021022000450d010b200241f6c000200010041a0b200220006a41003a0000410141004180c00010381a410041013602d84a0b0f0b41ccca00103c000b1900024041002d00cc4a410171450d0041002802d44a10340b0bb30101037f0240024041002d00e84a4101710d00410042003702dc4a410041003602e44a419bc500102e220041704f0d010240024002402000410b490d00200041106a417071220110332102410020003602e04a410020014101723602dc4a410020023602e44a0c010b410020004101743a00dc4a41ddca0021022000450d010b2002419bc500200010041a0b200220006a41003a0000410241004180c00010381a410041013602e84a0b0f0b41dcca00103c000b1900024041002d00dc4a410171450d0041002802e44a10340b0bb30101037f0240024041002d00f84a4101710d00410042003702ec4a410041003602f44a41fcca00102e220041704f0d010240024002402000410b490d00200041106a417071220110332102410020003602f04a410020014101723602ec4a410020023602f44a0c010b410020004101743a00ec4a41edca0021022000450d010b200241fcca00200010041a0b200220006a41003a0000410341004180c00010381a410041013602f84a0b0f0b41ecca00103c000b1900024041002d00ec4a410171450d0041002802f44a10340b0bb30101037f0240024041002d00b44b4101710d00410042003702a84b410041003602b04b41b8cb00102e220041704f0d010240024002402000410b490d00200041106a417071220110332102410020003602ac4b410020014101723602a84b410020023602b04b0c010b410020004101743a00a84b41a9cb0021022000450d010b200241b8cb00200010041a0b200220006a41003a0000410441004180c00010381a410041013602b44b0b0f0b41a8cb00103c000b1900024041002d00a84b410171450d0041002802b04b10340b0b8f0101037f230041e0006b22002400024041002d00f04b4101710d00200041f4cb0041e00010042101410042003702e44b410041003602ec4b410041e000103322023602e44b410020023602e84b4100200241e0006a3602ec4b2002200141e00010041a410041002802e84b41e0006a3602e84b410541004180c00010381a410041013602f04b0b200041e0006a24000b1e01017f024041002802e44b2201450d00410020013602e84b200110340b0b7601027f024041002d00e04c4101710d00410041c004103322003602d44c410020003602d84c4100200041c0046a3602dc4c410021010340200020016a41003a0000200141016a220141c004470d000b200041013a00004100200020016a3602d84c410641004180c00010381a410041013602e04c0b0b1e01017f024041002802d44c2201450d00410020013602d84c200110340b0be317012f7f23004180036b22062400024020014100480d002001411f6a220741ff3f4b0d00200541ff014a0d0020074105762108200641f8026a4200370300200641f0026a4200370300200641e8026a4200370300200641e0026a4200370300200641d8026a4200370300200641d0026a4200370300200642003703c802200642003703c002200620053a00bf0241002109200641003a00be02200620013a00bd0220062001410876220a3a00bc0220064188026a42abb38ffc91a3b3f0db0037030020064180026a42ffa4b988c591da829b7f370300200641f8016a42f2e6bbe3a3a7fda7a57f370300200642e7cca7d0d6d0ebb3bb7f3703f001200642003703e801200641003602e0014101210b4100210703402006200741016a3602e001200641a0016a20076a20093a0000024020062802e001220741c000470d00200641a0016a105f41002107200641003602e001200620062903e8014280047c3703e8010b0240200b41c000460d00200641c0026a200b6a2d00002109200b41016a210b0c010b0b02402003450d0003402006200741016a3602e001200641a0016a20076a20022d00003a0000024020062802e001220741c000470d00200641a0016a105f41002107200641003602e001200620062903e8014280047c3703e8010b200241016a21022003417f6a22030d000b0b2006200741016a3602e001200641a0016a20076a200a3a0000024020062802e001220741c000470d00200641a0016a105f41002107200641003602e001200620062903e8014280047c3703e8010b2006200741016a3602e001200641a0016a20076a20013a0000024020062802e001220741c000470d00200641a0016a105f41002107200641003602e001200620062903e8014280047c3703e8010b2006200741016a3602e001200641a0016a20076a41003a0000024020062802e001220741c000470d00200641a0016a105f200641003602e001200620062903e8014280047c3703e801410021070b02402005450d002004210b2005210903402006200741016a3602e001200641a0016a20076a200b2d00003a0000024020062802e001220741c000470d00200641a0016a105f41002107200641003602e001200620062903e8014280047c3703e8010b200b41016a210b2009417f6a22090d000b0b2006200741016a3602e001200641a0016a20076a20053a0000024020062802e00141c000470d00200641a0016a105f200641003602e001200620062903e8014280047c3703e8010b200641a0016a1060200620062802f00122074118763a009002200620062802f401220b4118763a009402200620062802f801220a4118763a009802200620062802fc01220c4118763a009c022006200628028002220d4118763a00a0022006200628028402220e4118763a00a40220062006280288022202411876220f3a00a8022006200628028c0222034118763a00ac02200620034110763a00ad02200620024110763a00a9022006200e41107622103a00a5022006200d41107622113a00a1022006200c41107622123a009d022006200a41107622133a0099022006200b41107622143a0095022006200741107622093a009102200620034108763a00ae02200620024108763a00aa022006200e41087622153a00a6022006200d41087622163a00a2022006200c41087622173a009e022006200a41087622183a009a022006200b41087622193a00960220062007410876221a3a009202200620033a00af02200620023a00ab022006200e3a00a7022006200c3a009f022006200a3a009b022006200b3a009702200620073a0093022006200d3a00a302200641f0006a41206a41003a0000200641f0006a41186a4200370300200641f0006a41106a420037030020064200370378200642003703702008450d00200641f0006a410172210220062d00bf02211b4100211c4100211d4100211e4100211f410021204100212141002122410021234100212441002125410021264100212741002128410021294100212a4100212b4100212c4100212d4100212e4100212f4100213041002131410021324100213341002134410121030340200620312007733a007320062032201a733a0072200620332009733a00712006203420062d0090027322093a00702006202d200b733a00772006202e2019733a00762006202f2014733a00752006203020062d009402733a007420062029200a733a007b2006202a2018733a007a2006202b2013733a00792006202c20062d009802733a007820062025200c733a007f200620262017733a007e200620272012733a007d2006202820062d009c02733a007c20062021200d733a008301200620222016733a008201200620232011733a0081012006201c200f733a0088012006201d200e733a0087012006201e2015733a0086012006201f2010733a0085012006202420062d00a002733a0080012006202020062d00a402733a008401200620062d00890120062d00a902733a008901200620062d008a0120062d00aa02733a008a01200620062d008b0120062d00ab02733a008b01200620033a009001200620062d008c0120062d00ac02733a008c01200620062d008d0120062d00ad02733a008d01200620062d008e0120062d00ae02733a008e01200620062d008f0120062d00af02733a008f01200642abb38ffc91a3b3f0db00370368200642ffa4b988c591da829b7f370360200642f2e6bbe3a3a7fda7a57f370358200642e7cca7d0d6d0ebb3bb7f3703502006420037034841002107200641003602404100210b03402006200741016a360240200620076a20093a000002402006280240220741c000470d002006105f4100210720064100360240200620062903484280047c3703480b0240200b4120460d002002200b6a2d00002109200b41016a210b0c010b0b02402005450d002004210b2005210903402006200741016a360240200620076a200b2d00003a000002402006280240220741c000470d002006105f4100210720064100360240200620062903484280047c3703480b200b41016a210b2009417f6a22090d000b0b2006200741016a360240200620076a201b3a00000240200628024041c000470d002006105f20064100360240200620062903484280047c3703480b200610602006200628025022074118763a007020062006280254220b4118763a00742006200628025822094118763a00782006200628025c220a4118763a007c20062006280260220c4118763a00800120062006280264220d4118763a00840120062006280268220e4118763a0088012006200628026c220f4118763a008c012006200f4110763a008d012006200e4110763a0089012006200d4110763a0085012006200c4110763a0081012006200a4110763a007d200620094110763a00792006200b4110763a0075200620074110763a00712006200f4108763a008e012006200e4108763a008a012006200d4108763a0086012006200c4108763a0082012006200a4108763a007e200620094108763a007a2006200b4108763a0076200620074108763a00722006200f3a008f012006200e3a008b012006200d3a0087012006200a3a007f200620093a007b2006200b3a0077200620073a00732006200c3a0083012003410574220720006a41606a200641f0006a200120076b2207411f7520077141206a10041a20032008460d01200341016a210320062d008801211c20062d00a802210f20062d008701211d20062d00a702210e20062d008601211e20062d00a602211520062d008501211f20062d00a502211020062d008401212020062d008301212120062d00a302210d20062d008201212220062d00a202211620062d008101212320062d00a102211120062d008001212420062d007f212520062d009f02210c20062d007e212620062d009e02211720062d007d212720062d009d02211220062d007c212820062d007b212920062d009b02210a20062d007a212a20062d009a02211820062d0079212b20062d009902211320062d0078212c20062d0077212d20062d009702210b20062d0076212e20062d009602211920062d0075212f20062d009502211420062d0074213020062d0073213120062d009302210720062d0072213220062d009202211a20062d0071213320062d009102210920062d007021340c000b0b20064180036a24000ba80401187f23004180026b2201240041002102410021030340200120026a2000200341ff017122046a28000022034118742003410874418080fc07717220034108764180fe037120034118767272360200200441046a2103200241046a220241c000470d000b41002102200128020021040340200120026a220341c0006a2004200341246a2802006a200341386a2802002204410d772004410a76732004410f77736a200341046a2802002203410e772003410376732003411977736a36020020032104200241046a220241c001470d000b41002104200041dc006a28020022052106200041ec006a28020022072108200041e8006a2802002209210a200041e4006a280200220b210c200041e0006a280200220d210e200041d8006a280200220f2110200041d4006a28020022112112200028025022132114034020102215201222167220142202712015201671722002411e772002411377732002410a77736a200441e8d0006a280200200120046a2802006a200a2217200e2203417f7371200c2218200371726a2003411a772003411577732003410777736a20086a220e6a2114200e20066a210e20152106201721082018210a2003210c2016211020022112200441046a2204418002470d000b2000200720176a36026c2000200920186a3602682000200b20036a3602642000200d200e6a3602602000200520156a36025c2000200f20166a3602582000201120026a3602542000201320146a36025020014180026a24000be80102027f027e2000200028024022016a22024180013a000002402001ad220342017c423842c00020014138491b22045a0d00200241016a21012003427f8520047c21030340200141003a0000200141016a21012003427f7c22034200520d000b0b0240200028024022014138490d002000105f20004100413810021a200028024021010b200020002903482001410374ad7c22033c003f20002003370348200020034208883c003e200020034210883c003d200020034218883c003c200020034220883c003b200020034228883c003a200020034230883c0039200020034238883c00382000105f0bb90801027f230041a0066b22032400200341a0046a418002200028020020002802042001280208200141016a20012d0000220041017122041b2001280204200041017620041b105e413f2101200341c0016a210003402000200341a0046a20016a2d00003a0000200041016a21002001417f6a2201417f470d000b200341e0036a41386a200341c0016a41386a290300370300200341e0036a41306a200341c0016a41306a290300370300200341e0036a41286a200341c0016a41286a290300370300200341e0036a41206a200341c0016a41206a290300370300200341e0036a41186a200341c0016a41186a290300370300200341e0036a41106a200341c0016a41106a290300370300200341e0036a41086a200341c0016a41086a290300370300200320032903c0013703e003200341e0036a41c00020034180036a413010061a41ff002101200341c0016a210003402000200341a0046a20016a2d00003a0000200041016a21002001417f6a2201413f470d000b200341e0036a41386a200341c0016a41386a290300370300200341e0036a41306a200341c0016a41306a290300370300200341e0036a41286a200341c0016a41286a290300370300200341e0036a41206a200341c0016a41206a290300370300200341e0036a41186a200341c0016a41186a290300370300200341e0036a41106a200341c0016a41106a290300370300200341e0036a41086a200341c0016a41086a290300370300200320032903c0013703e003200341e0036a41c00020034180036a41306a2204413010061a20034180036a41e000200341c0016a41c00110071a200341df056a2101410021000340200320006a20012d00003a00002001417f6a2101200041016a220041c000470d000b200341e0036a41386a200341386a290300370300200341e0036a41306a200341306a290300370300200341e0036a41286a200341286a290300370300200341e0036a41206a200341206a290300370300200341e0036a41186a200341186a290300370300200341e0036a41106a200341106a290300370300200341e0036a41086a200341086a290300370300200320032903003703e003200341e0036a41c00020034180036a413010061a2003419f066a2101410021000340200320006a20012d00003a00002001417f6a2101200041016a220041c000470d000b200341e0036a41386a200341386a290300370300200341e0036a41306a200341306a290300370300200341e0036a41286a200341286a290300370300200341e0036a41206a200341206a290300370300200341e0036a41186a200341186a290300370300200341e0036a41106a200341106a290300370300200341e0036a41086a200341086a290300370300200320032903003703e003200341e0036a41c0002004413010061a20034180036a41e000200341c00110071a200341c0016a41c001200341c001200241c00110081a200341a0066a24000b970503017f017e047f230041f0006b22032400200341206a4100360200200342003703182003427f37031020032000290300220437030820032004370300024002402004200442808080809aecb4ee312001100a22004100480d00024020032000106322002802302003460d0041004180d50010010b2003200236023020032000200341306a10640c010b02402004100b510d00410041cad50010010b41c000103322004200370310200041286a22054200370300200041206a22064200370300200041186a220742003703002000200336023020002001370300200341306a20022802002208200228020420086b104d2005200341306a41186a2903003703002006200341306a41106a29030037030020072003290338370300200020032903303703102003200341306a41286a3602682003200341306a360260200341306a2000410810041a2003200341306a410872360264200341e0006a200041106a10651a2000200329030842808080809aecb4ee31200120002903002204200341306a4128101c2205360234024020042003290310540d002003427e200442017c2004427d561b3703100b200320003602602003200029030022043703302003200536022c02400240200328021c220220032802204f0d00200220053602102002200437030820034100360260200220003602002003200241186a36021c0c010b200341186a200341e0006a200341306a2003412c6a10660b20032802602100200341003602602000450d00200010340b024020032802182205450d0002400240200328021c22002005470d00200521000c010b0340200041686a220028020021022000410036020002402002450d00200210340b20052000470d000b200328021821000b2003200536021c200010340b200341f0006a24000b840603097f027e017f230041e0006b220221032002240002400240200028021822042000411c6a2802002205460d0002400340200541786a2802002001460d012004200541686a2205470d000c020b0b20042005460d00200541686a28020021060c010b024002400240024020014100410010292205417f4a0d00410041b3d50010010c010b2005418104490d010b2005102f2107410121080c010b20022005410f6a4170716b22072400410021080b20012007200510291a41c0001033220642003703102006420037030020062000360230200641186a4200370300200641206a4200370300200641286a42003703000240200541074b0d0041004199d70010010b20062007410810041a200741086a21040240200541786a411f4b0d0041004199d70010010b200041186a2109200641106a210a200341c0006a2004412010041a4200210b41102105200341206a2102410021044200210c0340200341c0006a20046a210d0240024020054102490d00200c420886200b200d31000084220b42388884210c2005417f6a2105200b420886210b0c010b024020054101460d00410041f6d70010010b2002200c3703082002200b200d3100008437030041102105200241106a21024200210b4200210c0b200441016a22044120470d000b024020054110460d00024020054102490d00200341086a200b200c200541037441786a1011200341106a290300210c2003290308210b0b2002200b3703002002200c3703080b200a2003290320370300200a41086a2003290328370300200a41186a200341206a41186a290300370300200a41106a200341206a41106a290300370300200620013602342003200636022020032006290300220b3703402003200136021c02400240200028021c2205200041206a2802004f0d00200520013602102005200b37030820034100360220200520063602002000200541186a36021c0c010b2009200341206a200341c0006a2003411c6a10660b02402008450d00200710310b20032802202105200341003602202005450d00200510340b200341e0006a240020060b980203027f017e017f230041206b2203210420032400024020012802302000460d00410041fdd50010010b0240100b2000290300510d00410041abd60010010b200129030021052004200228020022022802002206200228020420066b104d200141286a200441186a290300370300200141206a200441106a290300370300200141186a200429030837030020012004290300370310200141106a2102024020052001290300510d00410041ded60010010b200341506a220324002004200341286a3602082004200336020020032001410810041a2004200341086a3602042004200210651a2001280234420020034128102a024020052000290310540d002000427e200542017c2005427d561b3703100b200441206a24000bd20303017f027e017f230041206b220224002002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722054108763a0016200220054110763a0015200220054118763a001420022004a722053a0007200220054108763a0006200220054110763a0005200220054118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c001002402000280208200028020422016b411f4a0d00410041bbd4001001200028020421010b20012002412010041a2000200028020441206a360204200241206a240020000b9a0301057f0240024002402000280204200028020022046b41186d220541016a220641abd5aad5004f0d0041aad5aad500210702400240200028020820046b41186d220441d4aad52a4b0d0020062004410174220720072006491b22070d0041002107410021040c010b200741186c103321040b20012802002106200141003602002004200541186c22086a2201200328020036021020012002290300370308200120063602002004200741186c6a2105200141186a21062000280204220220002802002207460d01200420086a41686a21010340200241686a220428020021032004410036020020012003360200200141086a200241706a2202290300370300200141106a200241086a280200360200200141686a21012004210220072004470d000b200141186a210120002802042107200028020021040c020b20001049000b200721040b200020053602082000200636020420002001360200024020072004460d000340200741686a220728020021012007410036020002402001450d00200110340b20042007470d000b0b02402004450d00200410340b0bb91806047f017e0c7f017e017f027d230041800c6b220224002000290300101d02402001410c6a2802002200200128020822036b41306d41818004490d00410041e4cc00100120012802082103200128020c21000b024020002003470d0041004195cd00100120012802082103200128020c21000b200241f0026a4100360200200242003703e802200220012903003703e002200241e0026a41086a2204200020036b41306d1068200241d0026a41086a4100360200200242003703d00202400240024041b4cd00102e220041704f0d000240024002402000410b490d00200041106a417071220510332103200220003602d402200220054101723602d002200220033602d8020c010b200220004101743a00d002200241d0026a41017221032000450d010b200341b4cd00200010041a0b200320006a41003a0000200241c8026a4100360200200242003703c002024041bccd00102e220041704f0d000240024002402000410b490d00200041106a417071220510332103200220003602c402200220054101723602c002200220033602c8020c010b200220004101743a00c002200241c0026a41017221032000450d010b200341bccd00200010041a0b200320006a41003a0000200241808080fc033602b80242002106200242003703b002200242003703a80220012802082203200128020c2207460d03200241c0076a41c0016a2108200241c00a6a41e0006a2109200241a8026a41086a210a200241c0026a410172210b200241f8026a410172210c200241d0026a410172210d200241f8026a410172210e420021060340024020032d0000410171450d002003280204418102490d00410041c4cd0010010b200241f8026a200341186a220f410020022802d40220022d00d002220041017620004101711b200f103e1a0240024020022802fc0220022d00f80222004101762210200041017122051b221120022802d40220022d00d0022200410176200041017122001b470d0020022802d802200d20001b2100024020050d00200e21052011450d02034020052d000020002d0000470d02200041016a2100200541016a21052010417f6a22100d000c030b0b2011450d01200228028003200e20051b200020111039450d010b410041f8cd0010010b024020022d00f802410171450d0020022802800310340b200241f8026a200341246a2212410020022802c40220022d00c002220041017620004101711b2012103e1a0240024020022802fc0220022d00f80222004101762210200041017122051b221120022802c40220022d00c0022200410176200041017122001b470d0020022802c802200b20001b2100024020050d00200c21052011450d02034020052d000020002d0000470d02200041016a2100200541016a21052010417f6a22100d000c030b0b2011450d01200228028003200c20051b200020111039450d010b4100419cce0010010b024020022d00f802410171450d0020022802800310340b0240200329031022132006427f85580d00410041d4ce001001200329031021130b02400240200f2d00002200410171450d002003411c6a2802002100200341206a28020021050c010b20004101762100200f41016a21050b200241c8016a2005200010692002200241c8016a3602c007200241f8026a200241c0076a410410041a20022802f8024195d3c7de056c22004118762000734195d3c7de056c41d4cc9efa06732200410d762000734195d3c7de056c2200410f76200073211002400240024020022802ac022205450d000240024020056941014b220f0d0020102005417f6a7121110c010b2010211120102005490d00201020057021110b20022802a80220114102746a2802002200450d000240200f0d002005417f6a2114034020002802002200450d0202402000280204220f2010460d00200f2014712011470d030b200041086a200241c8016a41e00010390d000c030b0b034020002802002200450d0102402000280204220f2010460d000240200f2005490d00200f200570210f0b200f2011470d020b200041086a200241c8016a41e0001039450d020c000b0b41e8001033220041086a200241c8016a41e00010041a200041003602002000201036020420022a02b802211520022802b40241016ab32116024002402005450d0020152005b39420165d4101730d010b2005410174200541034920052005417f6a714100477272210f024002402016201595104f2215430000804f5d201543000000006071450d002015a921110c010b410021110b4102210502402011200f200f2011491b220f4101460d000240200f200f417f6a710d00200f21050c010b200f105021050b02400240200520022802ac02220f4d0d00200241a8026a2005106a0c010b2005200f4f0d00200f41034921140240024020022802b402b320022a02b80295104f2215430000804f5d201543000000006071450d002015a921110c010b410021110b0240024020140d00200f6941014b0d0020114102490d01410141202011417f6a676b7421110c010b2011105021110b2011200520052011491b2205200f4f0d00200241a8026a2005106a0b024020022802ac0222052005417f6a220f710d00200f20107121110c010b0240201020054f0d00201021110c010b201020057021110b02400240024020022802a80220114102746a220f28020022100d00200020022802b002360200200220003602b002200f200a36020020002802002210450d02201028020421100240024020052005417f6a220f710d002010200f7121100c010b20102005490d00201020057021100b20022802a80220104102746a21100c010b200020102802003602000b201020003602000b200220022802b40241016a3602b4020c010b410041fcce0010010b0240024020122d00002200410171450d00200341286a28020021002003412c6a28020021050c010b20004101762100201241016a21050b200241086a20052000106b200241c00a6a410041c00110021a200241c0076a410041800310021a200241c00a6a41002802e44b220041002802e84b20006b10041a200241c0076a200241086a41c00110041a2009200241c8016a41e00010041a200241e0003602bc072002200241c8016a3602b807200220022903b807370300200241a8cb0020081061200241c00a6a41c001200241c0076a4180034102200241f8026a41c00410091a0240200241f8026a41002802d44c220041002802d84c20006b1039450d0041004191cf0010010b41e00010332200200241c8016a41e00010041a200241f8026a2003103d1a2002200041e0006a2205360298032002200536029403200220003602900320022003290310370388030240024020022802ec02220020022802f0024f0d00200020022903f8023702002000411c6a22054200370200200041086a200241f8026a41086a22102802003602002000410036021820052002280294033602002000200228029003360218200041206a200228029803360200201041003602002000200229038803370310200242003703f802200241003602940320024100360290032002410036029803200220022802ec0241286a3602ec020c010b2004200241f8026a106c2002280290032200450d002002200036029403200010340b024020022d00f802410171450d0020022802800310340b201320067c2106200341306a22032007460d030c000b0b200241c0026a103c000b200241d0026a103c000b200642018821060b024020012903002006560d00410041accf0010010b024020022802e802220020022802ec022203460d00034002402000411c6a280200200041186a2802006b41e000460d004100419fd40010010b2003200041286a2200470d000b0b200241f8026a200241e0026a106d20022802f802220020022802fc0220006b101e024020022802f8022200450d00200220003602fc02200010340b024020022802b0022200450d00034020002802002103200010342003210020030d000b0b20022802a8022100200241003602a80202402000450d00200010340b024020022d00c002410171450d0020022802c80210340b024020022d00d002410171450d0020022802d80210340b024020022802e8022205450d000240024020022802ec0222002005470d00200521000c010b03400240200041706a2802002203450d00200041746a2003360200200310340b200041586a21030240200041586a2d0000410171450d00200041606a28020010340b2003210020052003470d000b20022802e80221000b200220053602ec02200010340b200241800c6a24000bc403010b7f02402000280208200028020022026b41286d20014f0d00024002400240200141e7cc99334f0d0020002802042103200141286c22011033220420016a21052004200320026b41286d220641286c6a21072000280204220820002802002201460d01200120086b2109410021030340200720036a220241586a220a200820036a220141586a220b290200370200200a41086a200b41086a280200360200200241746a220a4200370200200241706a220c4100360200200a200141746a280200360200200c200141706a220a280200360200200241686a200141686a290300370300200241786a200141786a2202280200360200200141606a4100360200200b4200370200200a4200370200200241003602002009200341586a2203470d000b2004200641286c6a20036a210220002802042101200028020021030c020b1000000b20072102200121030b200020053602082000200736020420002002360200024020012003460d0003400240200141706a2802002202450d00200141746a2002360200200210340b200141586a21020240200141586a2d0000410171450d00200141606a28020010340b2002210120032002470d000b0b2003450d00200310340b0bd10902047f027e230041c0016b22032400024041002802d04a220441002d00cc4a22054101762206200541017122051b2002490d00410041e8d200100141002d00cc4a220541017621062005410171210541002802d04a21040b0240200141002802d44a41cdca0020051b2004200620051b1039450d0041004188d30010010b2003200241002802d04a41002d00cc4a220541017620054101711b22056b3602142003200120056a36021020032003290310370308200341b0016a200341086a41001051200341f0006a20032802b40120032d00b001220541017620054101711b2201104820034180016a41086a200341f0006a410041c1d3001046220541086a22022802003602002002410036020020032005290200370380012005420037020020034190016a41086a20034180016a41cfd3001043220541086a220228020036020020024100360200200320052902003703900120054200370200200341e0006a41e0001048200341a0016a41086a20034190016a2003280268200341e0006a41017220032d0060220541017122021b2003280264200541017620021b1042220541086a220228020036020020024100360200200320052902003703a00120054200370200200341386a41086a200341a0016a41eed3001043220541086a2202280200360200200241003602002003200529020037033820054200370200200341d0006a41041048200341106a41086a200341386a2003280258200341d0006a41017220032d0050220541017122021b2003280254200541017620021b1042220541086a22022802003602002002410036020020032005290200370310200542003702000240200141e400460d0041002003280218200341106a41017220032d0010220541017122011b2003280214200541017620011b10280b024020032d0010410171450d00200328021810340b024020032d0050410171450d00200328025810340b024020032d0038410171450d00200328024010340b024020032d00a001410171450d0020032802a80110340b024020032d0060410171450d00200328026810340b024020032d009001410171450d0020032802980110340b024020032d008001410171450d0020032802880110340b024020032d0070410171450d00200328027810340b0240024020032d00b0012201410171450d0020032802b801220520032802b4016a21010c010b200341b0016a410172220520014101766a21010b02402001417c6a220120056b2202450d0020002005200210031a0b200341106a200041e000104e2003200329031822074220883c003b200320074228883c003a200320074230883c0039200320074238883c00382003200341106a41186a29030022084220883c004b200320084228883c004a200320084230883c0049200320084238883c004820032007a722053a003f200320054108763a003e200320054110763a003d200320054118763a003c200320032903102207423886200742288642808080808080c0ff0083842007421886428080808080e03f8320074208864280808080f01f838484200742088842808080f80f832007421888428080fc07838420074228884280fe0383200742388884848437034002402001200341386a41041039450d00410041fbd30010010b024020032d00b001410171450d0020032802b80110340b200341c0016a24000baa0501077f02400240024002402001450d0020014180808080044f0d01200141027410332102200028020021032000200236020002402003450d00200310340b2000200136020441002103200121020340200028020020036a4100360200200341046a21032002417f6a22020d000b20002802082202450d03200041086a21032002280204210402400240200169220541014b0d0020042001417f6a7121040c010b20042001490d00200420017021040b200028020020044102746a200336020020022802002203450d03200541014b0d022001417f6a2106034002400240200328020420067122052004470d00200321020c010b0240024002402000280200200541027422076a2201280200450d002003210520032802002201450d0220032105200341086a2208200141086a41e00010390d02200321050c010b2001200236020020032102200521040c020b0340200528020022052802002201450d012008200141086a41e0001039450d000b0b200220052802003602002005200028020020076a280200280200360200200028020020076a28020020033602000b200228020022030d000c040b0b200028020021032000410036020002402003450d00200310340b200041003602040c020b1000000b03400240200328020422052001490d00200520017021050b0240024020052004470d00200321020c010b02402000280200200541027422066a22082802000d002008200236020020032102200521040c010b20032105024020032802002208450d0020032105200341086a2207200841086a41e00010390d00200321050340200528020022052802002208450d012007200841086a41e0001039450d000b0b200220052802003602002005200028020020066a280200280200360200200028020020066a28020020033602000b200228020022030d000b0b0bd10902047f027e230041c0016b22032400024041002802e04a220441002d00dc4a22054101762206200541017122051b2002490d00410041e8d200100141002d00dc4a220541017621062005410171210541002802e04a21040b0240200141002802e44a41ddca0020051b2004200620051b1039450d0041004188d30010010b2003200241002802e04a41002d00dc4a220541017620054101711b22056b3602142003200120056a36021020032003290310370308200341b0016a200341086a41001051200341f0006a20032802b40120032d00b001220541017620054101711b2201104820034180016a41086a200341f0006a410041c1d3001046220541086a22022802003602002002410036020020032005290200370380012005420037020020034190016a41086a20034180016a41cfd3001043220541086a220228020036020020024100360200200320052902003703900120054200370200200341e0006a41c0011048200341a0016a41086a20034190016a2003280268200341e0006a41017220032d0060220541017122021b2003280264200541017620021b1042220541086a220228020036020020024100360200200320052902003703a00120054200370200200341386a41086a200341a0016a41eed3001043220541086a2202280200360200200241003602002003200529020037033820054200370200200341d0006a41041048200341106a41086a200341386a2003280258200341d0006a41017220032d0050220541017122021b2003280254200541017620021b1042220541086a22022802003602002002410036020020032005290200370310200542003702000240200141c401460d0041002003280218200341106a41017220032d0010220541017122011b2003280214200541017620011b10280b024020032d0010410171450d00200328021810340b024020032d0050410171450d00200328025810340b024020032d0038410171450d00200328024010340b024020032d00a001410171450d0020032802a80110340b024020032d0060410171450d00200328026810340b024020032d009001410171450d0020032802980110340b024020032d008001410171450d0020032802880110340b024020032d0070410171450d00200328027810340b0240024020032d00b0012201410171450d0020032802b801220520032802b4016a21010c010b200341b0016a410172220520014101766a21010b02402001417c6a220120056b2202450d0020002005200210031a0b200341106a200041c001104e2003200329031822074220883c003b200320074228883c003a200320074230883c0039200320074238883c00382003200341106a41186a29030022084220883c004b200320084228883c004a200320084230883c0049200320084238883c004820032007a722053a003f200320054108763a003e200320054110763a003d200320054118763a003c200320032903102207423886200742288642808080808080c0ff0083842007421886428080808080e03f8320074208864280808080f01f838484200742088842808080f80f832007421888428080fc07838420074228884280fe0383200742388884848437034002402001200341386a41041039450d00410041fbd30010010b024020032d00b001410171450d0020032802b80110340b200341c0016a24000be60403047f027e067f0240024002402000280204200028020022026b41286d220341016a220441e7cc99334f0d0041e6cc9933210502400240200028020820026b41286d220241b2e6cc194b0d0020042002410174220520052004491b22050d0041002105410021020c010b200541286c103321020b2001411c6a2204290200210620044200370200200129020021072001420037020020012802182104200141003602182002200341286c6a22082007370200200141086a22032802002109200341003602002008200129031037031020082004360218200841086a20093602002008411c6a20063702002002200541286c6a210a200841286a210b2000280204220c20002802002201460d012001200c6b210d410021020340200820026a220541586a2204200c20026a220141586a2203290200370200200441086a200341086a280200360200200541746a22044200370200200541706a220941003602002004200141746a2802003602002009200141706a2204280200360200200541686a200141686a290300370300200541786a200141786a2205280200360200200141606a4100360200200342003702002004420037020020054100360200200d200241586a2202470d000b200820026a210820002802042101200028020021020c020b20001049000b200121020b2000200a3602082000200b36020420002008360200024020012002460d0003400240200141706a2802002205450d00200141746a2005360200200510340b200141586a21050240200141586a2d0000410171450d00200141606a28020010340b2005210120022005470d000b0b02402002450d00200210340b0be40203057f017e047f230041106b22022400200041003602082000420037020041082103200141086a21042001410c6a2802002205200128020822066b41286dad21070340200341016a2103200742078822074200520d000b0240024020062005460d00034020062802042208ad420020062d00002209410171220a1b2107200341086a210b0340200b41016a210b200742078822074200520d000b2006280218220320082009410176200a1b6b2006411c6a28020022096b2108200920036bad210703402008417f6a2108200742078822074200520d000b200b20086b2103200641286a22062005470d000b4100210341002106200b2008460d01200b20086b21030b20002003107a20002802042103200028020021060b2002200636020420022006360200200220033602080240200320066b41074a0d00410041bbd40010010b20062001410810041a2002200641086a36020420022004108a011a200241106a24000bd30203047f017e017f230041106b220224004100210320004100360208200042003702002002410036020020012802042204200128020022056b410575ad21060340200341016a2103200642078822064200520d000b2002200336020002400240024020052004460d0003402002200341086a3602002003410c6a2103200541186a2802002207ad21060340200341016a2103200642078822064200520d000b20022003417c6a3602002007417f460d022002200336020020022005410c6a108d011a20022802002103200541206a22052004470d000b20002802002105200028020421070c020b41002105410021070c010b108e01000b024002402003200720056b22074d0d002000200320076b107a200028020021050c010b200320074f0d002000200520036a3602040b20022005360204200220053602002002200028020436020820022001108f011a200241106a24000baf0302017f027e230041206b220224002000290300101d2002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722004108763a0016200220004110763a0015200220004118763a001420022004a722003a0007200220004108763a0006200220004110763a0005200220004118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c00102002102241a8d00010232001107041c3d0001023200241206a24000b9c0303017f027e017f230041206b220124002001200041186a29030022023c00172001200041086a29030022034220883c0003200120034228883c0002200120034230883c0001200120034238883c0000200120024220883c001320012002a722044108763a0016200120044110763a0015200120044118763a001420012003a722043a0007200120044108763a0006200120044110763a0005200120044118763a0004200120002903002203423886200342288642808080808080c0ff0083842003421886428080808080e03f8320034208864280808080f01f838484200342088842808080f80f832003421888428080fc07838420034228884280fe03832003423888848484370308200120002903102203423886200342288642808080808080c0ff0083842003421886428080808080e03f8320034208864280808080f01f838484200342088842808080f80f832003421888428080fc07838420034228884280fe03832003423888848484370318200120024230883c0011200120024228883c0012200120024238883c001020014120102b200141206a24000ba70303017f027e017f230041206b220224002002200141186a29030022033c00172002200141086a29030022044220883c0003200220044228883c0002200220044230883c0001200220044238883c0000200220034220883c001320022003a722054108763a0016200220054110763a0015200220054118763a001420022004a722053a0007200220054108763a0006200220054110763a0005200220054118763a0004200220012903002204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370308200220012903102204423886200442288642808080808080c0ff0083842004421886428080808080e03f8320044208864280808080f01f838484200442088842808080f80f832004421888428080fc07838420044228884280fe03832004423888848484370318200220034230883c0011200220034228883c0012200220034238883c00100240200210240d00410041c5d00010010b200241206a24000bb90101047f230041106b2202210320022400024002400240102522040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a20034200370308200341086a2105200441074b0d010b41004199d70010010b20052002410810041a20034200370300200241086a2102024020044178714108470d0041004199d70010010b20032002410810041a200341106a24000b4401037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b200324000b4401037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b200324000b4401037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b200324000b4401037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b200324000b4401037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b200324000bc90201047f230041306b220221032002240002400240102522040d00410021020c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b20032002360224200320023602202003200220046a2205360228200342003703180240200441074b0d0041004199d700100120032802282105200328022421020b200341186a2002410810041a2003200241086a2202360224024020052002470d0041004199d700100120032802282105200328022421020b200341176a2002410110041a2003200241016a2202360224024020052002470d0041004199d7001001200328022421020b200341166a2002410110041a2003200241016a3602242003410036021020034200370308200341206a200341086a10791a024020032802082202450d002003200236020c200210340b200341306a24000bff0103017f017e047f2000280204210242002103410021040340024020022000280208490d00410041c3d7001001200028020421020b20022d000021052000200241016a22063602042003200541ff0071200441ff0171220274ad842103200241076a2104200621022005418001710d000b0240024020012802042205200128020022026b22072003a722044f0d002001200420076b107a2000280204210620012802042105200128020021020c010b200720044d0d002001200220046a22053602040b0240200028020820066b200520026b22054f0d0041004199d7001001200028020421060b20022006200510041a2000200028020420056a36020420000b980201057f02400240024020002802082202200028020422036b2001490d000340200341003a00002000200028020441016a22033602042001417f6a22010d000c020b0b2003200028020022046b220520016a2206417f4c0d0141ffffffff07210302400240200220046b220241feffffff034b0d0020062002410174220320032006491b22030d0041002103410021020c010b2003103321020b200220036a2106200220056a220421030340200341003a0000200341016a21032001417f6a22010d000b20042000280204200028020022016b22026b2104024020024101480d0020042001200210041a200028020021010b2000200636020820002003360204200020043602002001450d00200110340b0f0b20001049000bb20202037f017e23004180016b220221032002240002400240102522040d00410021020c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b20032002360254200320023602502003200220046a360258200342003703480240200441074b0d0041004199d7001001200328025421020b200341c8006a2002410810041a2003200241086a3602542003410036024020034200370338200341d0006a200341386a10791a200341086a41086a200341d0006a41086a2802002202360200200341306a2002360200200320032903502205370308200320013703202003200037031820032005370328200341186a2003290348200341386a1062024020032802382202450d002003200236023c200210340b20034180016a24000b4c01037f230022022103024010252204450d00024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b410041fbcf001001200324000bcf0102047f017e230041106b2202210320022400024002400240102522040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a20034200370308200341086a2105200441074b0d010b41004199d70010010b20052002410810041a200241086a2102024020044108470d0041004199d70010010b200341076a2002410110041a2003290308210620032d000721042000101d20062004410047101f200341106a24000baa0202047f047e230041206b2202210320022400024002400240102522040d002003420037031841002102200341186a21050c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a20034200370318200341186a2105200441074b0d010b41004199d70010010b20052002410810041a200241086a21050240200441787122044108470d0041004199d70010010b200341106a2005410810041a200241106a2105024020044110470d0041004199d70010010b200341086a2005410810041a200241186a2102024020044118470d0041004199d70010010b20032002410810041a200329030021062003290308210720032903102108200329031821092000101d20092008200720061020200341206a24000ba203010b7f230041306b220221032002240041002104024010252205450d00024002402005418004490d002005102f21040c010b20022005410f6a4170716b220424000b2004200510261a0b20032004360214200320043602102003200420056a3602182003410036020820034200370300200341106a20031080011a2000101d200341206a2003106e420120032802202204200328022420046b10211a024020032802202204450d0020032004360224200410340b024020032802002206450d0002400240200328020422072006470d00200621040c010b03402007220441606a21070240200441786a2208280200417f460d002004416c6a2209280200220a450d00200a21050240200441706a220b2802002204200a460d000340200441486a21050240200441786a2202280200220c417f460d00200341206a200441486a200c41027441c8d7006a2802001101000b2002417f36020020052104200a2005470d000b200928020021050b200b200a360200200510340b2008417f36020020072006470d000b200328020021040b20032006360204200410340b200341306a24000bd20303027f017e097f230041106b220224002000280204210342002104410021050340024020032000280208490d00410041c3d7001001200028020421030b20032d000021062000200341016a22033602042004200641ff0071200541ff0171220574ad842104200541076a2105200321032006418001710d000b0240024020012802042207200128020022056b41057522062004a722034f0d002001200320066b108101200128020421070c010b200620034d0d000240200520034105746a22082007460d0003402007220341606a21070240200341786a2209280200417f460d002003416c6a220a280200220b450d00200b21060240200341706a220c2802002203200b460d000340200341486a21060240200341786a2205280200220d417f460d00200241086a200341486a200d41027441c8d7006a2802001101000b2005417f36020020062103200b2006470d000b200a28020021060b200c200b360200200610340b2009417f36020020072008470d000b0b20012008360204200821070b0240200128020022032007460d0003402002410236020420022000360200200220033602082002200341086a36020c200241086a2002108201200341206a22032007470d000b0b200241106a240020000b9f06030a7f017e037f230041106b220224000240024020002802082203200028020422046b4105752001490d000340200441186a2203420037030020044200370300200441106a4200370300200441086a4200370300200341003602002000200028020441206a22043602042001417f6a22010d000c020b0b02400240024002402004200028020022056b410575220620016a220741808080c0004f0d0041ffffff3f210402400240200320056b220341057541feffff1f4b0d00024020072003410475220420042007491b22040d0041002104410021030c020b200441808080c0004f0d030b2004410574103321030b200320044105746a2108200320064105746a22092104034020044200370300200441186a4200370300200441106a4200370300200441086a4200370300200441206a21042001417f6a22010d000b2000280204220a20002802002206460d022006200a6b210b410021050340200920056a220141786a2206417f360200200a20056a220341606a290300210c200141686a220741003a0000200141606a200c3703000240200341786a280200220d417f460d00200141706a220e42003702002001416c6a220f4100360200200e200341706a280200360200200f2003416c6a220e280200360200200141746a200341746a22012802003602002007200341686a2802003602002006200d36020020014100360200200e42003702000b200b200541606a2205470d000b200920056a2109200028020421062000280200210d0c030b20001049000b1000000b2006210d0b20002008360208200020043602042000200936020002402006200d460d0003402006220441606a21060240200441786a2207280200417f460d002004416c6a220e2802002200450d00200021010240200441706a220f28020022042000460d000340200441486a21010240200441786a22032802002205417f460d00200241086a200441486a200541027441c8d7006a2802001101000b2003417f3602002001210420002001470d000b200e28020021010b200f2000360200200110340b2007417f3602002006200d470d000b0b200d450d00200d10340b200241106a24000bcb0102037f017e20002802002102024020012802002203280208200328020422046b41074b0d0041004199d7001001200328020421040b20022004410810041a2003200328020441086a3602042000280204210220012802002201280204210342002105410021040340024020032001280208490d00410041c3d7001001200128020421030b20032d000021002001200341016a22033602042005200041ff0071200441ff0171220474ad842105200441076a2104200321032000418001710d000b200120022005a7109b010bb50302047f017e23004180016b220221032002240041002104024010252205450d00024002402005418004490d002005102f21040c010b20022005410f6a4170716b220424000b2004200510261a0b20032004360254200320043602502003200420056a360258200341c8006a410036020020034200370340200342003703380240200541074b0d0041004199d7001001200328025421040b200341386a2004410810041a2003200441086a360254200341d0006a200341386a41086a1084011a200341086a41086a200341d0006a41086a2802002204360200200341306a2004360200200320032903502206370308200320013703202003200037031820032006370328200341186a200341386a1067024020032802402202450d0002400240200328024422042002470d00200221040c010b03400240200441746a2d0000410171450d002004417c6a28020010340b0240200441686a2d0000410171450d00200441706a28020010340b200441506a21050240200441506a2d0000410171450d00200441586a28020010340b2005210420022005470d000b200328024021040b20032002360244200410340b20034180016a24000ba70303017f017e037f2000280204210242002103410021040340024020022000280208490d00410041c3d7001001200028020421020b20022d000021052000200241016a22023602042003200541ff0071200441ff0171220474ad842103200441076a2104200221022005418001710d000b0240024020012802042204200128020022066b41306d22052003a722024f0d002001200220056b10a401200128020421040c010b200520024d0d0002402006200241306c6a22052004460d0003400240200441746a2d0000410171450d002004417c6a28020010340b0240200441686a2d0000410171450d00200441706a28020010340b200441506a21020240200441506a2d0000410171450d00200441586a28020010340b2002210420052002470d000b0b20012005360204200521040b0240200128020022022004460d00034002402000200210a3012205280208200528020422016b41074b0d0041004199d7001001200528020421010b200241106a2001410810041a2005200528020441086a3602042005200241186a10a301200241246a10a3011a200241306a22022004470d000b0b20000b8a0101037f230041e0006b220221032002240002400240102522040d00410021020c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a0b20032002360254200320023602502003200220046a360258200341d0006a200341086a1086011a2000101d200341086a104b200341e0006a24000ba20801027f02402000280208200028020422026b41074b0d0041004199d7001001200028020421020b20012002410810041a2000200028020441086a2202360204200141086a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a22023602042001410c6a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141106a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141146a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141186a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a22023602042001411c6a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141206a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141246a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141286a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a22023602042001412c6a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141306a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141346a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141386a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a22023602042001413c6a21030240200028020820026b41034b0d0041004199d7001001200028020421020b20032002410410041a2000200028020441046a2202360204200141c0006a21030240200028020820026b41014b0d0041004199d7001001200028020421020b20032002410210041a2000200028020441026a2202360204200141c2006a21010240200028020820026b41014b0d0041004199d7001001200028020421020b20012002410210041a2000200028020441026a36020420000b940101047f230041106b2202210320022400024002400240102522040d002003420037030841002102200341086a21050c010b024002402004418004490d002004102f21020c010b20022004410f6a4170716b220224000b2002200410261a20034200370308200341086a2105200441074b0d010b41004199d70010010b20052002410810041a2003290308101d200341106a24000b8c0405047f017e037f017e017f230041f0006b220221032002240002400240102522040d00410021050c010b024002402004418004490d002004102f21050c010b20022004410f6a4170716b220524000b2005200410261a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004199d70010010b200520046a2107200341d0006a2005412010041a200541206a2108200341306a2109410021044200210a0340200341d0006a20046a210b0240024020024102490d00200a4208862006200b31000084220642388884210a2002417f6a2102200642088621060c010b024020024101460d00410041f6d70010010b2009200a37030820092006200b3100008437030041102102200941106a2109420021064200210a0b200441016a22044120470d000b024020024110460d00024020024102490d0020032006200a200241037441786a1011200341086a290300210a200329030021060b200920063703002009200a3703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a2903003703002003200329033837031820032003290330370310200341d0006a41186a2007360200200341e4006a2008360200200320053602602003200137035820032000370350200341d0006a200341106a106f200341f0006a24000bc80303047f027e017f230041f0006b220221032002240002400240102522040d00410021050c010b024002402004418004490d002004102f21050c010b20022004410f6a4170716b220524000b2005200410261a0b42002106200341286a420037030041102102200341106a41106a4200370300200342003703182003420037031002402004411f4b0d0041004199d70010010b200341d0006a2005412010041a200341306a210541002104420021070340200341d0006a20046a21080240024020024102490d002007420886200620083100008422064238888421072002417f6a2102200642088621060c010b024020024101460d00410041f6d70010010b200520073703082005200620083100008437030041102102200541106a210542002106420021070b200441016a22044120470d000b024020024110460d00024020024102490d00200320062007200241037441786a1011200341086a2903002107200329030021060b20052006370300200520073703080b200341106a41186a200341306a41186a290300370300200341106a41106a200341306a41106a29030037030020032003290338370318200320032903303703102002200341106a1071200341f0006a24000b850203017f017e037f230041106b22022400200128020420012802006b41286dad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d00410041bbd4001001200028020421040b20042002410f6a410110041a2000200028020441016a220436020420060d000b02402001280200220520012802042201460d000340024020002005108b012204280208200428020422066b41074a0d00410041bbd4001001200428020421060b2006200541106a410810041a2004200428020441086a3602042004200541186a108c011a200541286a22052001470d000b0b200241106a240020000bfd0103027f017e027f230041106b22022400200128020420012d0000220341017620034101711bad21042000280204210303402004a721052002200442078822044200522206410774200541ff0071723a000f0240200028020820036b41004a0d00410041bbd4001001200028020421030b20032002410f6a410110041a2000200028020441016a220336020420060d000b0240200128020420012d00002205410176200541017122061b2205450d002001280208200141016a20061b21060240200028020820036b20054e0d00410041bbd4001001200028020421030b20032006200510041a2000200028020420056a3602040b200241106a240020000bd20103017f017e037f230041106b22022400200128020420012802006bad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d00410041bbd4001001200028020421040b20042002410f6a410110041a2000200028020441016a220436020420060d000b0240200028020820046b2001280204200128020022066b22054e0d00410041bbd4001001200028020421040b20042006200510041a2000200028020420056a360204200241106a240020000bd60103037f017e017f230041106b2202240020012802042203200128020022046b41386dad2105200028020021010340200141016a2101200542078822054200520d000b200020013602000240024020042003460d00034020042802302206ad21050340200141016a2101200542078822054200520d000b20002001360200200220003602002006417f460d0220022002360208200241086a2004200641027441e8d4006a2802001101002000200028020041026a2201360200200441386a22042003470d000b0b200241106a240020000f0b108e01000b05001000000bff0103017f017e037f230041106b22022400200128020420012802006b410575ad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d00410041bbd4001001200028020421040b20042002410f6a410110041a2000200028020441016a220436020420060d000b02402001280200220520012802042206460d0003400240200028020820046b41074a0d00410041bbd4001001200028020421040b20042005410810041a2000200028020441086a3602042000200541086a1090011a200541206a22052006460d01200028020421040c000b0b200241106a240020000bdf0103027f017e027f230041106b22022400200028020421032001350210210403402004a721052002200442078822044200522206410774200541ff0071723a000f0240200028020820036b41004a0d00410041bbd4001001200028020421030b20032002410f6a410110041a2000200028020441016a220336020420060d000b02402001280210417f460d00200141046a21050240200028020820036b41034a0d00410041bbd4001001200028020421030b20032001410410041a2000200028020441046a360204200020051094011a200241106a240020000f0b108e01000b170020002802002802002200200028020041216a3602000b170020002802002802002200200028020041216a3602000b7602017f017e20002802002802002202200228020041226a2200360200200141286a350200420020012d00244101711b21030340200041016a2100200342078822034200520d000b200220003602000240200128022820012d0024220141017620014101711b2201450d002002200120006a3602000b0b9a0303017f017e047f230041106b22022400200128020420012802006b41386dad21032000280204210403402003a721052002200342078822034200522206410774200541ff0071723a000f0240200028020820046b41004a0d00410041bbd4001001200028020421040b20042002410f6a410110041a2000200028020441016a220436020420060d000b024002402001280200220720012802042201460d0003402007350230210303402003a721052002200342078822034200522206410774200541ff0071723a000e0240200028020820046b41004a0d00410041bbd4001001200028020421040b20042002410e6a410110041a2000200028020441016a220436020420060d000b2002200036020020072802302204417f460d0220022002360208200241086a2007200441027441f4d4006a280200110100200741346a210502402000280208200028020422046b41014a0d00410041bbd4001001200028020421040b20042005410210041a2000200028020441026a2204360204200741386a22072001470d000b0b200241106a240020000f0b108e01000b6401037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d00410041bbd4001001200028020421020b20022004410110041a2000200028020441016a2202360204200341016a22034121470d000b0b6401037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d00410041bbd4001001200028020421020b20022004410110041a2000200028020441016a2202360204200341016a22034121470d000b0bab0101037f200028020028020022002802042102410021030340200120036a21040240200028020820026b41004a0d00410041bbd4001001200028020421020b20022004410110041a2000200028020441016a2202360204200341016a22034121470d000b200141216a21030240200028020820026b41004a0d00410041bbd4001001200028020421020b20022003410110041a2000200028020441016a3602042000200141246a108b011a0b02000b02000b1a00024020012d0024410171450d002001412c6a28020010340b0baf0201047f230041206b220324000240024020020d00200341146a41003602002003420037020c200341086a410472210402402000280208200028020422026b41034b0d0041004199d7001001200028020421020b200341086a2002410410041a2000200028020441046a36020420002004109c011a02402001280210417f460d0020012802042205450d00200521020240200141086a28020022002005460d000340200041486a21020240200041786a22042802002206417f460d00200341186a200041486a200641027441c8d7006a2802001101000b2004417f3602002002210020052002470d000b200128020421020b20012005360208200210340b2001200329030837020020014100360210200141086a20032903103702000c010b410041e0d70010010b200341206a24000b890303027f017e047f230041106b220224002000280204210342002104410021050340024020032000280208490d00410041c3d7001001200028020421030b20032d000021062000200341016a22033602042004200641ff0071200541ff0171220574ad842104200541076a2105200321032006418001710d000b0240024020012802042205200128020022076b41386d22062004a722034f0d002001200320066b109d01200128020421050c010b200620034d0d0002402007200341386c6a22082005460d000340200541486a21030240200541786a22062802002207417f460d00200241086a200541486a200741027441c8d7006a2802001101000b2006417f3602002003210520082003470d000b0b20012008360204200821050b0240200128020022032005460d00034020002003109e011a02402000280208200028020422066b41014b0d0041004199d7001001200028020421060b200341346a2006410210041a2000200028020441026a360204200341386a22032005470d000b0b200241106a240020000ba105010c7f230041106b2202240002400240024020002802082203200028020422046b41386d2001490d000340200441306a2203420037020020044200370200200441286a4200370200200441186a4200370200200441106a4200370200200441086a4200370200200441206a4200370200200341003602002000200028020441386a22043602042001417f6a22010d000c020b0b2004200028020022056b41386d220620016a220741a592c9244f0d0141a492c924210402400240200320056b41386d22034191c9a4124b0d0020072003410174220420042007491b22040d0041002104410021030c010b200441386c103321030b2003200441386c6a21082003200641386c6a22092104034020044200370200200441286a4200370200200441186a4200370200200441106a4200370200200441086a4200370200200441206a4200370200200441306a4200370200200441386a21042001417f6a22010d000b024002402000280204220a20002802002205470d002000200836020820002004360204200020093602000c010b2005200a6b210b410021010340200920016a220341786a2206417f360200200341486a220741003a00000240200a20016a220541786a220c280200220d417f460d00200241086a2007200541486a200d41027441d4d7006a2802001102002006200c2802003602000b2003417c6a2005417c6a2f01003b0100200b200141486a2201470d000b200020083602082000280204210320002004360204200028020021052000200920016a36020020032005460d000340200341486a21040240200341786a22012802002200417f460d002002200341486a200041027441c8d7006a2802001101000b2001417f3602002004210320052004470d000b0b2005450d00200510340b200241106a24000f0b20001049000be00203027f017e037f230041306b220224002000280204210342002104410021050340024020032000280208490d00410041c3d7001001200028020421030b20032d000021062000200341016a22073602042004200641ff0071200541ff0171220374ad842104200341076a2105200721032006418001710d000b024002402004a722030d00410021030340200220036a2106024020002802082007470d0041004199d7001001200028020421070b20062007410110041a2000200028020441016a2207360204200341016a22034121470d000b024020012802302203417f460d00200241286a2001200341027441c8d7006a2802001101000b2001200229030037000020014100360230200141206a200241206a2d00003a0000200141186a200241186a290300370000200141106a200241106a290300370000200141086a200241086a2903003700000c010b20002001200310a2010b200241306a240020000b4c0020012002290000370000200141206a200241206a2d00003a0000200141186a200241186a290000370000200141106a200241106a290000370000200141086a200241086a2900003700000b4c0020012002290000370000200141206a200241206a2d00003a0000200141186a200241186a290000370000200141106a200241106a290000370000200141086a200241086a2900003700000b7801017f20012002290200370200200141206a200241206a2f01003b0100200141186a200241186a290200370200200141106a200241106a290200370200200141086a200241086a2902003702002001412c6a2002412c6a22032802003602002001200229022437022420024200370224200341003602000be80401037f230041c0006b22032400024002402002417f6a220241014b0d000240024020020e020001000b20002802042102410021040340200341086a20046a2105024020002802082002470d0041004199d7001001200028020421020b20052002410110041a2000200028020441016a2202360204200441016a22044121470d000b024020012802302200417f460d00200341386a2001200041027441c8d7006a2802001101000b2001200329030837000020014101360230200141206a200341086a41206a2d00003a0000200141186a200341086a41186a290300370000200141106a200341086a41106a290300370000200141086a200341086a41086a2903003700000c020b200341346a41003602002003420037022c20002802042102410021040340200341086a20046a2105024020002802082002470d0041004199d7001001200028020421020b20052002410110041a2000200028020441016a2202360204200441016a22044121470d000b200341296a2104024020002802082002470d0041004199d7001001200028020421020b20042002410110041a2000200028020441016a36020420002003412c6a220210a3011a024020012802302200417f460d00200341386a2001200041027441c8d7006a2802001101000b200120032903083702002001410236023020012002290200370224200141206a200341086a41206a2f01003b0100200141186a200341086a41186a290300370200200141106a200341086a41106a290300370200200141086a200341086a41086a2903003702002001412c6a200241086a2802003602000c010b410041e0d70010010b200341c0006a24000ba00301057f230041206b2202240020024100360218200242003703102000200241106a10791a0240024002402002280214200228021022036b2204450d00200241086a410036020020024200370300200441704f0d02024002402004410a4b0d00200220044101743a0000200241017221050c010b200441106a4170712206103321052002200436020420022006410172360200200220053602080b0340200520032d00003a0000200541016a2105200341016a21032004417f6a22040d000b200541003a00000240024020012d00004101710d00200141003b01000c010b200128020841003a00002001410036020420012d0000410171450d0020012802081034200141003602000b20012002290300370200200141086a200241086a2802003602000c010b0240024020012d00004101710d00200141003b01000c010b200128020841003a00002001410036020420012d0000410171450d0020012802081034200141003602000b20014100360208200142003702000b024020022802102205450d0020022005360214200510340b200241206a240020000f0b2002103c000bd80501097f0240024020002802082202200028020422036b41306d2001490d000340200341086a22024200370300200342003703002003420037021820034200370310200341286a4200370200200341206a4200370200200241003602002000200028020441306a22033602042001417f6a22010d000c020b0b0240024002402003200028020022046b41306d220520016a220641d6aad52a4f0d0041d5aad52a210302400240200220046b41306d220241a9d5aa154b0d0020062002410174220320032006491b22030d0041002103410021020c010b200341306c103321020b2002200341306c6a21072002200541306c6a22082103034020034200370300200341286a4200370200200341206a4200370200200341186a4200370200200341106a4200370300200341086a4200370300200341306a21032001417f6a22010d000b2000280204220920002802002201460d01200120096b210a410021020340200820026a220441506a2206200920026a220141506a2205290200370200200641086a200541086a280200360200200441606a200141606a290300370300200141586a410036020020054200370200200441686a220641086a200141686a220541086a28020036020020062005290200370200200141706a410036020020054200370200200441746a220541086a200141746a220441086a280200360200200520042902003702002001417c6a410036020020044200370200200a200241506a2202470d000b200820026a210820002802042101200028020021020c020b20001049000b200121020b200020073602082000200336020420002008360200024020012002460d0003400240200141746a2d0000410171450d002001417c6a28020010340b0240200141686a2d0000410171450d00200141706a28020010340b200141506a21030240200141506a2d0000410171450d00200141586a28020010340b2003210120022003470d000b0b2002450d00200210340b0b0bbc1606004190c0000b766661696c656420746f20616c6c6f6361746520706167657300756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f7200656e636f756e7465726564206e6f6e2d62617365363420636861726163746572005055425f424c535f000000000000000000418dc3000b9e0200000000000000000000000000000000000000303030313032303330343035303630373038303931303131313231333134313531363137313831393230323132323233323432353236323732383239333033313332333333343335333633373338333934303431343234333434343534363437343834393530353135323533353435353536353735383539363036313632363336343635363636373638363937303731373237333734373537363737373837393830383138323833383438353836383738383839393039313932393339343935393639373938393900000000000000006461746173747265616d20617474656d7074656420746f20777269746520706173742074686520656e64005349475f424c535f00000000000000000041abc5000ba605000000000000000000020000000300000005000000070000000b0000000d0000001100000013000000170000001d0000001f00000025000000290000002b0000002f000000350000003b0000003d0000004300000047000000490000004f00000053000000590000006100000065000000670000006b0000006d000000710000007f00000083000000890000008b00000095000000970000009d000000a3000000a7000000ad000000b3000000b5000000bf000000c1000000c5000000c7000000d3000000010000000b0000000d0000001100000013000000170000001d0000001f00000025000000290000002b0000002f000000350000003b0000003d0000004300000047000000490000004f00000053000000590000006100000065000000670000006b0000006d00000071000000790000007f00000083000000890000008b0000008f00000095000000970000009d000000a3000000a7000000a9000000ad000000b3000000b5000000bb000000bf000000c1000000c5000000c7000000d1000000404040404040404040404040404040404040404040404040404040404040404040404040404040404040403e403e403f3435363738393a3b3c3d40404040404040000102030405060708090a0b0c0d0e0f10111213141516171819404040403f401a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132334040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404077726f6e6720656e636f64656420737472696e672073697a6500000000000000000041d1ca000b810800000000000000000000000000000000000000000000000000000000000000000000000000000000000000424c535f5349475f424c53313233383147325f584d443a5348412d3235365f535357555f524f5f4e554c5f0000000000000000000000000000000000424c535f504f505f424c53313233383147325f584d443a5348412d3235365f535357555f524f5f504f505f0000000000000000000000000000000000bbc622db0af03afbef1a7af93fe8556c58ac1b173f3a4ea105b974974f8c68c30faca94f8c63952694d79731a7d3f117cac239b9d6dc54ad1b75cb0eba386f4e3642accad5b95566c907b51def6a8167f2212ecfc8767daaa845d555681d4d11000000000000000000000000000000006e756d626572206f662066696e616c697a657273206578636565647320746865206d6178696d756d20616c6c6f7765640072657175697265206174206c65617374206f6e652066696e616c697a6572005055425f424c53005349475f424c530046696e616c697a6572206465736372697074696f6e2067726561746572207468616e206d617820616c6c6f7765642073697a65007075626c6963206b65792073686f75642073746172742077697468205055425f424c530070726f6f66206f6620706f7373657373696f6e207369676e61747572652073686f756c642073746172742077697468205349475f424c530073756d206f662077656967687473206361757365732075696e7436345f74206f766572666c6f77006475706c6963617465207075626c6963206b65790070726f6f66206f6620706f7373657373696f6e206661696c65640066696e616c697a657220706f6c696379207468726573686f6c64206d7573742062652067726561746572207468616e2068616c66206f66207468652073756d206f6620746865207765696768747300746865206f6e6572726f7220616374696f6e2063616e6e6f742062652063616c6c6564206469726563746c79006665617475726520646967657374206163746976617465643a20000a0070726f746f636f6c2066656174757265206973206e6f74206163746976617465640000982f8a4291443771cffbc0b5a5dbb5e95bc25639f111f159a4823f92d55e1cab98aa07d8015b8312be853124c37d0c55745dbe72feb1de80a706dc9b74f19bc1c1699be48647beefc69dc10fcca10c246f2ce92daa84744adca9b05cda88f97652513e986dc631a8c82703b0c77f59bff30be0c64791a7d55163ca0667292914850ab72738211b2efc6d2c4d130d385354730a65bb0a6a762ec9c281852c7292a1e8bfa24b661aa8708b4bc2a3516cc719e892d1240699d685350ef470a06a1016c1a419086c371e4c774827b5bcb034b30c1c394aaad84e4fca9c5bf36f2e68ee828f746f63a57814780041d2d2000bd005c8840802c78cfaffbe90eb6c50a4f7a3f9bef27871c6656e636f64656420626173653634206b657920697320746f6f2073686f72740062617365363420656e636f6465642074797065206d75737420626567696e2066726f6d20636f72726573706f6e64696e6720707265666978006465636f6465642073697a65200020646f65736e2774206d61746368207374727563747572652073697a652000202b20636865636b73756d2000636865636b73756d206f662073747275637475726520646f65736e2774206d61746368007075626c6963206b65792068617320612077726f6e672073697a65006461746173747265616d20617474656d7074656420746f20777269746520706173742074686520656e640000000700000008000000090000000a0000000b0000000c0000006f626a6563742070617373656420746f206974657261746f725f746f206973206e6f7420696e206d756c74695f696e646578006572726f722072656164696e67206974657261746f720063616e6e6f7420637265617465206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e7472616374006f626a6563742070617373656420746f206d6f64696679206973206e6f7420696e206d756c74695f696e6465780063616e6e6f74206d6f64696679206f626a6563747320696e207461626c65206f6620616e6f7468657220636f6e747261637400757064617465722063616e6e6f74206368616e6765207072696d617279206b6579207768656e206d6f64696679696e6720616e206f626a656374006461746173747265616d20617474656d7074656420746f207265616420706173742074686520656e640067657400000d0000000e0000000f000000100000001100000012000000696e76616c69642076617269616e7420696e64657800756e6578706563746564206572726f7220696e2066697865645f627974657320636f6e7374727563746f72000041000b04282c0000000000000000000000001f5db111b030c2b86ec1d0f7de9234cbb55e88518781e1cf20919489cd2457b804000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b010000000000ea3055e42604000000000000000000000000 +DMLOG CREATION_OP ROOT 0 +DMLOG RAM_OP 0 eosio abi update setabi eosio 453263 361 +DMLOG DB_OP UPD 0 eosio:eosio eosio eosio abihash eosio 0000000000ea3055d7abd75d188060de8a01ab2672d1cc2cd768fddc56203181b43685cc11f5ce46:0000000000ea3055d2303fb7b300acbce6134a774ccdbe88c2bea499aaca2a8a9d9664ba5f2c7f1c +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304003,"value_ex":160609,"consumed":17801},"cpu_usage":{"last_ordinal":1262304003,"value_ex":302684,"consumed":4101},"ram_usage":453263} +DMLOG APPLIED_TRANSACTION 4 8da953b51a2c5b2ecf7e2f1d4bc2c929f2d92e83090e48e78f46ee3143a8015c04000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b0100d00700008c01000000000000000060040000000000000001010000010000000000ea3055c843ae0bfe7bfb9d664537bcb46ba4ffa14d4a4238f7c9b9e0183ffb6959b3441d000000000000001d00000000000000010000000000ea30551d0000000000000002020000000000ea30550000000000ea305500000000b863b2c2010000000000ea305500000000a8ed3232f3130000000000ea3055e9130e656f73696f3a3a6162692f312e320117626c6f636b5f7369676e696e675f617574686f726974792276617269616e745f626c6f636b5f7369676e696e675f617574686f726974795f76301c086162695f686173680002056f776e6572046e616d6504686173680b636865636b73756d32353608616374697661746500010e666561747572655f6469676573740b636865636b73756d32353609617574686f726974790004097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d086163636f756e7473197065726d697373696f6e5f6c6576656c5f7765696768745b5d0577616974730d776169745f7765696768745b5d1a626c6f636b5f7369676e696e675f617574686f726974795f76300002097468726573686f6c640675696e743332046b6579730c6b65795f7765696768745b5d15626c6f636b636861696e5f706172616d65746572730011136d61785f626c6f636b5f6e65745f75736167650675696e7436341a7461726765745f626c6f636b5f6e65745f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6e65745f75736167650675696e7433321e626173655f7065725f7472616e73616374696f6e5f6e65745f75736167650675696e743332106e65745f75736167655f6c65657761790675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f6e756d0675696e74333223636f6e746578745f667265655f646973636f756e745f6e65745f75736167655f64656e0675696e743332136d61785f626c6f636b5f6370755f75736167650675696e7433321a7461726765745f626c6f636b5f6370755f75736167655f7063740675696e743332196d61785f7472616e73616374696f6e5f6370755f75736167650675696e743332196d696e5f7472616e73616374696f6e5f6370755f75736167650675696e743332186d61785f7472616e73616374696f6e5f6c69666574696d650675696e7433321e64656665727265645f7472785f65787069726174696f6e5f77696e646f770675696e743332156d61785f7472616e73616374696f6e5f64656c61790675696e743332166d61785f696e6c696e655f616374696f6e5f73697a650675696e743332176d61785f696e6c696e655f616374696f6e5f64657074680675696e743136136d61785f617574686f726974795f64657074680675696e7431360b63616e63656c64656c617900020e63616e63656c696e675f61757468107065726d697373696f6e5f6c6576656c067472785f69640b636865636b73756d3235360a64656c657465617574680002076163636f756e74046e616d650a7065726d697373696f6e046e616d651366696e616c697a65725f617574686f7269747900040b6465736372697074696f6e06737472696e67067765696768740675696e7436340a7075626c69635f6b657906737472696e6703706f7006737472696e671066696e616c697a65725f706f6c6963790002097468726573686f6c640675696e7436340a66696e616c697a6572731566696e616c697a65725f617574686f726974795b5d0a6b65795f7765696768740002036b65790a7075626c69635f6b6579067765696768740675696e743136086c696e6b617574680004076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650b726571756972656d656e74046e616d650a6e65776163636f756e7400040763726561746f72046e616d65046e616d65046e616d65056f776e657209617574686f726974790661637469766509617574686f72697479076f6e6572726f7200020973656e6465725f69640775696e743132380873656e745f747278056279746573107065726d697373696f6e5f6c6576656c0002056163746f72046e616d650a7065726d697373696f6e046e616d65177065726d697373696f6e5f6c6576656c5f77656967687400020a7065726d697373696f6e107065726d697373696f6e5f6c6576656c067765696768740675696e7431361270726f64756365725f617574686f7269747900020d70726f64756365725f6e616d65046e616d6509617574686f7269747917626c6f636b5f7369676e696e675f617574686f726974790c72657161637469766174656400010e666561747572655f6469676573740b636865636b73756d323536077265716175746800010466726f6d046e616d65067365746162690002076163636f756e74046e616d65036162690562797465730a736574616c696d6974730004076163636f756e74046e616d650972616d5f627974657305696e7436340a6e65745f77656967687405696e7436340a6370755f77656967687405696e74363407736574636f64650004076163636f756e74046e616d6506766d747970650575696e743809766d76657273696f6e0575696e743804636f64650562797465730c73657466696e616c697a657200011066696e616c697a65725f706f6c6963791066696e616c697a65725f706f6c69637909736574706172616d73000106706172616d7315626c6f636b636861696e5f706172616d657465727307736574707269760002076163636f756e74046e616d650769735f707269760575696e74380873657470726f64730001087363686564756c651470726f64756365725f617574686f726974795b5d0a756e6c696e6b617574680003076163636f756e74046e616d6504636f6465046e616d650474797065046e616d650a757064617465617574680004076163636f756e74046e616d650a7065726d697373696f6e046e616d6506706172656e74046e616d65046175746809617574686f726974790b776169745f776569676874000208776169745f7365630675696e743332067765696768740675696e743136110000002a9bed32320861637469766174650000bc892a4585a6410b63616e63656c64656c6179000040cbdaa8aca24a0a64656c65746561757468000000002d6b03a78b086c696e6b617574680000409e9a2264b89a0a6e65776163636f756e7400000000e0d27bd5a4076f6e6572726f7200905436db6564acba0c72657161637469766174656400000000a0656dacba07726571617574680000000000b863b2c206736574616269000000ce4eba68b2c20a736574616c696d6974730000000040258ab2c207736574636f64650070d577d14cb7b2c20c73657466696e616c697a6572000000c0d25c53b3c209736574706172616d730000000060bb5bb3c207736574707269760000000038d15bb3c20873657470726f6473000040cbdac0e9e2d40a756e6c696e6b61757468000040cbdaa86c52d50a757064617465617574680001000000a061d3dc31036936340000086162695f68617368000000012276617269616e745f626c6f636b5f7369676e696e675f617574686f726974795f7630011a626c6f636b5f7369676e696e675f617574686f726974795f763000000000000000000000008da953b51a2c5b2ecf7e2f1d4bc2c929f2d92e83090e48e78f46ee3143a8015c04000000033b3d4b01000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b010000000000ea3055690100000000000000000000000000 +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":3,"value_ex":82933334,"consumed":9952},"average_block_cpu_usage":{"last_ordinal":3,"value_ex":401659723,"consumed":48101},"pending_net_usage":17800,"pending_cpu_usage":4100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1050675,"virtual_cpu_limit":200400} +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":4,"value_ex":230575556,"consumed":17883},"average_block_cpu_usage":{"last_ordinal":4,"value_ex":432479225,"consumed":4499},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1051726,"virtual_cpu_limit":200600} +DMLOG ACCEPTED_BLOCK 4 04000000040000000300000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add801000300000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c2d5b1b639d6ae94fcdd0536b224644931573d1ccb2a0c548613cd1feea18888b220cd08b8b04e606d8bece507e2cf86239f534f1f75f7d231de54a83ef11f5270300000000000000010000000000ea305504000000010000000000ea305503000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010000000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b033b3d4b0000000000ea3055000000000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c098ab5b4fa3ae6d0e17739b6e5fbcb7f54e605eb094b2e7c5f523734661c791b46b69a63382c85627f8fafa15bb601dda980ba3edcad5043ab2f6d7610f278310000000000010000c105161a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1618b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d63900020163e67fdaf8cc1d45374d696b46c0cc44b2b9a45eaacf726ec78eba55faf2e6d2ad4a24d5fbc0fd07c0dca3637b0f29efa2d0a345fcae866f02861b9c8dcd9170000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef261980000000000011709e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc160ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d63901a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72412652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670735c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c25443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b463320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974286bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1dfce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40001033b3d4b0000000000ea3055000000000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c098ab5b4fa3ae6d0e17739b6e5fbcb7f54e605eb094b2e7c5f523734661c791b46b69a63382c85627f8fafa15bb601dda980ba3edcad5043ab2f6d7610f278310000000000010000c105161a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1618b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d63900020163e67fdaf8cc1d45374d696b46c0cc44b2b9a45eaacf726ec78eba55faf2e6d2ad4a24d5fbc0fd07c0dca3637b0f29efa2d0a345fcae866f02861b9c8dcd9170200d0070000a5100101001f69076f2aa780105fc742ccd9527398f328419cb1ca99e1545a39285a640abed50d26f4989f29bae9a87bd0ab1e82f3810164b94ad25ba4ada6bc031b922ae1f70100fe810178daed7d0b7c5d5595f7d9e771ef4d4ed29c3e094d8173af51526c2149d3348d283d1d69a1055a7938383ed2d05cdadca479dcdca42d962640612aa2160479f9e0a52daf914ff01b153e0ca888885aa1607518edf70d3fa733e25867fccda032f45bffb5f639f7dc246d8a51c76f3ed39e7bf6d9673fd65a7bedb5d75e7bed7d92ffdbfd80650cbffe811906fd29fc18ff5c7f11fd2e7bf3b55f78321e61ecf97963e3d33f318b518fd243fbc0262452fb95bfce30d699c3c3c63a6b38fc55c3ebec61b92786877528391c05ededf44777436da784dbb75336277c6746bfdb75a4b12e81f41cafb6536548cf45506ddb39a1a21835cc25222fbda062191a4e95d2555344320aa9cbd5e52178009b4b950793329923c90556b667c869bfa4375f300c842bb2bd039dbd6ded0303598a52884a6cca6e1ac8160c0b0f497ad8d43b949527bc5adfb7551e527df9ce9ec2405bb7642bbfa47ba0edd2beb64dbd1d861d456c686cdbd4de571ad1ded16138887011d1d7de49e56c30121cd37149dba59d3d1d6d9dcd4d461231defac17c3edb5368cb67d7673b87b2792385e84a81b86d60637be3e266c3e4ccc5b8068989a55adcd028719e8ecb77f66537753434d74b74225e52591b51646377a17391518667bb5864225e56d998425c029288956febca6e35ca11e31325db2ee9ee5ddfb57e637b670f619b6fdf942d64f3407c7d57b64388e76f982c998b6473505a5fbeb7af7720db8140c7e07a4a6354705386942a746eca0a9566a1d8f6f505a25b2c3517352324430ce24a2e869a60a0d09bcf721b4ce3a87cb67fb09362da070b1b8d2a444d0324d452eddd9d97a14c154512570c7576673710cc1e226722329f1de81dccafcfb675776eea2c0c18d3f1e6f889b169cb6e316670cebe7c96816f2f64db2ecdb61706f35963263782b09e3cccea1c08dfb685c93b8c59d2d6f4dcdbd3d6d15e686f1b20488dd91c4de576b4c5de0949a6c7fb42dbfade8eac31872b2889df941d1868df9095062f276281c62015778a4a8a18eceb00c4883baed85136125acaba6cab513d6b963bd3983593fe2ccb7567bae5c7cf5596ab6ccb5233ab66baa65933cb346616ffcd9b39d33cc135dd139532e91ffdcd5427c99fabf44d9de4d23f0ad19fe1d2b3c2457fa6aba844936eb6a3fad4cc998ea50c9598630dab6064d470878de0aefdd12d59a69cf6bebeeeadc6c2b25a6504ca9d71c1457ff99ef7beff7a7583fab8ba497d42ddac6e51b7aadbd467d41dea4e55fec4e3e656cff01abdf3bd0bbd777b177b7fe5bdcffdb565f886b7ca5be01bbe7a97bd6cf9c8c8aff7ddbbe3d6976e1bf64d7a46b4977728fac1173f72d3edf75e548c1d0863effffe334631ba80e891835ff8f9e8f31f8e9531a8537fe92bbfbcb0183b44b1af7e6edf737bee7cfe8958199bb98c917d5fbfeb87cb8bb15b2476e43f6e89c77e5062ffe6373ffee77b62f56d93e8fff91f2fc6135f8ed82ffdfb3d37df168fddce69473ffe9b92b4c3cef243f7fde4c3dff9a707e3b157294efc89effc6c5f3cfa6a891ef9d103b7bff89518187f2df17bbff0f0a3b73616a37752f4ae7b7ff8c3bfbfeed692e41f528e6f0896a35fb9e505e37c7b59c0cf3f7cfee19f5ff90bc33bd91df7e7c4d2876d58420f2ae18ad21202c35be23ea29435ec1b69652ec33fdf08acfe332b0d84161a4625f1856f7455b914af7269652df3152750bea2d769931e552e63a5956f9e596956102b53ccb630a59dcba83ad3c898c1f0806f0687fff3b79b73fdc1c81557eed8824a5caa8562553f55a3b81ae45a68a892722b8d0a938ab582e15cdaa23b2574f3043440ac3402a38a10098c3af33a00ef0d65289fd94c4f12bb63598672e482aa5cd0d79f319a29826bf50abeb11ad929ac7248cf55a6158052cb8c607825de06bb460d4f51adeeb54aea2440bb336a4cc50a159b047049c5845558b1ad2bb650b12d155b61c580206d3235c6565c44d24716e599737da29be1362b35fc26c2b72b43f1cd6605bda44a2b88da4554479621163507c61906aa72172973186de91b356872d0db3b2d6354aac04833d82341c644f39ad30d839bd37013100e0d6e99818ccd5437870c84e8ff9da04bb0e309c35b6c2e637204198b40bef109236d057ca79a0317dc85d7bb828c1df82bc162c10ec2dcabe7e4c369879a99a0b0892e5c0a950f222173c0d5ed0af0dea1489b29b72b2066b003b340740f76a89c0fe028b79df3193806aad977dcb30121410302336f5bc2dd199bb938e3ac043f73eb0b9f832986898d34ebd9bed3c5ace7fe9a0434335e70f8891f9cb65af8d67b1b5a87e2469efcc1693dc4ecb87777a13e49faf19fbd64495a33a8cfb51a5447daa8506e60a6255215823bbe464c6a7af65c1745e6324064d7778d9e8c0a0e1fb6fa83970d941c4f48259b39cefeb2d1dd8557af2361fc3d51c222c4a89ceeae7841d6d8821228c83a7241a99cfb234b5057c1c7be8b9ec3c17f884265ab5956284190c9c85451e361ae828c002c1980dec3eda2827f4c4a0956ac04c6ad6f6c0996946c4d480deb68d4e09a4155dd362af8ea8eb787187ce623891208a8914cb0a449d2686c6d15222682c30ffc1f3bcc146ba61806d611db27b0a87c25341addf1f69e09d0b1c6a11fd82428288b090e51c22393b4ede464711dc3330cf751190a961b4b4c83db9c3041473259281336fdd2c4f34d4e01199dca91c041af2301856475a64d99fad680d9ebcc949099e5ad7b76257a9527d2d05b84aee6939cb67deece792e043d38253401af874494d145a1671039751f317381c109aa5c00ff0da5ec12e023d00926ddcd0335e4d34f7f265193717c1310d1ad8bc21605acb36b887845d8931a764b60b734ec2982ddf1593001f65404bb13c16ec561a7821976c74fb28cf113353e9a96d1a097a568bc2aa34cd0e3ab2e8650648890ffcc4a16f2a9b4d483d2536ed09b2e635efce9e1c387d18702375dc6dd2e538e36a256a45b392157da08e932d72f235c4cca6ca364d34ff936a0a288844490fc48fa091d4734eaa2b1d4eeca24a3b7243752b83b9450275341f90a7a9df29a5c69e11428422931068262659a6228807117cc9fd598f71e15f36484791298a7c6629e12cccb8a98978dc73ce5faa952cc9311e6c012385a711c931a47ab14c764098e9a13049bfda64a8ab4044345a8408487c8e0617b3f6b3578b4d33670a30443693b28a7f1867e1c8048cc4ed7ea1aeae58c86123490d30134c4442b741d4e500eed41a1481029a1a946a31ff30c717f8e304f482e482e6a69d63cc642874c540c5dc8889b263b06525777ea28b59d5602fcebaa1f0868f1c84da0007382686886243e53d38ec2292ec04f0809358700ca9098221ed09d5ca1ecd3c22721c816fab31d925413d411982cc064011e0bb414a8096fe12f96291c6547383b117b39d2392168202649eee4c030635892255889c042b6986802369c196c6541b501677a6740affa2e8b2ce59d1a2bf8ff657c7697b60cd803aa9db0b449104bc945b46c41cb7445d282a57c7b25c3416d4e4c6f43f1f362202a1a907dab32029006b748d01a11dc396623e06f84632510c088a380d2274c227c11501b803a1127339436774357ba1f1197a0e74e33a66112d206099218347e609408dbc089da2011b5414297a7256802228a1b85c606cf9a4bd853d84469745b4dc304687f6e0d467ca4c57b24230d996837beedeca8edc21ec345c5db2ec199fd4417c080764ddc80b693ae358d8760aac6f44ef5ce749f5425642ae14c5b588b99510b21c5325b6bd8255c3886020e28408d8bd626ce25f1dc25e53241148fed248f0953d40bc51ba3b4e656eb68ac9a08593542e99bc041e64966ad28db4ece6b65f9a87504ad34b0ecc75b02a628b44912af0e1932a68f088b8842e06865a628bc62ca8c4c713174ace09ee0109f5a3c8924d4509e9e7688bcce18c4fd3c85e67718af6a3522a2a295634a642cb10ebed33d4f2680070cd2fe183315ec377290a38a4708401386649a72ce5c7ed41a113171973787620e1854897b574acf6cea2089598feb0a92ab782a7ae8eb34150dfbaae87dac3a7214eb895c19855897e72228b735716e9aa0c572db51ee8a29e5f6a6947bd69472574f29f7bc29e5f6a794bb764ab9eba6947bc19472d74f2977d39472b74c29f7e953ca3d6a94665747ca6ec6b29b51f62729bb9a3cbb1a939d459bfb1b65a961733bcb9d7a529145a0f2ccc8f3961ba4b67a6918924c0a3b161bbb72d033127ab22d33aee5a96b48ee271a0c6347c65edeb27307a9370a83298df8f48ec75fd11e57306cf78a058e722eb1584994bc4b2c03d5c1e6473215b5a35656a031718585498af156e8093e576fc19a03236621d892f3a6138ca9dc7ccbc0340d379b87cf2596a1eb73d91a507b74dc67fd89e13eebf78cfb67a3415478e731aae3b821186e832b86617c9bb34a0f97a40f9e4580d37c8c74c3211ae44d1aac4d8c3e7f6118231fbca1c5ac805207f3da2a4c83839191916dbe35d82fa69058ba140f9cc679541a0cd95b380182af59505050c5e1643feb46e6e3ace7b92f4e5309adaf04cf2bcc07eba9491efa06358ab58c995a0d11b0b017a24741fda8c3407d76c6ae81c51926e601586cec1a1878a9607acdf57291dbd688353c784ef5641cfaedce24bad252c9a1a77ef74a90b92b30075931302827226057a19927a9cdb3d0ddb9680bb8e045860060b5545428d5bda252b1dd932a7e4ca1f1097f36c153853d3467d7492d9d1450965362946285a558d1ab0a02bd2793800ca144f29e14c7e8bd17bdb7277c3f6392fc7326c93f2f7a9f98f0fd8993945f3b49feba49ea3f6592f24f9d247fd324f5b74ef2fef449ea27197ef4029e322681f06963922abe3b5915cf4d56c5fec94a38504c909a30c1cb9355f1d3c9b0f8e7c94a7865b2120e4d56c2ab9361f1fa6474b8524d52c54e354909d7aa49b0f8e86455dc3859159f98ac8a4f4e56c25d9325f8dc6440ee990c8607262be10b93c1f0c864557c65b2121e9f0c86d1c9aaf8fa64253c3559097b8b0948cab3ec270580ade89c8ee798814c6561220dea5764cc9a341bc37332b2d1949dd260c6e99beedf26695ca5e1df67b527b2d5b0b1dfaed46b81293d8d96d1124600aabe5acfb02d7a65793ecfab4d9e7aead9bc2d16cdd2a9b485a9f42c5862792aed61dc6a36ab59a5125b82a5a7e39e58e5551eb9f82daf0cb0311756536dfbb0ea287702d8267d6ba1e16578110003305ecdf27961801eedd024066b84a5573dca571417076dac0adab22ac84bc1e68a4a99c243d559814cc82d8b0d6c2256be539ce543b3b639319a804181d6a6b4058cd726987004af862d27e6a804bd13d36a0e25d97e32cdd617e898b09753ed5dde4a98f5182146129a082aeecfa89a66d2bb2ca143d2c7630a8ff3ad94687ba4b679efe25c6265e3945e9358780ca6bdb420d7481382a1c0eaf6026949588465b95fd1bb3595989d24d9fa4e11a0e04334dc0849460d59db7e215cdb36e3ef1363de4b6d09d288087b9baaeccf07d585a07ad07ba7e8b45c1ddb9c288c1252c1f9392aa68a0ddbd33c71228cfe1457999abcbea1a0aa1f5516a2faa034c76ab3343ee70349aecd9ca8362a2b51e06506e78844904ad9c6aaeb72b5e3846f9f5dc9f64b6a272f5cd512860f97dd90db6713914c444e37e092f0d0335430faeec3a1413630161afb57f1025ec0ddf4d955b2a4be6f55f0ef04c6a919035db3e803c2bdd2d0bd5261ad443c09f6ae0a9899a95ba208d8e0f6ad8262ced2847a24c57ee719f88518bce865a278dfe0b52e136e18062fac52e523546dcbdc803d0af6af72abdce059029b1038de10789f5d25bd01ce00fb56794d6e293a07e3e8bca4d179795570fb37de003a078ae8bca4d179b9041d8afdfb1274a8f871e89863d039c8e8bc548ace4b31745e1e8fceab71745ed1e8fc6a55f0da336f009d4345745ed1e8fcaa041d8afd79093a54fc3874ac31e8bccae8bc528ace2b31747e351e9d4756c7d0d9b35ad0796875f0a56fbf01741e581da1832280ce43abe3e850ecbddf8ea343c58f43c71e83ce23ab81ce9e6f97a0b36775119d8756039d8f011d6d5f356a75b243abf52278f0ab6fe3956763ce0f1c5fd638be823bbd580483cbcb1aea837c87c68b045afbc5623ed7476ff90ddd9d31b01e5a0dbbb918684f546a583c685e5e9d510c2b86c583ab7d057887d87c0b200f9ca3691f8cda04077af93942d0fd7ca768aaeda573b4d86627a5c8144da3c6a88c4e0400c8c786ec1ce74d8c81eec0396e0caa7de7c4a0da7f8e40f50fc7a9d340c511ab2b93a81553042c04743b8946e3e0f019109e4e701876283f193843e91449cd57cd1cc61b12e2c1a162f0603178a018dc5f0cee8d82b83d6dca7d145a0f8dc25f25b64a97513242f671c4117eff8bef416a2853de6a3c460fc14e2ae3fe873ff6daf5773f7ce8ef74b9231477f89e2feffcc6f53fbaf2f661897b55e596ffeb4f1ffd87bb77ffe7eecf4adcf27f7c76f7de17f7feece147e9f99092da0f2ad448c422eaa6e1bf94b4b05e94e455aa037879070d9bc99c5f46d4c652521dc51265789490b75e1bb285c550e6c47c2a76f988bd0dc5634c7329f90a4e3e4a83a28b81863075a12ab8d461c245d723d46cca52c4ef54b9f65d63ef3c2cf6b9ee845594ffcee54f549afa7d96161c0df72395845adc7095cf4ebbbe932e3b0275dda950579ad02f0375cb8e485d2756feb1c1efea04ebf0a2ce3c04b0aa875a8d5da644fc4a655c8eb85147bcaa32e51c718b8e784d652a38e2933a62c4cc5472c41d3a62879999c611f7e8889d26694bd543992a92dc3aea3a3363719a0710413a0c051f44d0e4e0bd084e836fa5d76a7c160f957898de6adc89870a3ccc68353e8587723ccc6c356ec5838b8759adc64d26138c1ea87b5d2f75a4a8e0bf913a10bc4fea201130bbd5f89cd4410f735a8dbba40e7a38aed5f8b4d4410fd5adc66d52073d1cdf6a7c42eaa087b9adc60d5c47abf179aea0d5b89f4b6f35767359adc667b89856e3762ea1d5b89933b71a1fe78a5b8dbb21830ed1d8e56ba12c4fd54579484f5e5ccc6d915b9fb85248029a1051390b49e4a56b88c1e6d175025d27d275125d3e5d69ba3274bd89ae5abade4cd75be83a99ae3abae6d3750a5d6fa56b015d0be93a95aed3e8aaa7ab81ae46ba16d1d5044f1166ff063f39d06a0c50a8d19f4ba13c8516f96514eaa7501360da650ea031fae879a1efd29bcd143ad53f9e4243143acd9f45a1410ad523f58d263d15e869be5f4ea10f52e814bf9a429751e8adfe4c0a6da5d002a4bd0569891cfe9bfd0a0a0d53e82dfe7114da4ea193fd1914ba9c427548fb49a4dd464f69bf924257a18764fc3914bc12c137f9d329780582f3fc2a0aee44f0047f1a05ff1ac113fdd914bc06c1937c8f825723588ba2ef40d12378f4f1780f1e77700f5c687c48e1f75e447d48475dcb51f721ea5a1df5618eba1f5108829f76e957d7f1ab07f0ea3a1df5118e7a10511fd1511fe5a8bf41d44775d4c738eaf38842b0648cdb48cff1f18da442c9d876f198b16dadb0dc5945d9451a40da8d09400ec5c7b46545b9179779cb44309d2572e92c19cb7c5e2a9f78183b82c0d5f595c8da63acf2283256975a532cb258e0110a4b842275ad96a87df278a196a70579bc3853c68f5be4f17d5ab86e93c7755ab43213d1738796ac3bf4f3462d5877eae7ee4c153f334754b1e4644e98c64166b44a0e329356709079bb9c83e814651cdaaa45a774c02487fab94c48ca8f2a2d2935a75672f01aa5a5a4ee3ae51cdcce652274991695d2bd931c8258a8124684706426afd43dab5c7a6e99747657e44352844a05f7542b700a992414fc75391178bef2935d44f09306fd643f894ef1704ac9b28882831df1fe4e959e87fb1e335d85fb5fabf409b8ef36d3d370bf46a54fc4fd73667a36ee57abf449b87fd64c7bb8ef5069eed057a9741af7bbcd7425ee57aa7406f7bbccf41cdcaf50e937e17ea7999e8efb884ab35c184ebf19b7cf98e90adcb7a7df82dba7cdf471b85f9e3e19b74f99e919b86f4bb398fa607a3e6eb79be972dc2f4b9f82db6d66ba1af7ade9b7e276ab999e89fb96348bc1cde985b8dd6c1253d37d287d2a6e9f30d3c7e33e983e0db79bccf42cdc0b6916b303e906dc3e6ea2972c34f2e946dc6e30d37371ef4f2fc2ed7a13eaf342a32fddc43b034831b63033d963ab6a56f24d7645c122289c04d9cd85d451f68d86bd2d57c75e44d5050cc2856064e4b5647f9e07e460e475ab1ff6c2a13cfbce60e52c0d6f1cac7b4b9f83eaac4d87b62e99ca1aa5b6c786815a1844601c69e1f5313ba8dc0cd79da101faadda8cf5362a0a6fac60da667ab0e88d151c4f6fd83c99b6a3ba94aecba609d04bb2dae6c08d126b057848b223e4417928a36631b050810797da95276b78a8249e32b00882872a622023d8270fd3a9810dc88799e95984889799edcfc8ccc9fbb33266bf3fdb9f432431831337d3cfcccdd834530ef0ede0e05e5913845593712dcf1ce74fcb6035b1dfafc854fb567f1e68ce057ab33703bd2472a6729969396aeb69584d9ce6cf265c8e2314aa09728b009e43a081e5d8c5c0a62694b99f9ff48f23d274c35dd7afa6d0460ab9be45a10e0a55fad328b48e9d016753e87d14aaf2e750e8620a4df7e13070218566fab328b49666583485224e39a84c9aa56e17bf8265bc532718d1dabc7a30632d57db96b72c1fc5f4b0e5ec9a8cfd57e23dc80e52cb87aff6ed6dc2564189ebd4f2e16d94d5381fb25b5c1ba8682a00137512d49809b7f0be197a91e6d574834436af833f48394f37ce008f92f8c66d796ae7e9c63b38e451e8ed1caaa6d0e91cf229f4360ed551a89543f5145acaa1160ab5a052f7cb70a7a23e7147427c05ace00e3b475460e741f6aa602773319ab277ba213669f1ce32c422ed7d2038834ddba34afcc674413e1b568b9e8fec4340380f8b5d393860e5a827483e0accb7585d94f8fa30bebe34be2e8caf2b8df7c378bf34be3a8caf2e8df7c278af343e15c6a7a278583647d512eb80a5d38c22f10880f4127383c3c61bc1fd8cff36b8a3238200faf980a1338f2a2f89c89f383976350151b0b78df9473ac470b88589d849c4669c22135163224a4c448589283011f613615e8ab571a406a7e06712ff9d509ba819a509a53939e063d0f15288bd23012179b363a961b59d3d6e0e19a1f8f0e34b69588f19e6f51888321aefe01d64b1ef8eac5ff9366fffbced9547fea5c1575e391ca0cfaa141f57c35b9f210154cf2ea930e9bd20eb10b054d6e33d93ceeb90953cdb73dfc5c99ee16484166c8ab21a64c095c291b9a84110661261b83a9749ea3036cad4c3882894a396815b4d26859beda7babc73e1514ef1459a27e439a27712b46dd1a5cdb7ea05734e53c763933c6044e20a0cf14fd54952790c5a164666b6327bd9b9524e2a4e234d485daa372fe3349b4d4c4a2cbf5c8855b9e5db89ac6a1bfd5cfeee1a8281f7a272a5512b807e9471813843d599f332266efe1a9ed13858cc347d69ad80b39abeb42a710186d979b2e4c62c78c01026a69f0539ef521705ad4b1b614e83bda06517052f183aa1e335d74b4333662a0ebbc5b363d1c61c9a9dcde5e1de285ea635b18bc5f17597a292e04dc568cce3f2a9c2436c81de91b0ca42173898c54da868b57a730b20c0b8354f54a6157a9ba5136c11b505bab84d8f0405000bf755ae60f3ef46a4e1a5dde2ea32bbfecfcf38c1b0b8283e0cfe933d2c57d867f38e2aef34dea4c23b5b88bab2f195e601ac83a6b0149ca444f3e70ad3268469358f264078d27d63a6153f0ad5498817ee78639111dcfa03ee253c83017325e1ed6c9b826170524922f03f5b9abd1cd42cd152039fb22da76972e0c1e10e8ebb5093e1b957c1fe829c2a97aed44bc1ecb157018741d7af648741971d062be81d6f0971e130e84a2f754287c17f17084cbf024c668679c561d0618f7276517451adab57a3236749277418d4d543be5111a8b2e833c8dd92c0400f4abba8cb05d5a44ad72fc70b1f31e52c22e75b75f2502db2b6d8cdcb45acfab19e9ec0e24113b68b359b3e6ee85754fc32f1019817f21a754f9638c4366b78179f42d7720089ee5a3e3d25f4c6bc62d72ae3fa34b12d6256ea57e12ea4a4d7c0fcef73f348118877a40ff02a8c9f706f312d93c4b30a3d22b061b9566f34243927cdf09f2fc80237c908880691a0f7bfc86da3c4e5d21639c83f091186090843f8a751bb4454828b373b4844cf44485b2fdd2b8498add903de94dd15518d3f7e512f31afcdf13062f30888d57f1b62d9e0ddc1ccce36b735dc7c092e4848c2a6693938a0ce3b854b35b41834480c3a10834e24066d5e84769fb38a2eb2beecb730195c3422a9d9c7e92da32c2b5993862ba7cd9ab4c92168d2ecbfc19a34b28bc63d13a1dd240b603c98c37d1da689d91c82e563163251825623c971489788d239513ab8ca28a9bfe51a547d8d1c9b30327ad8b86a074554eb8803675cc51eb9fc74e8a4ab76e0656a271e5fadc2ab6a845f4b72a63a0abf6e5dc560efd8b1437a1e55e3fd81aba966028168d3390442cee010c8e795ece6505dc14922481fdd17f97ab3cbadc99229f4f5f699094c6e51df706fb394331caed4d6c9b62aa25e5750bd095e34f0b6b8ff85fb5e402f0cee4340b6cd4ac592cc0ef6ddf7c229ec92c16e3b99243695d2f42e0970d87d847d5c924175b7b7085ec83ccbc7ba1c8f8d1835aabb33a99cb8c418e8e90435f33d36bb242411b2e7d89dba5aef7bb43332ddca2465f049e568a011b7580c43f0cb4dc393c8900d32dc21c09ff4ba8f66a6517f83934d71a7b3e2427c3bcdbb4679d0e42a3549935c2561846ddaded9f436298ed58e6c714cc82e2125033095c05e43043d4194444e1ab4a2adcb103f5847057a2bf5f496774ad9585cfd7275c2264954c1d2c8bc1c16980ae9769039de093c8a56f0782c5b9a33565750bf29b862049e25c4092f3f0b4e908ddab855a47914a05e2f1e2a377d67fc7bf82b9b5a17357981d3d47d8a745f38931d30a11a4317f0b93a6f2345eee5c858aebda63055f0c877228f0063628f00388559a228ed33b927c323602f82d464fb4d19fdc52300b15c99ca638b17bb0558a843bb0558a15b00257bba048d51022878ec8d00f3f52230a31a98a74b80412c16644b81796c4260d8b1bdd9fc12c6e68440f49029f73da66e3c6e023033b4c5d1648e67c62984cb31bd80ca61067b98d2a4764455a3c95e956045913a95c5d869a8933dd7ace276420bc2e00a5318e5ebdf11ad0239583dcd54c1a3a18e1b6421513dc3060be4adf1abbc77cc95cd9c75e66bfcfe55799ff13845c6a9c94c9f3873c6a881d24aeff69b7e25ce28616584a2a651cf9e7e66257cec9d8598fbd3cfca4a33b40138b2d3de8329c023edd222bd643a1f6352678e58fe34dfa98161ca5b8a4620845efd4ed101edd5701f33a7456f0bf1accd6566089e5f6750474bf09c71ac788ecf5cc4f369a868313c2ba68467c5583c3ff9dda3e08906c720359374e2e1ab2fe696de870c1c9d9e294e8955a1714a5c3a59bf4fcb8c14a17087f110c555e98dc6d4b7147cda0caf93f76a3c8dc594d1a4a6abf02ebb68122404d54dcf7ff3c74e37d5514dd419d08fc1be673ffd9bc400c556c662e9b10a8f692fdc305c673e6016a73e4e67a05667aa08528fd5e4fef474c0e7a5a723e26c89ef4b4fe7bd1dd4b1a663a3060b4839f3a88aa723a45ec3346be817a61efb32553ed463bfca9fd5ef4f5f5969b9e292c708c2a367a9b4482cab2acd8a1a048c2a8051e5d29d0a32c715a4cf520a0ef26cbbe41d9fd921fb567d8fc7ea53486ca46703a347602c7d383da7b80379b6ef3c7ca33fe7fd811aa894fdff70a6b4cea6d661fa04c6ca7c3e5dc519e6f8b36ff2d66466630bce9af7fbb81bc63a6ef7d9f7322169a89e8e0351702cc174829da0a771bc8a271f8c5b15ca047ac21755dedae2367569aaaa73d9a6bb073bc8bd1ccfe7fc2ae8f05504557a56d8a88f980f0b62c70211679a8532d0fc749f8ee98b0a4875a6e0706e435741f8603ac1436d3f1dd833e8baee081eb7c8524c9f4c1563e37bc248f496f807393ccd58c24d45660c792a53050ef064a3729df99029275ec00bcea4f2ca9b4d7d068487fd511e34074fd3a958afe757f5a73d5d4fc4bc5ec8bc1e2a92f7068a108f504f0e3f42c7d30cd14c372655f09a160754cf8cd26e5d1775eb05e3baf58ca85ba7a45777e931c780e5ca9cab7b34bcbcf413deb19f9a7670f38d2eeeee48c7c52023459495b0353ce49acdc7924599f1250ecfb7be94e41926bcf6fc94d7aeab10db19d53c6205a61ece466daf6c2efbe8e249dcd21888fde70088a5eca1165cff3db665b19f9e31060616fddedbe7f2308e4d2aa4b8dd6289f5e6463df4efc29ddd72ad9d56d8f4af98196eeb43a66c1423b85f35c56f779e98c9421f5e018e94242f3c3d479cacb98fdc6849dfa6e02e0bb198e7e2e9167ee1857d9f8adf6961e6c74ac2ab20802110061a42dc1972068e277eafb082620b045eb754c2c24f10bbd10a4d4a130e8c34526c4b8b758ed4116822bc10c8aa06dc2945bde05062b9da29dbfda11bfa897733e11ff85e381e115b687a11ddacd0755edb8fd882820dead40032a1fecc3e6da4649ba3de261f8e27a4727a9b643091226938a6c63e51ea41648420de6804d12bf98d96d769c6ddb7d00ba478f4323e0b8c32eee151206673e321bd48a0a7cd287a6f2c7abf19d57630364e85ec5262a363fcfb6499922b0a0a393d4de78366828bf5d65f0ac4ce7d08d6210b1b29780f87b552ef5d3c68b2be0ec67d45d086a25981a9fad72de5c677fb1bd8ed5fb7c95745c55705fff8ecad8bd684c73dc05250d70df75c6c3dccb101c3f23917cd01eb68ea9594914e97c7063fe5a7bad2657a3138e95b58efbb389729f753082b845d7d9a141b8bdce234cb24e433e5d269783e56218c5f4e7014c24415385f8792457936e6787ea6a76e66b005cf5b726c6591c9db3aadf4bb5274540347c23c443009056d9ff1c2611ba69e2e6b3ea1b91d6f8d4962451ae75344b3bba4ccee4c3d7ff323c656ba61d9e20a0c7c1911d028a0039b6e5449c3aa62c39abc2bc614ae67de0047b8df2f33ed6173fb9b2087f5de1c88babdab68cecd1ef47ce4552621875e916aa8d5f983cf51678aa5484b0a1cad87dc72ee8912c77bb8f343abb4e1445b130ad09dcf876b06922556181745236b97ece151be93933d3c222f619e7b48c9d289ec3a61070e9a6e3ca230ff7848c54b51de59583e517ab505be6d46f00455be02c6a062bbeaf6b37c07ec648d50add2b056b02bcc8c52be4759ffe2a85977c5b292f4c7b00010ee084bd9a518d88dfab5ca03e875b2a3c8acc1ab0ed94f64d678cb8f5ad31db19a5a74e9a8e65f2685b1a59871af0155faac705b081705202ed6ef04beb571f82e3c46f83c5d0bf3c2cb2287650b91de7415db4ba56a623ba9548d57e7ca6cd28bed68d2330e82a618797114d9528c5c1645dea18ab17b5414bdae18bb318adc154b7b4b31ed482c7a6731baaf18bb8523259ab82f96fe4b0ac7f38033f5912fccba8479c6e17ddee169a1c136d8a69c2ee9ddbc8fceb3e6eaad3dbc1de03ce1ffea4c527b10d0e8a93d0810120f0284c483c012128be534c55956c3a189b3ace210b29ccd2164390b99d9fe7906ef9b83fdf31d1c82fdf3ed1c82fdf374dd0f098e966b0042a961325962984c961a269331c3643266984c4686c96468985c26c20f6c6d8722e3b7cfeba19fc91c27b2d8d7471506a6fb1c3e372d1c83440e02067b0d6fc3330bd81f128dd7a1a48d64a298d66c0c39d857a787763e652f3c1c941d8be4744f3e8d2a856693f380f08a8f8092da3bc1dc984fd8bcfedcaf8f869213cde8d6c796bd3a765832c3b1dad4e3376f5f5c8ded8e7c5094b52c5c9b23494ac2580ebab2f4f102a1cd14b34fb39049cab9b98c549a59d0c0e60a539ed067536ca994492747cbf109a6b833d198cf1648581178394e1740e37131df99a2e4700a6473048024069d502f8d221837572347d35cdb75a34608cd95b1464094ad87492bc43b2367446185a2af382d2ca1438c060942921e709460295ae8873c0365b2a42222812c493f159185a34b2890020562498e4081c4580a2426a0807bc4e1f7801e7e5f9a74f87d6982e1f7407cf87d99b79f4d36fc1e58152becbfd9f03baafe3cfcf2a6f03f0fbfbfebf04b2cf4e7e1f71887df9fda16445a62fc929fccf42cf117d033c3e0a77487288e1d284589cce00b3f7df678193771341f6c69be0d6b1b2ff839811caa4334a9c3829fe9ea335fe6f3aa9f2d7d0ba70cd39452e927b8a5d8b222c8ab9e9899d120985ca2d7ec32180cd265b2708f2143b1b8f3703c2db240e6fb65fc8aeaf2137280b1c313579a8ee2283357e6af1525f3d78aae74652067fba4e474848bb1b6558130cf5f2d3d7fb5437fb0702cc1fc55e3e260925a16ae66c6e6af65327fb5a33ca5f357279abf3ae3e6af96141dd5c09158d42198c48917f0a653253357b3b82aa9e41ce97299b7bafa30e370de6a8e9db73ad1bcd56148a379ab73b479abc3674e38e1d265e46ee4be6c5a4ee8e3a68f662c394522e0154776b189d6301d59064b80c71ee46d94dab73fb93cb913ee66e7b3130befc970645535c1068907971b705b37a809485696d7105ba1a178c38adefc112f02b9aa33d4b57152f1905f5ed3e527b45b535917119498ac4bea4f41b34b95667661f2b0d8870823bff62627b624d676654b8224e19dfdde65a17585db27016d8457884d59d6e655c514bbed25a2e3181fdda75d91b4ef06b68c89ef064ebeb856cdd50747bacf9b961dbaaa08958348610b291d0e3b729a072fbd93f21038830f42678db64f00c5844691810acd444eb8826d6a0f120c8b38ca91dd6e78f5fda82591f0469e6430bca252afa24acfacc8791f5173e50c2e14e6e7b4d2ac154f7d266e52be1a00a182b3a1bd8f2acd04169cbfba32c973e578763fd925c4d6b66d8a58a3470b78b5dbda5066cb8dad85543ce884f36d3f5624eae72d5389d6e7c717db7fcfae2e46e4ea6244ae2e46dcd5c510571723727531225717e3ff3f5717922a9960cf5ec37b1335565ff0350e89138bfbc952b724392cd5d06d65a2ad144ffeb8e9745b295d051fd8a6db4ae9caf8e03fdd5614da4de3a1b415ce8791b64248da4aa1316db415e2a4adc2744e94ce0efd4f2d10d11a4b44ab8488562911ad1811ad1811ad888856b1adb81aef0f5c4d351348da0a21692b84a4ad54e07b6f65bf366a9ddd7f761afb53731a33bd5a1e67beb1577b7d70437d39769272d1f197660b6fe643a0d82f3dc59bb4787075b45d83389cdd8d68643a2daddd63ecd053970ae167ef2d7375013a332549ae161706ed530bdf55f1ea5eae6dfb29319adbc196fe20b532ee7f6b45694564bf938ff200d8026e7442f5b181e6c2c1f3cf854c50c8b74ce189fa129e108e088cc8b6f5060a85a9a7566ef0ffc50718b0005ba7f73d7059718f6c4c61eac444560b8d967de7c3d66726c1412cb5e280b0f268f98e433e15e65347c9a733cc199381010fd83d31e2655fe604ded6b962af4ac91c95f1ab90d51f7dc6ea61a5777de85378978bd2685b722c3ea657623dfada0f4a8eeac51e69471f7b9f61d511cad261a39f08855d9766e1c11d6cf14c42974ec086168ce07c18b778889b236a9fd985f3d168b8e24fa0c0e7b02bd44f139c30ede8b3dbd85dd3b7cf956f1ae936b2dd701a98e8d2df555913239c94c30a2e4d1f228753b64fe170e15bcc124fd394386fda19ab8bad86ac41cab62f715455fcb52d5b6fff0a3f1216faa7022738a70e9f53a9e4ac58fdc91f131ff73083d77144adf8a4f214d582868d0fc030e35ae114d592f9290ee64ff3d04093355eccd4b068a5368401c6e1e2945951455dbc4780aa384b4cc6f2f5055643d96d437ff8427f4cc6664d3cf4faacd273b52f98a645ac01ff4ef57bed6817ca6dadee68d8bdc99c7bd684ddec4261fba78d311d8ccb91cdf09cbb456c5f6c7f20c61753234f8e61114bc932a8cc13eaf532285b45d6661cd9c6a49658e8fbb2cf092711d7e92d31d8b5c9a55e22bda925d69b4e0f7bd3081b32ce99a27883c5e47b8c38457e4f99b634c07ff5e0171bf5c60c7941b2288eb0312301434f12db98696e02c76b63a577523820de671242763887391242d51aa1eadf15a16a9d7932841c3d8c67c66385b3f1c2945e98d21b9328154b541dd2a7fa082a01765f2484464971744cb16d305dc6a42aa3793fbe79e4eba3f5dcbbe02a5132c6412c33a58a6e879a2ad891648fd98d64d7b2587074b7d3475bda629417f1572dfd2715ea368cb6e58dd0a41320c930e2f52ce76ee863931a0d4224c7bcb41e567cf97e06175bcbcee1e1f24822b629cc26f19ee0e32b1848b86410c5d7e5e0b20ffacb820b26e2306775e7326528a11c25eb2f11c158e5b280f5cb65d66f076745ed27a6ec4c859460c9be95b3727e05ceb47efa07bc7f7dba622fa16136221177968b89c42fe38939cc27e5917d2925c90468de9ac367945b6221b16383e77316efc9298b9b93d8acc207b3f381ecb2f571a251d4e29338c11422cf655881f707465187475107a328362224f9835e6ce0193b8a2663c6934c02330b4b0652d86ebc2b940ca14939ced33a57f659f161166cc9d4fb003256d82216285a16b588851629e78306d0222e7f8fc662cf185e92f2dd157a943c2ba7df69c361a6524a30450a538b548e6d1147489d20c45c4dee726d90aa807110479e34f11929d22229fd9d12580c6d3f258761298143636206a698538ca2212b3212550830a677a5624641b6d0a7cb939d279f4958e5d4a656b14d8baa817c300bdb509cc1503bc096a98cee40b6be452725c13a1bf62ded3fa777bbc857c9e24a44b4faac87f1b041717c2c96a24779b30be90f67e87562f9301a25825671927c4e1026467b90bf7293c449063c4cf3b2344cb076588e85b083356d4b4eb52566803c22f6025b94f159171136d5b923e3055cd2764c13610a950b0a09d8d6127e79179f408745d132a8326c5a4e489b962302bc279bfb14ef4709cf595897c3e63e4333558ca5d87c3d4ddbe8c1a05542de69cc99da685d25bc3b2db26163a91ce671a56340ac8d61ea845f29a9a4202e1b72c12128325a5810ac10d7a299b232555961696d893db312e94a57ecd9b1cf8a9589759b2ad0d661917e89507a254ba5dfb4d02f5cbe8e2bd2af4aa49f11937e2acccf02d789f535967eced8be6609c1559af7013142d3b43e58259d85976492ba4bfa95d2312a0149a538f1f120fe6dc56aa1b6ec47fd0f0ab8a57bc75815ce4edb326287fba3e5fb3dfa7359b63e6025a342d9e9443310de7c52223b555c76cab72843d989bd99c519880dd9c93a0018d46203b4c84e9e3c38bbbddb95fb452b54b1623aee5407da0be5b6361a682f16fd35b6933ed459c76df5158d17c30d9674238ad991c21baab898deed5013abb976a4e6da31353731919a9b28aab9d07037c890bd2c63c606f077e29469de38a20770e6bc426cedc50eb6e9b5177eb5b1e4555fb82c63076bc3417b6d498a8b8b0b3776b870c360c8b84b02fd9de1b82b6a365bef20a4a7326b35c7cf5a6d99b56a9e31279cb5eac58a04369961a17877c65ca327a44e97778fe6505bb6189832de62b657dfcd2b09d169ddbf2b01cda311d084b6107a6c2a595970b467074f808a006049ec6e85913a054f14deaa59c2898edeaa69f2c7bb84131d8ed57dd761bd970aa17b2dee73f943c319f99a1e7fc3f9dad871b67fe019a4ee19d42dae8914587a581deea13ec2a7c6568fb51598b239f5d83e3536516e3e40ea983e35f6c6727b53ca3d6b4ab9aba7947bde9472fb53ca5d3ba5dc7553cabd604ab9eba794bb694ab95ba694fbf429e51ef7a9b123653fc64f8d1da9874ff4a9b11bff54561be65b299660ac725d673b360f777a51fdd011a4e984e03863c0716af9e3aa2c4d79d7af159e3b127869f9b258a4e747a7fec0318ccb2d3d71c4c1c921492d7b1dd9d08f2573cc25c0bf65faa491729e20ee959346c2aff8f2511fe5386924e1bb7cd248824f1a29673f599c248b934612facb1e634e1a29f3cb41c8b2306f087e19c46419f042b5e34e1a31c39346ccf0d364343dc19715c67c9aac5c1b6c5051029490fadcd0436bccc9419a6863ce0fb2f4f941d51292f383584da30292a2a3bd4c344969ff3b39d347e1e053d6cfd646839a97f37aa3f3709eb6d859ea4f8b134a5880dbded16d9f8cb77d2adef64969fb54aced93c7d2f68e9f44db3b61de1060874d75baed93c7d4f6c909da3e19b5bd236d2ff54dbdedf567fbfaa396bcdad46a6cec4bb1d14752d9dd28fc443768b81b07e9500b2477f2b18334834e16447bcdb71a55b1c32f8c71076ed8b2f98eda5dc5849ed2568884fe6c5fe4ed64c30d2dd4121defc30aae62297c51cece8cf107e248d02ac1e40f4f75b18b5a229fa391f3ae53100d38b84b269f3193cb7f2a36a399e3c8a03fff3bc45fdfad7930fc703316426ca1860d6ad847a6863511352c193ec653c32aa5868620724cce246ab4435a748462a286e7f1ba2edf396fc2ba2658798aa1ff9c3a0a17fc715940672fd64fcd9d198f149735d1725a84d38bcab24a5dc084af2c2995206c21ee76f48a94fe8a4f1aaed1849aa3ddb4c2e34ae438346dfaa833eb338907d99874e43ca1f92f11ba77996c0564e3087fd92738b82fb48cf8facbdaf0ac9443605be06a6995326995b878c9e7900f1fadc5d887edbfaadf26a27eabcb4c4e5ca633667e137e439a264cbbd4dcd0ed2d01b741fd71f2921efb93f13d36dcadb9d8f4c47af847e8a05e680db4c5ae1096684d5ca21aa3706aa4bd1b232fbba89d8f13a690336c983dd2f0173c62fc90a9429b984cb3b124116472196d7aaccb2d368de506044a2d6419cb307db846c419e29a181ec25367d671f2e207b56af84b273e7fd7946b756fb38a67368e67c4963fee0012b262323e84f8c9c566bd2c5d1fb5fa69c752fdb423571ff6f824e4833dbedb2779af54f0ab62b74f628ae494d803ece8c3c4e3fbcb98c906579c84a8488e1dcf340b7528314f09b3f0bc233a9eda9233fcc229ca78844dfdcdb6526cc562c51d214873a57f944aee577f845a88c3d3a5f3be2314658d294a1f6c050bd487d55c97ba05fd9fcb5d483a9b38872fd02632f7f3da79a8f85941dfe475ae597117f70a31acdaf92276a1c968e2e9ac1cb0309180b1bd4faab9a5028bbd5ff8903fbd013ba58fcbd20ee4060cfcbcf31f067e9b07e27039ae3ac7af65188b1bf86db1dc9bf24545a16934dde58de2b2ed5b3ec017ae757872f698fed89b9cd77120d4e97925fa43b2ca69ff97ae726a5fa124a4daf855ce4f69abab53b2ca8989690befd7d0647540564b1f9e99497081b17513bca63e3d86ac7aa10a7bfd52a156102d403ab105c8b0ed7ccbfb343778916b1213992112a0701337e4384b048cf128728ca67ca7a32a4a1726c72f4db66c2a2e4dd6cbd26471c3862d333c335ad633a3653d335ad63365592fdc6c165fb86c9960e132b660c96dc30b969fbde15bb56b2a55700fdd63eb95486105d77feb9e1970824203a9c284ab95d8f04a8d575ca1a486d42b9468d2e20ae5ef84142813865a4ad62fa3ed47e11a261f9531d1929e18f8f5daa6e2b54d9666c5b54dac79ebc54c2ab41caf99f12ac62e8e2785f578797c9f663d5e83ab287e6f711bf8735bee3465bc4de14c7c85c2953e365fa0d33b3a34907ab30283a50d0cb1957a3b5ca967e6d5eb8fa62cd31b63bb80127c50ba13daf68b67940a77865e6307e49c522bf45d999ab4488e971696480b4ba44572acb4806410673ad91c922b4ed0922bc776c1a4fee84ae9d8941c3b36e95356f1d50369b3ba1c2f3c8e25939c0e6968c95a2f67a8c28f393a4d9cbd44c5746044673c7ac5e7949c5324cf72c21255e4ddc5cb30f52204cee1c5a6f986ce72a42a8c31551863aae0e7df6f615b941ae6e24cbd32af8bd34c5b2c2e7c1f16173e87c585cf0bf0bc4096b8e5b44c2abb768959cba70f858eaf867bd09665a6512336a8f3667b96bbf23ccd34947650902390b92fa4d82e15e981e6ca0906772774711ba3bf680353098f446e332d39dee0318e4778f495cf48443cc2ba4629d939aa945338aa9459f4eaafe617534694981ab3e00f852e15353faef94d548e3dae1c919e38ec2b83c5c7b96f8c68cc127c5a7cc863b525e48bb3598c7ce678f299e3c957e4b7702fe5185d68944d7677e060dad89e11bd33541f4ccfdccc5ed7e1714bb3f0e365ac2e7195334b8f2335f4b7a9b5a9b69c3fe9ab3f32cdabd4795169c20f5527683ce459b5deabc7a780ea23de537cec02cb4f64d647366a4fe1f00bd2f06791f1c8d4df99e60f8b92bc22caf1590ed49aa1fb8dd1ac4d60a1124defbc26ed221389bb78c78d08f77b295f9c13652bae3eccca13b599119e150e3fbe9e76917e7bbae1ee7754d9f004eedc41fda6a24737567463866ddd5b62df9bae8bd49f5067304b14a1fa09dcc075ab47cee0e220c0fee02fdef7c22970df7a81ee318f70a430837b5fb86ff631f88407f5dd915b38bc09d2b27d087e0529996187ae66e381b722c5c82a9af463ce69f5a5eee44cb2b2921dc9655de9f2f88e64b82090b257263b9229ec68a99dd07eb1a163159c9dd8972b3ad8f862dd059c5005db0812a5640f72269635e13bd150d03736534192b25f173c78a31dd0be1d65daa6336945112b3d6bdfd03ee564899ffc44fb940b255b90635e1caac48b4315bd38402d9e85a8122f0e55f4e2b078ff7268818c762fbb8fcd4918c1ae51c31dbab4bdb33bdbe1177afdf6eeeedef5ed85acdfd7be213b600cf664b7f465d717e865369fefcdfb9d3dfea59d5bb21d6d976c2d6407fcf5bd3d0385fce0fa426fdec8f6acef1dec2964f394b8a7b767e125ed03d9e6267ffdc6f67c3b959037d65eb4bc6df93917b4451f170f3ef235c3fdb4698cffabafaf6fa86fac5f54df54bfb8beb97e497d4bfdd286fa868686c686450d4d0d8b1b9a1b9634b4342c6dac6f6c686c6c5cd4d8d4b8b8b1b97149634be3d245f58b1a16352e5ab4a869d1e245cd8b962c6a59b4b4a9bea9a1a9b169515353d3e2a6e6a6254d2d4d4b17d72f6e58dcb878d1e2a6c58b17372f5eb2b865f1d2e6fae686e6c6e645cd4dcd8b9b9b9b9734b7342f5d52bfa46149e392454b9a962c5ed2bc64c99296254b5bea5b1a5a1a5b16b534b52c6e696e59d2d2d2b2742981b894aa5f4a452fa56c4b292ac4a7a3bdd04e84cab66ff2db0b85eca6be82907b73be93693d50f00b1bb37eb6a7c3b8e0ec9563a874ff370cf7734ef40c7a5974212649974b57255dd3e99a49d771749d40d74974bd992ed2a68cb7d2751a5d8be97a1b5d6fa70bc790aea4eb6cbad6d075015defa1ab9dae2c5d1be8eaa26b135dfd740dd375155d1fa2ebc374dd44d7cd747d8aaebbe9da4dd783743d4cd717e9fa2a5d4fd0f50dbabe49d7f3f89cfc1f11dead13c0fcb123c07def18d81f9d00feefd3b5ecd8ffde41ffce685adcbca46569ebdb4e7fbb8e359469d94e22992a2b772b2aa75579d367cc9c357bce71d5c7e3ed19cbe6d6cc3be1c493fc74e64db56f7ecbc975f34f79eb8285a79e56dfd0b868d99fc2dfe67c6fcf061f3dbd83189838ba931e073a2fcb16b9f5fbcf18ee1529e3d8ffc0ec9ae909cd9686958d6d179ffbced60bce0a16362e6e6ebbe082bfbca8edfc356de75d744edb8479d7ae593b595e241997f7d1a7327f577ea8f5b7bf987bd9afcf387851f7c50fd41c7746eb79773a5f2edcbce6ba8d5fab7ae0de35d7adbfe92d37fee0e686ddcffff2b8679e5cfae5175fbaf0c19ac16f4ffb4a4bef79cdcb1f78e6852f5f74e9b7925f3ce117b92b36fc6bfad4ef3d3d74f97d7bce7ce1a28d279c3b7d6c7d3d839b2ec9e6fdde4b4982f6b47713d1f2037e76cbfa6cb663807bffa6f62d9d9b0637b10cde9ced30f2d9fec1ce7c962486df9d8588e8edc916f386e2341418c68af08ddf911d589fefec2b74f6f6f81b48e890f0a50ada7b504358bab45adfe025dd9debfdaeec567f6063ef209ab43d5ff037771636fa61f97df95e8299fef7f50e0c64070650ea40e7869ef6c22001876cdd25f94278060815cab539dbb9616381868af641caed0f76f6149a9bda0899a16cfe5202c5e818ec231878cc89a099b05219a58c8802f48e926f25d4f2590283a0d8344854ba245b8af4c6f66e2e0924d63021a8e1321026c2f2e846e3142a5adfded3d3cb05ad276a11ad3aa819d617bab71a976605eb8e4e1a1d0b9c7e886aea68f58d72c05ce85ddfdbed87a93a077c9413a5328c5b4ebb76f9f5ef5cd2ffbddf8e7ef1b37ff7c57f7aef93ef5efacbe9bf7ccf3d579e71c30b1f9877ff2df725f7abf75e35e3f1ab1b6abf7679c54585f73f9e7ffd7ffc786477e2a5db0bbfbcfd89273a6f7ff99a958fffe2a94f3d51f5ec9d15b5bd0bfe69e17d3b0aab5ebaf7a1f7fd68e7af87ce7fd73b6ed9f454c39ea74fb61efae6f07bbefa6fee81a7565ebffb8577ad7f26b161fefc595797ffedc92de99a535fdbb4e0dc99952d175c38509e7db43c3774eab79ebce2ea05f91bee3cf8d5bb565f3a774fdf87573f79f7bbbabf79fcc11bbe5f9bb8f5c5ab174ffb55df1d396fce13f71c9fea5e72e2399bcf3af98b8f3dd4f470c5bca5abeedb7fde9a673ef9de7feb3d75e3bf5cf93122c167b7ccda6204cf3d67b87b9da777a4cc6f5ef79bc38feffa59f7da7bfee3ee5f3ffeaf5bfa9f0a2588d611c07f44ac426f2ff8295f30747c98acb0b52f1b36ee06a820f9de4da47be4a9ddfb7a7b3a2084faf259524c8c8eac164dc420a46275f466077a4e2e10e317d66ff4455561ae95d76f25ed24bbbe0b5c614421e28f62c29202e2dd6563fb80dfee8b3ce4aef406867819b72123cbe82ad76362055dbd97e488d1907a407253de7c3b69576d080b3b11fe9b06bb0b9d6d9d3d1dd92d86702ed5cb6408d31b9a87d7734ff0a5dc01e42db45fd29d0592edf47e237512d2e10ad0d0c657bea9b7a3f3d2ad47a85757a0d31c5305837d1ddc2d43d8a8776e20d2e43b37b5e7b73255376fccf6e822810df55e29f888d405dea5c4dd902d887e318dae2aba3cad6bcca0abb367886447873fd49eef6cef014e40e58d68b9348972edba0554d6ff05317960a700d00700008c010101002054ef1d43bf08fa5fc98e2abd54d4b861ba92e2e6e1a8aa4376b5bd177ca49fad081e3efbd969cfed9a0d7de17c2de54b7cbb57c06c74563cebbe8583baaf0d630100b10878da8d563d8c1b45141efffbeccbe992e3382e4214413491809c13ac53aaa3a04188829fea4896f1ceb33df2eeec32336b9fa1a0a2001aea20a108214871d5a5203a2822b74848095c03144017242a7ac49bddf5fe39676e0a7bf7bd376fdecff7be9dc61fed9b15f2e1bf372f105c25f343febaf2b6f9bb671fcd72823b7f773aff6ccc258f36d6c053dcbb7e9df6f88b3b2f744a5b3dc7b34796e203c1c5c0a2811e7a92ebe9a531959c0a6d9da2b7c6579e6ea2136b48d590946bde4480ac0aea42d548daf610ec910adcce4bdd26b5351f530da4b4d607aa030916e303503a6bb592b826d5153d94a0869ec3ea0117fa6aa73a82a95ac51f6b027c30d4fb37d0a9ed0542ab6d1fa4cb1526252c07c6e02426b509e55a9d33bf89ece2e9e990f2198edd0cf7db43ca85e55389e96a908a9cdf70e9415c2a01da0a141d40e8a47beda2a67200baa8b57c5bc7c76c9bcd5a52a14ca5308fbc8bab9d677a54e106904badd653df0ec0844e63f9b3b627341c68ab2fc1545e8585cb442202f7aca60c446c9ac9d8f6835c20f98c13edb28c8b2eb65d2cf03283a78a1e1cde07cddda4640cfa202530343ab0e0c0e7928676132e983789ad368b5e183849dd9e344a2e1c2ec08ad58abf3f3f606b51cbc0d7c350bdd30dcb93c22bab6adb54d8e0844791f25af43647e37a11ce75133f67d95169e156c49d3127e5463c08e1ecb5d2dde1fb469f0bea60d0d2ca8c579b81b225f74dd075a5259e5d8f001e43b6e5073d87db16223fd6577ccf8f1fd7539fbe8756d385c14107898dda7c4c08fb375ae9509172055fb2476662d9e936b134a330d56a2ed5aaed31889ef4d48f9eda12de0bb80417e6f5103807d126dc6e4b641f2f66a9e427a2ae947eea215d412a6878a8979ec43c1508868970d60883ebec3651a20dc46abda906b5d03d644674179f59ecced629d445ca19cb4540e4ca73c1971e0bec5c83cbe7126192659ace698cbf8ac59b33355b4ad50d63693a52aaf6a5e786feeb0a347e0e0a78aca028aa4ccbe81dee2223171ab9822c6a8536b5083b866da21c638199fdaca081be4cf70b8eea63d720a1660ab3bb3276c7883eac5af41ec2250a6515b727a024a5053c2f08b0ed3a247b454af5e8e1f1df0113982ff9b850850657961147913443238fa1b3a6c2aab2c0812716bb8833128804fb95ffc57e2bf0198d49a1ba94144c0af301a91afb141bedccc792949be19b023ba6bc3cf2cee395e2f2e778bd48bfefe4fb8f2fbff2d1d72fe7188eecfdf0cb9dc32f5fcdb216aee747956f3e4d879bec7d71eb12bb772b3bb1b87e7ff8c1c957c9007ef656f7576087c779a8e2ba0deee17102cbf945688e49427e7cfd787834cb6210d7de739f1ccd122cf9279307af7d7b34cba38390fb0fdf79f3ee2c03015cef7eb77f7796341bd7ee0314a48d3529df7ff4e7cfd90e866570de38c9f6c9dcd46ed39f7edba9f0ee3542d2fb14deeace70012b2dbbcd90ff00c6a7af7900010169de0a4bf1da68c633312273e9de4a2661682be9a3abcd44b70002f3fe6c6f38 DMLOG START_BLOCK 5 DMLOG CREATION_OP ROOT 0 -DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304004,"value_ex":102737,"consumed":1},"cpu_usage":{"last_ordinal":1262304004,"value_ex":291686,"consumed":101},"ram_usage":199629} -DMLOG TRX_OP CREATE onblock 136018ca05564fd0c886d02fccf608e1b6d3c79b0c81e49569afef12a478fa94 0000000000000000000000000000010000000000ea305500000000221acfa4010000000000ea305500000000a8ed32329906033b3d4b0000000000ea30550000000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440fb1731b78038b7fd1e418d83062417a2c12a515dde5d4d8bbd2610bb46ce01b8888fd15e295bb3585742c5737bf6bd6deb93ece7f776af86767a2bc20834cff0000000000010000a105151a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc16000000 -DMLOG APPLIED_TRANSACTION 5 136018ca05564fd0c886d02fccf608e1b6d3c79b0c81e49569afef12a478fa9405000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d901006400000000000000000000000000000000000000000001010000010000000000ea3055ab1772852933c699ca399b6f18cd329e094798e0b65417b0ee6bcc679475f7201d000000000000001d00000000000000010000000000ea30551d0000000000000002020000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed32329906033b3d4b0000000000ea30550000000000033f35e12a8f3c21ef9cda1761752bf23de04ebd9a635044c75fda0b440fb1731b78038b7fd1e418d83062417a2c12a515dde5d4d8bbd2610bb46ce01b8888fd15e295bb3585742c5737bf6bd6deb93ece7f776af86767a2bc20834cff0000000000010000a105151a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1600000000000000000000136018ca05564fd0c886d02fccf608e1b6d3c79b0c81e49569afef12a478fa9405000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d90000000000000000 +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304004,"value_ex":160608,"consumed":1},"cpu_usage":{"last_ordinal":1262304004,"value_ex":303261,"consumed":101},"ram_usage":453263} +DMLOG TRX_OP CREATE onblock f1dbd4cfc0d4ec8e8270b5ec9a485c57f1630f8796b6c381f256fa5dfd93a78e 0000000000000000000000000000010000000000ea305500000000221acfa4010000000000ea305500000000a8ed3232b906033b3d4b0000000000ea3055000000000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c098ab5b4fa3ae6d0e17739b6e5fbcb7f54e605eb094b2e7c5f523734661c791b46b69a63382c85627f8fafa15bb601dda980ba3edcad5043ab2f6d7610f278310000000000010000c105161a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1618b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d6390000000 +DMLOG APPLIED_TRANSACTION 5 f1dbd4cfc0d4ec8e8270b5ec9a485c57f1630f8796b6c381f256fa5dfd93a78e05000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b01006400000000000000000000000000000000000000000001010000010000000000ea3055dc3c8d65ab4eda07d51bc1ce776cc85ec8b90c0739fdc4a0a4f3483c7054e54f1e000000000000001e00000000000000010000000000ea30551e0000000000000002020000000000ea30550000000000ea305500000000221acfa4010000000000ea305500000000a8ed3232b906033b3d4b0000000000ea3055000000000003c7b6f0c37ffe5e12561dcbf5632e864d5d34c363c41e4c941ff2405c098ab5b4fa3ae6d0e17739b6e5fbcb7f54e605eb094b2e7c5f523734661c791b46b69a63382c85627f8fafa15bb601dda980ba3edcad5043ab2f6d7610f278310000000000010000c105161a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea994a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0fe0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff52668dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c438ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a4052652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead450715443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb406bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b63320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011afce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb409e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc1618b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d639000000000000000000000f1dbd4cfc0d4ec8e8270b5ec9a485c57f1630f8796b6c381f256fa5dfd93a78e05000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b0000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG PERM_OP INS 0 9 {"usage_id":8,"parent":0,"owner":"alice","name":"owner","last_updated":"2020-01-01T00:00:02.000","auth":{"threshold":1,"keys":[{"key":"EOS6JvuLaCqV8qHbSqUBVRPMo9N7V3vgE8YqHmweG568YmTDJ3opq","weight":1}],"accounts":[{"permission":{"actor":"alice","permission":"eosio.code"},"weight":1}],"waits":[]}} DMLOG PERM_OP INS 0 10 {"usage_id":9,"parent":9,"owner":"alice","name":"active","last_updated":"2020-01-01T00:00:02.000","auth":{"threshold":1,"keys":[{"key":"EOS8d5yGFrYpdXW1SUmaavRZKm5X7Bp9jK634JABCYPciwTkm7Wv2","weight":1}],"accounts":[{"permission":{"actor":"alice","permission":"eosio.code"},"weight":1}],"waits":[]}} DMLOG RLIMIT_OP ACCOUNT_LIMITS INS {"owner":"alice","net_weight":-1,"cpu_weight":-1,"ram_bytes":-1} DMLOG RLIMIT_OP ACCOUNT_USAGE INS {"owner":"alice","net_usage":{"last_ordinal":0,"value_ex":0,"consumed":0},"cpu_usage":{"last_ordinal":0,"value_ex":0,"consumed":0},"ram_usage":0} DMLOG RAM_OP 0 alice account add newaccount alice 2788 2788 -DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304004,"value_ex":104080,"consumed":233},"cpu_usage":{"last_ordinal":1262304004,"value_ex":303261,"consumed":2101},"ram_usage":199629} -DMLOG APPLIED_TRANSACTION 5 e833edff0c91079174afc19cd1e3bd119711c08616746603e472d5a21c885f8505000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d90100d00700001d0000000000000000e8000000000000000001010000010000000000ea30554895e298f1f3e56596649fb49ff53d0f76174ef57ef7c50f28152765cef1f97f1e000000000000001e00000000000000010000000000ea30551e0000000000000002020000000000ea30550000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed32328a010000000000ea30550000000000855c3401000000010002bb30f6894f29bb6fca635b1df728ad77e48fdd6123ce5e4455b0f71e072e7df80100010000000000855c3400804a1401ea305501000001000000010003ebcf44b45a71d4f225768f602d1e2e2b25ef779ee9897fe744bf1a16e85423d50100010000000000855c3400804a1401ea305501000000000000000000000000e833edff0c91079174afc19cd1e3bd119711c08616746603e472d5a21c885f8505000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d9010000000000855c34e40a00000000000000000000000000 +DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"eosio","net_usage":{"last_ordinal":1262304004,"value_ex":161951,"consumed":233},"cpu_usage":{"last_ordinal":1262304004,"value_ex":314836,"consumed":2101},"ram_usage":453263} +DMLOG APPLIED_TRANSACTION 5 4828bee2f6d5d9082fb08eec89af59167db84510b1cdc2fb188d943ba32220cf05000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b0100d00700001d0000000000000000e8000000000000000001010000010000000000ea30554895e298f1f3e56596649fb49ff53d0f76174ef57ef7c50f28152765cef1f97f1f000000000000001f00000000000000010000000000ea30551f0000000000000002020000000000ea30550000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed32328a010000000000ea30550000000000855c3401000000010002bb30f6894f29bb6fca635b1df728ad77e48fdd6123ce5e4455b0f71e072e7df80100010000000000855c3400804a1401ea305501000001000000010003ebcf44b45a71d4f225768f602d1e2e2b25ef779ee9897fe744bf1a16e85423d50100010000000000855c3400804a1401ea3055010000000000000000000000004828bee2f6d5d9082fb08eec89af59167db84510b1cdc2fb188d943ba32220cf05000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b010000000000855c34e40a00000000000000000000000000 DMLOG CREATION_OP ROOT 0 DMLOG PERM_OP INS 0 11 {"usage_id":10,"parent":10,"owner":"alice","name":"test1","last_updated":"2020-01-01T00:00:02.000","auth":{"threshold":1,"keys":[],"accounts":[{"permission":{"actor":"eosio","permission":"active"},"weight":1}],"waits":[]}} DMLOG RAM_OP 0 11 auth add updateauth_create alice 3108 320 DMLOG RLIMIT_OP ACCOUNT_USAGE UPD {"owner":"alice","net_usage":{"last_ordinal":1262304004,"value_ex":834,"consumed":144},"cpu_usage":{"last_ordinal":1262304004,"value_ex":11575,"consumed":2000},"ram_usage":3108} -DMLOG APPLIED_TRANSACTION 5 1df1c2ab06533303d4172d84dd42b5e5ed8297f65fa0214b0a4f439788d47b4705000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d90100d007000012000000000000000090000000000000000001010000010000000000ea3055f3d881d2f7fbf2f7cb6081aff84e7aca1dd3914a0948ef4fc9422e734e8d4d571f000000000000001f00000000000000010000000000855c34010000000000000002020000000000ea30550000000000ea30550040cbdaa86c52d5010000000000855c3400000000a8ed3232310000000000855c34000000008090b1ca00000000a8ed32320100000000010000000000ea305500000000a8ed3232010000000000000000000000001df1c2ab06533303d4172d84dd42b5e5ed8297f65fa0214b0a4f439788d47b4705000000043b3d4b010000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d9010000000000855c34400100000000000000000000000000 -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":4,"value_ex":147251111,"consumed":8010},"average_block_cpu_usage":{"last_ordinal":4,"value_ex":415951447,"consumed":4482},"pending_net_usage":376,"pending_cpu_usage":4100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1051726,"virtual_cpu_limit":200600} -DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":5,"value_ex":149157352,"consumed":523},"average_block_cpu_usage":{"last_ordinal":5,"value_ex":446651851,"consumed":4513},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1052778,"virtual_cpu_limit":200800} -DMLOG ACCEPTED_BLOCK 5 05000000050000000400000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add801000103aae4b42beeefea68f9878b7c7389d18a14b2d0a0209958df6d3f76f5ed5f480400000000000000010000000000ea305505000000010000000000ea305504000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add80100000000000592c5b38047f9308c633fa2d88c9df8de6c6a0caee99923c031c5a0d9043b3d4b0000000000ea3055000000000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e86e28d6bbfdb013ed6f68649464fdd7677c41d9a51a4f16c062b63e708cd1bc48dbf44801695e167a47f19f378d872cc2126e8dae13812dd5bbc0e55b959a1fd000000000000002059e8e647d1b8192cd906247d0ccd066da0fcf8fba0748a861ab3f2d20784f0c9716e75cc460fc5885f32a2973edf02cf2fa382af09c07b7b3243ae61d24571790000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef261980000000000011609e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc160ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72412652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670735c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c25443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b463320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974286bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1dfce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40001043b3d4b0000000000ea3055000000000004119dde5dc2ba60b1c4f1013a50ea81abfa00d53b1c9bd42d37f5a79e86e28d6bbfdb013ed6f68649464fdd7677c41d9a51a4f16c062b63e708cd1bc48dbf44801695e167a47f19f378d872cc2126e8dae13812dd5bbc0e55b959a1fd000000000000002059e8e647d1b8192cd906247d0ccd066da0fcf8fba0748a861ab3f2d20784f0c9716e75cc460fc5885f32a2973edf02cf2fa382af09c07b7b3243ae61d24571790200d00700001d01010020778bb3ea5abb6b92bc3b7d45a0185e56911ab64544edec77baa4400e8190a3257d22d082e982c40c059de118168eaff6c868ffa3b0f67d37b431f442bd1b46c30000bd0107e10b5e0400c2ba60b100000000010000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed32328a010000000000ea30550000000000855c3401000000010002bb30f6894f29bb6fca635b1df728ad77e48fdd6123ce5e4455b0f71e072e7df80100010000000000855c3400804a1401ea305501000001000000010003ebcf44b45a71d4f225768f602d1e2e2b25ef779ee9897fe744bf1a16e85423d50100010000000000855c3400804a1401ea30550100000000d0070000120101001f2ea70bcd36a59b5cffc0188010939d1990335ca2c57c9bff33212db45802bc426a261cfbebd74e78d2ae520b3c8ccd0ee633aefb2df81156bd4bfc3dcac46a7200006307e10b5e0400c2ba60b100000000010000000000ea30550040cbdaa86c52d5010000000000855c3400000000a8ed3232310000000000855c34000000008090b1ca00000000a8ed32320100000000010000000000ea305500000000a8ed3232010000000001 +DMLOG APPLIED_TRANSACTION 5 a5da917661cfe1fd15ea07da73e09f9e2675f29e3609394b8fb57b522694022005000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b0100d007000012000000000000000090000000000000000001010000010000000000ea3055f3d881d2f7fbf2f7cb6081aff84e7aca1dd3914a0948ef4fc9422e734e8d4d5720000000000000002000000000000000010000000000855c34010000000000000002020000000000ea30550000000000ea30550040cbdaa86c52d5010000000000855c3400000000a8ed3232310000000000855c34000000008090b1ca00000000a8ed32320100000000010000000000ea305500000000a8ed323201000000000000000000000000a5da917661cfe1fd15ea07da73e09f9e2675f29e3609394b8fb57b522694022005000000043b3d4b0100000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b010000000000855c34400100000000000000000000000000 +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":4,"value_ex":230575556,"consumed":17883},"average_block_cpu_usage":{"last_ordinal":4,"value_ex":432479225,"consumed":4499},"pending_net_usage":376,"pending_cpu_usage":4100,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1051726,"virtual_cpu_limit":200600} +DMLOG RLIMIT_OP STATE UPD {"average_block_net_usage":{"last_ordinal":5,"value_ex":231787427,"consumed":605},"average_block_cpu_usage":{"last_ordinal":5,"value_ex":463041898,"consumed":4529},"pending_net_usage":0,"pending_cpu_usage":0,"total_net_weight":0,"total_cpu_weight":0,"total_ram_bytes":0,"virtual_net_limit":1052778,"virtual_cpu_limit":200800} +DMLOG ACCEPTED_BLOCK 5 05000000050000000400000000000000010000000000ea3055000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add8010001ac65c5394281653b4b4ef83ce55bbc235296a3d4d53d227b6cf7023f92543b620400000000000000010000000000ea305505000000010000000000ea305504000000000100000001000240e54a7b27e042b80a810153bec1dd166eef95fa69f6c9886ae283363bc2add801000000000005061c1447694382f67c35c70ed92deadb9350a3f72f9cb9fa5542702b043b3d4b0000000000ea30550000000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b6a6382d7021a0b4267ccfb7b44da5e2fbd57fb1ea60ee71bd3b9f8b416837762b3d88bf26dff797ee7ac7d53958e15db3fe5e9a6091984126007e376fb8440fb000000000000001f0e193512b825eabbd33482ea2e83d27dc93cac16e065f9ba8ceae6e2813d6d0a4b43ea89522b5dae8f7f88e614d14209418c28a6afc19282cdf1f9cf94d39c250000000029807708239aa7de914d3ed61e9009ab2280bfbc50f1d9769f27f8341ef261980000000000011709e86cb0accf8d81c9e85d34bea4b925ae936626d00c984e4691186891f5bc160ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd18b790108f5e277cf7141dc626a98f7edeb776912278e4cd14a50b763d1d63901a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b72412652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b447670735c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d674fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c25443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b463320dd4a58212e4d32d1f58926b73ca33a247326c2a5e9fd39268d2384e011a68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a2974286bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1dfce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb40001043b3d4b0000000000ea30550000000000041fa25bbe71dbc993529c7492969157cd6769334517fb381c2518038b6a6382d7021a0b4267ccfb7b44da5e2fbd57fb1ea60ee71bd3b9f8b416837762b3d88bf26dff797ee7ac7d53958e15db3fe5e9a6091984126007e376fb8440fb000000000000001f0e193512b825eabbd33482ea2e83d27dc93cac16e065f9ba8ceae6e2813d6d0a4b43ea89522b5dae8f7f88e614d14209418c28a6afc19282cdf1f9cf94d39c250200d00700001d01010020438dac40a541d483f964d534967669f59a9b256256fb9e659517014be363a16863863aef6ed65590301e5cb107d8ef3341cb27a0019825dbd40475a565fcc6f70000bd0107e10b5e040071dbc99300000000010000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed32328a010000000000ea30550000000000855c3401000000010002bb30f6894f29bb6fca635b1df728ad77e48fdd6123ce5e4455b0f71e072e7df80100010000000000855c3400804a1401ea305501000001000000010003ebcf44b45a71d4f225768f602d1e2e2b25ef779ee9897fe744bf1a16e85423d50100010000000000855c3400804a1401ea30550100000000d007000012010100206b469b0116de366510ae560294198f79cabb08ac61f4a9b754e59f750bee02bb13347317613e627ca4ed9d9da4095887739f470e2240752c1856890c7d334d9200006307e10b5e040071dbc99300000000010000000000ea30550040cbdaa86c52d5010000000000855c3400000000a8ed3232310000000000855c34000000008090b1ca00000000a8ed32320100000000010000000000ea305500000000a8ed3232010000000001013eefa22bab3d9803251f84f37e54dbc00c9f99040eebbd82d53308ca700db1b9 diff --git a/unittests/eosio.system_tests.cpp b/unittests/eosio.system_tests.cpp new file mode 100644 index 0000000000..28f389e855 --- /dev/null +++ b/unittests/eosio.system_tests.cpp @@ -0,0 +1,2851 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eosio_system_tester.hpp" + +struct _abi_hash { + eosio::chain::name owner; + fc::sha256 hash; +}; +FC_REFLECT( _abi_hash, (owner)(hash) ); + +struct connector { + asset balance; + double weight = .5; +}; +FC_REFLECT( connector, (balance)(weight) ); + +using namespace eosio_system; + +bool within_error(int64_t a, int64_t b, int64_t err) { return std::abs(a - b) <= err; }; +bool within_one(int64_t a, int64_t b) { return within_error(a, b, 1); } + +BOOST_AUTO_TEST_SUITE(eosio_system_part1_tests) + +BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { + using namespace eosio::chain; + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake( "alice1111111"_n ); + auto init_bytes = total["ram_bytes"].as_uint64(); + + const asset initial_ram_balance = get_balance("eosio.ram"_n); + const asset initial_ramfee_balance = get_balance("eosio.ramfee"_n); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("800.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( initial_ram_balance + core_from_string("199.0000"), get_balance("eosio.ram"_n) ); + BOOST_REQUIRE_EQUAL( initial_ramfee_balance + core_from_string("1.0000"), get_balance("eosio.ramfee"_n) ); + + total = get_total_stake( "alice1111111"_n ); + auto bytes = total["ram_bytes"].as_uint64(); + auto bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + BOOST_REQUIRE_EQUAL( true, 0 < bought_bytes ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, bought_bytes ) ); + BOOST_REQUIRE_EQUAL( core_from_string("998.0049"), get_balance( "alice1111111"_n ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( true, total["ram_bytes"].as_uint64() == init_bytes ); + + transfer( "eosio"_n, "alice1111111"_n, core_from_string("100000000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("100000998.0049"), get_balance( "alice1111111"_n ) ); + // alice buys ram for 10000000.0000, 0.5% = 50000.0000 go to ramfee + // after fee 9950000.0000 go to bought bytes + // when selling back bought bytes, pay 0.5% fee and get back 99.5% of 9950000.0000 = 9900250.0000 + // expected account after that is 90000998.0049 + 9900250.0000 = 99901248.0049 with a difference + // of order 0.0001 due to rounding errors + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("90000998.0049"), get_balance( "alice1111111"_n ) ); + + total = get_total_stake( "alice1111111"_n ); + bytes = total["ram_bytes"].as_uint64(); + bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, bought_bytes ) ); + total = get_total_stake( "alice1111111"_n ); + + bytes = total["ram_bytes"].as_uint64(); + bought_bytes = bytes - init_bytes; + wdump((init_bytes)(bought_bytes)(bytes) ); + + BOOST_REQUIRE_EQUAL( true, total["ram_bytes"].as_uint64() == init_bytes ); + BOOST_REQUIRE_EQUAL( core_from_string("99901248.0048"), get_balance( "alice1111111"_n ) ); + + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("30.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("99900688.0048"), get_balance( "alice1111111"_n ) ); + + auto newtotal = get_total_stake( "alice1111111"_n ); + + auto newbytes = newtotal["ram_bytes"].as_uint64(); + bought_bytes = newbytes - bytes; + wdump((newbytes)(bytes)(bought_bytes) ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, bought_bytes ) ); + BOOST_REQUIRE_EQUAL( core_from_string("99901242.4187"), get_balance( "alice1111111"_n ) ); + + newtotal = get_total_stake( "alice1111111"_n ); + auto startbytes = newtotal["ram_bytes"].as_uint64(); + + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("10000000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("300000.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("49301242.4187"), get_balance( "alice1111111"_n ) ); + + auto finaltotal = get_total_stake( "alice1111111"_n ); + auto endbytes = finaltotal["ram_bytes"].as_uint64(); + + bought_bytes = endbytes - startbytes; + wdump((startbytes)(endbytes)(bought_bytes) ); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, bought_bytes ) ); + + BOOST_REQUIRE_EQUAL( false, get_row_by_account( config::system_account_name, config::system_account_name, + "rammarket"_n, account_name(symbol{SY(4,RAMCORE)}.value()) ).empty() ); + + auto get_ram_market = [this]() -> fc::variant { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, + "rammarket"_n, account_name(symbol{SY(4,RAMCORE)}.value()) ); + BOOST_REQUIRE( !data.empty() ); + return abi_ser.binary_to_variant("exchange_state", data, abi_serializer::create_yield_function(abi_serializer_max_time)); + }; + + { + transfer( config::system_account_name, "alice1111111"_n, core_from_string("10000000.0000"), config::system_account_name ); + uint64_t bytes0 = get_total_stake( "alice1111111"_n )["ram_bytes"].as_uint64(); + + auto market = get_ram_market(); + const asset r0 = market["base"].as().balance; + const asset e0 = market["quote"].as().balance; + BOOST_REQUIRE_EQUAL( asset::from_string("0 RAM").get_symbol(), r0.get_symbol() ); + BOOST_REQUIRE_EQUAL( core_from_string("0.0000").get_symbol(), e0.get_symbol() ); + + const asset payment = core_from_string("10000000.0000"); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, payment ) ); + uint64_t bytes1 = get_total_stake( "alice1111111"_n )["ram_bytes"].as_uint64(); + + const int64_t fee = (payment.get_amount() + 199) / 200; + const double net_payment = payment.get_amount() - fee; + const uint64_t expected_delta = net_payment * r0.get_amount() / ( net_payment + e0.get_amount() ); + + BOOST_REQUIRE_EQUAL( expected_delta, bytes1 - bytes0 ); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_unstake, eosio_system_tester ) try { + cross_15_percent_threshold(); + + produce_blocks( 10 ); + produce_block( fc::hours(3*24) ); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + + const auto init_eosio_stake_balance = get_balance( "eosio.stake"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( init_eosio_stake_balance + core_from_string("300.0000"), get_balance( "eosio.stake"_n ) ); + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + // testing balance still the same + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( init_eosio_stake_balance + core_from_string("300.0000"), get_balance( "eosio.stake"_n ) ); + // call refund expected to fail too early + BOOST_REQUIRE_EQUAL( wasm_assert_msg("refund is not available yet"), + push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + + // after 1 hour refund ready + produce_block( fc::hours(1) ); + produce_blocks(1); + // now we can do the refund + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( init_eosio_stake_balance, get_balance( "eosio.stake"_n ) ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + total = get_total_stake("bob111111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000").get_amount(), total["net_weight"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000").get_amount(), total["cpu_weight"].as().get_amount() ); + + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000")), get_voter_info( "alice1111111"_n ) ); + + auto bytes = total["ram_bytes"].as_uint64(); + BOOST_REQUIRE_EQUAL( true, 0 < bytes ); + + //unstake from bob111111111 + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + total = get_total_stake("bob111111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as()); + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + //after 3 days funds should be released + produce_block( fc::hours(1) ); + produce_blocks(1); + + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("0.0000") ), get_voter_info( "alice1111111"_n ) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance( "alice1111111"_n ) ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_unstake_with_transfer, eosio_system_tester ) try { + cross_15_percent_threshold(); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + + //eosio stakes for alice with transfer flag + + transfer( "eosio"_n, "bob111111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "bob111111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + //check that alice has both bandwidth and voting power + auto total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000")), get_voter_info( "alice1111111"_n ) ); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + + //alice stakes for herself + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + //now alice's stake should be equal to transferred from eosio + own stake + total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("410.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("600.0000")), get_voter_info( "alice1111111"_n ) ); + + //alice can unstake everything (including what was transferred) + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "alice1111111"_n, core_from_string("400.0000"), core_from_string("200.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + //after 3 days funds should be released + + produce_block( fc::hours(1) ); + produce_blocks(1); + + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("1300.0000"), get_balance( "alice1111111"_n ) ); + + //stake should be equal to what was staked in constructor, voting power should be 0 + total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("0.0000")), get_voter_info( "alice1111111"_n ) ); + + // Now alice stakes to bob with transfer flag + BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "alice1111111"_n, "bob111111111"_n, core_from_string("100.0000"), core_from_string("100.0000") ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_to_self_with_transfer, eosio_system_tester ) try { + cross_15_percent_threshold(); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("cannot use transfer flag if delegating to self"), + stake_with_transfer( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) + ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_while_pending_refund, eosio_system_tester ) try { + cross_15_percent_threshold(); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + + //eosio stakes for alice with transfer flag + transfer( "eosio"_n, "bob111111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "bob111111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + //check that alice has both bandwidth and voting power + auto total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000")), get_voter_info( "alice1111111"_n ) ); + + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + + //alice stakes for herself + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + //now alice's stake should be equal to transferred from eosio + own stake + total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("410.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("600.0000")), get_voter_info( "alice1111111"_n ) ); + + //alice can unstake everything (including what was transferred) + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "alice1111111"_n, core_from_string("400.0000"), core_from_string("200.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + produce_block( fc::hours(3*24-1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + //after 3 days funds should be released + + produce_block( fc::hours(1) ); + produce_blocks(1); + + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("1300.0000"), get_balance( "alice1111111"_n ) ); + + //stake should be equal to what was staked in constructor, voting power should be 0 + total = get_total_stake("alice1111111"_n); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("0.0000")), get_voter_info( "alice1111111"_n ) ); + + // Now alice stakes to bob with transfer flag + BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "alice1111111"_n, "bob111111111"_n, core_from_string("100.0000"), core_from_string("100.0000") ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( fail_without_auth, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake( "eosio"_n, "alice1111111"_n, core_from_string("2000.0000"), core_from_string("1000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("10.0000"), core_from_string("10.0000") ) ); + + BOOST_REQUIRE_EQUAL( error("missing authority of alice1111111"), + push_action( "alice1111111"_n, "delegatebw"_n, mvo() + ("from", "alice1111111"_n) + ("receiver", "bob111111111"_n) + ("stake_net_quantity", core_from_string("10.0000")) + ("stake_cpu_quantity", core_from_string("10.0000")) + ("transfer", 0 ) + ,false + ) + ); + + BOOST_REQUIRE_EQUAL( error("missing authority of alice1111111"), + push_action("alice1111111"_n, "undelegatebw"_n, mvo() + ("from", "alice1111111"_n) + ("receiver", "bob111111111"_n) + ("unstake_net_quantity", core_from_string("200.0000")) + ("unstake_cpu_quantity", core_from_string("100.0000")) + ("transfer", 0 ) + ,false + ) + ); + //REQUIRE_MATCHING_OBJECT( , get_voter_info( "alice1111111"_n ) ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( stake_negative, eosio_system_tester ) try { + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must stake a positive amount"), + stake( "alice1111111"_n, core_from_string("-0.0001"), core_from_string("0.0000") ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must stake a positive amount"), + stake( "alice1111111"_n, core_from_string("0.0000"), core_from_string("-0.0001") ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must stake a positive amount"), + stake( "alice1111111"_n, core_from_string("00.0000"), core_from_string("00.0000") ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must stake a positive amount"), + stake( "alice1111111"_n, core_from_string("0.0000"), core_from_string("00.0000") ) + + ); + + BOOST_REQUIRE_EQUAL( true, get_voter_info( "alice1111111"_n ).is_null() ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unstake_negative, eosio_system_tester ) try { + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0001"), core_from_string("100.0001") ) ); + + auto total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0001"), total["net_weight"].as()); + auto vinfo = get_voter_info("alice1111111"_n ); + wdump((vinfo)); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0002") ), get_voter_info( "alice1111111"_n ) ); + + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must unstake a positive amount"), + unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("-1.0000"), core_from_string("0.0000") ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must unstake a positive amount"), + unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("0.0000"), core_from_string("-1.0000") ) + ); + + //unstake all zeros + BOOST_REQUIRE_EQUAL( wasm_assert_msg("must unstake a positive amount"), + unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("0.0000"), core_from_string("0.0000") ) + + ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unstake_more_than_at_stake, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + //trying to unstake more net bandwidth than at stake + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked net bandwidth"), + unstake( "alice1111111"_n, core_from_string("200.0001"), core_from_string("0.0000") ) + ); + + //trying to unstake more cpu bandwidth than at stake + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked cpu bandwidth"), + unstake( "alice1111111"_n, core_from_string("0.0000"), core_from_string("100.0001") ) + + ); + + //check that nothing has changed + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( delegate_to_another_user, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + + BOOST_REQUIRE_EQUAL( success(), stake ( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + //all voting power goes to alice1111111 + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000") ), get_voter_info( "alice1111111"_n ) ); + //but not to bob111111111 + BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob111111111"_n ).is_null() ); + + //bob111111111 should not be able to unstake what was staked by alice1111111 + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked cpu bandwidth"), + unstake( "bob111111111"_n, core_from_string("0.0000"), core_from_string("10.0000") ) + + ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked net bandwidth"), + unstake( "bob111111111"_n, core_from_string("10.0000"), core_from_string("0.0000") ) + ); + + issue_and_transfer( "carol1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, "bob111111111"_n, core_from_string("20.0000"), core_from_string("10.0000") ) ); + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("230.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("120.0000"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("970.0000"), get_balance( "carol1111111"_n ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol1111111"_n, core_from_string("30.0000") ), get_voter_info( "carol1111111"_n ) ); + + //alice1111111 should not be able to unstake money staked by carol1111111 + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked net bandwidth"), + unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("2001.0000"), core_from_string("1.0000") ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient staked cpu bandwidth"), + unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("1.0000"), core_from_string("101.0000") ) + + ); + + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("230.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("120.0000"), total["cpu_weight"].as()); + //balance should not change after unsuccessful attempts to unstake + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + //voting power too + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000") ), get_voter_info( "alice1111111"_n ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol1111111"_n, core_from_string("30.0000") ), get_voter_info( "carol1111111"_n ) ); + BOOST_REQUIRE_EQUAL( true, get_voter_info( "bob111111111"_n ).is_null() ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( stake_unstake_separate, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance( "alice1111111"_n ) ); + + //everything at once + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("10.0000"), core_from_string("20.0000") ) ); + auto total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("20.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("30.0000"), total["cpu_weight"].as()); + + //cpu + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("100.0000"), core_from_string("0.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("120.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("30.0000"), total["cpu_weight"].as()); + + //net + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("0.0000"), core_from_string("200.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("120.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("230.0000"), total["cpu_weight"].as()); + + //unstake cpu + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("100.0000"), core_from_string("0.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("20.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("230.0000"), total["cpu_weight"].as()); + + //unstake net + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("0.0000"), core_from_string("200.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("20.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("30.0000"), total["cpu_weight"].as()); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( adding_stake_partial_unstake, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000") ), get_voter_info( "alice1111111"_n ) ); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("100.0000"), core_from_string("50.0000") ) ); + + auto total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("310.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("160.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("450.0000") ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("550.0000"), get_balance( "alice1111111"_n ) ); + + //unstake a share + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("150.0000"), core_from_string("75.0000") ) ); + + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("160.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("85.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("225.0000") ), get_voter_info( "alice1111111"_n ) ); + + //unstake more + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "bob111111111"_n, core_from_string("50.0000"), core_from_string("25.0000") ) ); + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("150.0000") ), get_voter_info( "alice1111111"_n ) ); + + //combined amount should be available only in 3 days + produce_block( fc::days(2) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( core_from_string("550.0000"), get_balance( "alice1111111"_n ) ); + produce_block( fc::days(1) ); + produce_blocks(1); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "refund"_n, mvo()("owner", "alice1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("850.0000"), get_balance( "alice1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_from_refund, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("50.0000"), core_from_string("50.0000") ) ); + + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as()); + + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("400.0000") ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("600.0000"), get_balance( "alice1111111"_n ) ); + + //unstake a share + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000"), core_from_string("50.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("250.0000") ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("600.0000"), get_balance( "alice1111111"_n ) ); + auto refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string( "50.0000"), refund["cpu_amount"].as() ); + //XXX auto request_time = refund["request_time"].as_int64(); + + //alice delegates to bob, should pull from liquid balance not refund + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("50.0000"), core_from_string("50.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("350.0000") ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111"_n ) ); + refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string( "50.0000"), refund["cpu_amount"].as() ); + + //stake less than pending refund, entire amount should be taken from refund + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("50.0000"), core_from_string("25.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("160.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("85.0000"), total["cpu_weight"].as()); + refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("50.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string("25.0000"), refund["cpu_amount"].as() ); + //request time should stay the same + //BOOST_REQUIRE_EQUAL( request_time, refund["request_time"].as_int64() ); + //balance should stay the same + BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111"_n ) ); + + //stake exactly pending refund amount + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("50.0000"), core_from_string("25.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + //pending refund should be removed + refund = get_refund_request( "alice1111111"_n ); + BOOST_TEST_REQUIRE( refund.is_null() ); + //balance should stay the same + BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111"_n ) ); + + //create pending refund again + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111"_n ) ); + refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as() ); + + //stake more than pending refund + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("300.0000"), core_from_string("200.0000") ) ); + total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("310.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["cpu_weight"].as()); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("700.0000") ), get_voter_info( "alice1111111"_n ) ); + refund = get_refund_request( "alice1111111"_n ); + BOOST_TEST_REQUIRE( refund.is_null() ); + //200 core tokens should be taken from alice's account + BOOST_REQUIRE_EQUAL( core_from_string("300.0000"), get_balance( "alice1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( stake_to_another_user_not_from_refund, eosio_system_tester ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + auto total = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n, core_from_string("300.0000") ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111"_n ) ); + + //unstake + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + auto refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as() ); + //auto orig_request_time = refund["request_time"].as_int64(); + + //stake to another user + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "bob111111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + total = get_total_stake( "bob111111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as()); + BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as()); + //stake should be taken from alice's balance, and refund request should stay the same + BOOST_REQUIRE_EQUAL( core_from_string("400.0000"), get_balance( "alice1111111"_n ) ); + refund = get_refund_request( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as() ); + BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as() ); + //BOOST_REQUIRE_EQUAL( orig_request_time, refund["request_time"].as_int64() ); + +} FC_LOG_AND_RETHROW() + +// Tests for voting +BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try { + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + + //fc::variant params = producer_parameters_example(1); + auto key = fc::crypto::public_key( std::string("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV") ); // cspell:disable-line + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", key ) + ("url", "http://block.one") + ("location", 1) + ) + ); + + auto info = get_producer_info( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", info["url"].as_string() ); + + //change parameters one by one to check for things like #3783 + //fc::variant params2 = producer_parameters_example(2); + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", key ) + ("url", "http://block.two") + ("location", 1) + ) + ); + info = get_producer_info( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( key, fc::crypto::public_key(info["producer_key"].as_string()) ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( 1, info["location"].as_int64() ); + + auto key2 = fc::crypto::public_key( std::string("EOS5jnmSKrzdBHE9n8hw58y7yxFWBC8SNiG7m8S1crJH3KvAnf9o6") ); // cspell:disable-line + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", key2 ) + ("url", "http://block.two") + ("location", 2) + ) + ); + info = get_producer_info( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( key2, fc::crypto::public_key(info["producer_key"].as_string()) ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); + BOOST_REQUIRE_EQUAL( 2, info["location"].as_int64() ); + + //unregister producer + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "unregprod"_n, mvo() + ("producer", "alice1111111"_n) + ) + ); + info = get_producer_info( "alice1111111"_n ); + //key should be empty + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(info["producer_key"].as_string()) ); + //everything else should stay the same + BOOST_REQUIRE_EQUAL( "alice1111111", info["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, info["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.two", info["url"].as_string() ); + + //unregister bob111111111 who is not a producer + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "producer not found" ), + push_action( "bob111111111"_n, "unregprod"_n, mvo() + ("producer", "bob111111111"_n) + ) + ); + +} FC_LOG_AND_RETHROW() + + + +BOOST_FIXTURE_TEST_CASE( vote_for_producer, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + cross_15_percent_threshold(); + + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url", "http://block.one") + ("location", 0 ) + ) + ); + auto prod = get_producer_info( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( "alice1111111", prod["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( 0, prod["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "http://block.one", prod["url"].as_string() ); + + issue_and_transfer( "bob111111111"_n, core_from_string("2000.0000"), config::system_account_name ); + issue_and_transfer( "carol1111111"_n, core_from_string("3000.0000"), config::system_account_name ); + + //bob111111111 makes stake + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("11.0000"), core_from_string("0.1111") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("1988.8889"), get_balance( "bob111111111"_n ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("11.1111") ), get_voter_info( "bob111111111"_n ) ); + + //bob111111111 votes for alice1111111 + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, { "alice1111111"_n } ) ); + + //check that producer parameters stay the same after voting + prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("11.1111")) == prod["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( "alice1111111", prod["owner"].as_string() ); + BOOST_REQUIRE_EQUAL( "http://block.one", prod["url"].as_string() ); + + //carol1111111 makes stake + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("22.0000"), core_from_string("0.2222") ) ); + REQUIRE_MATCHING_OBJECT( voter( "carol1111111"_n, core_from_string("22.2222") ), get_voter_info( "carol1111111"_n ) ); + BOOST_REQUIRE_EQUAL( core_from_string("2977.7778"), get_balance( "carol1111111"_n ) ); + //carol1111111 votes for alice1111111 + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, { "alice1111111"_n } ) ); + + //new stake votes be added to alice1111111's total_votes + prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("33.3333")) == prod["total_votes"].as_double() ); + + //bob111111111 increases his stake + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("33.0000"), core_from_string("0.3333") ) ); + //alice1111111 stake with transfer to bob111111111 + BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "alice1111111"_n, "bob111111111"_n, core_from_string("22.0000"), core_from_string("0.2222") ) ); + //should increase alice1111111's total_votes + prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("88.8888")) == prod["total_votes"].as_double() ); + + //carol1111111 unstakes part of the stake + BOOST_REQUIRE_EQUAL( success(), unstake( "carol1111111"_n, core_from_string("2.0000"), core_from_string("0.0002")/*"2.0000 EOS", "0.0002 EOS"*/ ) ); + + //should decrease alice1111111's total_votes + prod = get_producer_info( "alice1111111"_n ); + wdump((prod)); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("86.8886")) == prod["total_votes"].as_double() ); + + //bob111111111 revokes his vote + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, vector() ) ); + + //should decrease alice1111111's total_votes + prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("20.2220")) == prod["total_votes"].as_double() ); + //but eos should still be at stake + BOOST_REQUIRE_EQUAL( core_from_string("1955.5556"), get_balance( "bob111111111"_n ) ); + + //carol1111111 unstakes rest of eos + BOOST_REQUIRE_EQUAL( success(), unstake( "carol1111111"_n, core_from_string("20.0000"), core_from_string("0.2220") ) ); + //should decrease alice1111111's total_votes to zero + prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( 0.0 == prod["total_votes"].as_double() ); + + //carol1111111 should receive funds in 3 days + produce_block( fc::days(3) ); + produce_block(); + + // do a bid refund for carol + BOOST_REQUIRE_EQUAL( success(), push_action( "carol1111111"_n, "refund"_n, mvo()("owner", "carol1111111"_n) ) ); + BOOST_REQUIRE_EQUAL( core_from_string("3000.0000"), get_balance( "carol1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unregistered_producer_voting, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + issue_and_transfer( "bob111111111"_n, core_from_string("2000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("13.0000"), core_from_string("0.5791") ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("13.5791") ), get_voter_info( "bob111111111"_n ) ); + + //bob111111111 should not be able to vote for alice1111111 who is not a producer + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "producer is not registered" ), + vote( "bob111111111"_n, { "alice1111111"_n } ) ); + + //alice1111111 registers as a producer + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url", "") + ("location", 0) + ) + ); + //and then unregisters + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "unregprod"_n, mvo() + ("producer", "alice1111111"_n) + ) + ); + //key should be empty + auto prod = get_producer_info( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(prod["producer_key"].as_string()) ); + + //bob111111111 should not be able to vote for alice1111111 who is an unregistered producer + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "producer is not currently registered" ), + vote( "bob111111111"_n, { "alice1111111"_n } ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( more_than_30_producer_voting, eosio_system_tester ) try { + issue_and_transfer( "bob111111111"_n, core_from_string("2000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("13.0000"), core_from_string("0.5791") ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("13.5791") ), get_voter_info( "bob111111111"_n ) ); + + //bob111111111 should not be able to vote for alice1111111 who is not a producer + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "attempt to vote for too many producers" ), + vote( "bob111111111"_n, vector(31, "alice1111111"_n) ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_same_producer_30_times, eosio_system_tester ) try { + issue_and_transfer( "bob111111111"_n, core_from_string("2000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("50.0000"), core_from_string("50.0000") ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("100.0000") ), get_voter_info( "bob111111111"_n ) ); + + //alice1111111 becomes a producer + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + fc::variant params = producer_parameters_example(1); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key("alice1111111"_n, "active") ) + ("url", "") + ("location", 0) + ) + ); + + //bob111111111 should not be able to vote for alice1111111 who is not a producer + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "producer votes must be unique and sorted" ), + vote( "bob111111111"_n, vector(30, "alice1111111"_n) ) ); + + auto prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( 0 == prod["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( producer_keep_votes, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + fc::variant params = producer_parameters_example(1); + vector key = fc::raw::pack( get_public_key( "alice1111111"_n, "active" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url", "") + ("location", 0) + ) + ); + + //bob111111111 makes stake + issue_and_transfer( "bob111111111"_n, core_from_string("2000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("13.0000"), core_from_string("0.5791") ) ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("13.5791") ), get_voter_info( "bob111111111"_n ) ); + + //bob111111111 votes for alice1111111 + BOOST_REQUIRE_EQUAL( success(), vote("bob111111111"_n, { "alice1111111"_n } ) ); + + auto prod = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("13.5791")) == prod["total_votes"].as_double() ); + + //unregister producer + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "unregprod"_n, mvo() + ("producer", "alice1111111"_n) + ) + ); + prod = get_producer_info( "alice1111111"_n ); + //key should be empty + BOOST_REQUIRE_EQUAL( fc::crypto::public_key(), fc::crypto::public_key(prod["producer_key"].as_string()) ); + //check parameters just in case + //REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes(core_from_string("13.5791")), prod["total_votes"].as_double() ); + + //regtister the same producer again + params = producer_parameters_example(2); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url", "") + ("location", 0) + ) + ); + prod = get_producer_info( "alice1111111"_n ); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes(core_from_string("13.5791")), prod["total_votes"].as_double() ); + + //change parameters + params = producer_parameters_example(3); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url","") + ("location", 0) + ) + ); + prod = get_producer_info( "alice1111111"_n ); + //votes should stay the same + BOOST_TEST_REQUIRE( stake2votes(core_from_string("13.5791")), prod["total_votes"].as_double() ); + //check parameters just in case + //REQUIRE_MATCHING_OBJECT( params, prod["prefs"]); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_for_two_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + //alice1111111 becomes a producer + fc::variant params = producer_parameters_example(1); + auto key = get_public_key( "alice1111111"_n, "active" ); + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproducer"_n, mvo() + ("producer", "alice1111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url","") + ("location", 0) + ) + ); + //bob111111111 becomes a producer + params = producer_parameters_example(2); + key = get_public_key( "bob111111111"_n, "active" ); + BOOST_REQUIRE_EQUAL( success(), push_action( "bob111111111"_n, "regproducer"_n, mvo() + ("producer", "bob111111111"_n) + ("producer_key", get_public_key( "alice1111111"_n, "active") ) + ("url","") + ("location", 0) + ) + ); + + //carol1111111 votes for alice1111111 and bob111111111 + issue_and_transfer( "carol1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("15.0005"), core_from_string("5.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, { "alice1111111"_n, "bob111111111"_n } ) ); + + auto alice_info = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("20.0005")) == alice_info["total_votes"].as_double() ); + auto bob_info = get_producer_info( "bob111111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("20.0005")) == bob_info["total_votes"].as_double() ); + + //carol1111111 votes for alice1111111 (but revokes vote for bob111111111) + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, { "alice1111111"_n } ) ); + + alice_info = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("20.0005")) == alice_info["total_votes"].as_double() ); + bob_info = get_producer_info( "bob111111111"_n ); + BOOST_TEST_REQUIRE( 0 == bob_info["total_votes"].as_double() ); + + //alice1111111 votes for herself and bob111111111 + issue_and_transfer( "alice1111111"_n, core_from_string("2.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("1.0000"), core_from_string("1.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), vote("alice1111111"_n, { "alice1111111"_n, "bob111111111"_n } ) ); + + alice_info = get_producer_info( "alice1111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("22.0005")) == alice_info["total_votes"].as_double() ); + + bob_info = get_producer_info( "bob111111111"_n ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("2.0000")) == bob_info["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_register_unregister_keeps_stake, eosio_system_tester ) try { + //register proxy by first action for this user ever + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true ) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n ), get_voter_info( "alice1111111"_n ) ); + + //unregister proxy + BOOST_REQUIRE_EQUAL( success(), push_action("alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n ), get_voter_info( "alice1111111"_n ) ); + + //stake and then register as a proxy + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("200.0002"), core_from_string("100.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( "bob111111111"_n, "regproxy"_n, mvo() + ("proxy", "bob111111111"_n) + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "bob111111111"_n )( "staked", 3000003 ), get_voter_info( "bob111111111"_n ) ); + //unrgister and check that stake is still in place + BOOST_REQUIRE_EQUAL( success(), push_action( "bob111111111"_n, "regproxy"_n, mvo() + ("proxy", "bob111111111"_n) + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "bob111111111"_n, core_from_string("300.0003") ), get_voter_info( "bob111111111"_n ) ); + + //register as a proxy and then stake + BOOST_REQUIRE_EQUAL( success(), push_action( "carol1111111"_n, "regproxy"_n, mvo() + ("proxy", "carol1111111"_n) + ("isproxy", true) + ) + ); + issue_and_transfer( "carol1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("246.0002"), core_from_string("531.0001") ) ); + //check that both proxy flag and stake a correct + REQUIRE_MATCHING_OBJECT( proxy( "carol1111111"_n )( "staked", 7770003 ), get_voter_info( "carol1111111"_n ) ); + + //unregister + BOOST_REQUIRE_EQUAL( success(), push_action( "carol1111111"_n, "regproxy"_n, mvo() + ("proxy", "carol1111111"_n) + ("isproxy", false) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "carol1111111"_n, core_from_string("777.0003") ), get_voter_info( "carol1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_stake_unstake_keeps_proxy_flag, eosio_system_tester ) try { + cross_15_percent_threshold(); + + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true) + ) + ); + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n ), get_voter_info( "alice1111111"_n ) ); + + //stake + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("100.0000"), core_from_string("50.0000") ) ); + //check that account is still a proxy + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "staked", 1500000 ), get_voter_info( "alice1111111"_n ) ); + + //stake more + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("30.0000"), core_from_string("20.0000") ) ); + //check that account is still a proxy + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )("staked", 2000000 ), get_voter_info( "alice1111111"_n ) ); + + //unstake more + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("65.0000"), core_from_string("35.0000") ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )("staked", 1000000 ), get_voter_info( "alice1111111"_n ) ); + + //unstake the rest + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("65.0000"), core_from_string("35.0000") ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "staked", 0 ), get_voter_info( "alice1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_actions_affect_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+5) ) try { + cross_15_percent_threshold(); + + create_accounts_with_resources( { "defproducer1"_n, "defproducer2"_n, "defproducer3"_n } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer1"_n, 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer2"_n, 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer3"_n, 3) ); + + //register as a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true) + ) + ); + + //accumulate proxied votes + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote("bob111111111"_n, vector(), "alice1111111"_n ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "proxied_vote_weight", stake2votes(core_from_string("150.0003")) ), get_voter_info( "alice1111111"_n ) ); + + //vote for producers + BOOST_REQUIRE_EQUAL( success(), vote("alice1111111"_n, { "defproducer1"_n, "defproducer2"_n } ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //vote for another producers + BOOST_REQUIRE_EQUAL( success(), vote( "alice1111111"_n, { "defproducer1"_n, "defproducer3"_n } ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //unregister proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", false) + ) + ); + //REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n )( "proxied_vote_weight", stake2votes(core_from_string("150.0003")) ), get_voter_info( "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //register proxy again + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true) + ) + ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //stake increase by proxy itself affects producers + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("30.0001"), core_from_string("20.0001") ) ); + BOOST_REQUIRE_EQUAL( stake2votes(core_from_string("200.0005")), get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( stake2votes(core_from_string("200.0005")), get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //stake decrease by proxy itself affects producers + BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111"_n, core_from_string("10.0001"), core_from_string("10.0001") ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("180.0003")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("180.0003")) == get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(eosio_system_part2_tests) + +BOOST_FIXTURE_TEST_CASE(multiple_producer_votepay_share, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + const std::vector voters = { "producvotera"_n, "producvoterb"_n, "producvoterc"_n, "producvoterd"_n }; + for (const auto& v: voters) { + create_account_with_resources( v, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, v, core_from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL(success(), stake(v, core_from_string("30000000.0000"), core_from_string("30000000.0000")) ); + } + + // create accounts {defproducera, defproducerb, ..., defproducerz, abcproducera, ..., defproducern} and register as producers + std::vector producer_names; + { + producer_names.reserve('z' - 'a' + 1); + { + const std::string root("defproducer"); + for ( char c = 'a'; c <= 'z'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + } + { + const std::string root("abcproducer"); + for ( char c = 'a'; c <= 'n'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + } + setup_producer_accounts(producer_names); + for (const auto& p: producer_names) { + BOOST_REQUIRE_EQUAL( success(), regproducer(p) ); + produce_blocks(1); + ilog( "------ get pro----------" ); + wdump((p)); + BOOST_TEST_REQUIRE(0 == get_producer_info(p)["total_votes"].as_double()); + BOOST_TEST_REQUIRE(0 == get_producer_info2(p)["votepay_share"].as_double()); + BOOST_REQUIRE(0 < microseconds_since_epoch_of_iso_string( get_producer_info2(p)["last_votepay_share_update"] )); + } + } + + produce_block( fc::hours(24) ); + + // producvotera votes for defproducera ... defproducerj + // producvoterb votes for defproducera ... defproduceru + // producvoterc votes for defproducera ... defproducerz + // producvoterd votes for abcproducera ... abcproducern + { + BOOST_TEST_REQUIRE( 0 == get_global_state3()["total_vpay_share_change_rate"].as_double() ); + BOOST_REQUIRE_EQUAL( success(), vote("producvotera"_n, vector(producer_names.begin(), producer_names.begin()+10)) ); + produce_block( fc::hours(10) ); + BOOST_TEST_REQUIRE( 0 == get_global_state2()["total_producer_votepay_share"].as_double() ); + const auto& init_info = get_producer_info(producer_names[0]); + const auto& init_info2 = get_producer_info2(producer_names[0]); + uint64_t init_update = microseconds_since_epoch_of_iso_string( init_info2["last_votepay_share_update"] ); + double init_votes = init_info["total_votes"].as_double(); + BOOST_REQUIRE_EQUAL( success(), vote("producvoterb"_n, vector(producer_names.begin(), producer_names.begin()+21)) ); + const auto& info = get_producer_info(producer_names[0]); + const auto& info2 = get_producer_info2(producer_names[0]); + BOOST_TEST_REQUIRE( ((microseconds_since_epoch_of_iso_string( info2["last_votepay_share_update"] ) - init_update)/double(1E6)) * init_votes == info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( info2["votepay_share"].as_double() * 10 == get_global_state2()["total_producer_votepay_share"].as_double() ); + + BOOST_TEST_REQUIRE( 0 == get_producer_info2(producer_names[11])["votepay_share"].as_double() ); + produce_block( fc::hours(13) ); + BOOST_REQUIRE_EQUAL( success(), vote("producvoterc"_n, vector(producer_names.begin(), producer_names.begin()+26)) ); + BOOST_REQUIRE( 0 < get_producer_info2(producer_names[11])["votepay_share"].as_double() ); + produce_block( fc::hours(1) ); + BOOST_REQUIRE_EQUAL( success(), vote("producvoterd"_n, vector(producer_names.begin()+26, producer_names.end())) ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(producer_names[26])["votepay_share"].as_double() ); + } + + { + auto proda = get_producer_info( "defproducera"_n ); + auto prodj = get_producer_info( "defproducerj"_n ); + auto prodk = get_producer_info( "defproducerk"_n ); + auto produ = get_producer_info( "defproduceru"_n ); + auto prodv = get_producer_info( "defproducerv"_n ); + auto prodz = get_producer_info( "defproducerz"_n ); + + BOOST_REQUIRE (0 == proda["unpaid_blocks"].as() && 0 == prodz["unpaid_blocks"].as()); + + // check vote ratios + BOOST_REQUIRE ( 0 < proda["total_votes"].as_double() && 0 < prodz["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( proda["total_votes"].as_double() == prodj["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( prodk["total_votes"].as_double() == produ["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( prodv["total_votes"].as_double() == prodz["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( 2 * proda["total_votes"].as_double() == 3 * produ["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( proda["total_votes"].as_double() == 3 * prodz["total_votes"].as_double() ); + } + + std::vector vote_shares(producer_names.size()); + { + double total_votes = 0; + for (uint32_t i = 0; i < producer_names.size(); ++i) { + vote_shares[i] = get_producer_info(producer_names[i])["total_votes"].as_double(); + total_votes += vote_shares[i]; + } + BOOST_TEST_REQUIRE( total_votes == get_global_state()["total_producer_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( total_votes == get_global_state3()["total_vpay_share_change_rate"].as_double() ); + BOOST_REQUIRE_EQUAL( microseconds_since_epoch_of_iso_string( get_producer_info2(producer_names.back())["last_votepay_share_update"] ), + microseconds_since_epoch_of_iso_string( get_global_state3()["last_vpay_state_update"] ) ); + + std::for_each( vote_shares.begin(), vote_shares.end(), [total_votes](double& x) { x /= total_votes; } ); + BOOST_TEST_REQUIRE( double(1) == std::accumulate(vote_shares.begin(), vote_shares.end(), double(0)) ); + BOOST_TEST_REQUIRE( double(3./71.) == vote_shares.front() ); + BOOST_TEST_REQUIRE( double(1./71.) == vote_shares.back() ); + } + + std::vector votepay_shares(producer_names.size()); + { + const auto& gs3 = get_global_state3(); + double total_votepay_shares = 0; + double expected_total_votepay_shares = 0; + for (uint32_t i = 0; i < producer_names.size() ; ++i) { + const auto& info = get_producer_info(producer_names[i]); + const auto& info2 = get_producer_info2(producer_names[i]); + votepay_shares[i] = info2["votepay_share"].as_double(); + total_votepay_shares += votepay_shares[i]; + expected_total_votepay_shares += votepay_shares[i]; + expected_total_votepay_shares += info["total_votes"].as_double() + * double( ( microseconds_since_epoch_of_iso_string( gs3["last_vpay_state_update"] ) + - microseconds_since_epoch_of_iso_string( info2["last_votepay_share_update"] ) + ) / 1E6 ); + } + BOOST_TEST( expected_total_votepay_shares > total_votepay_shares ); + BOOST_TEST_REQUIRE( expected_total_votepay_shares == get_global_state2()["total_producer_votepay_share"].as_double() ); + } + + { + const uint32_t prod_index = 15; + const account_name prod_name = producer_names[prod_index]; + const auto& init_info = get_producer_info(prod_name); + const auto& init_info2 = get_producer_info2(prod_name); + BOOST_REQUIRE( 0 < init_info2["votepay_share"].as_double() ); + BOOST_REQUIRE( 0 < microseconds_since_epoch_of_iso_string( init_info2["last_votepay_share_update"] ) ); + + BOOST_REQUIRE_EQUAL( success(), push_action(prod_name, "claimrewards"_n, mvo()("owner", prod_name)) ); + + BOOST_TEST_REQUIRE( 0 == get_producer_info2(prod_name)["votepay_share"].as_double() ); + BOOST_REQUIRE_EQUAL( get_producer_info(prod_name)["last_claim_time"].as_string(), + get_producer_info2(prod_name)["last_votepay_share_update"].as_string() ); + BOOST_REQUIRE_EQUAL( get_producer_info(prod_name)["last_claim_time"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + const auto& gs3 = get_global_state3(); + double expected_total_votepay_shares = 0; + for (uint32_t i = 0; i < producer_names.size(); ++i) { + const auto& info = get_producer_info(producer_names[i]); + const auto& info2 = get_producer_info2(producer_names[i]); + expected_total_votepay_shares += info2["votepay_share"].as_double(); + expected_total_votepay_shares += info["total_votes"].as_double() + * double( ( microseconds_since_epoch_of_iso_string( gs3["last_vpay_state_update"] ) + - microseconds_since_epoch_of_iso_string( info2["last_votepay_share_update"] ) + ) / 1E6 ); + } + BOOST_TEST_REQUIRE( expected_total_votepay_shares == get_global_state2()["total_producer_votepay_share"].as_double() ); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(votepay_share_invariant, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + + cross_15_percent_threshold(); + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + const std::vector accounts = { "aliceaccount"_n, "bobbyaccount"_n, "carolaccount"_n, "emilyaccount"_n }; + for (const auto& a: accounts) { + create_account_with_resources( a, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, a, core_from_string("1000.0000"), config::system_account_name ); + } + const auto vota = accounts[0]; + const auto votb = accounts[1]; + const auto proda = accounts[2]; + const auto prodb = accounts[3]; + + BOOST_REQUIRE_EQUAL( success(), stake( vota, core_from_string("100.0000"), core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), stake( votb, core_from_string("100.0000"), core_from_string("100.0000") ) ); + + BOOST_REQUIRE_EQUAL( success(), regproducer( proda ) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( prodb ) ); + + BOOST_REQUIRE_EQUAL( success(), vote( vota, { proda } ) ); + BOOST_REQUIRE_EQUAL( success(), vote( votb, { prodb } ) ); + + produce_block( fc::hours(25) ); + + BOOST_REQUIRE_EQUAL( success(), vote( vota, { proda } ) ); + BOOST_REQUIRE_EQUAL( success(), vote( votb, { prodb } ) ); + + produce_block( fc::hours(1) ); + + BOOST_REQUIRE_EQUAL( success(), push_action(proda, "claimrewards"_n, mvo()("owner", proda)) ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(proda)["votepay_share"].as_double() ); + + produce_block( fc::hours(24) ); + + BOOST_REQUIRE_EQUAL( success(), vote( vota, { proda } ) ); + + produce_block( fc::hours(24) ); + + BOOST_REQUIRE_EQUAL( success(), push_action(prodb, "claimrewards"_n, mvo()("owner", prodb)) ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(prodb)["votepay_share"].as_double() ); + + produce_block( fc::hours(10) ); + + BOOST_REQUIRE_EQUAL( success(), vote( votb, { prodb } ) ); + + produce_block( fc::hours(16) ); + + BOOST_REQUIRE_EQUAL( success(), vote( votb, { prodb } ) ); + produce_block( fc::hours(2) ); + BOOST_REQUIRE_EQUAL( success(), vote( vota, { proda } ) ); + + const auto& info = get_producer_info(prodb); + const auto& info2 = get_producer_info2(prodb); + const auto& gs2 = get_global_state2(); + const auto& gs3 = get_global_state3(); + + double expected_total_vpay_share = info2["votepay_share"].as_double() + + info["total_votes"].as_double() + * ( microseconds_since_epoch_of_iso_string( gs3["last_vpay_state_update"] ) + - microseconds_since_epoch_of_iso_string( info2["last_votepay_share_update"] ) ) / 1E6; + + BOOST_TEST_REQUIRE( expected_total_vpay_share == gs2["total_producer_votepay_share"].as_double() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(votepay_share_proxy, eosio_system_tester, * boost::unit_test::tolerance(1e-5)) try { + + cross_15_percent_threshold(); + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + const std::vector accounts = { "aliceaccount"_n, "bobbyaccount"_n, "carolaccount"_n, "emilyaccount"_n }; + for (const auto& a: accounts) { + create_account_with_resources( a, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, a, core_from_string("1000.0000"), config::system_account_name ); + } + const auto alice = accounts[0]; + const auto bob = accounts[1]; + const auto carol = accounts[2]; + const auto emily = accounts[3]; + + // alice becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( alice, "regproxy"_n, mvo()("proxy", alice)("isproxy", true) ) ); + REQUIRE_MATCHING_OBJECT( proxy( alice ), get_voter_info( alice ) ); + + // carol and emily become producers + BOOST_REQUIRE_EQUAL( success(), regproducer( carol, 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( emily, 1) ); + + // bob chooses alice as proxy + BOOST_REQUIRE_EQUAL( success(), stake( bob, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), stake( alice, core_from_string("150.0000"), core_from_string("150.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( bob, { }, alice ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_voter_info(alice)["proxied_vote_weight"].as_double() ); + + // alice (proxy) votes for carol + BOOST_REQUIRE_EQUAL( success(), vote( alice, { carol } ) ); + double total_votes = get_producer_info(carol)["total_votes"].as_double(); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("450.0003")) == total_votes ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(carol)["votepay_share"].as_double() ); + uint64_t last_update_time = microseconds_since_epoch_of_iso_string( get_producer_info2(carol)["last_votepay_share_update"] ); + + produce_block( fc::hours(15) ); + + // alice (proxy) votes again for carol + BOOST_REQUIRE_EQUAL( success(), vote( alice, { carol } ) ); + auto cur_info2 = get_producer_info2(carol); + double expected_votepay_share = double( (microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ) - last_update_time) / 1E6 ) * total_votes; + BOOST_TEST_REQUIRE( stake2votes(core_from_string("450.0003")) == get_producer_info(carol)["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == cur_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == get_global_state2()["total_producer_votepay_share"].as_double() ); + last_update_time = microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ); + total_votes = get_producer_info(carol)["total_votes"].as_double(); + + produce_block( fc::hours(40) ); + + // bob unstakes + BOOST_REQUIRE_EQUAL( success(), unstake( bob, core_from_string("10.0002"), core_from_string("10.0001") ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("430.0000")), get_producer_info(carol)["total_votes"].as_double() ); + + cur_info2 = get_producer_info2(carol); + expected_votepay_share += double( (microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ) - last_update_time) / 1E6 ) * total_votes; + BOOST_TEST_REQUIRE( expected_votepay_share == cur_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == get_global_state2()["total_producer_votepay_share"].as_double() ); + last_update_time = microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ); + total_votes = get_producer_info(carol)["total_votes"].as_double(); + + // carol claims rewards + BOOST_REQUIRE_EQUAL( success(), push_action(carol, "claimrewards"_n, mvo()("owner", carol)) ); + + produce_block( fc::hours(20) ); + + // bob votes for carol + BOOST_REQUIRE_EQUAL( success(), vote( bob, { carol } ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("430.0000")), get_producer_info(carol)["total_votes"].as_double() ); + cur_info2 = get_producer_info2(carol); + expected_votepay_share = double( (microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ) - last_update_time) / 1E6 ) * total_votes; + BOOST_TEST_REQUIRE( expected_votepay_share == cur_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == get_global_state2()["total_producer_votepay_share"].as_double() ); + + produce_block( fc::hours(54) ); + + // bob votes for carol again + // carol hasn't claimed rewards in over 3 days + total_votes = get_producer_info(carol)["total_votes"].as_double(); + BOOST_REQUIRE_EQUAL( success(), vote( bob, { carol } ) ); + BOOST_REQUIRE_EQUAL( get_producer_info2(carol)["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(carol)["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_global_state2()["total_producer_votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_global_state3()["total_vpay_share_change_rate"].as_double() ); + + produce_block( fc::hours(20) ); + + // bob votes for carol again + // carol still hasn't claimed rewards + BOOST_REQUIRE_EQUAL( success(), vote( bob, { carol } ) ); + BOOST_REQUIRE_EQUAL(get_producer_info2(carol)["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(carol)["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_global_state2()["total_producer_votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_global_state3()["total_vpay_share_change_rate"].as_double() ); + + produce_block( fc::hours(24) ); + + // carol finally claims rewards + BOOST_REQUIRE_EQUAL( success(), push_action( carol, "claimrewards"_n, mvo()("owner", carol) ) ); + BOOST_TEST_REQUIRE( 0 == get_producer_info2(carol)["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == get_global_state2()["total_producer_votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( total_votes == get_global_state3()["total_vpay_share_change_rate"].as_double() ); + + produce_block( fc::hours(5) ); + + // alice votes for carol and emily + // emily hasn't claimed rewards in over 3 days + last_update_time = microseconds_since_epoch_of_iso_string( get_producer_info2(carol)["last_votepay_share_update"] ); + BOOST_REQUIRE_EQUAL( success(), vote( alice, { carol, emily } ) ); + cur_info2 = get_producer_info2(carol); + auto cur_info2_emily = get_producer_info2(emily); + + expected_votepay_share = double( (microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ) - last_update_time) / 1E6 ) * total_votes; + BOOST_TEST_REQUIRE( expected_votepay_share == cur_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == cur_info2_emily["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == get_global_state2()["total_producer_votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( get_producer_info(carol)["total_votes"].as_double() == + get_global_state3()["total_vpay_share_change_rate"].as_double() ); + BOOST_REQUIRE_EQUAL( cur_info2["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + BOOST_REQUIRE_EQUAL( cur_info2_emily["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + + produce_block( fc::hours(10) ); + + // bob chooses alice as proxy + // emily still hasn't claimed rewards + last_update_time = microseconds_since_epoch_of_iso_string( get_producer_info2(carol)["last_votepay_share_update"] ); + BOOST_REQUIRE_EQUAL( success(), vote( bob, { }, alice ) ); + cur_info2 = get_producer_info2(carol); + cur_info2_emily = get_producer_info2(emily); + + expected_votepay_share += double( (microseconds_since_epoch_of_iso_string( cur_info2["last_votepay_share_update"] ) - last_update_time) / 1E6 ) * total_votes; + BOOST_TEST_REQUIRE( expected_votepay_share == cur_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == cur_info2_emily["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( expected_votepay_share == get_global_state2()["total_producer_votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( get_producer_info(carol)["total_votes"].as_double() == + get_global_state3()["total_vpay_share_change_rate"].as_double() ); + BOOST_REQUIRE_EQUAL( cur_info2["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + BOOST_REQUIRE_EQUAL( cur_info2_emily["last_votepay_share_update"].as_string(), + get_global_state3()["last_vpay_state_update"].as_string() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(votepay_share_update_order, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + + cross_15_percent_threshold(); + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + const std::vector accounts = { "aliceaccount"_n, "bobbyaccount"_n, "carolaccount"_n, "emilyaccount"_n }; + for (const auto& a: accounts) { + create_account_with_resources( a, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, a, core_from_string("1000.0000"), config::system_account_name ); + } + const auto alice = accounts[0]; + const auto bob = accounts[1]; + const auto carol = accounts[2]; + const auto emily = accounts[3]; + + BOOST_REQUIRE_EQUAL( success(), regproducer( carol ) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( emily ) ); + + produce_block( fc::hours(24) ); + + BOOST_REQUIRE_EQUAL( success(), stake( alice, core_from_string("100.0000"), core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), stake( bob, core_from_string("100.0000"), core_from_string("100.0000") ) ); + + BOOST_REQUIRE_EQUAL( success(), vote( alice, { carol, emily } ) ); + + + BOOST_REQUIRE_EQUAL( success(), push_action( carol, "claimrewards"_n, mvo()("owner", carol) ) ); + produce_block( fc::hours(1) ); + BOOST_REQUIRE_EQUAL( success(), push_action( emily, "claimrewards"_n, mvo()("owner", emily) ) ); + + produce_block( fc::hours(3 * 24 + 1) ); + + { + signed_transaction trx; + set_transaction_headers(trx); + + trx.actions.emplace_back( get_action( config::system_account_name, "claimrewards"_n, { {carol, config::active_name} }, + mvo()("owner", carol) ) ); + + std::vector prods = { carol, emily }; + trx.actions.emplace_back( get_action( config::system_account_name, "voteproducer"_n, { {alice, config::active_name} }, + mvo()("voter", alice)("proxy", name(0))("producers", prods) ) ); + + trx.actions.emplace_back( get_action( config::system_account_name, "claimrewards"_n, { {emily, config::active_name} }, + mvo()("owner", emily) ) ); + + trx.sign( get_private_key( carol, "active" ), control->get_chain_id() ); + trx.sign( get_private_key( alice, "active" ), control->get_chain_id() ); + trx.sign( get_private_key( emily, "active" ), control->get_chain_id() ); + + push_transaction( trx ); + } + + const auto& carol_info = get_producer_info(carol); + const auto& carol_info2 = get_producer_info2(carol); + const auto& emily_info = get_producer_info(emily); + const auto& emily_info2 = get_producer_info2(emily); + const auto& gs3 = get_global_state3(); + BOOST_REQUIRE_EQUAL( carol_info2["last_votepay_share_update"].as_string(), gs3["last_vpay_state_update"].as_string() ); + BOOST_REQUIRE_EQUAL( emily_info2["last_votepay_share_update"].as_string(), gs3["last_vpay_state_update"].as_string() ); + BOOST_TEST_REQUIRE( 0 == carol_info2["votepay_share"].as_double() ); + BOOST_TEST_REQUIRE( 0 == emily_info2["votepay_share"].as_double() ); + BOOST_REQUIRE( 0 < carol_info["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( carol_info["total_votes"].as_double() == emily_info["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( gs3["total_vpay_share_change_rate"].as_double() == 2 * carol_info["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(votepay_transition, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + const std::vector voters = { "producvotera"_n, "producvoterb"_n, "producvoterc"_n, "producvoterd"_n }; + for (const auto& v: voters) { + create_account_with_resources( v, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, v, core_from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL(success(), stake(v, core_from_string("30000000.0000"), core_from_string("30000000.0000")) ); + } + + // create accounts {defproducera, defproducerb, ..., defproducerz} and register as producers + std::vector producer_names; + { + producer_names.reserve('z' - 'a' + 1); + { + const std::string root("defproducer"); + for ( char c = 'a'; c <= 'd'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + } + setup_producer_accounts(producer_names); + for (const auto& p: producer_names) { + BOOST_REQUIRE_EQUAL( success(), regproducer(p) ); + BOOST_TEST_REQUIRE(0 == get_producer_info(p)["total_votes"].as_double()); + BOOST_TEST_REQUIRE(0 == get_producer_info2(p)["votepay_share"].as_double()); + BOOST_REQUIRE(0 < microseconds_since_epoch_of_iso_string( get_producer_info2(p)["last_votepay_share_update"] )); + } + } + + BOOST_REQUIRE_EQUAL( success(), vote("producvotera"_n, vector(producer_names.begin(), producer_names.end())) ); + auto* tbl = control->db().find( + boost::make_tuple( config::system_account_name, + config::system_account_name, + "producers2"_n ) ); + BOOST_REQUIRE( tbl ); + BOOST_REQUIRE( 0 < microseconds_since_epoch_of_iso_string( get_producer_info2("defproducera"_n)["last_votepay_share_update"] ) ); + + // const_cast hack for now + const_cast(control->db()).remove( *tbl ); + tbl = control->db().find( + boost::make_tuple( config::system_account_name, + config::system_account_name, + "producers2"_n ) ); + BOOST_REQUIRE( !tbl ); + + BOOST_REQUIRE_EQUAL( success(), vote("producvoterb"_n, vector(producer_names.begin(), producer_names.end())) ); + tbl = control->db().find( + boost::make_tuple( config::system_account_name, + config::system_account_name, + "producers2"_n ) ); + BOOST_REQUIRE( !tbl ); + BOOST_REQUIRE_EQUAL( success(), regproducer("defproducera"_n) ); + BOOST_REQUIRE( microseconds_since_epoch_of_iso_string( get_producer_info("defproducera"_n)["last_claim_time"] ) < microseconds_since_epoch_of_iso_string( get_producer_info2("defproducera"_n)["last_votepay_share_update"] ) ); + + create_account_with_resources( "defproducer1"_n, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + BOOST_REQUIRE_EQUAL( success(), regproducer("defproducer1"_n) ); + BOOST_REQUIRE( 0 < microseconds_since_epoch_of_iso_string( get_producer_info("defproducer1"_n)["last_claim_time"] ) ); + BOOST_REQUIRE_EQUAL( get_producer_info("defproducer1"_n)["last_claim_time"].as_string(), + get_producer_info2("defproducer1"_n)["last_votepay_share_update"].as_string() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(producers_upgrade_system_contract, eosio_system_tester) try { + //install multisig contract + abi_serializer msig_abi_ser = initialize_multisig(); + auto producer_names = active_and_vote_producers(); + + //change `default_max_inline_action_size` to 512 KB + eosio::chain::chain_config params = control->get_global_properties().configuration; + params.max_inline_action_size = 512 * 1024; + base_tester::push_action( config::system_account_name, "setparams"_n, config::system_account_name, mutable_variant_object() + ("params", params) ); + + produce_blocks(); + + //helper function + auto push_action_msig = [&]( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) -> action_result { + string action_type_name = msig_abi_ser.get_action_type(name); + + action act; + act.account = "eosio.msig"_n; + act.name = name; + act.data = msig_abi_ser.variant_to_binary( action_type_name, data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + + return base_tester::push_action( std::move(act), (auth ? signer : signer == "bob111111111"_n ? "alice1111111"_n : "bob111111111"_n).to_uint64_t() ); + }; + // test begins + vector prod_perms; + for ( auto& x : producer_names ) { + prod_perms.push_back( { name(x), config::active_name } ); + } + + transaction trx; + { + //prepare system contract with different hash (contract differs in one byte) + auto code = test_contracts::eosio_system_wasm(); + string msg = "producer votes must be unique and sorted"; + auto it = std::search( code.begin(), code.end(), msg.begin(), msg.end() ); + BOOST_REQUIRE( it != code.end() ); + msg[0] = 'P'; + std::copy( msg.begin(), msg.end(), it ); + + fc::variant pretty_trx = fc::mutable_variant_object() + ("expiration", "2020-01-01T00:30") + ("ref_block_num", 2) + ("ref_block_prefix", 3) + ("net_usage_words", 0) + ("max_cpu_usage_ms", 0) + ("delay_sec", 0) + ("actions", fc::variants({ + fc::mutable_variant_object() + ("account", name(config::system_account_name)) + ("name", "setcode") + ("authorization", vector{ { config::system_account_name, config::active_name } }) + ("data", fc::mutable_variant_object() ("account", name(config::system_account_name)) + ("vmtype", 0) + ("vmversion", "0") + ("code", bytes( code.begin(), code.end() )) + ) + }) + ); + abi_serializer::from_variant(pretty_trx, trx, get_resolver(), abi_serializer::create_yield_function(abi_serializer_max_time)); + } + + BOOST_REQUIRE_EQUAL(success(), push_action_msig( "alice1111111"_n, "propose"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "upgrade1") + ("trx", trx) + ("requested", prod_perms) + ) + ); + + // get 15 approvals + for ( size_t i = 0; i < 14; ++i ) { + BOOST_REQUIRE_EQUAL(success(), push_action_msig( name(producer_names[i]), "approve"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "upgrade1") + ("level", permission_level{ name(producer_names[i]), config::active_name }) + ) + ); + } + + //should fail + BOOST_REQUIRE_EQUAL(wasm_assert_msg("transaction authorization failed"), + push_action_msig( "alice1111111"_n, "exec"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "upgrade1") + ("executer", "alice1111111"_n) + ) + ); + + // one more approval + BOOST_REQUIRE_EQUAL(success(), push_action_msig( name(producer_names[14]), "approve"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "upgrade1") + ("level", permission_level{ name(producer_names[14]), config::active_name }) + ) + ); + + transaction_trace_ptr trace; + control->applied_transaction().connect( + [&]( std::tuple p ) { + trace = std::get<0>(p); + } ); + + BOOST_REQUIRE_EQUAL(success(), push_action_msig( "alice1111111"_n, "exec"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "upgrade1") + ("executer", "alice1111111"_n) + ) + ); + + BOOST_REQUIRE( bool(trace) ); + BOOST_REQUIRE_EQUAL( 1u, trace->action_traces.size() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); + + produce_blocks( 250 ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(producer_onblock_check, eosio_system_tester) try { + + const asset large_asset = core_from_string("80.0000"); + create_account_with_resources( "producvotera"_n, config::system_account_name, core_from_string("1.0000"), false, large_asset, large_asset ); + create_account_with_resources( "producvoterb"_n, config::system_account_name, core_from_string("1.0000"), false, large_asset, large_asset ); + create_account_with_resources( "producvoterc"_n, config::system_account_name, core_from_string("1.0000"), false, large_asset, large_asset ); + + // create accounts {defproducera, defproducerb, ..., defproducerz} and register as producers + std::vector producer_names; + producer_names.reserve('z' - 'a' + 1); + const std::string root("defproducer"); + for ( char c = 'a'; c <= 'z'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + setup_producer_accounts(producer_names); + + for (auto a:producer_names) + regproducer(a); + + produce_block(fc::hours(24)); + + BOOST_REQUIRE_EQUAL(0, get_producer_info( producer_names.front() )["total_votes"].as()); + BOOST_REQUIRE_EQUAL(0, get_producer_info( producer_names.back() )["total_votes"].as()); + + + transfer(config::system_account_name, "producvotera"_n, core_from_string("200000000.0000"), config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("producvotera"_n, core_from_string("70000000.0000"), core_from_string("70000000.0000") )); + BOOST_REQUIRE_EQUAL(success(), vote( "producvotera"_n, vector(producer_names.begin(), producer_names.begin()+10))); + BOOST_CHECK_EQUAL( wasm_assert_msg( "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ), + unstake( "producvotera"_n, core_from_string("50.0000"), core_from_string("50.0000") ) ); + + // give a chance for everyone to produce blocks + { + produce_blocks(21 * 12); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + all_21_produced= false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE_EQUAL(false, all_21_produced); + BOOST_REQUIRE_EQUAL(true, rest_didnt_produce); + } + + { + const char* claimrewards_activation_error_message = "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)"; + BOOST_CHECK_EQUAL(0u, get_global_state()["total_unpaid_blocks"].as()); + BOOST_REQUIRE_EQUAL(wasm_assert_msg( claimrewards_activation_error_message ), + push_action(producer_names.front(), "claimrewards"_n, mvo()("owner", producer_names.front()))); + BOOST_REQUIRE_EQUAL(0, get_balance(producer_names.front()).get_amount()); + BOOST_REQUIRE_EQUAL(wasm_assert_msg( claimrewards_activation_error_message ), + push_action(producer_names.back(), "claimrewards"_n, mvo()("owner", producer_names.back()))); + BOOST_REQUIRE_EQUAL(0, get_balance(producer_names.back()).get_amount()); + } + + // stake across 15% boundary + transfer(config::system_account_name, "producvoterb"_n, core_from_string("100000000.0000"), config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("producvoterb"_n, core_from_string("4000000.0000"), core_from_string("4000000.0000"))); + transfer(config::system_account_name, "producvoterc"_n, core_from_string("100000000.0000"), config::system_account_name); + BOOST_REQUIRE_EQUAL(success(), stake("producvoterc"_n, core_from_string("2000000.0000"), core_from_string("2000000.0000"))); + + BOOST_REQUIRE_EQUAL(success(), vote( "producvoterb"_n, vector(producer_names.begin(), producer_names.begin()+21))); + BOOST_REQUIRE_EQUAL(success(), vote( "producvoterc"_n, vector(producer_names.begin(), producer_names.end()))); + + // give a chance for everyone to produce blocks + { + produce_blocks(21 * 12); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + all_21_produced= false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE_EQUAL(true, all_21_produced); + BOOST_REQUIRE_EQUAL(true, rest_didnt_produce); + BOOST_REQUIRE_EQUAL(success(), + push_action(producer_names.front(), "claimrewards"_n, mvo()("owner", producer_names.front()))); + BOOST_REQUIRE(0 < get_balance(producer_names.front()).get_amount()); + } + + BOOST_CHECK_EQUAL( success(), unstake( "producvotera"_n, core_from_string("50.0000"), core_from_string("50.0000") ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( voters_actions_affect_proxy_and_producers, eosio_system_tester, * boost::unit_test::tolerance(1e+6) ) try { + cross_15_percent_threshold(); + + create_accounts_with_resources( { "donald111111"_n, "defproducer1"_n, "defproducer2"_n, "defproducer3"_n } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer1"_n, 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer2"_n, 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer3"_n, 3) ); + + //alice1111111 becomes a producer + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n ), get_voter_info( "alice1111111"_n ) ); + + //alice1111111 makes stake and votes + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("30.0001"), core_from_string("20.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "alice1111111"_n, { "defproducer1"_n, "defproducer2"_n } ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("50.0002")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("50.0002")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + BOOST_REQUIRE_EQUAL( success(), push_action( "donald111111"_n, "regproxy"_n, mvo() + ("proxy", "donald111111") + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "donald111111"_n ), get_voter_info( "donald111111"_n ) ); + + //bob111111111 chooses alice1111111 as a proxy + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, vector(), "alice1111111"_n ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("150.0003")) == get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("200.0005")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("200.0005")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //carol1111111 chooses alice1111111 as a proxy + issue_and_transfer( "carol1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("30.0001"), core_from_string("20.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, vector(), "alice1111111"_n ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("200.0005")) == get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("250.0007")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("250.0007")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //proxied voter carol1111111 increases stake + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("50.0000"), core_from_string("70.0000") ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("320.0005")) == get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("370.0007")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("370.0007")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //proxied voter bob111111111 decreases stake + BOOST_REQUIRE_EQUAL( success(), unstake( "bob111111111"_n, core_from_string("50.0001"), core_from_string("50.0001") ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("220.0003")) == get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("270.0005")) == get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("270.0005")) == get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //proxied voter carol1111111 chooses another proxy + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, vector(), "donald111111"_n ) ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("50.0001")), get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("170.0002")), get_voter_info( "donald111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("100.0003")), get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("100.0003")), get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + + //bob111111111 switches to direct voting and votes for one of the same producers, but not for another one + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, { "defproducer2"_n } ) ); + BOOST_TEST_REQUIRE( 0.0 == get_voter_info( "alice1111111"_n )["proxied_vote_weight"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("50.0002")), get_producer_info( "defproducer1"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( stake2votes(core_from_string("100.0003")), get_producer_info( "defproducer2"_n )["total_votes"].as_double() ); + BOOST_TEST_REQUIRE( 0.0 == get_producer_info( "defproducer3"_n )["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( vote_both_proxy_and_producers, eosio_system_tester ) try { + //alice1111111 becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", true) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n ), get_voter_info( "alice1111111"_n ) ); + + //carol1111111 becomes a producer + BOOST_REQUIRE_EQUAL( success(), regproducer( "carol1111111"_n, 1) ); + + //bob111111111 chooses alice1111111 as a proxy + + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("cannot vote for producers and proxy at same time"), + vote( "bob111111111"_n, { "carol1111111"_n }, "alice1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( select_invalid_proxy, eosio_system_tester ) try { + //accumulate proxied votes + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + + //selecting account not registered as a proxy + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "invalid proxy specified" ), + vote( "bob111111111"_n, vector(), "alice1111111"_n ) ); + + //selecting not existing account as a proxy + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "invalid proxy specified" ), + vote( "bob111111111"_n, vector(), "notexist"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( double_register_unregister_proxy_keeps_votes, eosio_system_tester ) try { + //alice1111111 becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", 1) + ) + ); + issue_and_transfer( "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, core_from_string("5.0000"), core_from_string("5.0000") ) ); + edump((get_voter_info("alice1111111"_n))); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "staked", 100000 ), get_voter_info( "alice1111111"_n ) ); + + //bob111111111 stakes and selects alice1111111 as a proxy + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, vector(), "alice1111111"_n ) ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "proxied_vote_weight", stake2votes( core_from_string("150.0003") ))( "staked", 100000 ), get_voter_info( "alice1111111"_n ) ); + + //double regestering should fail without affecting total votes and stake + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "action has no effect" ), + push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", 1) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice1111111"_n )( "proxied_vote_weight", stake2votes(core_from_string("150.0003")) )( "staked", 100000 ), get_voter_info( "alice1111111"_n ) ); + + //uregister + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", 0) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n )( "proxied_vote_weight", stake2votes(core_from_string("150.0003")) )( "staked", 100000 ), get_voter_info( "alice1111111"_n ) ); + + //double unregistering should not affect proxied_votes and stake + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "action has no effect" ), + push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", 0) + ) + ); + REQUIRE_MATCHING_OBJECT( voter( "alice1111111"_n )( "proxied_vote_weight", stake2votes(core_from_string("150.0003")))( "staked", 100000 ), get_voter_info( "alice1111111"_n ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proxy_cannot_use_another_proxy, eosio_system_tester ) try { + //alice1111111 becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "alice1111111"_n, "regproxy"_n, mvo() + ("proxy", "alice1111111"_n) + ("isproxy", 1) + ) + ); + + //bob111111111 becomes a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( "bob111111111"_n, "regproxy"_n, mvo() + ("proxy", "bob111111111"_n) + ("isproxy", 1) + ) + ); + + //proxy should not be able to use a proxy + issue_and_transfer( "bob111111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "account registered as a proxy is not allowed to use a proxy" ), + vote( "bob111111111"_n, vector(), "alice1111111"_n ) ); + + //voter that uses a proxy should not be allowed to become a proxy + issue_and_transfer( "carol1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol1111111"_n, core_from_string("100.0002"), core_from_string("50.0001") ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "carol1111111"_n, vector(), "alice1111111"_n ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "account that uses a proxy is not allowed to become a proxy" ), + push_action( "carol1111111"_n, "regproxy"_n, mvo() + ("proxy", "carol1111111"_n) + ("isproxy", 1) + ) + ); + + //proxy should not be able to use itself as a proxy + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "cannot proxy to self" ), + vote( "bob111111111"_n, vector(), "bob111111111"_n ) ); + +} FC_LOG_AND_RETHROW() + +fc::mutable_variant_object config_to_variant( const eosio::chain::chain_config& config ) { + return mutable_variant_object() + ( "max_block_net_usage", config.max_block_net_usage ) + ( "target_block_net_usage_pct", config.target_block_net_usage_pct ) + ( "max_transaction_net_usage", config.max_transaction_net_usage ) + ( "base_per_transaction_net_usage", config.base_per_transaction_net_usage ) + ( "context_free_discount_net_usage_num", config.context_free_discount_net_usage_num ) + ( "context_free_discount_net_usage_den", config.context_free_discount_net_usage_den ) + ( "max_block_cpu_usage", config.max_block_cpu_usage ) + ( "target_block_cpu_usage_pct", config.target_block_cpu_usage_pct ) + ( "max_transaction_cpu_usage", config.max_transaction_cpu_usage ) + ( "min_transaction_cpu_usage", config.min_transaction_cpu_usage ) + ( "max_transaction_lifetime", config.max_transaction_lifetime ) + ( "deferred_trx_expiration_window", config.deferred_trx_expiration_window ) + ( "max_transaction_delay", config.max_transaction_delay ) + ( "max_inline_action_size", config.max_inline_action_size ) + ( "max_inline_action_depth", config.max_inline_action_depth ) + ( "max_authority_depth", config.max_authority_depth ); +} + +BOOST_FIXTURE_TEST_CASE( elect_producers /*_and_parameters*/, eosio_system_tester ) try { + create_accounts_with_resources( { "defproducer1"_n, "defproducer2"_n, "defproducer3"_n } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer1"_n, 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer2"_n, 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "defproducer3"_n, 3) ); + + //stake more than 15% of total EOS supply to activate chain + transfer( "eosio"_n, "alice1111111"_n, core_from_string("600000000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111"_n, "alice1111111"_n, core_from_string("300000000.0000"), core_from_string("300000000.0000") ) ); + //vote for producers + BOOST_REQUIRE_EQUAL( success(), vote( "alice1111111"_n, { "defproducer1"_n } ) ); + produce_blocks(250); + auto producer_keys = control->active_producers(); + BOOST_REQUIRE_EQUAL( 1u, producer_keys.producers.size() ); + BOOST_REQUIRE_EQUAL( name("defproducer1"_n), producer_keys.producers[0].producer_name ); + + //auto config = config_to_variant( control->get_global_properties().configuration ); + //auto prod1_config = testing::filter_fields( config, producer_parameters_example( 1 ) ); + //REQUIRE_EQUAL_OBJECTS(prod1_config, config); + + // elect 2 producers + issue_and_transfer( "bob111111111"_n, core_from_string("80000.0000"), config::system_account_name ); + ilog("stake"); + BOOST_REQUIRE_EQUAL( success(), stake( "bob111111111"_n, core_from_string("40000.0000"), core_from_string("40000.0000") ) ); + ilog("start vote"); + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, { "defproducer2"_n } ) ); + ilog("."); + produce_blocks(250); + producer_keys = control->active_producers(); + BOOST_REQUIRE_EQUAL( 2u, producer_keys.producers.size() ); + BOOST_REQUIRE_EQUAL( name("defproducer1"_n), producer_keys.producers[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("defproducer2"_n), producer_keys.producers[1].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //auto prod2_config = testing::filter_fields( config, producer_parameters_example( 2 ) ); + //REQUIRE_EQUAL_OBJECTS(prod2_config, config); + + // elect 3 producers + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, { "defproducer2"_n, "defproducer3"_n } ) ); + produce_blocks(250); + producer_keys = control->active_producers(); + BOOST_REQUIRE_EQUAL( 3u, producer_keys.producers.size() ); + BOOST_REQUIRE_EQUAL( name("defproducer1"_n), producer_keys.producers[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("defproducer2"_n), producer_keys.producers[1].producer_name ); + BOOST_REQUIRE_EQUAL( name("defproducer3"_n), producer_keys.producers[2].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //REQUIRE_EQUAL_OBJECTS(prod2_config, config); + + // try to go back to 2 producers and fail + BOOST_REQUIRE_EQUAL( success(), vote( "bob111111111"_n, { "defproducer3"_n } ) ); + produce_blocks(250); + producer_keys = control->active_producers(); + BOOST_REQUIRE_EQUAL( 3u, producer_keys.producers.size() ); + + // The test below is invalid now, producer schedule is not updated if there are + // fewer producers in the new schedule + /* + BOOST_REQUIRE_EQUAL( 2, producer_keys.size() ); + BOOST_REQUIRE_EQUAL( name("defproducer1"_n), producer_keys[0].producer_name ); + BOOST_REQUIRE_EQUAL( name("defproducer3"_n), producer_keys[1].producer_name ); + //config = config_to_variant( control->get_global_properties().configuration ); + //auto prod3_config = testing::filter_fields( config, producer_parameters_example( 3 ) ); + //REQUIRE_EQUAL_OBJECTS(prod3_config, config); + */ + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( buyname, eosio_system_tester ) try { + create_accounts_with_resources( { "dan"_n, "sam"_n } ); + transfer( config::system_account_name, "dan"_n, core_from_string( "10000.0000" ) ); + transfer( config::system_account_name, "sam"_n, core_from_string( "10000.0000" ) ); + stake_with_transfer( config::system_account_name, "sam"_n, core_from_string( "80000000.0000" ), core_from_string( "80000000.0000" ) ); + stake_with_transfer( config::system_account_name, "dan"_n, core_from_string( "80000000.0000" ), core_from_string( "80000000.0000" ) ); + + regproducer( config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), vote( "sam"_n, { config::system_account_name } ) ); + // wait 14 days after min required amount has been staked + produce_block( fc::days(7) ); + BOOST_REQUIRE_EQUAL( success(), vote( "dan"_n, { config::system_account_name } ) ); + produce_block( fc::days(7) ); + produce_block(); + + BOOST_REQUIRE_EXCEPTION( create_accounts_with_resources( { "fail"_n }, "dan"_n ), // dan shouldn't be able to create fail + eosio_assert_message_exception, eosio_assert_message_is( "no active bid for name" ) ); + bidname( "dan"_n, "nofail"_n, core_from_string( "1.0000" ) ); + BOOST_REQUIRE_EQUAL( "assertion failure with message: must increase bid by 10%", bidname( "sam"_n, "nofail"_n, core_from_string( "1.0000" ) )); // didn't increase bid by 10% + BOOST_REQUIRE_EQUAL( success(), bidname( "sam"_n, "nofail"_n, core_from_string( "2.0000" ) )); // didn't increase bid by 10% + produce_block( fc::days(1) ); + produce_block(); + + BOOST_REQUIRE_EXCEPTION( create_accounts_with_resources( { "nofail"_n }, "dan"_n ), // dan shoudn't be able to do this, sam won + eosio_assert_message_exception, eosio_assert_message_is( "only highest bidder can claim" ) ); + //wlog( "verify sam can create nofail" ); + create_accounts_with_resources( { "nofail"_n }, "sam"_n ); // sam should be able to do this, he won the bid + //wlog( "verify nofail can create test.nofail" ); + transfer( "eosio"_n, "nofail"_n, core_from_string( "1000.0000" ) ); + create_accounts_with_resources( { "test.nofail"_n }, "nofail"_n ); // only nofail can create test.nofail + //wlog( "verify dan cannot create test.fail" ); + BOOST_REQUIRE_EXCEPTION( create_accounts_with_resources( { "test.fail"_n }, "dan"_n ), // dan shouldn't be able to do this + eosio_assert_message_exception, eosio_assert_message_is( "only suffix may create this account" ) ); + + create_accounts_with_resources( { "goodgoodgood"_n }, "dan"_n ); /// 12 char names should succeed +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( bid_invalid_names, eosio_system_tester ) try { + create_accounts_with_resources( { "dan"_n } ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "you can only bid on top-level suffix" ), + bidname( "dan"_n, "abcdefg.12345"_n, core_from_string( "1.0000" ) ) ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "the empty name is not a valid account name to bid on" ), + bidname( "dan"_n, ""_n, core_from_string( "1.0000" ) ) ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "13 character names are not valid account names to bid on" ), + bidname( "dan"_n, "abcdefgh12345"_n, core_from_string( "1.0000" ) ) ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "accounts with 12 character names and no dots can be created without bidding required" ), + bidname( "dan"_n, "abcdefg12345"_n, core_from_string( "1.0000" ) ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( multiple_namebids, eosio_system_tester ) try { + + const std::string not_closed_message("auction for name is not closed yet"); + + std::vector accounts = { "alice"_n, "bob"_n, "carl"_n, "david"_n, "eve"_n }; + create_accounts_with_resources( accounts ); + for ( const auto& a: accounts ) { + transfer( config::system_account_name, a, core_from_string( "10000.0000" ) ); + BOOST_REQUIRE_EQUAL( core_from_string( "10000.0000" ), get_balance(a) ); + } + create_accounts_with_resources( { "producer"_n } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer"_n ) ); + + produce_block(); + // stake but not enough to go live + stake_with_transfer( config::system_account_name, "bob"_n, core_from_string( "35000000.0000" ), core_from_string( "35000000.0000" ) ); + stake_with_transfer( config::system_account_name, "carl"_n, core_from_string( "35000000.0000" ), core_from_string( "35000000.0000" ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "bob"_n, { "producer"_n } ) ); + BOOST_REQUIRE_EQUAL( success(), vote( "carl"_n, { "producer"_n } ) ); + + // start bids + bidname( "bob"_n, "prefa"_n, core_from_string("1.0003") ); + BOOST_REQUIRE_EQUAL( core_from_string( "9998.9997" ), get_balance("bob"_n) ); + bidname( "bob"_n, "prefb"_n, core_from_string("1.0000") ); + bidname( "bob"_n, "prefc"_n, core_from_string("1.0000") ); + BOOST_REQUIRE_EQUAL( core_from_string( "9996.9997" ), get_balance("bob"_n) ); + + bidname( "carl"_n, "prefd"_n, core_from_string("1.0000") ); + bidname( "carl"_n, "prefe"_n, core_from_string("1.0000") ); + BOOST_REQUIRE_EQUAL( core_from_string( "9998.0000" ), get_balance("carl"_n) ); + + BOOST_REQUIRE_EQUAL( error("assertion failure with message: account is already highest bidder"), + bidname( "bob"_n, "prefb"_n, core_from_string("1.1001") ) ); + BOOST_REQUIRE_EQUAL( error("assertion failure with message: must increase bid by 10%"), + bidname( "alice"_n, "prefb"_n, core_from_string("1.0999") ) ); + BOOST_REQUIRE_EQUAL( core_from_string( "9996.9997" ), get_balance("bob"_n) ); + BOOST_REQUIRE_EQUAL( core_from_string( "10000.0000" ), get_balance("alice"_n) ); + + + // alice outbids bob on prefb + { + const asset initial_names_balance = get_balance("eosio.names"_n); + BOOST_REQUIRE_EQUAL( success(), + bidname( "alice"_n, "prefb"_n, core_from_string("1.1001") ) ); + // refund bob's failed bid on prefb + BOOST_REQUIRE_EQUAL( success(), push_action( "bob"_n, "bidrefund"_n, mvo()("bidder","bob")("newname", "prefb") ) ); + BOOST_REQUIRE_EQUAL( core_from_string( "9997.9997" ), get_balance("bob"_n) ); + BOOST_REQUIRE_EQUAL( core_from_string( "9998.8999" ), get_balance("alice"_n) ); + BOOST_REQUIRE_EQUAL( initial_names_balance + core_from_string("0.1001"), get_balance("eosio.names"_n) ); + } + + // david outbids carl on prefd + { + BOOST_REQUIRE_EQUAL( core_from_string( "9998.0000" ), get_balance("carl"_n) ); + BOOST_REQUIRE_EQUAL( core_from_string( "10000.0000" ), get_balance("david"_n) ); + BOOST_REQUIRE_EQUAL( success(), + bidname( "david"_n, "prefd"_n, core_from_string("1.9900") ) ); + // refund carls's failed bid on prefd + BOOST_REQUIRE_EQUAL( success(), push_action( "carl"_n, "bidrefund"_n, mvo()("bidder","carl")("newname", "prefd") ) ); + BOOST_REQUIRE_EQUAL( core_from_string( "9999.0000" ), get_balance("carl"_n) ); + BOOST_REQUIRE_EQUAL( core_from_string( "9998.0100" ), get_balance("david"_n) ); + } + + // eve outbids carl on prefe + { + BOOST_REQUIRE_EQUAL( success(), + bidname( "eve"_n, "prefe"_n, core_from_string("1.7200") ) ); + } + + produce_block( fc::days(14) ); + produce_block(); + + // highest bid is from david for prefd but no bids can be closed yet + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefd"_n, "david"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + + // stake enough to go above the 15% threshold + stake_with_transfer( config::system_account_name, "alice"_n, core_from_string( "10000000.0000" ), core_from_string( "10000000.0000" ) ); + BOOST_REQUIRE_EQUAL(0u, get_producer_info("producer"_n)["unpaid_blocks"].as()); + BOOST_REQUIRE_EQUAL( success(), vote( "alice"_n, { "producer"_n } ) ); + + // need to wait for 14 days after going live + produce_blocks(10); + produce_block( fc::days(2) ); + produce_blocks( 10 ); + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefd"_n, "david"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + // it's been 14 days, auction for prefd has been closed + produce_block( fc::days(12) ); + create_account_with_resources( "prefd"_n, "david"_n ); + produce_blocks(2); + produce_block( fc::hours(23) ); + // auctions for prefa, prefb, prefc, prefe haven't been closed + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefa"_n, "bob"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefb"_n, "alice"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefc"_n, "bob"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefe"_n, "eve"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + // attemp to create account with no bid + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefg"_n, "alice"_n ), + fc::exception, fc_assert_exception_message_is( "no active bid for name" ) ); + // changing highest bid pushes auction closing time by 24 hours + BOOST_REQUIRE_EQUAL( success(), + bidname( "eve"_n, "prefb"_n, core_from_string("2.1880") ) ); + + produce_block( fc::hours(22) ); + produce_blocks(2); + + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefb"_n, "eve"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + // but changing a bid that is not the highest does not push closing time + BOOST_REQUIRE_EQUAL( success(), + bidname( "carl"_n, "prefe"_n, core_from_string("2.0980") ) ); + produce_block( fc::hours(2) ); + produce_blocks(2); + // bid for prefb has closed, only highest bidder can claim + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefb"_n, "alice"_n ), + eosio_assert_message_exception, eosio_assert_message_is( "only highest bidder can claim" ) ); + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefb"_n, "carl"_n ), + eosio_assert_message_exception, eosio_assert_message_is( "only highest bidder can claim" ) ); + create_account_with_resources( "prefb"_n, "eve"_n ); + + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefe"_n, "carl"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + produce_block(); + produce_block( fc::hours(24) ); + // by now bid for prefe has closed + create_account_with_resources( "prefe"_n, "carl"_n ); + // prefe can now create *.prefe + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "xyz.prefe"_n, "carl"_n ), + fc::exception, fc_assert_exception_message_is("only suffix may create this account") ); + transfer( config::system_account_name, "prefe"_n, core_from_string("10000.0000") ); + create_account_with_resources( "xyz.prefe"_n, "prefe"_n ); + + // other auctions haven't closed + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( "prefa"_n, "bob"_n ), + fc::exception, fc_assert_exception_message_is( not_closed_message ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( vote_producers_in_and_out, eosio_system_tester ) try { + + const asset net = core_from_string("80.0000"); + const asset cpu = core_from_string("80.0000"); + std::vector voters = { "producvotera"_n, "producvoterb"_n, "producvoterc"_n, "producvoterd"_n }; + for (const auto& v: voters) { + create_account_with_resources(v, config::system_account_name, core_from_string("1.0000"), false, net, cpu); + } + + // create accounts {defproducera, defproducerb, ..., defproducerz} and register as producers + std::vector producer_names; + { + producer_names.reserve('z' - 'a' + 1); + const std::string root("defproducer"); + for ( char c = 'a'; c <= 'z'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + setup_producer_accounts(producer_names); + for (const auto& p: producer_names) { + BOOST_REQUIRE_EQUAL( success(), regproducer(p) ); + produce_blocks(1); + ilog( "------ get pro----------" ); + wdump((p)); + BOOST_TEST(0 == get_producer_info(p)["total_votes"].as()); + } + } + + for (const auto& v: voters) { + transfer( config::system_account_name, v, core_from_string("200000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL(success(), stake(v, core_from_string("30000000.0000"), core_from_string("30000000.0000")) ); + } + + { + BOOST_REQUIRE_EQUAL(success(), vote("producvotera"_n, vector(producer_names.begin(), producer_names.begin()+20))); + BOOST_REQUIRE_EQUAL(success(), vote("producvoterb"_n, vector(producer_names.begin(), producer_names.begin()+21))); + BOOST_REQUIRE_EQUAL(success(), vote("producvoterc"_n, vector(producer_names.begin(), producer_names.end()))); + } + + // give a chance for everyone to produce blocks + { + produce_blocks(23 * 12 + 20); + bool all_21_produced = true; + for (uint32_t i = 0; i < 21; ++i) { + if (0 == get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + all_21_produced = false; + } + } + bool rest_didnt_produce = true; + for (uint32_t i = 21; i < producer_names.size(); ++i) { + if (0 < get_producer_info(producer_names[i])["unpaid_blocks"].as()) { + rest_didnt_produce = false; + } + } + BOOST_REQUIRE(all_21_produced && rest_didnt_produce); + } + + { + produce_block(fc::hours(7)); + const uint32_t voted_out_index = 20; + const uint32_t new_prod_index = 23; + BOOST_REQUIRE_EQUAL(success(), stake("producvoterd"_n, core_from_string("40000000.0000"), core_from_string("40000000.0000"))); + BOOST_REQUIRE_EQUAL(success(), vote("producvoterd"_n, { producer_names[new_prod_index] })); + BOOST_REQUIRE_EQUAL(0u, get_producer_info(producer_names[new_prod_index])["unpaid_blocks"].as()); + produce_blocks(4 * 12 * 21); + BOOST_REQUIRE(0 < get_producer_info(producer_names[new_prod_index])["unpaid_blocks"].as()); + const uint32_t initial_unpaid_blocks = get_producer_info(producer_names[voted_out_index])["unpaid_blocks"].as(); + produce_blocks(2 * 12 * 21); + BOOST_REQUIRE_EQUAL(initial_unpaid_blocks, get_producer_info(producer_names[voted_out_index])["unpaid_blocks"].as()); + produce_block(fc::hours(24)); + BOOST_REQUIRE_EQUAL(success(), vote("producvoterd"_n, { producer_names[voted_out_index] })); + produce_blocks(2 * 12 * 21); + BOOST_REQUIRE(fc::crypto::public_key() != fc::crypto::public_key(get_producer_info(producer_names[voted_out_index])["producer_key"].as_string())); + BOOST_REQUIRE_EQUAL(success(), push_action(producer_names[voted_out_index], "claimrewards"_n, mvo()("owner", producer_names[voted_out_index]))); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( setparams, eosio_system_tester ) try { + //install multisig contract + abi_serializer msig_abi_ser = initialize_multisig(); + auto producer_names = active_and_vote_producers(); + + //helper function + auto push_action_msig = [&]( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) -> action_result { + string action_type_name = msig_abi_ser.get_action_type(name); + + action act; + act.account = "eosio.msig"_n; + act.name = name; + act.data = msig_abi_ser.variant_to_binary( action_type_name, data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + + return base_tester::push_action( std::move(act), (auth ? signer : signer == "bob111111111"_n ? "alice1111111"_n : "bob111111111"_n).to_uint64_t() ); + }; + + // test begins + vector prod_perms; + for ( auto& x : producer_names ) { + prod_perms.push_back( { name(x), config::active_name } ); + } + + eosio::chain::chain_config params; + params = control->get_global_properties().configuration; + //change some values + params.max_block_net_usage += 10; + params.max_transaction_lifetime += 1; + + transaction trx; + { + fc::variant pretty_trx = fc::mutable_variant_object() + ("expiration", "2020-01-01T00:30") + ("ref_block_num", 2) + ("ref_block_prefix", 3) + ("net_usage_words", 0) + ("max_cpu_usage_ms", 0) + ("delay_sec", 0) + ("actions", fc::variants({ + fc::mutable_variant_object() + ("account", name(config::system_account_name)) + ("name", "setparams") + ("authorization", vector{ { config::system_account_name, config::active_name } }) + ("data", fc::mutable_variant_object() + ("params", params) + ) + }) + ); + abi_serializer::from_variant(pretty_trx, trx, get_resolver(), abi_serializer::create_yield_function(abi_serializer_max_time)); + } + + BOOST_REQUIRE_EQUAL(success(), push_action_msig( "alice1111111"_n, "propose"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "setparams1") + ("trx", trx) + ("requested", prod_perms) + ) + ); + + // get 16 approvals + for ( size_t i = 0; i < 15; ++i ) { + BOOST_REQUIRE_EQUAL(success(), push_action_msig( name(producer_names[i]), "approve"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "setparams1") + ("level", permission_level{ name(producer_names[i]), config::active_name }) + ) + ); + } + + transaction_trace_ptr trace; + control->applied_transaction().connect( + [&]( std::tuple p ) { + trace = std::get<0>(p); + } ); + + BOOST_REQUIRE_EQUAL(success(), push_action_msig( "alice1111111"_n, "exec"_n, mvo() + ("proposer", "alice1111111"_n) + ("proposal_name", "setparams1") + ("executer", "alice1111111"_n) + ) + ); + + BOOST_REQUIRE( bool(trace) ); + BOOST_REQUIRE_EQUAL( 1u, trace->action_traces.size() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); + + produce_blocks( 250 ); + + // make sure that changed parameters were applied + auto active_params = control->get_global_properties().configuration; + BOOST_REQUIRE_EQUAL( params.max_block_net_usage, active_params.max_block_net_usage ); + BOOST_REQUIRE_EQUAL( params.max_transaction_lifetime, active_params.max_transaction_lifetime ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( setram_effect, eosio_system_tester ) try { + + const asset net = core_from_string("8.0000"); + const asset cpu = core_from_string("8.0000"); + std::vector accounts = { "aliceaccount"_n, "bobbyaccount"_n }; + for (const auto& a: accounts) { + create_account_with_resources(a, config::system_account_name, core_from_string("1.0000"), false, net, cpu); + } + + { + const auto name_a = accounts[0]; + transfer( config::system_account_name, name_a, core_from_string("1000.0000") ); + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance(name_a) ); + const uint64_t init_bytes_a = get_total_stake(name_a)["ram_bytes"].as_uint64(); + BOOST_REQUIRE_EQUAL( success(), buyram( name_a, name_a, core_from_string("300.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance(name_a) ); + const uint64_t bought_bytes_a = get_total_stake(name_a)["ram_bytes"].as_uint64() - init_bytes_a; + + // after buying and selling balance should be 700 + 300 * 0.995 * 0.995 = 997.0075 (actually 997.0074 due to rounding fees up) + BOOST_REQUIRE_EQUAL( success(), sellram(name_a, bought_bytes_a ) ); + BOOST_REQUIRE_EQUAL( core_from_string("997.0074"), get_balance(name_a) ); + } + + { + const auto name_b = accounts[1]; + transfer( config::system_account_name, name_b, core_from_string("1000.0000") ); + BOOST_REQUIRE_EQUAL( core_from_string("1000.0000"), get_balance(name_b) ); + const uint64_t init_bytes_b = get_total_stake(name_b)["ram_bytes"].as_uint64(); + // name_b buys ram at current price + BOOST_REQUIRE_EQUAL( success(), buyram( name_b, name_b, core_from_string("300.0000") ) ); + BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance(name_b) ); + const uint64_t bought_bytes_b = get_total_stake(name_b)["ram_bytes"].as_uint64() - init_bytes_b; + + // increase max_ram_size, ram bought by name_b loses part of its value + BOOST_REQUIRE_EQUAL( wasm_assert_msg("ram may only be increased"), + push_action(config::system_account_name, "setram"_n, mvo()("max_ram_size", 64ll*1024 * 1024 * 1024)) ); + BOOST_REQUIRE_EQUAL( error("missing authority of eosio"), + push_action(name_b, "setram"_n, mvo()("max_ram_size", 80ll*1024 * 1024 * 1024)) ); + BOOST_REQUIRE_EQUAL( success(), + push_action(config::system_account_name, "setram"_n, mvo()("max_ram_size", 80ll*1024 * 1024 * 1024)) ); + + BOOST_REQUIRE_EQUAL( success(), sellram(name_b, bought_bytes_b ) ); + BOOST_REQUIRE( core_from_string("900.0000") < get_balance(name_b) ); + BOOST_REQUIRE( core_from_string("950.0000") > get_balance(name_b) ); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( ram_inflation, eosio_system_tester ) try { + + const uint64_t init_max_ram_size = 64ll*1024 * 1024 * 1024; + + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + produce_blocks(20); + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + transfer( config::system_account_name, "alice1111111"_n, core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + produce_blocks(3); + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + uint16_t rate = 1000; + BOOST_REQUIRE_EQUAL( success(), push_action( config::system_account_name, "setramrate"_n, mvo()("bytes_per_block", rate) ) ); + BOOST_REQUIRE_EQUAL( rate, get_global_state2()["new_ram_per_block"].as() ); + // last time update_ram_supply called is in buyram, num of blocks since then to + // the block that includes the setramrate action is 1 + 3 = 4. + // However, those 4 blocks were accumulating at a rate of 0, so the max_ram_size should not have changed. + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + // But with additional blocks, it should start accumulating at the new rate. + uint64_t cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(10); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 11 * rate, get_global_state()["max_ram_size"].as_uint64() ); + cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(5); + BOOST_REQUIRE_EQUAL( cur_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, 100 ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 6 * rate, get_global_state()["max_ram_size"].as_uint64() ); + cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(); + BOOST_REQUIRE_EQUAL( success(), buyrambytes( "alice1111111"_n, "alice1111111"_n, 100 ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 2 * rate, get_global_state()["max_ram_size"].as_uint64() ); + + BOOST_REQUIRE_EQUAL( error("missing authority of eosio"), + push_action( "alice1111111"_n, "setramrate"_n, mvo()("bytes_per_block", rate) ) ); + + cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(10); + uint16_t old_rate = rate; + rate = 5000; + BOOST_REQUIRE_EQUAL( success(), push_action( config::system_account_name, "setramrate"_n, mvo()("bytes_per_block", rate) ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 11 * old_rate, get_global_state()["max_ram_size"].as_uint64() ); + produce_blocks(5); + BOOST_REQUIRE_EQUAL( success(), buyrambytes( "alice1111111"_n, "alice1111111"_n, 100 ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 11 * old_rate + 6 * rate, get_global_state()["max_ram_size"].as_uint64() ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( eosioram_ramusage, eosio_system_tester ) try { + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111"_n ) ); + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("1000.0000") ) ); + + BOOST_REQUIRE_EQUAL( false, get_row_by_account( "eosio.token"_n, "alice1111111"_n, "accounts"_n, account_name(symbol{}.to_symbol_code()) ).empty() ); + + //remove row + base_tester::push_action( "eosio.token"_n, "close"_n, "alice1111111"_n, mvo() + ( "owner", "alice1111111"_n ) + ( "symbol", symbol{} ) + ); + BOOST_REQUIRE_EQUAL( true, get_row_by_account( "eosio.token"_n, "alice1111111"_n, "accounts"_n, account_name(symbol{}.to_symbol_code()) ).empty() ); + + auto rlm = control->get_resource_limits_manager(); + auto eosioram_ram_usage = rlm.get_account_ram_usage("eosio.ram"_n); + auto alice_ram_usage = rlm.get_account_ram_usage("alice1111111"_n); + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, 2048 ) ); + + //make sure that ram was billed to alice, not to eosio.ram + BOOST_REQUIRE_EQUAL( true, alice_ram_usage < rlm.get_account_ram_usage("alice1111111"_n) ); + BOOST_REQUIRE_EQUAL( eosioram_ram_usage, rlm.get_account_ram_usage("eosio.ram"_n) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( ram_gift, eosio_system_tester ) try { + active_and_vote_producers(); + + auto rlm = control->get_resource_limits_manager(); + int64_t ram_bytes_orig, net_weight, cpu_weight; + rlm.get_account_limits( "alice1111111"_n, ram_bytes_orig, net_weight, cpu_weight ); + + /* + * It seems impossible to write this test, because buyrambytes action doesn't give you exact amount of bytes requested + * + //check that it's possible to create account bying required_bytes(2724) + userres table(112) + userres row(160) - ram_gift_bytes(1400) + create_account_with_resources( "abcdefghklmn"_n, "alice1111111"_n, 2724 + 112 + 160 - 1400 ); + + //check that one byte less is not enough + BOOST_REQUIRE_THROW( create_account_with_resources( "abcdefghklmn"_n, "alice1111111"_n, 2724 + 112 + 160 - 1400 - 1 ), + ram_usage_exceeded ); + */ + + //check that stake/unstake keeps the gift + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1000.0000"), "eosio"_n ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio"_n, "alice1111111"_n, core_from_string("200.0000"), core_from_string("100.0000") ) ); + int64_t ram_bytes_after_stake; + rlm.get_account_limits( "alice1111111"_n, ram_bytes_after_stake, net_weight, cpu_weight ); + BOOST_REQUIRE_EQUAL( ram_bytes_orig, ram_bytes_after_stake ); + + BOOST_REQUIRE_EQUAL( success(), unstake( "eosio"_n, "alice1111111"_n, core_from_string("20.0000"), core_from_string("10.0000") ) ); + int64_t ram_bytes_after_unstake; + rlm.get_account_limits( "alice1111111"_n, ram_bytes_after_unstake, net_weight, cpu_weight ); + BOOST_REQUIRE_EQUAL( ram_bytes_orig, ram_bytes_after_unstake ); + + uint64_t ram_gift = 1400; + + int64_t ram_bytes; + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111"_n, "alice1111111"_n, core_from_string("1000.0000") ) ); + rlm.get_account_limits( "alice1111111"_n, ram_bytes, net_weight, cpu_weight ); + auto userres = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( userres["ram_bytes"].as_uint64() + ram_gift, static_cast(ram_bytes) ); // safe to cast in this case + + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111"_n, 1024 ) ); + rlm.get_account_limits( "alice1111111"_n, ram_bytes, net_weight, cpu_weight ); + userres = get_total_stake( "alice1111111"_n ); + BOOST_REQUIRE_EQUAL( userres["ram_bytes"].as_uint64() + ram_gift, static_cast(ram_bytes) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( change_limited_account_back_to_unlimited, eosio_system_tester ) try { + BOOST_REQUIRE( get_total_stake( "eosio"_n ).is_null() ); + + transfer( "eosio"_n, "alice1111111"_n, core_from_string("1.0000") ); + + auto error_msg = stake( "alice1111111"_n, "eosio"_n, core_from_string("0.0000"), core_from_string("1.0000") ); + auto semicolon_pos = error_msg.find(';'); + + BOOST_REQUIRE_EQUAL( error("account eosio has insufficient ram"), + error_msg.substr(0, semicolon_pos) ); + + int64_t ram_bytes_needed = 0; + { + std::istringstream s( error_msg ); + s.seekg( semicolon_pos + 7, std::ios_base::beg ); + s >> ram_bytes_needed; + ram_bytes_needed += 256; // enough room to cover total_resources_table + } + + push_action( "eosio"_n, "setalimits"_n, mvo() + ("account", "eosio"_n) + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ); + + stake( "alice1111111"_n, "eosio"_n, core_from_string("0.0000"), core_from_string("1.0000") ); + + REQUIRE_MATCHING_OBJECT( get_total_stake( "eosio"_n ), mvo() + ("owner", "eosio"_n) + ("net_weight", core_from_string("0.0000")) + ("cpu_weight", core_from_string("1.0000")) + ("ram_bytes", 0) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "only supports unlimited accounts" ), + push_action( "eosio"_n, "setalimits"_n, mvo() + ("account", "eosio"_n) + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + + BOOST_REQUIRE_EQUAL( error( "transaction net usage is too high: 128 > 0" ), + push_action( "eosio"_n, "setalimits"_n, mvo() + ("account", "eosio.saving") + ("ram_bytes", -1) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/eosio_system_tester.hpp b/unittests/eosio_system_tester.hpp index a477d6d41b..d8988c21ed 100644 --- a/unittests/eosio_system_tester.hpp +++ b/unittests/eosio_system_tester.hpp @@ -280,6 +280,54 @@ class eosio_system_tester : public validating_tester { ); } + action_result deposit( const account_name& owner, const asset& amount ) { + return push_action( name(owner), "deposit"_n, mvo() + ("owner", owner) + ("amount", amount) + ); + } + + action_result withdraw( const account_name& owner, const asset& amount ) { + return push_action( name(owner), "withdraw"_n, mvo() + ("owner", owner) + ("amount", amount) + ); + } + + asset get_rex_balance( const account_name& act ) const { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "rexbal"_n, act ); + return data.empty() ? asset(0, symbol(SY(4, REX))) : abi_ser.binary_to_variant("rex_balance", data, abi_serializer::create_yield_function(abi_serializer_max_time))["rex_balance"].as(); + } + + asset get_rex_fund( const account_name& act ) const { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "rexfund"_n, act ); + return data.empty() ? asset(0, symbol{}) : abi_ser.binary_to_variant("rex_fund", data, abi_serializer::create_yield_function(abi_serializer_max_time))["balance"].as(); + } + + void setup_rex_accounts( const std::vector& accounts, + const asset& init_balance, + const asset& net = core_from_string("80.0000"), + const asset& cpu = core_from_string("80.0000"), + bool deposit_into_rex_fund = true ) { + const asset nstake = core_from_string("10.0000"); + const asset cstake = core_from_string("10.0000"); + create_account_with_resources( "proxyaccount"_n, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + BOOST_REQUIRE_EQUAL( success(), push_action( "proxyaccount"_n, "regproxy"_n, mvo()("proxy", "proxyaccount")("isproxy", true) ) ); + for (const auto& a: accounts) { + create_account_with_resources( a, config::system_account_name, core_from_string("1.0000"), false, net, cpu ); + transfer( config::system_account_name, a, init_balance + nstake + cstake, config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( a, a, nstake, cstake) ); + BOOST_REQUIRE_EQUAL( success(), vote( a, { }, "proxyaccount"_n ) ); + BOOST_REQUIRE_EQUAL( init_balance, get_balance(a) ); + BOOST_REQUIRE_EQUAL( asset::from_string("0.0000 REX"), get_rex_balance(a) ); + if (deposit_into_rex_fund) { + BOOST_REQUIRE_EQUAL( success(), deposit( a, init_balance ) ); + BOOST_REQUIRE_EQUAL( init_balance, get_rex_fund( a ) ); + BOOST_REQUIRE_EQUAL( 0, get_balance( a ).get_amount() ); + } + } + } + static fc::variant_object producer_parameters_example( int n ) { return mutable_variant_object() ("max_block_net_usage", 10000000 + n ) @@ -345,6 +393,11 @@ class eosio_system_tester : public validating_tester { return abi_ser.binary_to_variant( "producer_info", data, abi_serializer::create_yield_function( abi_serializer_max_time ) ); } + fc::variant get_producer_info2( const account_name& act ) { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "producers2"_n, act ); + return abi_ser.binary_to_variant( "producer_info2", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + } + void create_currency( name contract, name manager, asset maxsupply ) { auto act = mutable_variant_object() ("issuer", manager ) @@ -369,6 +422,32 @@ class eosio_system_tester : public validating_tester { ); } + void issue_and_transfer( const name& to, const asset& amount, const name& manager = config::system_account_name ) { + signed_transaction trx; + trx.actions.emplace_back( get_action( "eosio.token"_n, "issue"_n, + vector{{manager, config::active_name}}, + mutable_variant_object() + ("to", manager ) + ("quantity", amount ) + ("memo", "") + ) + ); + if ( to != manager ) { + trx.actions.emplace_back( get_action( "eosio.token"_n, "transfer"_n, + vector{{manager, config::active_name}}, + mutable_variant_object() + ("from", manager) + ("to", to ) + ("quantity", amount ) + ("memo", "") + ) + ); + } + set_transaction_headers( trx ); + trx.sign( get_private_key( manager, "active" ), control->get_chain_id() ); + push_transaction( trx ); + } + double stake2votes( asset stake ) { auto now = control->pending_block_time().time_since_epoch().count() / 1000000; return stake.get_amount() * pow(2, int64_t((now - (config::block_timestamp_epoch / 1000)) / (86400 * 7))/ double(52) ); // 52 week periods (i.e. ~years) @@ -389,11 +468,24 @@ class eosio_system_tester : public validating_tester { return get_stats("4," CORE_SYMBOL_NAME)["supply"].as(); } + uint64_t microseconds_since_epoch_of_iso_string( const fc::variant& v ) { + return static_cast( time_point::from_iso_string( v.as_string() ).time_since_epoch().count() ); + } + fc::variant get_global_state() { vector data = get_row_by_account( config::system_account_name, config::system_account_name, "global"_n, "global"_n ); if (data.empty()) std::cout << "\nData is empty\n" << std::endl; return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state", data, abi_serializer::create_yield_function( abi_serializer_max_time ) ); + } + + fc::variant get_global_state2() { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "global2"_n, "global2"_n ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state2", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + } + fc::variant get_global_state3() { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "global3"_n, "global3"_n ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state3", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); } fc::variant get_refund_request( name account ) { @@ -473,7 +565,7 @@ class eosio_system_tester : public validating_tester { } produce_blocks( 250 ); - auto producer_keys = control->head_block_state()->active_schedule.producers; + auto producer_keys = control->active_producers().producers; BOOST_REQUIRE_EQUAL( 21u, producer_keys.size() ); BOOST_REQUIRE_EQUAL( name("defproducera"), producer_keys[0].producer_name ); diff --git a/unittests/finality_core_tests.cpp b/unittests/finality_core_tests.cpp new file mode 100644 index 0000000000..23a81fd193 --- /dev/null +++ b/unittests/finality_core_tests.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include + +using namespace eosio::chain; + +struct test_core { + finality_core core; + block_time_type timestamp; + + test_core() { + core = finality_core::create_core_for_genesis_block(0); + + next(0, qc_claim_t{.block_num = 0, .is_strong_qc = true}); + verify_post_conditions(0, 0); + // block 1 -- last_final_block_num: 0, final_on_strong_qc_block_num: 0 + + next(1, qc_claim_t{.block_num = 1, .is_strong_qc = true}); + verify_post_conditions(0, 0); + // block 2 -- last_final_block_num: 0, final_on_strong_qc_block_num: 0 + + // Make a strong qc_claim on block 2. + // block 2 has a strong qc_claim on block 1, which makes final_on_strong_qc_block_num 1; + // block 1 has a qc_claim on block 0, which makes last_final_block_num 0 + next(2, qc_claim_t{.block_num = 2, .is_strong_qc = true}); + verify_post_conditions(0, 1); + // block 3 -- last_final_block_num: 0, final_on_strong_qc_block_num: 1 + + // Make a strong QC claim on block 3. + // block 3 has a strong qc_claim on block 2, which makes final_on_strong_qc_block_num 2; + // block 2 has a qc_claim on block 1, which makes last_final_block_num 1 + next(3, qc_claim_t{.block_num = 3, .is_strong_qc = true}); + verify_post_conditions(1, 2); + } + + void next(block_num_type curr_block_num, qc_claim_t qc_claim) { + timestamp = timestamp.next(); + core = core.next( + block_ref {.block_id = id_from_num(curr_block_num), .timestamp = timestamp}, + qc_claim); + // next block num is current block number + 1, qc_claim becomes latest_qc_claim + BOOST_REQUIRE_EQUAL(core.current_block_num(), curr_block_num + 1); + BOOST_REQUIRE(core.latest_qc_claim() == qc_claim); + } + + void verify_post_conditions( block_num_type expected_last_final_block_num, + block_num_type expected_final_on_strong_qc_block_num) { + BOOST_REQUIRE_EQUAL(core.last_final_block_num(), expected_last_final_block_num); + BOOST_REQUIRE_EQUAL(core.final_on_strong_qc_block_num, expected_final_on_strong_qc_block_num); + } + + // This function is intentionally simplified for tests here only. + block_id_type id_from_num(block_num_type block_num) { + block_id_type result; + result._hash[0] &= 0xffffffff00000000; + result._hash[0] += fc::endian_reverse_u32(block_num); + return result; + } +}; + +BOOST_AUTO_TEST_SUITE(finality_core_tests) + +// Verify post conditions of IF genesis block core +BOOST_AUTO_TEST_CASE(create_core_for_genesis_block_test) { try { + finality_core core = finality_core::create_core_for_genesis_block(0); + + BOOST_REQUIRE_EQUAL(core.current_block_num(), 0u); + qc_claim_t qc_claim{.block_num=0, .is_strong_qc=false}; + BOOST_REQUIRE(core.latest_qc_claim() == qc_claim); + BOOST_REQUIRE_EQUAL(core.final_on_strong_qc_block_num, 0u); + BOOST_REQUIRE_EQUAL(core.last_final_block_num(), 0u); +} FC_LOG_AND_RETHROW() } + +// verify straight strong qc claims work +BOOST_AUTO_TEST_CASE(strong_qc_claim_test) { try { + { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // Strong QC claim on block 3 is the same as the latest qc_claim; + // Nothing changes. + core.next(4, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + } + { + test_core core; + + // strong QC claim on block 4 will advance LIB to 2 + core.next(4, qc_claim_t{.block_num = 4, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + // strong QC claim on block 5 will advance LIB to 2 + core.next(5, qc_claim_t{.block_num = 5, .is_strong_qc = true }); + core.verify_post_conditions(3, 4); + } +} FC_LOG_AND_RETHROW() } + +// verify blocks b4, b5 and b6 have same qc claims on b3 and then a qc claim on b4 +BOOST_AUTO_TEST_CASE(same_strong_qc_claim_test_1) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // same QC claim on block 3 will not advance last_final_block_num + core.next(4, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // same QC claim on block 3 will not advance last_final_block_num + core.next(5, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // strong QC claim on block 4. + core.next(6, qc_claim_t{.block_num = 4, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + core.next(7, qc_claim_t{.block_num = 5, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + core.next(8, qc_claim_t{.block_num = 6, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + core.next(9, qc_claim_t{.block_num = 7, .is_strong_qc = true }); + core.verify_post_conditions(3, 4); +} FC_LOG_AND_RETHROW() } + +// verify blocks b4, b5 and b6 have same strong qc claims on b3 and +// then a qc claim on b5 (b4 is skipped) +BOOST_AUTO_TEST_CASE(same_strong_qc_claim_test_2) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // same QC claim on block 3 will not advance last_final_block_num + core.next(4, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // same QC claim on block 3 will not advance last_final_block_num + core.next(5, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // Skip qc claim on block 4. Make a strong QC claim on block 5. + core.next(6, qc_claim_t{.block_num = 5, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + // A new qc claim advances last_final_block_num + core.next(7, qc_claim_t{.block_num = 7, .is_strong_qc = true }); + core.verify_post_conditions(3, 5); +} FC_LOG_AND_RETHROW() } + +// verify blocks b4, b5 and b6 have same strong qc claims on b3 and then +// a qc claim on b6 (b4 and b5 is skipped) +BOOST_AUTO_TEST_CASE(same_strong_qc_claim_test_3) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // same QC claim on block 3 will not advance last_final_block_num + core.next(4, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // same QC claim on block 3 will not advance last_final_block_num + core.next(5, qc_claim_t{.block_num = 3, .is_strong_qc = true }); + core.verify_post_conditions(1, 2); + + // Skip qc claim on block 4, 5. Make a strong QC claim on block 6. + core.next(6, qc_claim_t{.block_num = 6, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); +} FC_LOG_AND_RETHROW() } + +// verify blocks b5, b6 and b7 have same weak qc claims on b4 and then +// b8 has a strong qc claim on b4 +BOOST_AUTO_TEST_CASE(same_weak_qc_claim_test_1) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // weak QC claim on block 4; nothing changes + core.next(4, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(5, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(6, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // strong QC claim on block 4 + core.next(7, qc_claim_t{.block_num = 4, .is_strong_qc = true }); + core.verify_post_conditions(2, 3); + + core.next(8, qc_claim_t{.block_num = 5, .is_strong_qc = true }); + core.verify_post_conditions(2, 4); + + core.next(9, qc_claim_t{.block_num = 6, .is_strong_qc = true }); + core.verify_post_conditions(2, 4); + + core.next(10, qc_claim_t{.block_num = 7, .is_strong_qc = true }); + core.verify_post_conditions(2, 4); + + core.next(11, qc_claim_t{.block_num = 8, .is_strong_qc = true }); + core.verify_post_conditions(3, 4); + + core.next(12, qc_claim_t{.block_num = 9, .is_strong_qc = true }); + core.verify_post_conditions(4, 5); +} FC_LOG_AND_RETHROW() } + +// verify blocks b5, b6 and b7 have same weak qc claims on b4 and then +// b8 has a strong qc claim on b5 +BOOST_AUTO_TEST_CASE(same_weak_qc_claim_test_2) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // weak QC claim on block 4; nothing changes + core.next(4, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(5, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(6, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // strong QC claim on block 5 + core.next(7, qc_claim_t{.block_num = 5, .is_strong_qc = true }); + core.verify_post_conditions(1, 4); + + core.next(8, qc_claim_t{.block_num = 6, .is_strong_qc = true }); + core.verify_post_conditions(1, 4); + + core.next(9, qc_claim_t{.block_num = 7, .is_strong_qc = true }); + core.verify_post_conditions(1, 4); + + core.next(10, qc_claim_t{.block_num = 8, .is_strong_qc = true }); + core.verify_post_conditions(4, 5); + + core.next(11, qc_claim_t{.block_num = 9, .is_strong_qc = true }); + core.verify_post_conditions(4, 6); + + core.next(12, qc_claim_t{.block_num = 10, .is_strong_qc = true }); + core.verify_post_conditions(4, 7); + + core.next(13, qc_claim_t{.block_num = 11, .is_strong_qc = true }); + core.verify_post_conditions(5, 8); +} FC_LOG_AND_RETHROW() } + +// verify blocks b5, b6 and b7 have same weak qc claims on b4 and then +// b8 has a strong qc claim on b6 +BOOST_AUTO_TEST_CASE(same_weak_qc_claim_test_3) { try { + test_core core; + // post conditions of core:: + // current_block_num() == 4, + // last_final_block_num() == 1, + // final_on_strong_qc_block_num == 2 + // latest qc_claim == {"block_num":3,"is_strong_qc":true} + + // weak QC claim on block 4; nothing changes + core.next(4, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(5, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // same weak QC claim on block 4; nothing changes + core.next(6, qc_claim_t{.block_num = 4, .is_strong_qc = false }); + core.verify_post_conditions(1, 2); + + // strong QC claim on block 6 + core.next(7, qc_claim_t{.block_num = 6, .is_strong_qc = true }); + core.verify_post_conditions(1, 4); + + core.next(8, qc_claim_t{.block_num = 7, .is_strong_qc = true }); + core.verify_post_conditions(1, 4); + + core.next(9, qc_claim_t{.block_num = 8, .is_strong_qc = true }); + core.verify_post_conditions(4, 6); + + core.next(10, qc_claim_t{.block_num = 9, .is_strong_qc = true }); + core.verify_post_conditions(4, 7); + + core.next(11, qc_claim_t{.block_num = 10, .is_strong_qc = true }); + core.verify_post_conditions(6, 8); + + core.next(12, qc_claim_t{.block_num = 11, .is_strong_qc = true }); + core.verify_post_conditions(7, 9); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/finality_test_cluster.cpp b/unittests/finality_test_cluster.cpp new file mode 100644 index 0000000000..0ea13c13ed --- /dev/null +++ b/unittests/finality_test_cluster.cpp @@ -0,0 +1,199 @@ +#include "finality_test_cluster.hpp" + +// Construct a test network and activate IF. +finality_test_cluster::finality_test_cluster() { + using namespace eosio::testing; + + setup_node(node0, "node0"_n); + setup_node(node1, "node1"_n); + setup_node(node2, "node2"_n); + + produce_and_push_block(); // make setfinalizer irreversible + + // collect node1's votes + node1.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) { + node1.votes.emplace_back(vote); + }); + // collect node2's votes + node2.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) { + node2.votes.emplace_back(vote); + }); + + // form a 3-chain to make LIB advacing on node0 + // node0's vote (internal voting) and node1's vote make the quorum + for (auto i = 0; i < 3; ++i) { + produce_and_push_block(); + process_node1_vote(); + } + FC_ASSERT(node0_lib_advancing(), "LIB has not advanced on node0"); + + // QC extension in the block sent to node1 and node2 makes them LIB advancing + produce_and_push_block(); + process_node1_vote(); + FC_ASSERT(node1_lib_advancing(), "LIB has not advanced on node1"); + FC_ASSERT(node2_lib_advancing(), "LIB has not advanced on node2"); + + // clean up processed votes + for (auto& n : nodes) { + n.votes.clear(); + n.prev_lib_num = n.node.control->if_irreversible_block_num(); + } +} + +// node0 produces a block and pushes it to node1 and node2 +void finality_test_cluster::produce_and_push_block() { + auto b = node0.node.produce_block(); + node1.node.push_block(b); + node2.node.push_block(b); +} + +// send node1's vote identified by "vote_index" in the collected votes +eosio::chain::vote_status finality_test_cluster::process_node1_vote(uint32_t vote_index, vote_mode mode) { + return process_vote( node1, vote_index, mode ); +} + +// send node1's latest vote +eosio::chain::vote_status finality_test_cluster::process_node1_vote(vote_mode mode) { + return process_vote( node1, mode ); +} + +// send node2's vote identified by "vote_index" in the collected votes +eosio::chain::vote_status finality_test_cluster::process_node2_vote(uint32_t vote_index, vote_mode mode) { + return process_vote( node2, vote_index, mode ); +} + +// send node2's latest vote +eosio::chain::vote_status finality_test_cluster::process_node2_vote(vote_mode mode) { + return process_vote( node2, mode ); +} + +// returns true if node0's LIB has advanced +bool finality_test_cluster::node0_lib_advancing() { + return lib_advancing(node0); +} + +// returns true if node1's LIB has advanced +bool finality_test_cluster::node1_lib_advancing() { + return lib_advancing(node1); +} + +// returns true if node2's LIB has advanced +bool finality_test_cluster::node2_lib_advancing() { + return lib_advancing(node2); +} + +// Produces a number of blocks and returns true if LIB is advancing. +// This function can be only used at the end of a test as it clears +// node1_votes and node2_votes when starting. +bool finality_test_cluster::produce_blocks_and_verify_lib_advancing() { + // start from fresh + node1.votes.clear(); + node2.votes.clear(); + + for (auto i = 0; i < 3; ++i) { + produce_and_push_block(); + process_node1_vote(); + produce_and_push_block(); + if (!node0_lib_advancing() || !node1_lib_advancing() || !node2_lib_advancing()) { + return false; + } + } + + return true; +} + +void finality_test_cluster::node1_corrupt_vote_proposal_id() { + node1_orig_vote = node1.votes[0]; + + if( node1.votes[0].block_id.data()[0] == 'a' ) { + node1.votes[0].block_id.data()[0] = 'b'; + } else { + node1.votes[0].block_id.data()[0] = 'a'; + } +} + +void finality_test_cluster::node1_corrupt_vote_finalizer_key() { + node1_orig_vote = node1.votes[0]; + + // corrupt the finalizer_key (manipulate so it is different) + auto g1 = node1.votes[0].finalizer_key.jacobian_montgomery_le(); + g1 = bls12_381::aggregate_public_keys(std::array{g1, g1}); + auto affine = g1.toAffineBytesLE(bls12_381::from_mont::yes); + node1.votes[0].finalizer_key = fc::crypto::blslib::bls_public_key(affine); +} + +void finality_test_cluster::node1_corrupt_vote_signature() { + node1_orig_vote = node1.votes[0]; + + // corrupt the signature + auto g2 = node1.votes[0].sig.jacobian_montgomery_le(); + g2 = bls12_381::aggregate_signatures(std::array{g2, g2}); + auto affine = g2.toAffineBytesLE(bls12_381::from_mont::yes); + node1.votes[0].sig = fc::crypto::blslib::bls_signature(affine); +} + +void finality_test_cluster::node1_restore_to_original_vote() { + node1.votes[0] = node1_orig_vote; +} + +bool finality_test_cluster::lib_advancing(node_info& node) { + auto curr_lib_num = node.node.control->if_irreversible_block_num(); + auto advancing = curr_lib_num > node.prev_lib_num; + // update pre_lib_num for next time check + node.prev_lib_num = curr_lib_num; + return advancing; +} + +// private methods follow +void finality_test_cluster::setup_node(node_info& node, eosio::chain::account_name local_finalizer) { + using namespace eosio::testing; + + node.node.produce_block(); + node.node.produce_block(); + + // activate hotstuff + eosio::testing::base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "node0"_n, .weight = 1}, + {.name = "node1"_n, .weight = 1}, + {.name = "node2"_n, .weight = 1}}, + .threshold = 2, + .local_finalizers = {local_finalizer} + }; + + 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 + std::optional ext = block->extract_header_extension(eosio::chain::instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional fin_policy = std::get(*ext).new_finalizer_policy; + BOOST_TEST(!!fin_policy); + BOOST_TEST(fin_policy->finalizers.size() == 3); + BOOST_TEST(fin_policy->generation == 1); +} + +// send a vote to node0 +eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, size_t vote_index, vote_mode mode) { + FC_ASSERT( vote_index < node.votes.size(), "out of bound index in process_vote" ); + auto& vote = node.votes[vote_index]; + if( mode == vote_mode::strong ) { + vote.strong = true; + } else { + vote.strong = false; + + // fetch the strong digest + auto strong_digest = node.node.control->get_strong_digest_by_id(vote.block_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 ); +} + +eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, vote_mode mode) { + auto vote_index = node.votes.size() - 1; + return process_vote( node, vote_index, mode ); +} diff --git a/unittests/finality_test_cluster.hpp b/unittests/finality_test_cluster.hpp new file mode 100644 index 0000000000..97ab1aa4f0 --- /dev/null +++ b/unittests/finality_test_cluster.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#include +#pragma GCC diagnostic pop +#include + +// Set up a test network which consists of 3 nodes: +// * node0 produces blocks and pushes them to node1 and node2; +// node0 votes the blocks it produces internally. +// * node1 votes on the proposal sent by node0 +// * node2 votes on the proposal sent by node0 +// Each node has one finalizer: node0 -- "node0"_n, node1 -- "node1"_n, node2 -- "node2"_n. +// Quorum is set to 2. +// After starup up, IF are activated on both nodes. +// +// APIs are provided to modify/delay/reoder/remove votes from node1 and node2 to node0. + + +class finality_test_cluster { +public: + + enum class vote_mode { + strong, + weak, + }; + + // Construct a test network and activate IF. + finality_test_cluster(); + + // node0 produces a block and pushes it to node1 and node2 + void produce_and_push_block(); + + // send node1's vote identified by "index" in the collected votes + eosio::chain::vote_status process_node1_vote(uint32_t vote_index, vote_mode mode = vote_mode::strong); + + // send node1's latest vote + eosio::chain::vote_status process_node1_vote(vote_mode mode = vote_mode::strong); + + // send node2's vote identified by "index" in the collected votes + eosio::chain::vote_status process_node2_vote(uint32_t vote_index, vote_mode mode = vote_mode::strong); + + // send node2's latest vote + eosio::chain::vote_status process_node2_vote(vote_mode mode = vote_mode::strong); + + // returns true if node0's LIB has advanced + bool node0_lib_advancing(); + + // returns true if node1's LIB has advanced + bool node1_lib_advancing(); + + // returns true if node2's LIB has advanced + bool node2_lib_advancing(); + + // Produces a number of blocks and returns true if LIB is advancing. + // This function can be only used at the end of a test as it clears + // node1_votes and node2_votes when starting. + bool produce_blocks_and_verify_lib_advancing(); + + // Intentionally corrupt node1's vote's proposal_id and save the original vote + void node1_corrupt_vote_proposal_id(); + + // Intentionally corrupt node1's vote's finalizer_key and save the original vote + void node1_corrupt_vote_finalizer_key(); + + // Intentionally corrupt node1's vote's signature and save the original vote + void node1_corrupt_vote_signature(); + + // Restore node1's original vote + void node1_restore_to_original_vote(); + +private: + + struct node_info { + eosio::testing::tester node; + uint32_t prev_lib_num{0}; + std::vector votes; + fc::crypto::blslib::bls_private_key priv_key; + }; + + std::array nodes; + node_info& node0 = nodes[0]; + node_info& node1 = nodes[1]; + node_info& node2 = nodes[2]; + + eosio::chain::vote_message node1_orig_vote; + + // sets up "node_index" node + void setup_node(node_info& node, eosio::chain::account_name local_finalizer); + + // returns true if LIB advances on "node_index" node + bool lib_advancing(node_info& node); + + // send "vote_index" vote on node to node0 + eosio::chain::vote_status process_vote(node_info& node, size_t vote_index, vote_mode mode); + + // send the latest vote on "node_index" node to node0 + eosio::chain::vote_status process_vote(node_info& node, vote_mode mode); +}; diff --git a/unittests/finality_tests.cpp b/unittests/finality_tests.cpp new file mode 100644 index 0000000000..6d68774fe5 --- /dev/null +++ b/unittests/finality_tests.cpp @@ -0,0 +1,565 @@ +#include "finality_test_cluster.hpp" + +/* + * register test suite `finality_tests` + */ +BOOST_AUTO_TEST_SUITE(finality_tests) + +// verify LIB advances with 2 finalizers voting. +BOOST_AUTO_TEST_CASE(two_votes) { try { + finality_test_cluster cluster; + + for (auto i = 0; i < 3; ++i) { + // node0 produces a block and pushes to node1 and node2 + cluster.produce_and_push_block(); + // process node1's votes only + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + + // all nodes advance LIB + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify LIB does not advances with finalizers not voting. +BOOST_AUTO_TEST_CASE(no_votes) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + cluster.node0_lib_advancing(); // reset + cluster.node1_lib_advancing(); // reset + cluster.node2_lib_advancing(); // reset + for (auto i = 0; i < 3; ++i) { + // node0 produces a block and pushes to node1 and node2 + cluster.produce_and_push_block(); + // process no votes + cluster.produce_and_push_block(); + + // all nodes don't advance LIB + BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify LIB advances with all of the three finalizers voting +BOOST_AUTO_TEST_CASE(all_votes) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + for (auto i = 0; i < 3; ++i) { + // process node1 and node2's votes + cluster.process_node1_vote(); + cluster.process_node2_vote(); + // node0 produces a block and pushes to node1 and node2 + cluster.produce_and_push_block(); + + // all nodes advance LIB + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify LIB advances when votes conflict (strong first and followed by weak) +BOOST_AUTO_TEST_CASE(conflicting_votes_strong_first) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + for (auto i = 0; i < 3; ++i) { + cluster.process_node1_vote(); // strong + cluster.process_node2_vote(finality_test_cluster::vote_mode::weak); // weak + cluster.produce_and_push_block(); + + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify LIB advances when votes conflict (weak first and followed by strong) +BOOST_AUTO_TEST_CASE(conflicting_votes_weak_first) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + for (auto i = 0; i < 3; ++i) { + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); // weak + cluster.process_node2_vote(); // strong + cluster.produce_and_push_block(); + + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// Verify a delayed vote works +BOOST_AUTO_TEST_CASE(one_delayed_votes) { try { + finality_test_cluster cluster; + + // hold the vote for the first block to simulate delay + cluster.produce_and_push_block(); + // LIB advanced on nodes because a new block was received + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + cluster.produce_and_push_block(); + // vote block 0 (index 0) to make it have a strong QC, + // prompting LIB advacing on node2 + cluster.process_node1_vote(0); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // block 1 (index 1) has the same QC claim as block 0. It cannot move LIB + cluster.process_node1_vote(1); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // producing, pushing, and voting a new block makes LIB moving + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// Verify 3 consecutive delayed votes work +BOOST_AUTO_TEST_CASE(three_delayed_votes) { try { + finality_test_cluster cluster; + + // produce 4 blocks and hold the votes for the first 3 to simulate delayed votes + // The 4 blocks have the same QC claim as no QCs are created because missing one vote + for (auto i = 0; i < 4; ++i) { + cluster.produce_and_push_block(); + } + // LIB advanced on nodes because a new block was received + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // vote block 0 (index 0) to make it have a strong QC, + // prompting LIB advacing on nodes + cluster.process_node1_vote(0); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // blocks 1 to 3 have the same QC claim as block 0. It cannot move LIB + for (auto i=1; i < 4; ++i) { + cluster.process_node1_vote(i); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + } + + // producing, pushing, and voting a new block makes LIB moving + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(out_of_order_votes) { try { + finality_test_cluster cluster; + + // produce 3 blocks and hold the votes to simulate delayed votes + // The 3 blocks have the same QC claim as no QCs are created because missing votes + for (auto i = 0; i < 3; ++i) { + cluster.produce_and_push_block(); + } + + // vote out of the order: the newest to oldest + + // vote block 2 (index 2) to make it have a strong QC, + // prompting LIB advacing + cluster.process_node1_vote(2); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // block 1 (index 1) has the same QC claim as block 2. It will not move LIB + cluster.process_node1_vote(1); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // block 0 (index 0) has the same QC claim as block 2. It will not move LIB + cluster.process_node1_vote(0); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // producing, pushing, and voting a new block makes LIB moving + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// Verify a vote which was delayed by a large number of blocks does not cause any issues +BOOST_AUTO_TEST_CASE(long_delayed_votes) { try { + finality_test_cluster cluster; + + // Produce and push a block, vote on it after a long delay. + constexpr uint32_t delayed_vote_index = 0; + cluster.produce_and_push_block(); + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + // the vote makes a strong QC for the current block, prompting LIB advance on nodes + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + for (auto i = 2; i < 100; ++i) { + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + } + + // Late vote does not cause any issues + BOOST_REQUIRE_NO_THROW(cluster.process_node1_vote(delayed_vote_index)); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(lost_votes) { try { + finality_test_cluster cluster; + + // Produce and push a block, never vote on it to simulate lost. + // The block contains a strong QC extension for prior block + cluster.produce_and_push_block(); + + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + cluster.produce_and_push_block(); + // The block is not voted, so no strong QC is created and LIB does not advance on nodes + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + + // vote causes lib to advance + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(one_weak_vote) { try { + finality_test_cluster cluster; + + // Produce and push a block + 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); + // The strong QC extension for prior block makes LIB advance on node1 + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + cluster.produce_and_push_block(); + // A weak QC is created and LIB does not advance on node2 + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + // no 2-chain was formed as prior block was not a strong block + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + // the vote makes a strong QC and a higher final_on_strong_qc, + // prompting LIB advance on nodes + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_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(two_weak_votes) { try { + finality_test_cluster cluster; + + // Produce and push a block + cluster.produce_and_push_block(); + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + // Change the vote to a weak vote and process it + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + // A weak QC cannot advance LIB on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + // A weak QC cannot advance LIB on node2 + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + // no 2-chain was formed as prior block was not a strong block + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + 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(intertwined_weak_votes) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // Weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // Strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // Weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + // A weak QC cannot advance LIB on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // Strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + // the vote makes a strong QC for the current block, prompting LIB advance on node0 + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // Strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// Verify a combination of weak, delayed, lost votes still work +BOOST_AUTO_TEST_CASE(weak_delayed_lost_vote) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // A weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // A delayed vote (index 1) + constexpr uint32_t delayed_index = 1; + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // A strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // A lost vote + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // The delayed vote arrives, does not advance lib because it is weak + cluster.process_node1_vote(delayed_index); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // strong vote advances lib + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// Verify a combination of delayed, weak, lost votes still work +BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { + finality_test_cluster cluster; + + // A delayed vote (index 0) + constexpr uint32_t delayed_index = 0; + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // A strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // A weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // A strong vote + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // A lost vote + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + // The delayed vote arrives + cluster.process_node1_vote(delayed_index); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// verify duplicate votes do not affect LIB advancing +BOOST_AUTO_TEST_CASE(duplicate_votes) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + for (auto i = 0; i < 5; ++i) { + cluster.process_node1_vote(i); + // vote again to make it duplicate + BOOST_REQUIRE(cluster.process_node1_vote(i) == eosio::chain::vote_status::duplicate); + cluster.produce_and_push_block(); + + // verify duplicate votes do not affect LIB advancing + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify unknown_proposal votes are handled properly +BOOST_AUTO_TEST_CASE(unknown_proposal_votes) { try { + finality_test_cluster cluster; + + // node0 produces a block and pushes to node1 + cluster.produce_and_push_block(); + // intentionally corrupt proposal_id in node1's vote + cluster.node1_corrupt_vote_proposal_id(); + + // process the corrupted vote + cluster.process_node1_vote(0); + BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::unknown_block); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + + // restore to original vote + cluster.node1_restore_to_original_vote(); + + // process the original vote. LIB should advance + cluster.produce_and_push_block(); + cluster.process_node1_vote(0); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// verify unknown finalizer_key votes are handled properly +BOOST_AUTO_TEST_CASE(unknown_finalizer_key_votes) { try { + finality_test_cluster cluster; + + // node0 produces a block and pushes to node1 + cluster.produce_and_push_block(); + + // intentionally corrupt finalizer_key in node1's vote + cluster.node1_corrupt_vote_finalizer_key(); + + // process the corrupted vote. LIB should not advance + cluster.process_node1_vote(0); + BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::unknown_public_key); + + // restore to original vote + cluster.node1_restore_to_original_vote(); + + // process the original vote. LIB should advance + cluster.process_node1_vote(0); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +// verify corrupted signature votes are handled properly +BOOST_AUTO_TEST_CASE(corrupted_signature_votes) { try { + finality_test_cluster cluster; + + // node0 produces a block and pushes to node1 + cluster.produce_and_push_block(); + + // intentionally corrupt signature in node1's vote + cluster.node1_corrupt_vote_signature(); + + // process the corrupted vote. LIB should not advance + BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::invalid_signature); + + // restore to original vote + cluster.node1_restore_to_original_vote(); + + // process the original vote. LIB should advance + cluster.process_node1_vote(); + + BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp new file mode 100644 index 0000000000..7d5fd9fcd0 --- /dev/null +++ b/unittests/finalizer_tests.cpp @@ -0,0 +1,213 @@ +#include + +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; + +using tstamp = block_timestamp_type; +using fsi_t = finalizer_safety_information; + +struct bls_keys_t { + bls_private_key privkey; + bls_public_key pubkey; + std::string privkey_str; + std::string pubkey_str; + + bls_keys_t(name n) { + bls_signature pop; + std::tie(privkey, pubkey, pop) = eosio::testing::get_bls_key(n); + std::tie(privkey_str, pubkey_str) = std::pair{ privkey.to_string(), pubkey.to_string() }; + } +}; + +template +std::vector create_random_fsi(size_t count) { + std::vector res; + res.reserve(count); + for (size_t i=0; i create_proposal_refs(size_t count) { + std::vector res; + res.reserve(count); + for (size_t i=0; i create_keys(size_t count) { + std::vector res; + res.reserve(count); + for (size_t i=0; i +bls_pub_priv_key_map_t create_local_finalizers(const std::vector& keys) { + bls_pub_priv_key_map_t res; + ((res[keys[I].pubkey_str] = keys[I].privkey_str), ...); + return res; +} + +template +void set_fsi(my_finalizers_t& fset, const std::vector& keys, const FSI_VEC& fsi) { + ((fset.set_fsi(keys[I].pubkey, fsi[I])), ...); +} + +BOOST_AUTO_TEST_SUITE(finalizer_tests) + +BOOST_AUTO_TEST_CASE( basic_finalizer_safety_file_io ) try { + fc::temp_directory tempdir; + auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; + auto proposals { create_proposal_refs(10) }; + + fsi_t fsi { .last_vote_range_start = tstamp(0), + .last_vote = proposals[6], + .lock = proposals[2] }; + + bls_keys_t k("alice"_n); + bls_pub_priv_key_map_t local_finalizers = { { k.pubkey_str, k.privkey_str } }; + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + fset.set_keys(local_finalizers); + + fset.set_fsi(k.pubkey, fsi); + fset.save_finalizer_safety_info(); + + // at this point we have saved the finalizer safety file + // so destroy the my_finalizers_t object + } + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + fset.set_keys(local_finalizers); // that's when the finalizer safety file is read + + // make sure the safety info for our finalizer that we saved above is restored correctly + BOOST_CHECK_EQUAL(fset.get_fsi(k.pubkey), fsi); + } + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( corrupt_finalizer_safety_file ) try { + fc::temp_directory tempdir; + auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; + auto proposals { create_proposal_refs(10) }; + + fsi_t fsi { .last_vote_range_start = tstamp(0), + .last_vote = proposals[6], + .lock = proposals[2] }; + + bls_keys_t k("alice"_n); + bls_pub_priv_key_map_t local_finalizers = { { k.pubkey_str, k.privkey_str } }; + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + fset.set_keys(local_finalizers); + + fset.set_fsi(k.pubkey, fsi); + fset.save_finalizer_safety_info(); + + // at this point we have saved the finalizer safety file + // corrupt it, so we can check that we throw an exception when reading it later. + + fc::datastream f; + f.set_file_path(safety_file_path); + f.open(fc::cfile::truncate_rw_mode); + size_t junk_data = 0xf0f0f0f0f0f0f0f0ull; + fc::raw::pack(f, junk_data); + } + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + BOOST_REQUIRE_THROW(fset.set_keys(local_finalizers), // that's when the finalizer safety file is read + finalizer_safety_exception); + + // make sure the safety info for our finalizer that we saved above is restored correctly + BOOST_CHECK_NE(fset.get_fsi(k.pubkey), fsi); + BOOST_CHECK_EQUAL(fset.get_fsi(k.pubkey), fsi_t()); + } + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( finalizer_safety_file_io ) try { + fc::temp_directory tempdir; + auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; + + std::vector fsi = create_random_fsi(10); + std::vector keys = create_keys(10); + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + bls_pub_priv_key_map_t local_finalizers = create_local_finalizers<1, 3, 5, 6>(keys); + fset.set_keys(local_finalizers); + + set_fsi(fset, keys, fsi); + fset.save_finalizer_safety_info(); + + // at this point we have saved the finalizer safety file, containing a specific fsi for finalizers <1, 3, 5, 6> + // so destroy the my_finalizers_t object + } + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + bls_pub_priv_key_map_t local_finalizers = create_local_finalizers<3>(keys); + fset.set_keys(local_finalizers); + + // make sure the safety info for our finalizer that we saved above is restored correctly + BOOST_CHECK_EQUAL(fset.get_fsi(keys[3].pubkey), fsi[3]); + + // OK, simulate a couple rounds of voting + fset.set_fsi(keys[3].pubkey, fsi[4]); + fset.save_finalizer_safety_info(); + + // now finalizer 3 should have fsi[4] saved + } + + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + bls_pub_priv_key_map_t local_finalizers = create_local_finalizers<3>(keys); + fset.set_keys(local_finalizers); + + // make sure the safety info for our finalizer that we saved above is restored correctly + BOOST_CHECK_EQUAL(fset.get_fsi(keys[3].pubkey), fsi[4]); + } + + // even though we didn't activate finalizers 1, 5, or 6 in the prior test, and we wrote the safety file, + // make sure we have not lost the fsi that was set originally for these finalizers. + { + my_finalizers_t fset{block_timestamp_type{}, safety_file_path}; + bls_pub_priv_key_map_t local_finalizers = create_local_finalizers<1, 5, 6>(keys); + fset.set_keys(local_finalizers); + + // make sure the safety info for our previously inactive finalizer was preserved + BOOST_CHECK_EQUAL(fset.get_fsi(keys[1].pubkey), fsi[1]); + BOOST_CHECK_EQUAL(fset.get_fsi(keys[5].pubkey), fsi[5]); + BOOST_CHECK_EQUAL(fset.get_fsi(keys[6].pubkey), fsi[6]); + } + +} FC_LOG_AND_RETHROW() + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/finalizer_vote_tests.cpp b/unittests/finalizer_vote_tests.cpp new file mode 100644 index 0000000000..26d5eb49c8 --- /dev/null +++ b/unittests/finalizer_vote_tests.cpp @@ -0,0 +1,332 @@ +#include +#include + +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; + +using bs = eosio::chain::block_state; +using bsp = eosio::chain::block_state_ptr; +using bhs = eosio::chain::block_header_state; +using bhsp = eosio::chain::block_header_state_ptr; +using vote_decision = finalizer::vote_decision; +using vote_result = finalizer::vote_result; +using tstamp = block_timestamp_type; +using fsi_t = finalizer_safety_information; + +// --------------------------------------------------------------------------------------- +struct bls_keys_t { + bls_private_key privkey; + bls_public_key pubkey; + std::string privkey_str; + std::string pubkey_str; + + bls_keys_t(name n) { + bls_signature pop; + std::tie(privkey, pubkey, pop) = eosio::testing::get_bls_key(n); + std::tie(privkey_str, pubkey_str) = std::pair{ privkey.to_string(), pubkey.to_string() }; + } +}; + +// --------------------------------------------------------------------------------------- +inline block_id_type calc_id(block_id_type id, uint32_t block_number) { + id._hash[0] &= 0xffffffff00000000; + id._hash[0] += fc::endian_reverse_u32(block_number); + return id; +} + +// --------------------------------------------------------------------------------------- +struct proposal_t { + uint32_t block_number; + std::string proposer_name; + block_timestamp_type block_timestamp; + + proposal_t(uint32_t block_number, const char* proposer, std::optional timestamp = {}) : + block_number(block_number), proposer_name(proposer), block_timestamp(timestamp ? *timestamp : block_number) + {} + + const std::string& proposer() const { return proposer_name; } + block_timestamp_type timestamp() const { return block_timestamp; } + uint32_t block_num() const { return block_number; } + + block_id_type calculate_id() const + { + std::string id_str = proposer_name + std::to_string(block_number); + return calc_id(fc::sha256::hash(id_str.c_str()), block_number); + } + + explicit operator block_ref() const { + return block_ref{calculate_id(), timestamp()}; + } +}; + +// --------------------------------------------------------------------------------------- +bsp make_bsp(const proposal_t& p, const bsp& previous, finalizer_policy_ptr finpol, + std::optional claim = {}) { + auto makeit = [](bhs &&h) { + bs new_bs; + dynamic_cast(new_bs) = std::move(h); + return std::make_shared(std::move(new_bs)); + }; + + if (p.block_num() == 0) { + // special case of genesis block + block_ref ref{calc_id(fc::sha256::hash("genesis"), 0), block_timestamp_type{0}}; + bhs new_bhs { ref.block_id, block_header{ref.timestamp}, {}, + finality_core::create_core_for_genesis_block(0), std::move(finpol) }; + return makeit(std::move(new_bhs)); + } + + assert(claim); + block_ref ref{previous->id(), previous->timestamp()}; + bhs new_bhs { p.calculate_id(), block_header{p.block_timestamp, {}, {}, previous->id()}, {}, previous->core.next(ref, *claim), + std::move(finpol) }; + return makeit(std::move(new_bhs)); +} + +// --------------------------------------------------------------------------------------- +// simulates one finalizer voting on its own proposals "n0", and other proposals received +// from the network. +struct simulator_t { + using core = finality_core; + + bls_keys_t keys; + finalizer my_finalizer; + fork_database_if_t forkdb; + finalizer_policy_ptr finpol; + std::vector bsp_vec; + + struct result { + bsp new_bsp; + vote_result vote; + + qc_claim_t new_claim() const { + if (vote.decision == vote_decision::no_vote) + return new_bsp->core.latest_qc_claim(); + return { new_bsp->block_num(), vote.decision == vote_decision::strong_vote }; + } + }; + + simulator_t() : + keys("alice"_n), + my_finalizer(keys.privkey) { + + finalizer_policy fin_policy; + fin_policy.threshold = 0; + fin_policy.finalizers.push_back({"n0", 1, keys.pubkey}); + finpol = std::make_shared(fin_policy); + + auto genesis = make_bsp(proposal_t{0, "n0"}, bsp(), finpol); + bsp_vec.push_back(genesis); + forkdb.reset_root(genesis); + + block_ref genesis_ref(genesis->id(), genesis->timestamp()); + my_finalizer.fsi = fsi_t{block_timestamp_type(0), genesis_ref, genesis_ref}; + } + + vote_result vote(const bsp& p) { + auto vote_res = my_finalizer.decide_vote(p); + return vote_res; + } + + vote_result propose(const proposal_t& p, std::optional _claim = {}) { + bsp h = forkdb.head(); + qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); + bsp new_bsp = make_bsp(p, h, finpol, old_claim); + bsp_vec.push_back(new_bsp); + auto v = vote(new_bsp); + return v; + } + + result add(const proposal_t& p, std::optional _claim = {}, const bsp& parent = {}) { + bsp h = parent ? parent : forkdb.head(); + qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); + bsp new_bsp = make_bsp(p, h, finpol, old_claim); + bsp_vec.push_back(new_bsp); + forkdb.add(new_bsp, mark_valid_t::yes, ignore_duplicate_t::no); + + auto v = vote(new_bsp); + return { new_bsp, v }; + } +}; + +BOOST_AUTO_TEST_SUITE(finalizer_vote_tests) + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_basic ) try { + simulator_t sim; + // this proposal verifies all properties and extends genesis => expect strong vote + auto res = sim.add({1, "n0"}); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_no_vote_if_finalizer_safety_lock_empty ) try { + simulator_t sim; + sim.my_finalizer.fsi.lock = {}; // force lock empty... finalizer should not vote + auto res = sim.add({1, "n0"}); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_normal_vote_sequence ) try { + simulator_t sim; + qc_claim_t new_claim { 0, true }; + for (uint32_t i=1; i<10; ++i) { + auto res = sim.add({i, "n0"}, new_claim); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(new_claim, res.new_bsp->core.latest_qc_claim()); + new_claim = { res.new_bsp->block_num(), res.vote.decision == vote_decision::strong_vote }; + + auto lib { res.new_bsp->core.last_final_block_num() }; + BOOST_CHECK_EQUAL(lib, i <= 2 ? 0 : i - 3); + + auto final_on_strong_qc { res.new_bsp->core.final_on_strong_qc_block_num }; + BOOST_CHECK_EQUAL(final_on_strong_qc, i <= 1 ? 0 : i - 2); + } +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_monotony_check ) try { + simulator_t sim; + + auto res = sim.add({1, "n0", 1}); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + + auto res2 = sim.add({2, "n0", 1}); + BOOST_CHECK_EQUAL(res2.vote.monotony_check, false); + BOOST_CHECK(res2.vote.decision == vote_decision::no_vote); // use same timestamp as previous proposal => should not vote + +} FC_LOG_AND_RETHROW() + + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_liveness_and_safety_check ) try { + simulator_t sim; + qc_claim_t new_claim { 0, true }; + for (uint32_t i=1; i<10; ++i) { + auto res = sim.add({i, "n0", i}, new_claim); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(new_claim, res.new_bsp->core.latest_qc_claim()); + new_claim = res.new_claim(); + + auto lib { res.new_bsp->core.last_final_block_num() }; + BOOST_CHECK_EQUAL(lib, i <= 2 ? 0 : i - 3); + + auto final_on_strong_qc { res.new_bsp->core.final_on_strong_qc_block_num }; + BOOST_CHECK_EQUAL(final_on_strong_qc, i <= 1 ? 0 : i - 2); + + if (i > 2) + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[i-2]->id()); + } + + // we just issued proposal #9. Verify we are locked on proposal #7 and our last_vote is #9 + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[7]->id()); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 9u); + + // proposal #6 from "n0" is final (although "n1" may not know it yet). + // proposal #7 would be final if it receives a strong QC + + // let's have "n1" build on proposal #6. Default will use timestamp(7) so we will fail the monotony check + auto res = sim.add({7, "n1"}, {}, sim.bsp_vec[6]); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + // let's vote for a couple more proposals, and finally when we'll reach timestamp 10 the + // monotony check will pass (both liveness and safety check should still fail) + // ------------------------------------------------------------------------------------ + res = sim.add({8, "n1"}, {}, res.new_bsp); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + res = sim.add({9, "n1"}, {}, res.new_bsp); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + res = sim.add({10, "n1"}, {}, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, false); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); + + // No matter how long we keep voting on this branch without a new qc claim, we will never achieve + // liveness or safety again + // ---------------------------------------------------------------------------------------------- + for (uint32_t i=11; i<20; ++i) { + res = sim.add({i, "n1"}, {}, res.new_bsp); + + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, false); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); + } + + // Now suppose we receive a qc in a block that was created in the "n0" branch, for example the qc from + // proposal 8. We can get it from sim.bsp_vec[9]->core.latest_qc_claim(). + // liveness should be restored, because core.latest_qc_block_timestamp() > fsi.lock.timestamp + // --------------------------------------------------------------------------------------------------- + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 9u); + new_claim = sim.bsp_vec[9]->core.latest_qc_claim(); + res = sim.add({20, "n1"}, new_claim, res.new_bsp); + + BOOST_CHECK(res.vote.decision == vote_decision::weak_vote); // because !time_range_disjoint and fsi.last_vote == 9 + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 20u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + + new_claim = res.new_claim(); + res = sim.add({21, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); // because core.extends(fsi.last_vote.block_id); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 21u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + + // this new proposal we just voted strong on was just building on proposal #6 and we had not advanced + // the core until the last proposal which provided a new qc_claim_t. + // as a result we now have a final_on_strong_qc = 5 (because the vote on 20 was weak) + // -------------------------------------------------------------------------------------------------- + auto final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 5u); + + // Our finalizer should still be locked on the initial proposal 7 (we have not updated our lock because + // `(final_on_strong_qc_block_ref.timestamp > fsi.lock.timestamp)` is false + // ---------------------------------------------------------------------------------------------------- + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[7]->id()); + + // this new strong vote will finally advance the final_on_strong_qc thanks to the chain + // weak 20 - strong 21 (meaning that if we get a strong QC on 22, 20 becomes final, so the core of + // 22 has a final_on_strong_qc = 20. + // ----------------------------------------------------------------------------------------------- + new_claim = res.new_claim(); + res = sim.add({22, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 22u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 20u); + BOOST_CHECK_EQUAL(res.new_bsp->core.last_final_block_num(), 4u); + + // OK, add one proposal + strong vote. This should finally move lib to 20 + // ---------------------------------------------------------------------- + new_claim = res.new_claim(); + res = sim.add({23, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 23u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 21u); + BOOST_CHECK_EQUAL(res.new_bsp->core.last_final_block_num(), 20u); + +} FC_LOG_AND_RETHROW() + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/fork_db_tests.cpp b/unittests/fork_db_tests.cpp new file mode 100644 index 0000000000..05d5bc8ebe --- /dev/null +++ b/unittests/fork_db_tests.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + + +namespace eosio::chain { + +inline block_id_type make_block_id(block_num_type block_num) { + static uint32_t nonce = 0; + ++nonce; + block_id_type id = fc::sha256::hash(std::to_string(block_num) + "-" + std::to_string(nonce)); + id._hash[0] &= 0xffffffff00000000; + id._hash[0] += fc::endian_reverse_u32(block_num); // store the block num in the ID, 160 bits is plenty for the hash + return id; +} + +// Used to access privates of block_state +struct block_state_accessor { + static auto make_genesis_block_state() { + block_state_ptr root = std::make_shared(); + block_id_type genesis_id = make_block_id(10); + root->block_id = genesis_id; + root->header.timestamp = block_timestamp_type{10}; + root->core = finality_core::create_core_for_genesis_block(10); + return root; + } + + // use block_num > 10 + static auto make_unique_block_state(block_num_type block_num, const block_state_ptr& prev) { + block_state_ptr bsp = std::make_shared(); + bsp->block_id = make_block_id(block_num); + bsp->header.timestamp.slot = prev->header.timestamp.slot + 1; + bsp->header.previous = prev->id(); + block_ref parent_block { + .block_id = prev->id(), + .timestamp = prev->timestamp() + }; + bsp->core = prev->core.next(parent_block, prev->core.latest_qc_claim()); + return bsp; + } +}; + +} // namespace eosio::chain + +using namespace eosio::chain; + +BOOST_AUTO_TEST_SUITE(fork_database_tests) + +BOOST_AUTO_TEST_CASE(add_remove_test) try { + fork_database_if_t forkdb; + + // Setup fork database with blocks based on a root of block 10 + // Add a number of forks in the fork database + auto root = block_state_accessor::make_genesis_block_state(); + auto bsp11a = block_state_accessor::make_unique_block_state(11, root); + auto bsp12a = block_state_accessor::make_unique_block_state(12, bsp11a); + auto bsp13a = block_state_accessor::make_unique_block_state(13, bsp12a); + auto bsp11b = block_state_accessor::make_unique_block_state(11, root); + auto bsp12b = block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp13b = block_state_accessor::make_unique_block_state(13, bsp12b); + auto bsp14b = block_state_accessor::make_unique_block_state(14, bsp13b); + auto bsp12bb = block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp13bb = block_state_accessor::make_unique_block_state(13, bsp12bb); + auto bsp13bbb = block_state_accessor::make_unique_block_state(13, bsp12bb); + auto bsp12bbb = block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp11c = block_state_accessor::make_unique_block_state(11, root); + auto bsp12c = block_state_accessor::make_unique_block_state(12, bsp11c); + auto bsp13c = block_state_accessor::make_unique_block_state(13, bsp12c); + + // keep track of all those added for easy verification + std::vector all { bsp11a, bsp12a, bsp13a, bsp11b, bsp12b, bsp12bb, bsp12bbb, bsp13b, bsp13bb, bsp13bbb, bsp14b, bsp11c, bsp12c, bsp13c }; + + forkdb.reset_root(root); + forkdb.add(bsp11a, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp11b, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp11c, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp12a, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp13a, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp12b, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp12bb, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp12bbb, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp12c, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp13b, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp13bb, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp13bbb, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp14b, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp13c, mark_valid_t::no, ignore_duplicate_t::no); + + // test get_block + for (auto& i : all) { + BOOST_TEST(forkdb.get_block(i->id()) == i); + } + + // test remove, should remove descendants + forkdb.remove(bsp12b->id()); + BOOST_TEST(!forkdb.get_block(bsp12b->id())); + BOOST_TEST(!forkdb.get_block(bsp13b->id())); + BOOST_TEST(!forkdb.get_block(bsp14b->id())); + forkdb.add(bsp12b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists + forkdb.add(bsp13b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists + forkdb.add(bsp14b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists + + // test search + BOOST_TEST(forkdb.search_on_branch( bsp13bb->id(), 11) == bsp11b); + BOOST_TEST(forkdb.search_on_branch( bsp13bb->id(), 9) == block_state_ptr{}); + + // test fetch branch + auto branch = forkdb.fetch_branch( bsp13b->id(), 12); + BOOST_REQUIRE(branch.size() == 2); + BOOST_TEST(branch[0] == bsp12b); + BOOST_TEST(branch[1] == bsp11b); + branch = forkdb.fetch_branch( bsp13bbb->id(), 13); + BOOST_REQUIRE(branch.size() == 3); + BOOST_TEST(branch[0] == bsp13bbb); + BOOST_TEST(branch[1] == bsp12bb); + BOOST_TEST(branch[2] == bsp11b); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 57ffa266ec..487677a36c 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -30,8 +29,8 @@ BOOST_AUTO_TEST_CASE( irrblock ) try { } FC_LOG_AND_RETHROW() struct fork_tracker { - vector blocks; - incremental_merkle block_merkle; + vector blocks; + incremental_merkle_tree_legacy block_merkle; }; BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { @@ -53,7 +52,7 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { // produce 6 blocks on bios for (int i = 0; i < 6; i ++) { bios.produce_block(); - BOOST_REQUIRE_EQUAL( bios.control->head_block_state()->header.producer.to_string(), "a" ); + BOOST_REQUIRE_EQUAL( bios.control->head_block()->producer.to_string(), "a" ); } vector forks(7); @@ -73,7 +72,7 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { auto copy_b = std::make_shared(b->clone()); if (j == i) { // corrupt this block - fork.block_merkle = remote.control->head_block_state()->blockroot_merkle; + fork.block_merkle = remote.control->head_block_state_legacy()->blockroot_merkle; copy_b->action_mroot._hash[0] ^= 0x1ULL; } else if (j < i) { // link to a corrupted chain @@ -82,7 +81,7 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { // re-sign the block auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), fork.block_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); copy_b->producer_signature = remote.get_private_key("b"_n, "active").sign(sig_digest); // add this new block to our corrupted block merkle @@ -111,15 +110,15 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { // push the block which should attempt the corrupted fork and fail BOOST_REQUIRE_EXCEPTION( bios.push_block(fork.blocks.back()), fc::exception, - fc_exception_message_is( "Block ID does not match" ) + fc_exception_message_starts_with( "Block ID does not match" ) ); } } // make sure we can still produce a blocks until irreversibility moves - auto lib = bios.control->head_block_state()->dpos_irreversible_blocknum; + auto lib = bios.control->head_block_state_legacy()->dpos_irreversible_blocknum; size_t tries = 0; - while (bios.control->head_block_state()->dpos_irreversible_blocknum == lib && ++tries < 10000) { + while (bios.control->head_block_state_legacy()->dpos_irreversible_blocknum == lib && ++tries < 10000) { bios.produce_block(); } @@ -266,10 +265,10 @@ BOOST_AUTO_TEST_CASE( forking ) try { signed_block bad_block = std::move(*b); bad_block.action_mroot = bad_block.previous; auto bad_id = bad_block.calculate_id(); - auto bad_block_bsf = c.control->create_block_state_future( bad_id, std::make_shared(std::move(bad_block)) ); + auto bad_block_btf = c.control->create_block_handle_future( bad_id, std::make_shared(std::move(bad_block)) ); c.control->abort_block(); controller::block_report br; - BOOST_REQUIRE_EXCEPTION(c.control->push_block( br, bad_block_bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c.control->push_block( br, bad_block_btf.get(), {}, trx_meta_cache_lookup{} ), fc::exception, [] (const fc::exception &ex)->bool { return ex.to_detail_string().find("block signed by unexpected key") != std::string::npos; }); @@ -303,7 +302,7 @@ BOOST_AUTO_TEST_CASE( prune_remove_branch ) try { auto nextproducer = [](tester &c, int skip_interval) ->account_name { auto head_time = c.control->head_block_time(); auto next_time = head_time + fc::milliseconds(config::block_interval_ms * skip_interval); - return c.control->head_block_state()->get_scheduled_producer(next_time).producer_name; + return c.control->active_producers().get_scheduled_producer(next_time).producer_name; }; // fork c: 2 producers: dan, sam @@ -355,7 +354,7 @@ BOOST_AUTO_TEST_CASE( validator_accepts_valid_blocks ) try { block_id_type first_id; signed_block_header first_header; - auto c = n2.control->accepted_block.connect( [&]( block_signal_params t ) { + auto c = n2.control->accepted_block().connect( [&]( block_signal_params t ) { const auto& [ block, id ] = t; first_block = block; first_id = id; @@ -367,10 +366,9 @@ BOOST_AUTO_TEST_CASE( validator_accepts_valid_blocks ) try { BOOST_CHECK_EQUAL( n2.control->head_block_id(), id ); BOOST_REQUIRE( first_block ); - const auto& first_bsp = n2.control->fetch_block_state_by_id(first_id); - first_bsp->verify_signee(); - BOOST_CHECK_EQUAL( first_header.calculate_id(), first_block->calculate_id() ); - BOOST_CHECK( first_header.producer_signature == first_block->producer_signature ); + const auto& first_bp = n2.control->fetch_block_by_id(first_id); + BOOST_CHECK_EQUAL( first_bp->calculate_id(), first_block->calculate_id() ); + BOOST_CHECK( first_bp->producer_signature == first_block->producer_signature ); c.disconnect(); @@ -495,8 +493,9 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { BOOST_CHECK_EQUAL( does_account_exist( irreversible, "alice"_n ), true ); { - auto bs = irreversible.control->fetch_block_state_by_id( fork_first_block_id ); - BOOST_REQUIRE( bs && bs->id == fork_first_block_id ); + auto b = irreversible.control->fetch_block_by_id( fork_first_block_id ); + BOOST_REQUIRE( b && b->calculate_id() == fork_first_block_id ); + BOOST_TEST( irreversible.control->block_exists(fork_first_block_id) ); } main.produce_block(); @@ -508,8 +507,9 @@ BOOST_AUTO_TEST_CASE( irreversible_mode ) try { push_blocks( main, irreversible, hbn5 ); { - auto bs = irreversible.control->fetch_block_state_by_id( fork_first_block_id ); - BOOST_REQUIRE( !bs ); + auto b = irreversible.control->fetch_block_by_id( fork_first_block_id ); + BOOST_REQUIRE( !b ); + BOOST_TEST( !irreversible.control->block_exists(fork_first_block_id) ); } } FC_LOG_AND_RETHROW() @@ -703,7 +703,7 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { // test forked blocks signal accepted_block in order, required by trace_api_plugin std::vector accepted_blocks; - auto conn = c.control->accepted_block.connect( [&]( block_signal_params t ) { + auto conn = c.control->accepted_block().connect( [&]( block_signal_params t ) { const auto& [ block, id ] = t; accepted_blocks.emplace_back( block ); } ); diff --git a/unittests/merkle_tree_tests.cpp b/unittests/merkle_tree_tests.cpp new file mode 100644 index 0000000000..25a2325423 --- /dev/null +++ b/unittests/merkle_tree_tests.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include + +using namespace eosio::chain; +using eosio::chain::detail::make_legacy_digest_pair; + +std::vector create_test_digests(size_t n) { + std::vector v; + v.reserve(n); + for (size_t i=0; i digests { node1, node2, node3, node4, node5, node6, node7, node8, node9 }; + auto first = digests.cbegin(); + + tree.append(node1); + BOOST_CHECK_EQUAL(tree.get_root(), node1); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 1)), node1); + + tree.append(node2); + BOOST_CHECK_EQUAL(tree.get_root(), hash(node1, node2)); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 2)), hash(node1, node2)); + + tree.append(node3); + auto calculated_root = hash(hash(node1, node2), node3); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 3)), calculated_root); + + tree.append(node4); + auto first_four_tree = hash(hash(node1, node2), hash(node3, node4)); + calculated_root = first_four_tree; + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 4)), calculated_root); + + tree.append(node5); + calculated_root = hash(first_four_tree, node5); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 5)), calculated_root); + + tree.append(node6); + calculated_root = hash(first_four_tree, hash(node5, node6)); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 6)), calculated_root); + + tree.append(node7); + calculated_root = hash(first_four_tree, hash(hash(node5, node6), node7)); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 7)), calculated_root); + + tree.append(node8); + auto next_four_tree = hash(hash(node5, node6), hash(node7, node8)); + calculated_root = hash(first_four_tree, next_four_tree); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 8)), calculated_root); + + tree.append(node9); + calculated_root = hash(hash(first_four_tree, next_four_tree), node9); + BOOST_CHECK_EQUAL(tree.get_root(), calculated_root); + BOOST_CHECK_EQUAL(calculate_merkle(std::span(first, 9)), calculated_root); +} + +BOOST_AUTO_TEST_CASE(consistency_over_large_range) { + constexpr size_t num_digests = 1001ull; + + const std::vector digests = create_test_digests(num_digests); + for (size_t i=1; i +#include + +#include + +using namespace eosio::testing; +using namespace eosio::chain; +using mvo = fc::mutable_variant_object; + +BOOST_AUTO_TEST_SUITE(producer_schedule_if_tests) + +namespace { + +// Calculate expected producer given the schedule and slot number +inline account_name get_expected_producer(const vector& schedule, block_timestamp_type t) { + const auto& index = (t.slot % (schedule.size() * config::producer_repetitions)) / config::producer_repetitions; + return schedule.at(index).producer_name; +}; + +} // anonymous namespace + +BOOST_FIXTURE_TEST_CASE( verify_producer_schedule_after_instant_finality_activation, validating_tester ) try { + + // Utility function to ensure that producer schedule work as expected + const auto& confirm_schedule_correctness = [&](const vector& new_prod_schd, uint32_t expected_schd_ver, uint32_t expected_block_num = 0) { + const uint32_t check_duration = 100; // number of blocks + bool scheduled_changed_to_new = false; + for (uint32_t i = 0; i < check_duration; ++i) { + const auto current_schedule = control->active_producers(); + if (new_prod_schd == current_schedule.producers) { + scheduled_changed_to_new = true; + if (expected_block_num != 0) + BOOST_TEST(control->head_block_num() == expected_block_num); + } + + auto b = produce_block(); + BOOST_TEST( b->confirmed == 0); // must be 0 after instant finality is enabled + + // Check if the producer is the same as what we expect + const auto block_time = control->head_block_time(); + const auto& expected_producer = get_expected_producer(current_schedule.producers, block_time); + BOOST_TEST(control->head_block_producer() == expected_producer); + + if (scheduled_changed_to_new) + break; + } + + BOOST_TEST(scheduled_changed_to_new); + }; + + uint32_t lib = 0; + control->irreversible_block().connect([&](const block_signal_params& t) { + const auto& [ block, id ] = t; + lib = block->block_num(); + }); + + // Create producer accounts + vector producers = { + "inita"_n, "initb"_n, "initc"_n, "initd"_n, "inite"_n, "initf"_n, "initg"_n, + "inith"_n, "initi"_n, "initj"_n, "initk"_n, "initl"_n, "initm"_n, "initn"_n, + "inito"_n, "initp"_n, "initq"_n, "initr"_n, "inits"_n, "initt"_n, "initu"_n + }; + create_accounts(producers); + + // enable instant_finality + set_finalizers(producers); + auto setfin_block = produce_block(); // this block contains the header extension of the finalizer set + + for (block_num_type active_block_num = setfin_block->block_num(); active_block_num > lib; produce_block()) { + set_producers({"initc"_n, "inite"_n}); // should be ignored since in transition + (void)active_block_num; // avoid warning + }; + + // ---- Test first set of producers ---- + // Send set prods action and confirm schedule correctness + auto trace = set_producers(producers); + const auto first_prod_schd = get_producer_authorities(producers); + // called in first round so complete it, skip one round of 12 and start on next round, so block 24 + confirm_schedule_correctness(first_prod_schd, 1, 24); + + // ---- Test second set of producers ---- + vector second_set_of_producer = { + producers[3], producers[6], producers[9], producers[12], producers[15], producers[18], producers[20] + }; + // Send set prods action and confirm schedule correctness + set_producers(second_set_of_producer); + const auto second_prod_schd = get_producer_authorities(second_set_of_producer); + // called after block 24, so next,next is 48 + confirm_schedule_correctness(second_prod_schd, 2, 48); + + // ---- Test deliberately miss some blocks ---- + const int64_t num_of_missed_blocks = 5000; + produce_block(fc::microseconds(500 * 1000 * num_of_missed_blocks)); + // Ensure schedule is still correct + confirm_schedule_correctness(second_prod_schd, 2); + produce_block(); + + // ---- Test third set of producers ---- + vector third_set_of_producer = { + producers[2], producers[5], producers[8], producers[11], producers[14], producers[17], producers[20], + producers[0], producers[3], producers[6], producers[9], producers[12], producers[15], producers[18], + producers[1], producers[4], producers[7], producers[10], producers[13], producers[16], producers[19] + }; + // Send set prods action and confirm schedule correctness + set_producers(third_set_of_producer); + const auto third_prod_schd = get_producer_authorities(third_set_of_producer); + confirm_schedule_correctness(third_prod_schd, 3); + +} FC_LOG_AND_RETHROW() + +bool compare_schedules( const vector& a, const producer_authority_schedule& b ) { + return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); +}; + +BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) try { + create_accounts( {"alice"_n,"bob"_n,"carol"_n} ); + + while (control->head_block_num() < 3) { + produce_block(); + } + + // activate instant_finality + set_finalizers({"alice"_n,"bob"_n,"carol"_n}); + produce_block(); // this block contains the header extension of the finalizer set + produce_block(); // one producer, lib here + + // current proposer schedule stays the same as the one prior to IF transition + vector prev_sch = { + producer_authority{"eosio"_n, block_signing_authority_v0{1, {{get_public_key("eosio"_n, "active"), 1}}}}}; + BOOST_CHECK_EQUAL( true, compare_schedules( prev_sch, control->active_producers() ) ); + BOOST_CHECK_EQUAL( 0, control->active_producers().version ); + + // set a new proposer policy sch1 + set_producers( {"alice"_n} ); + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "active"), 1}}}} + }; + + // start a round of production + produce_blocks(config::producer_repetitions); + + // sch1 cannot become active before one round of production + BOOST_CHECK_EQUAL( 0, control->active_producers().version ); + BOOST_CHECK_EQUAL( true, compare_schedules( prev_sch, control->active_producers() ) ); + + // set another ploicy to have multiple pending different active time policies + set_producers( {"bob"_n,"carol"_n} ); + vector sch2 = { + producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{get_public_key("bob"_n, "active"),1}}}}, + producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{get_public_key("carol"_n, "active"),1}}}} + }; + produce_block(); + + // set another ploicy should replace sch2 + set_producers( {"bob"_n,"alice"_n} ); + vector sch3 = { + producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{get_public_key("bob"_n, "active"),1}}}}, + producer_authority{"alice"_n, block_signing_authority_v0{ 1, {{get_public_key("alice"_n, "active"),1}}}} + }; + + // another round + produce_blocks(config::producer_repetitions-1); // -1, already produced one of the round above + + // sch1 must become active no later than 2 rounds but sch2 cannot become active yet + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); + + produce_blocks(config::producer_repetitions); + + // sch3 becomes active + BOOST_CHECK_EQUAL( 2u, control->active_producers().version ); // should be 2 as sch2 was replaced by sch3 + BOOST_CHECK_EQUAL( true, compare_schedules( sch3, control->active_producers() ) ); +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( proposer_policy_misc_tests, validating_tester ) try { + create_accounts( {"alice"_n,"bob"_n} ); + + while (control->head_block_num() < 3) { + produce_block(); + } + + // activate instant_finality + set_finalizers({"alice"_n,"bob"_n}); + produce_block(); // this block contains the header extension of the finalizer set + produce_block(); // one producer, lib here + + { // set multiple policies in the same block. The last one will be chosen + set_producers( {"alice"_n} ); + set_producers( {"bob"_n} ); + + produce_blocks(2 * config::producer_repetitions); + + vector sch = { + producer_authority{"bob"_n, block_signing_authority_v0{1, {{get_public_key("bob"_n, "active"), 1}}}} + }; + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch, control->active_producers() ) ); + } + + { // unknown account in proposer policy + BOOST_CHECK_THROW( set_producers({"carol"_n}), wasm_execution_error ); + } + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index eea947804b..ad894303b1 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -28,13 +28,14 @@ BOOST_FIXTURE_TEST_CASE( verify_producer_schedule, validating_tester ) try { const uint32_t check_duration = 1000; // number of blocks bool scheduled_changed_to_new = false; for (uint32_t i = 0; i < check_duration; ++i) { - const auto current_schedule = control->head_block_state()->active_schedule.producers; + const auto current_schedule = control->active_producers().producers; if (new_prod_schd == current_schedule) { scheduled_changed_to_new = true; } // Produce block produce_block(); + control->abort_block(); // abort started block in produce_block so activate_producers() is off head // Check if the producer is the same as what we expect const auto block_time = control->head_block_time(); @@ -136,12 +137,13 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, validating_tester ) t }; //wdump((fc::json::to_pretty_string(res))); wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers_legacy() ) ); + BOOST_REQUIRE(control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 0u ); produce_block(); // Starts new block which promotes the proposed schedule to pending - BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->pending_producers_legacy() ) ); BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); produce_block(); produce_block(); // Starts new block which promotes the pending schedule to active @@ -156,15 +158,16 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, validating_tester ) t producer_authority{"carol"_n, block_signing_authority_v0{1, {{get_public_key("carol"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers() ) ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers_legacy() ) ); produce_block(); produce_blocks(23); // Alice produces the last block of her first round. // Bob's first block (which advances LIB to Alice's last block) is started but not finalized. BOOST_REQUIRE_EQUAL( control->head_block_producer(), "alice"_n ); BOOST_REQUIRE_EQUAL( control->pending_block_producer(), "bob"_n ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 2u ); + BOOST_REQUIRE(control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 2u ); produce_blocks(12); // Bob produces his first 11 blocks BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); @@ -201,12 +204,13 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{get_public_key("carol"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers_legacy() ) ); + BOOST_REQUIRE(control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 0u ); produce_block(); // Starts new block which promotes the proposed schedule to pending - BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->pending_producers_legacy() ) ); BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); produce_block(); produce_block(); // Starts new block which promotes the pending schedule to active @@ -220,13 +224,14 @@ BOOST_FIXTURE_TEST_CASE( producer_schedule_reduction, tester ) try { producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{ get_public_key("bob"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers() ) ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers_legacy() ) ); produce_blocks(48); BOOST_REQUIRE_EQUAL( control->head_block_producer(), "bob"_n ); BOOST_REQUIRE_EQUAL( control->pending_block_producer(), "carol"_n ); - BOOST_CHECK_EQUAL( control->pending_producers().version, 2u ); + BOOST_REQUIRE(control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( control->pending_producers_legacy()->version, 2u ); produce_blocks(47); BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); @@ -263,14 +268,16 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{ get_public_key("bob"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( c.control->pending_producers().producers.size(), 0u ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers_legacy() ) ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->producers.size(), 0u ); // Start a new block which promotes the proposed schedule to pending c.produce_block(); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->pending_producers() ) ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->pending_producers_legacy() ) ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 0u ); // Start a new block which promotes the pending schedule to active @@ -281,22 +288,25 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { res = c.set_producers_legacy( {} ); wlog("set producer schedule to []"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( c.control->proposed_producers()->producers.size(), 0u ); - BOOST_CHECK_EQUAL( c.control->proposed_producers()->version, 2u ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( c.control->proposed_producers_legacy()->producers.size(), 0u ); + BOOST_CHECK_EQUAL( c.control->proposed_producers_legacy()->version, 2u ); c.produce_blocks(12); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 1u ); // Empty producer schedule does get promoted from proposed to pending c.produce_block(); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); - BOOST_CHECK_EQUAL( false, c.control->proposed_producers().has_value() ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 2u ); + BOOST_CHECK_EQUAL( false, c.control->proposed_producers_legacy().has_value() ); // However it should not get promoted from pending to active c.produce_blocks(24); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 2u ); // Setting a new producer schedule should still use version 2 res = c.set_producers_legacy( {"alice"_n,"bob"_n,"carol"_n} ); @@ -306,15 +316,16 @@ BOOST_AUTO_TEST_CASE( empty_producer_schedule_has_no_effect ) try { producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{get_public_key("carol"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( c.control->proposed_producers()->version, 2u ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers_legacy() ) ); + BOOST_CHECK_EQUAL( c.control->proposed_producers_legacy()->version, 2u ); // Produce enough blocks to promote the proposed schedule to pending, which it can do because the existing pending has zero producers c.produce_blocks(24); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch2, c.control->pending_producers() ) ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 2u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->pending_producers_legacy() ) ); // Produce enough blocks to promote the pending schedule to active c.produce_blocks(24); @@ -341,12 +352,14 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{c.get_public_key("carol"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob,carol]"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 0u ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers_legacy() ) ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 0u ); c.produce_block(); // Starts new block which promotes the proposed schedule to pending - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->pending_producers() ) ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->pending_producers_legacy() ) ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 0u ); c.produce_block(); c.produce_block(); // Starts new block which promotes the pending schedule to active @@ -363,16 +376,18 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{c.get_public_key("bob"_n, "active"),1}}}} }; wlog("set producer schedule to [alice,bob]"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers() ) ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers_legacy() ) ); produce_until_transition( c, "bob"_n, "carol"_n ); produce_until_transition( c, "alice"_n, "bob"_n ); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 2u ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); produce_until_transition( c, "carol"_n, "alice"_n ); - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 2u ); BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); produce_until_transition( c, "bob"_n, "carol"_n ); @@ -387,8 +402,8 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { res = c.set_producers( {"alice"_n,"bob"_n,"carol"_n} ); wlog("set producer schedule to [alice,bob,carol]"); - BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); - BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers_legacy().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers_legacy() ) ); produce_until_transition( c, "bob"_n, "alice"_n ); @@ -401,8 +416,8 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { wdump((alice_last_produced_block_num)); { - wdump((c.control->head_block_state()->producer_to_last_produced)); - const auto& last_produced = c.control->head_block_state()->producer_to_last_produced; + wdump((c.control->head_block_state_legacy()->producer_to_last_produced)); + const auto& last_produced = c.control->head_block_state_legacy()->producer_to_last_produced; auto alice_itr = last_produced.find( "alice"_n ); BOOST_REQUIRE( alice_itr != last_produced.end() ); BOOST_CHECK_EQUAL( alice_itr->second, alice_last_produced_block_num ); @@ -414,7 +429,8 @@ BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { BOOST_CHECK_EQUAL( carol_itr->second, carol_last_produced_block_num ); } - BOOST_CHECK_EQUAL( c.control->pending_producers().version, 3u ); + BOOST_REQUIRE(c.control->pending_producers_legacy()); + BOOST_CHECK_EQUAL( c.control->pending_producers_legacy()->version, 3u ); BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); produce_until_transition( c, "bob"_n, "alice"_n ); @@ -494,7 +510,7 @@ BOOST_FIXTURE_TEST_CASE( satisfiable_msig_test, validating_tester ) try { fc_exception_message_is( "producer schedule includes an unsatisfiable authority for alice" ) ); - BOOST_REQUIRE_EQUAL( false, control->proposed_producers().has_value() ); + BOOST_REQUIRE_EQUAL( false, control->proposed_producers_legacy().has_value() ); } FC_LOG_AND_RETHROW() @@ -513,7 +529,7 @@ BOOST_FIXTURE_TEST_CASE( duplicate_producers_test, validating_tester ) try { fc_exception_message_is( "duplicate producer name in producer schedule" ) ); - BOOST_REQUIRE_EQUAL( false, control->proposed_producers().has_value() ); + BOOST_REQUIRE_EQUAL( false, control->proposed_producers_legacy().has_value() ); } FC_LOG_AND_RETHROW() @@ -531,7 +547,7 @@ BOOST_FIXTURE_TEST_CASE( duplicate_keys_test, validating_tester ) try { fc_exception_message_is( "producer schedule includes a duplicated key for alice" ) ); - BOOST_REQUIRE_EQUAL( false, control->proposed_producers().has_value() ); + BOOST_REQUIRE_EQUAL( false, control->proposed_producers_legacy().has_value() ); // ensure that multiple producers are allowed to share keys vector sch2 = { @@ -540,7 +556,7 @@ BOOST_FIXTURE_TEST_CASE( duplicate_keys_test, validating_tester ) try { }; set_producer_schedule( sch2 ); - BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers_legacy().has_value() ); } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_CASE( large_authority_overflow_test ) try { @@ -602,7 +618,7 @@ BOOST_AUTO_TEST_CASE( extra_signatures_test ) try { }; main.set_producer_schedule( sch1 ); - BOOST_REQUIRE_EQUAL( true, main.control->proposed_producers().has_value() ); + BOOST_REQUIRE_EQUAL( true, main.control->proposed_producers_legacy().has_value() ); main.block_signing_private_keys.emplace(get_public_key("alice"_n, "bs1"), get_private_key("alice"_n, "bs1")); main.block_signing_private_keys.emplace(get_public_key("alice"_n, "bs2"), get_private_key("alice"_n, "bs2")); @@ -638,8 +654,8 @@ BOOST_AUTO_TEST_CASE( extra_signatures_test ) try { BOOST_REQUIRE_EQUAL( additional_sigs.size(), 1u ); // Generate the extra signature and add to additonal_sigs. - auto header_bmroot = digest_type::hash( std::make_pair( b->digest(), remote.control->head_block_state()->blockroot_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( b->digest(), remote.control->head_block_state_legacy()->blockroot_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); additional_sigs.emplace_back( remote.get_private_key("alice"_n, "bs3").sign(sig_digest) ); additional_sigs.emplace_back( remote.get_private_key("alice"_n, "bs4").sign(sig_digest) ); diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index ebef30e756..f589745ec3 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE( activate_preactivate_feature ) try { // Cannot set latest bios contract since it requires intrinsics that have not yet been whitelisted. BOOST_CHECK_EXCEPTION( c.set_code( config::system_account_name, contracts::eosio_bios_wasm() ), - wasm_exception, fc_exception_message_is("env.set_proposed_producers_ex unresolveable") + wasm_exception, fc_exception_message_is("env.bls_fp_mod unresolveable") ); // But the old bios contract can still be set. @@ -414,7 +414,7 @@ BOOST_AUTO_TEST_CASE( replace_deferred_test ) try { c.init( cfg ); transaction_trace_ptr trace; - auto h = c.control->applied_transaction.connect( [&](std::tuple x) { + auto h = c.control->applied_transaction().connect( [&](std::tuple x) { auto& t = std::get<0>(x); if( t && !eosio::chain::is_onblock(*t)) { trace = t; @@ -558,7 +558,7 @@ BOOST_AUTO_TEST_CASE( no_duplicate_deferred_id_test ) try { c2.produce_empty_block( fc::minutes(10) ); transaction_trace_ptr trace0; - auto h2 = c2.control->applied_transaction.connect( [&](std::tuple x) { + auto h2 = c2.control->applied_transaction().connect( [&](std::tuple x) { auto& t = std::get<0>(x); if( t && t->receipt && t->receipt->status == transaction_receipt::expired) { trace0 = t; @@ -576,7 +576,7 @@ BOOST_AUTO_TEST_CASE( no_duplicate_deferred_id_test ) try { const auto& index = c.control->db().get_index(); transaction_trace_ptr trace1; - auto h = c.control->applied_transaction.connect( [&](std::tuple x) { + auto h = c.control->applied_transaction().connect( [&](std::tuple x) { auto& t = std::get<0>(x); if( t && t->receipt && t->receipt->status == transaction_receipt::executed) { trace1 = t; @@ -1071,6 +1071,101 @@ BOOST_AUTO_TEST_CASE( get_sender_test ) { try { ); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( protocol_activatation_works_after_transition_to_savanna ) { try { + validating_tester c({}, {}, setup_policy::preactivate_feature_and_new_bios ); + + c.preactivate_savanna_protocol_features(); + c.produce_block(); + + c.set_bios_contract(); + c.produce_block(); + + uint32_t lib = 0; + c.control->irreversible_block().connect([&](const block_signal_params& t) { + const auto& [ block, id ] = t; + lib = block->block_num(); + }); + + c.produce_block(); + + vector accounts = { + "alice"_n, "bob"_n, "carol"_n + }; + + base_tester::finalizer_policy_input policy_input = { + .finalizers = { {.name = "alice"_n, .weight = 1}, + {.name = "bob"_n, .weight = 3}, + {.name = "carol"_n, .weight = 5} }, + .threshold = 5, + .local_finalizers = {"carol"_n} + }; + + // Create finalizer accounts + c.create_accounts(accounts); + c.produce_block(); + + // activate savanna + c.set_finalizers(policy_input); + auto block = c.produce_block(); // this block contains the header extension for the instant finality + + std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional fin_policy = std::get(*ext).new_finalizer_policy; + BOOST_TEST(!!fin_policy); + BOOST_TEST(fin_policy->finalizers.size() == accounts.size()); + + block = c.produce_block(); // savanna now active + auto fb = c.control->fetch_block_by_id(block->calculate_id()); + BOOST_REQUIRE(!!fb); + BOOST_TEST(fb == block); + ext = fb->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_REQUIRE(ext); + + auto lib_after_transition = lib; + + c.produce_blocks(4); + BOOST_CHECK_GT(lib, lib_after_transition); + + // verify protocol feature activation works under savanna + + const auto& tester1_account = account_name("tester1"); + const auto& tester2_account = account_name("tester2"); + c.create_accounts( {tester1_account, tester2_account} ); + c.produce_block(); + + BOOST_CHECK_EXCEPTION( c.set_code( tester1_account, test_contracts::get_sender_test_wasm() ), + wasm_exception, + fc_exception_message_is( "env.get_sender unresolveable" ) ); + + const auto& pfm = c.control->get_protocol_feature_manager(); + const auto& d2 = pfm.get_builtin_digest( builtin_protocol_feature_t::get_sender ); + BOOST_REQUIRE( d2 ); + + c.preactivate_protocol_features( {*d2} ); + c.produce_block(); + + c.set_code( tester1_account, test_contracts::get_sender_test_wasm() ); + c.set_abi( tester1_account, test_contracts::get_sender_test_abi() ); + c.set_code( tester2_account, test_contracts::get_sender_test_wasm() ); + c.set_abi( tester2_account, test_contracts::get_sender_test_abi() ); + c.produce_block(); + + BOOST_CHECK_EXCEPTION( c.push_action( tester1_account, "sendinline"_n, tester1_account, mutable_variant_object() + ("to", tester2_account.to_string()) + ("expected_sender", account_name{}) ), + eosio_assert_message_exception, + eosio_assert_message_is( "sender did not match" ) ); + + c.push_action( tester1_account, "sendinline"_n, tester1_account, mutable_variant_object() + ("to", tester2_account.to_string()) + ("expected_sender", tester1_account.to_string()) + ); + + c.push_action( tester1_account, "assertsender"_n, tester1_account, mutable_variant_object() + ("expected_sender", account_name{}) + ); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( ram_restrictions_test ) { try { tester c( setup_policy::preactivate_feature_and_new_bios ); @@ -1593,19 +1688,17 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { { // ensure producer_schedule_change_extension is rejected - const auto& hbs = remote.control->head_block_state(); - // create a bad block that has the producer schedule change extension before the feature upgrade auto bad_block = std::make_shared(last_legacy_block->clone()); emplace_extension( bad_block->header_extensions, producer_schedule_change_extension::extension_id(), - fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) + fc::raw::pack(std::make_pair(remote.control->active_producers().version + 1, std::vector{})) ); // re-sign the bad block - auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state_legacy()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); bad_block->producer_signature = remote.get_private_key("eosio"_n, "active").sign(sig_digest); // ensure it is rejected as an unknown extension @@ -1616,21 +1709,19 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { } { // ensure that non-null new_producers is accepted (and fails later in validation) - const auto& hbs = remote.control->head_block_state(); - // create a bad block that has the producer schedule change extension before the feature upgrade auto bad_block = std::make_shared(last_legacy_block->clone()); - bad_block->new_producers = legacy::producer_schedule_type{hbs->active_schedule.version + 1, {}}; + bad_block->new_producers = legacy::producer_schedule_type{remote.control->active_producers().version + 1, {}}; // re-sign the bad block - auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state_legacy()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); bad_block->producer_signature = remote.get_private_key("eosio"_n, "active").sign(sig_digest); // ensure it is accepted (but rejected because it doesn't match expected state) BOOST_REQUIRE_EXCEPTION( remote.push_block(bad_block), wrong_signing_key, - fc_exception_message_is( "block signed by unexpected key" ) + fc_exception_message_starts_with( "block signed by unexpected key" ) ); } @@ -1640,38 +1731,34 @@ BOOST_AUTO_TEST_CASE( producer_schedule_change_extension_test ) { try { auto first_new_block = c.produce_block(); { - const auto& hbs = remote.control->head_block_state(); - // create a bad block that has the producer schedule change extension that is valid but not warranted by actions in the block auto bad_block = std::make_shared(first_new_block->clone()); emplace_extension( bad_block->header_extensions, producer_schedule_change_extension::extension_id(), - fc::raw::pack(std::make_pair(hbs->active_schedule.version + 1, std::vector{})) + fc::raw::pack(std::make_pair(remote.control->active_producers().version + 1, std::vector{})) ); // re-sign the bad block - auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state_legacy()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); bad_block->producer_signature = remote.get_private_key("eosio"_n, "active").sign(sig_digest); // ensure it is rejected because it doesn't match expected state (but the extention was accepted) BOOST_REQUIRE_EXCEPTION( remote.push_block(bad_block), wrong_signing_key, - fc_exception_message_is( "block signed by unexpected key" ) + fc_exception_message_starts_with( "block signed by unexpected key" ) ); } { // ensure that non-null new_producers is rejected - const auto& hbs = remote.control->head_block_state(); - // create a bad block that has the producer schedule change extension before the feature upgrade auto bad_block = std::make_shared(first_new_block->clone()); - bad_block->new_producers = legacy::producer_schedule_type{hbs->active_schedule.version + 1, {}}; + bad_block->new_producers = legacy::producer_schedule_type{remote.control->active_producers().version + 1, {}}; // re-sign the bad block - auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state()->blockroot_merkle ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( bad_block->digest(), remote.control->head_block_state_legacy()->blockroot_merkle ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); bad_block->producer_signature = remote.get_private_key("eosio"_n, "active").sign(sig_digest); // ensure it is rejected because the new_producers field is not null @@ -2243,11 +2330,11 @@ BOOST_AUTO_TEST_CASE( block_validation_after_stage_1_test ) { try { const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = calculate_merkle_legacy( std::move(trx_digests) ); // Re-sign the block - auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), tester1.control->head_block_state()->blockroot_merkle.get_root() ) ); - auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, tester1.control->head_block_state()->pending_schedule.schedule_hash) ); + auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), tester1.control->head_block_state_legacy()->blockroot_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, tester1.control->head_block_state_legacy()->pending_schedule.schedule_hash) ); copy_b->producer_signature = tester1.get_private_key(config::system_account_name, "active").sign(sig_digest); // Create the second chain @@ -2260,15 +2347,75 @@ BOOST_AUTO_TEST_CASE( block_validation_after_stage_1_test ) { try { tester2.produce_block(); // Push the block with delayed transaction to the second chain - auto bsf = tester2.control->create_block_state_future( copy_b->calculate_id(), copy_b ); + auto btf = tester2.control->create_block_handle_future( copy_b->calculate_id(), copy_b ); tester2.control->abort_block(); controller::block_report br; // The block is invalidated - BOOST_REQUIRE_EXCEPTION(tester2.control->push_block( br, bsf.get(), forked_branch_callback{}, trx_meta_cache_lookup{} ), + BOOST_REQUIRE_EXCEPTION(tester2.control->push_block( br, btf.get(), {}, trx_meta_cache_lookup{} ), fc::exception, fc_exception_message_starts_with("transaction cannot be delayed") ); } FC_LOG_AND_RETHROW() } /// block_validation_after_stage_1_test +static const char import_set_finalizers_wast[] = R"=====( +(module + (import "env" "set_finalizers" (func $set_finalizers (param i32 i32))) + (memory $0 1) + (export "apply" (func $apply)) + (func $apply (param $0 i64) (param $1 i64) (param $2 i64) + (call $set_finalizers + (i32.const 0) + (i32.const 4) + ) + ) + (data (i32.const 0) "\00\00\00\00") +) +)====="; + +BOOST_AUTO_TEST_CASE( set_finalizers_test ) { try { + tester c( setup_policy::preactivate_feature_and_new_bios ); + + const auto alice_account = account_name("alice"); + c.create_accounts( {alice_account} ); + c.produce_block(); + + BOOST_CHECK_EXCEPTION( c.set_code( config::system_account_name, import_set_finalizers_wast ), + wasm_exception, + fc_exception_message_is( "env.set_finalizers unresolveable" ) ); + + c.preactivate_savanna_protocol_features(); + c.produce_block(); + + // ensure it now resolves + c.set_code( config::system_account_name, import_set_finalizers_wast ); + + // ensure it can be called + auto action_priv = action( {//vector of permission_level + { config::system_account_name, + permission_name("active") } + }, + config::system_account_name, + action_name(), + {} ); + // if able to call then will get error on unpacking field `fthreshold`, top message of: 'read datastream of length 4 over by -3' + base_tester::action_result r = c.push_action(std::move(action_priv), config::system_account_name.to_uint64_t()); + BOOST_CHECK(r.find("read datastream of length 4 over by -3") != std::string::npos); + + c.produce_block(); + + + c.set_code( alice_account, import_set_finalizers_wast ); + auto action_non_priv = action( {//vector of permission_level + { alice_account, + permission_name("active") } + }, + alice_account, + action_name(), + {} ); + //ensure privileged host function cannot be called by regular account + BOOST_REQUIRE_EQUAL(c.push_action(std::move(action_non_priv), alice_account.to_uint64_t()), + c.error("alice does not have permission to call this API")); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/restart_chain_tests.cpp b/unittests/restart_chain_tests.cpp index c810f50548..d60fe80ef8 100644 --- a/unittests/restart_chain_tests.cpp +++ b/unittests/restart_chain_tests.cpp @@ -60,7 +60,7 @@ class replay_tester : public base_tester { replay_tester(controller::config config, const genesis_state& genesis, OnAppliedTrx&& on_applied_trx) { cfg = config; base_tester::open(make_protocol_feature_set(), genesis.compute_chain_id(), [&genesis,&control=this->control, &on_applied_trx]() { - control->applied_transaction.connect(on_applied_trx); + control->applied_transaction().connect(on_applied_trx); control->startup( [](){}, []() { return false; }, genesis ); }); } @@ -226,15 +226,17 @@ BOOST_AUTO_TEST_CASE(test_light_validation_restart_from_block_log) { BOOST_CHECK(*trace->receipt == *other_trace->receipt); BOOST_CHECK_EQUAL(2u, other_trace->action_traces.size()); + auto check_action_traces = [](const auto& t, const auto& ot) { + BOOST_CHECK_EQUAL("", ot.console); // cfa not executed for replay + BOOST_CHECK_EQUAL(t.receipt->global_sequence, ot.receipt->global_sequence); + BOOST_CHECK_EQUAL(t.digest_legacy(), ot.digest_legacy()); // digest_legacy because test doesn't switch to Savanna + }; + BOOST_CHECK(other_trace->action_traces.at(0).context_free); // cfa - BOOST_CHECK_EQUAL("", other_trace->action_traces.at(0).console); // cfa not executed for replay - BOOST_CHECK_EQUAL(trace->action_traces.at(0).receipt->global_sequence, other_trace->action_traces.at(0).receipt->global_sequence); - BOOST_CHECK_EQUAL(trace->action_traces.at(0).receipt->digest(), other_trace->action_traces.at(0).receipt->digest()); + check_action_traces(trace->action_traces.at(0), other_trace->action_traces.at(0)); BOOST_CHECK(!other_trace->action_traces.at(1).context_free); // non-cfa - BOOST_CHECK_EQUAL("", other_trace->action_traces.at(1).console); - BOOST_CHECK_EQUAL(trace->action_traces.at(1).receipt->global_sequence, other_trace->action_traces.at(1).receipt->global_sequence); - BOOST_CHECK_EQUAL(trace->action_traces.at(1).receipt->digest(), other_trace->action_traces.at(1).receipt->digest()); + check_action_traces(trace->action_traces.at(1), other_trace->action_traces.at(1)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 710db93312..d4ac1646bb 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -36,8 +36,9 @@ std::filesystem::path get_parent_path(std::filesystem::path blocks_dir, int ordi controller::config copy_config(const controller::config& config, int ordinal) { controller::config copied_config = config; auto parent_path = get_parent_path(config.blocks_dir, ordinal); + copied_config.finalizers_dir = parent_path / config.finalizers_dir.filename().generic_string();; copied_config.blocks_dir = parent_path / config.blocks_dir.filename().generic_string(); - copied_config.state_dir = parent_path / config.state_dir.filename().generic_string(); + copied_config.state_dir = parent_path / config.state_dir.filename().generic_string(); return copied_config; } @@ -420,10 +421,10 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot std::filesystem::copy(source_log_dir / "blocks.index", config.blocks_dir / "blocks.index"); tester base_chain(config, *genesis); - std::string current_version = "v6"; + std::string current_version = "v7"; int ordinal = 0; - for(std::string version : {"v2", "v3", "v4" , "v5", "v6"}) + for(std::string version : {"v2", "v3", "v4" , "v5", "v6", "v7"}) { if(save_snapshot && version == current_version) continue; static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); @@ -443,6 +444,17 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot } // This isn't quite fully automated. The snapshots still need to be gzipped and moved to // the correct place in the source tree. + // ------------------------------------------------------------------------------------------------------------- + // Process for supporting a new snapshot version in this test: + // ---------------------------------------------------------- + // 1. update `current_version` and the list of versions in `for` loop + // 2. run: `unittests/unit_test -t "snapshot_tests/test_com*" -- --save-snapshot` to generate new snapshot files + // 3. copy the newly generated files (see `ls -lrth ./unittests/snapshots/snap_*` to `leap/unittests/snapshots` + // for example `cp ./unittests/snapshots/snap_v7.* ../unittests/snapshots` + // 4. edit `unittests/snapshots/CMakeLists.txt` and add the `configure_file` commands for the 3 new files. + // now the test should pass. + // 5. add the 3 new snapshot files in git. + // ------------------------------------------------------------------------------------------------------------- if (save_snapshot) { // create a latest snapshot diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index 6a47ed826d..1f3735bc6c 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -20,3 +20,6 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v5.bin.json.gz ${CMAKE_CURRENT_ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.bin.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.json.gz COPYONLY ) diff --git a/unittests/snapshots/snap_v7.bin.gz b/unittests/snapshots/snap_v7.bin.gz new file mode 100644 index 0000000000..214b1475d8 Binary files /dev/null and b/unittests/snapshots/snap_v7.bin.gz differ diff --git a/unittests/snapshots/snap_v7.bin.json.gz b/unittests/snapshots/snap_v7.bin.json.gz new file mode 100644 index 0000000000..84ed5ad83d Binary files /dev/null and b/unittests/snapshots/snap_v7.bin.json.gz differ diff --git a/unittests/snapshots/snap_v7.json.gz b/unittests/snapshots/snap_v7.json.gz new file mode 100644 index 0000000000..ad4b3e005c Binary files /dev/null and b/unittests/snapshots/snap_v7.json.gz differ diff --git a/unittests/special_accounts_tests.cpp b/unittests/special_accounts_tests.cpp index bd3965ddc8..133e39ccef 100644 --- a/unittests/special_accounts_tests.cpp +++ b/unittests/special_accounts_tests.cpp @@ -1,19 +1,10 @@ -#include -#include -#include - #include #include #include #include #include -#include - -#include -#include -#include -#include +#include using namespace eosio; using namespace chain; @@ -44,7 +35,7 @@ BOOST_FIXTURE_TEST_CASE(accounts_exists, tester) auto producers = chain1_db.find(config::producers_account_name); BOOST_CHECK(producers != nullptr); - const auto& active_producers = control->head_block_state()->active_schedule; + const auto& active_producers = control->active_producers(); const auto& producers_active_authority = chain1_db.get(boost::make_tuple(config::producers_account_name, config::active_name)); auto expected_threshold = (active_producers.producers.size() * 2)/3 + 1; diff --git a/unittests/state_history_tests.cpp b/unittests/state_history_tests.cpp index 2114bf7647..cac7d5e9e2 100644 --- a/unittests/state_history_tests.cpp +++ b/unittests/state_history_tests.cpp @@ -588,7 +588,7 @@ BOOST_AUTO_TEST_CASE(test_deltas_resources_history) { fc::temp_directory state_history_dir; eosio::state_history::trace_converter log; - c.control->applied_transaction.connect( + c.control->applied_transaction().connect( [&](std::tuple t) { log.add_transaction(std::get<0>(t), std::get<1>(t)); }); @@ -626,16 +626,16 @@ struct state_history_tester : state_history_tester_logs, tester { state_history_tester(const std::filesystem::path& dir, const eosio::state_history_log_config& config) : state_history_tester_logs(dir, config), tester ([this](eosio::chain::controller& control) { - control.applied_transaction.connect( + control.applied_transaction().connect( [&](std::tuple t) { trace_converter.add_transaction(std::get<0>(t), std::get<1>(t)); }); - control.accepted_block.connect([&](block_signal_params t) { + control.accepted_block().connect([&](block_signal_params t) { const auto& [ block, id ] = t; eosio::state_history_log_header header{.magic = eosio::ship_magic(eosio::ship_current_version, 0), - .block_id = id, - .payload_size = 0}; + .block_id = id, + .payload_size = 0}; traces_log.pack_and_write_entry(header, block->previous, [this, &block](auto&& buf) { trace_converter.pack(buf, false, block); @@ -645,7 +645,7 @@ struct state_history_tester : state_history_tester_logs, tester { eosio::state_history::pack_deltas(buf, control.db(), true); }); }); - control.block_start.connect([this](uint32_t block_num) { + control.block_start().connect([this](uint32_t block_num) { trace_converter.cached_traces.clear(); trace_converter.onblock_trace.reset(); }); diff --git a/unittests/svnn_ibc_tests.cpp b/unittests/svnn_ibc_tests.cpp new file mode 100644 index 0000000000..f1d1fe998a --- /dev/null +++ b/unittests/svnn_ibc_tests.cpp @@ -0,0 +1,136 @@ +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include "fork_test_utilities.hpp" + +using namespace eosio::chain; +using namespace eosio::testing; + +#include +using mvo = mutable_variant_object; + + +BOOST_AUTO_TEST_SUITE(svnn_ibc) + +// Extending the default chain tester +// ( libraries/testing/include/eosio/testing/tester.hpp ) +class ibc_tester : public tester { +public: + const account_name _bridge = "bridge"_n; + + // This is mostly for creating accounts and loading contracts. + void setup(){ + // load bridge contract + create_account( _bridge ); + set_code( _bridge, eosio::testing::test_contracts::svnn_ibc_wasm()); + set_abi( _bridge, eosio::testing::test_contracts::svnn_ibc_abi()); + } + + //set a finalizer policy + void set_policy(){ + auto cr = push_action( _bridge, "setfpolicy"_n, _bridge, mutable_variant_object() + ("from_block_num", 1) + ("policy", mutable_variant_object() + ("generation", 1) + ("fthreshold", 3) + ("last_block_num", 0) + ("finalizers", fc::variants({ + mutable_variant_object() + ("description","finalizer1") + ("fweight", 1) + ("public_key", "b12eba13063c6cdc7bbe40e7de62a1c0f861a9ad55e924cdd5049be9b58e205053968179cede5be79afdcbbb90322406aefb7a5ce64edc2a4482d8656daed1eeacfb4286f661c0f9117dcd83fad451d301b2310946e5cd58808f7b441b280a02") + , + mutable_variant_object() + ("description","finalizer2") + ("fweight", 1) + ("public_key", "0728121cffe7b8ddac41817c3a6faca76ae9de762d9c26602f936ac3e283da756002d3671a2858f54c355f67b31b430b23b957dba426d757eb422db617be4cc13daf41691aa059b0f198fa290014d3c3e4fa1def2abc6a3328adfa7705c75508") + , + mutable_variant_object() + ("description","finalizer3") + ("fweight", 1) + ("public_key", "e06c31c83f70b4fe9507877563bfff49235774d94c98dbf9673d61d082ef589f7dd4865281f37d60d1bb433514d4ef0b787424fb5e53472b1d45d28d90614fad29a4e5e0fe70ea387f7845e22c843f6061f9be20a7af21d8b72d02f4ca494a0a") + , + mutable_variant_object() + ("description","finalizer4") + ("fweight", 1) + ("public_key", "08c9bd408bac02747e493d918e4b3e6bd1a2ffaf9bfca4f2e79dd22e12556bf46e911f25613c24d9f6403996c5246c19ef94aff48094868425eda1e46bcd059c59f3b060521be797f5cc2e6debe2180efa12c0814618a38836a64c3d7440740f") + })) + ) + ); + } + + //verify a proof + void check_proof(){ + auto cr = push_action( _bridge, "checkproof"_n, _bridge, mutable_variant_object() + ("proof", mutable_variant_object() + ("finality_proof", mutable_variant_object() + ("qc_block", mutable_variant_object() + ("major_version", 1) + ("minor_version", 0) + ("finalizer_policy_generation", 1) + ("witness_hash", "888ceeb757ea240d1c1ae2f4f717e67b73dcd592b2ba097f63b4c3e3ca4350e1") + ("finality_mroot", "1d2ab7379301370d3fa1b27a9f4ac077f6ea445a1aa3dbf7e18e9cc2c25b140c") + ) + ("qc", mutable_variant_object() + ("signature", "") + ("finalizers", fc::variants({})) + ) + ) + ("target_block_proof_of_inclusion", mutable_variant_object() + ("target_node_index", 7) + ("last_node_index", 7) + ("target", fc::variants({"block_data", mutable_variant_object() + ("finality_data", mutable_variant_object() + ("major_version", 1) + ("minor_version", 0) + ("finalizer_policy_generation", 1) + ("witness_hash", "dff620c1c4d31cade95ed609269a86d4ecb2357f9302d17675c0665c75786508") + ("finality_mroot", "1397eb7c86719f160188fa740fc3610ccb5a6681ad56807dc99a17fe73a7b7fd") + ) + ("dynamic_data", mutable_variant_object() + ("block_num", 28) + ("action_proofs", fc::variants()) + ("action_mroot", "4e890ef0e014f93bd1b31fabf1041ecc9fb1c44e957c2f7b1682333ee426677a") + ) + })) + ("merkle_branches", fc::variants({ + mutable_variant_object() + ("direction", 1) + ("hash", "4e17da018040c80339f2714828d1927d5b616f9af7aa4768c1876df6f05e5602") + , + mutable_variant_object() + ("direction", 1) + ("hash", "7ee0e16f1941fb5a98d80d20ca92e0c689e9284285d5f90ecd4f8f1ea2ffb53c") + , + mutable_variant_object() + ("direction", 1) + ("hash", "401526ba03ec4a955c83cda131dacd3e89becaad2cf04107170e436dd90a553f") + })) + ) + ) + ); + } +}; + +BOOST_AUTO_TEST_CASE( first_test ) try { + + ibc_tester chain_a; + + chain_a.setup(); + + chain_a.set_policy(); + chain_a.check_proof(); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/unittests/test-contracts/CMakeLists.txt b/unittests/test-contracts/CMakeLists.txt index a62d82809c..accc16bf7b 100644 --- a/unittests/test-contracts/CMakeLists.txt +++ b/unittests/test-contracts/CMakeLists.txt @@ -45,3 +45,4 @@ add_subdirectory( crypto_primitives_test ) add_subdirectory( bls_primitives_test ) add_subdirectory( get_block_num_test ) add_subdirectory( nested_container_multi_index ) +add_subdirectory( svnn_ibc ) diff --git a/unittests/test-contracts/svnn_ibc/CMakeLists.txt b/unittests/test-contracts/svnn_ibc/CMakeLists.txt new file mode 100644 index 0000000000..31dfbd568d --- /dev/null +++ b/unittests/test-contracts/svnn_ibc/CMakeLists.txt @@ -0,0 +1,8 @@ +set( IBC_CONTRACT "svnn_ibc" ) + +if( EOSIO_COMPILE_TEST_CONTRACTS ) + add_contract( ${IBC_CONTRACT} ${IBC_CONTRACT} ${IBC_CONTRACT}.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${IBC_CONTRACT}.wasm ${CMAKE_CURRENT_BINARY_DIR}/${IBC_CONTRACT}.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${IBC_CONTRACT}.abi ${CMAKE_CURRENT_BINARY_DIR}/${IBC_CONTRACT}.abi COPYONLY ) +endif() diff --git a/unittests/test-contracts/svnn_ibc/bitset.hpp b/unittests/test-contracts/svnn_ibc/bitset.hpp new file mode 100644 index 0000000000..711e089fad --- /dev/null +++ b/unittests/test-contracts/svnn_ibc/bitset.hpp @@ -0,0 +1,45 @@ +using namespace eosio; + +class bitset { +public: + bitset(size_t size) + : num_bits(size), data((size + 63) / 64) {} + + bitset(size_t size, const std::vector& raw_bitset) + : num_bits(size), data(raw_bitset) { + check(raw_bitset.size() == (size + 63) / 64, "invalid raw bitset size"); + + } + + // Set a bit to 1 + void set(size_t index) { + check_bounds(index); + data[index / 64] |= (1ULL << (index % 64)); + } + + // Clear a bit (set to 0) + void clear(size_t index) { + check_bounds(index); + data[index / 64] &= ~(1ULL << (index % 64)); + } + + // Check if a bit is set + bool test(size_t index) const { + check_bounds(index); + return (data[index / 64] & (1ULL << (index % 64))) != 0; + } + + // Size of the bitset + size_t size() const { + return num_bits; + } + +private: + size_t num_bits; + std::vector data; + + // Check if the index is within bounds + void check_bounds(size_t index) const { + check(index < num_bits, "bitset index out of bounds"); + } +}; diff --git a/unittests/test-contracts/svnn_ibc/svnn_ibc.abi b/unittests/test-contracts/svnn_ibc/svnn_ibc.abi new file mode 100644 index 0000000000..a6737df603 --- /dev/null +++ b/unittests/test-contracts/svnn_ibc/svnn_ibc.abi @@ -0,0 +1,366 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [ + { + "new_type_name": "bls_public_key", + "type": "bytes" + }, + { + "new_type_name": "bls_signature", + "type": "bytes" + }, + { + "new_type_name": "target_data", + "type": "variant_block_data_action_data" + } + ], + "structs": [ + { + "name": "action_data", + "base": "", + "fields": [ + { + "name": "action", + "type": "r_action" + }, + { + "name": "action_receipt_digest", + "type": "checksum256" + }, + { + "name": "return_value", + "type": "bytes" + } + ] + }, + { + "name": "block_data", + "base": "", + "fields": [ + { + "name": "finality_data", + "type": "block_finality_data" + }, + { + "name": "dynamic_data", + "type": "dynamic_data_v0" + } + ] + }, + { + "name": "block_finality_data", + "base": "", + "fields": [ + { + "name": "major_version", + "type": "uint32" + }, + { + "name": "minor_version", + "type": "uint32" + }, + { + "name": "finalizer_policy_generation", + "type": "uint32" + }, + { + "name": "active_finalizer_policy", + "type": "fpolicy?" + }, + { + "name": "witness_hash", + "type": "checksum256" + }, + { + "name": "finality_mroot", + "type": "checksum256" + } + ] + }, + { + "name": "checkproof", + "base": "", + "fields": [ + { + "name": "proof", + "type": "proof" + } + ] + }, + { + "name": "clear", + "base": "", + "fields": [] + }, + { + "name": "dynamic_data_v0", + "base": "", + "fields": [ + { + "name": "block_num", + "type": "uint32" + }, + { + "name": "action_proofs", + "type": "proof_of_inclusion[]" + }, + { + "name": "action_mroot", + "type": "checksum256?" + } + ] + }, + { + "name": "finality_proof", + "base": "", + "fields": [ + { + "name": "qc_block", + "type": "block_finality_data" + }, + { + "name": "qc", + "type": "quorum_certificate" + } + ] + }, + { + "name": "finalizer_authority", + "base": "", + "fields": [ + { + "name": "description", + "type": "string" + }, + { + "name": "fweight", + "type": "uint64" + }, + { + "name": "public_key", + "type": "bls_public_key" + } + ] + }, + { + "name": "fpolicy", + "base": "", + "fields": [ + { + "name": "generation", + "type": "uint32" + }, + { + "name": "fthreshold", + "type": "uint64" + }, + { + "name": "finalizers", + "type": "finalizer_authority[]" + } + ] + }, + { + "name": "lastproof", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "block_num", + "type": "uint32" + }, + { + "name": "finality_mroot", + "type": "checksum256" + }, + { + "name": "cache_expiry", + "type": "time_point" + } + ] + }, + { + "name": "merkle_branch", + "base": "", + "fields": [ + { + "name": "direction", + "type": "uint8" + }, + { + "name": "hash", + "type": "checksum256" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "proof", + "base": "", + "fields": [ + { + "name": "finality_proof", + "type": "finality_proof?" + }, + { + "name": "target_block_proof_of_inclusion", + "type": "proof_of_inclusion" + } + ] + }, + { + "name": "proof_of_inclusion", + "base": "", + "fields": [ + { + "name": "target_node_index", + "type": "uint64" + }, + { + "name": "last_node_index", + "type": "uint64" + }, + { + "name": "target", + "type": "target_data" + }, + { + "name": "merkle_branches", + "type": "merkle_branch[]" + } + ] + }, + { + "name": "quorum_certificate", + "base": "", + "fields": [ + { + "name": "finalizers", + "type": "uint64[]" + }, + { + "name": "signature", + "type": "bls_signature" + } + ] + }, + { + "name": "r_action", + "base": "r_action_base", + "fields": [ + { + "name": "data", + "type": "bytes" + }, + { + "name": "returnvalue", + "type": "bytes" + } + ] + }, + { + "name": "r_action_base", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "authorization", + "type": "permission_level[]" + } + ] + }, + { + "name": "setfpolicy", + "base": "", + "fields": [ + { + "name": "policy", + "type": "fpolicy" + }, + { + "name": "from_block_num", + "type": "uint32" + } + ] + }, + { + "name": "storedpolicy", + "base": "fpolicy", + "fields": [ + { + "name": "last_block_num", + "type": "uint32" + }, + { + "name": "cache_expiry", + "type": "time_point" + } + ] + } + ], + "actions": [ + { + "name": "checkproof", + "type": "checkproof", + "ricardian_contract": "" + }, + { + "name": "clear", + "type": "clear", + "ricardian_contract": "" + }, + { + "name": "setfpolicy", + "type": "setfpolicy", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "lastproofs", + "type": "lastproof", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "policies", + "type": "storedpolicy", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [ + { + "name": "variant_block_data_action_data", + "types": ["block_data","action_data"] + } + ], + "action_results": [] +} \ No newline at end of file diff --git a/unittests/test-contracts/svnn_ibc/svnn_ibc.cpp b/unittests/test-contracts/svnn_ibc/svnn_ibc.cpp new file mode 100644 index 0000000000..a5ec47f869 --- /dev/null +++ b/unittests/test-contracts/svnn_ibc/svnn_ibc.cpp @@ -0,0 +1,209 @@ +#include "svnn_ibc.hpp" + +//add two numbers from the g1 group (aggregation) +std::vector svnn_ibc::_g1add(const std::vector& op1, const std::vector& op2) { + check(op1.size() == std::tuple_size::value, "wrong op1 size passed"); + check(op2.size() == std::tuple_size::value, "wrong op2 size passed"); + bls_g1 r; + bls_g1_add(*reinterpret_cast(op1.data()), *reinterpret_cast(op2.data()), r); + std::vector v(r.begin(), r.end()); + return v; +} + +void svnn_ibc::_maybe_set_finalizer_policy(const fpolicy& policy, const uint32_t from_block_num){ + policies_table _policies_table(get_self(), get_self().value); + auto itr = _policies_table.rbegin(); + //if the new policy is more recent than the most recent we are aware of, we record the new one + if (itr==_policies_table.rend() || itr->generation < policy.generation){ + + //if a previous policy was in force, it is now superseded by the newer one for any future proof verification + if (itr!=_policies_table.rend()){ + auto fwd_itr = itr.base(); + fwd_itr--; + _policies_table.modify( fwd_itr, same_payer, [&]( auto& sfp ) { + sfp.last_block_num = from_block_num; + }); + } + svnn_ibc::storedpolicy spolicy; + spolicy.generation = policy.generation; + spolicy.fthreshold = policy.fthreshold; + spolicy.finalizers = policy.finalizers; + + //policy is in force until a newer policy is proven + spolicy.last_block_num = std::numeric_limits::max(); + //set cache expiry + spolicy.cache_expiry = add_time(current_time_point(), POLICY_CACHE_EXPIRY); + _policies_table.emplace( get_self(), [&]( auto& p ) { + p = spolicy; + }); + } +} + +//adds the newly proven root if necessary +void svnn_ibc::_maybe_add_proven_root(const uint32_t block_num, const checksum256& finality_mroot){ + proofs_table _proofs_table(get_self(), get_self().value); + auto block_num_index = _proofs_table.get_index<"blocknum"_n>(); + auto merkle_index = _proofs_table.get_index<"merkleroot"_n>(); + auto last_itr = block_num_index.rbegin(); + + //if first proven root or newer than the last proven root, we store it + if (last_itr == block_num_index.rend() || last_itr->block_num& pk, const std::vector& sig, std::vector& msg){ + check(pk.size() == std::tuple_size::value, "wrong pk size passed"); + check(sig.size() == std::tuple_size::value, "wrong sig size passed"); + bls_g1 g1_points[2]; + bls_g2 g2_points[2]; + + memcpy(g1_points[0].data(), eosio::detail::G1_ONE_NEG.data(), sizeof(bls_g1)); + memcpy(g2_points[0].data(), sig.data(), sizeof(bls_g2)); + + bls_g2 p_msg; + eosio::detail::g2_fromMessage(msg, eosio::detail::CIPHERSUITE_ID, p_msg); + memcpy(g1_points[1].data(), pk.data(), sizeof(bls_g1)); + memcpy(g2_points[1].data(), p_msg.data(), sizeof(bls_g2)); + + bls_gt r; + bls_pairing(g1_points, g2_points, 2, r); + check(0 == memcmp(r.data(), eosio::detail::GT_ONE.data(), sizeof(bls_gt)), "bls signature verify failed"); +} + +//verify that the quorum certificate over the finality digest is valid +void svnn_ibc::_check_qc(const quorum_certificate& qc, const checksum256& finality_digest, const uint64_t finalizer_policy_generation){ + + policies_table _policies_table(get_self(), get_self().value); + check(_policies_table.begin() != _policies_table.end(), "must set a finalizer policy before checking proofs"); + + //fetch the finalizer policy where generation num is equal prepare.input.finalizer_policy_generation, and that it is still in force + auto itr = _policies_table.find(finalizer_policy_generation); + check(itr!=_policies_table.end(), "finalizer policy not found"); + storedpolicy target_policy = *itr; + auto fa_itr = target_policy.finalizers.begin(); + auto fa_end_itr = target_policy.finalizers.end(); + size_t finalizer_count = std::distance(fa_itr, fa_end_itr); + std::vector bitset_data(qc.finalizers); + bitset b(finalizer_count, bitset_data); + + bool first = true; + + size_t index = 0; + uint64_t weight = 0; + + bls_public_key agg_pub_key; + + while (fa_itr != fa_end_itr){ + if (b.test(index)){ + if (first){ + first=false; + agg_pub_key = fa_itr->public_key; + } + else agg_pub_key = _g1add(agg_pub_key, fa_itr->public_key); + weight+=fa_itr->fweight; + + } + index++; + fa_itr++; + } + + //verify that we have enough vote weight to meet the quorum threshold of the target policy + check(weight>=target_policy.fthreshold, "insufficient signatures to reach quorum"); + std::array data = finality_digest.extract_as_byte_array(); + std::vector v_data(data.begin(), data.end()); + //verify signature validity + _verify(agg_pub_key, qc.signature, v_data); +} + +void svnn_ibc::_check_target_block_proof_of_inclusion(const proof_of_inclusion& proof, const std::optional reference_root){ + + //verify that the proof of inclusion is over a target block + check(std::holds_alternative(proof.target), "must supply proof of inclusion over block data"); + + //resolve the proof to its merkle root + checksum256 finality_mroot = proof.root(); + if (reference_root.has_value()){ + check(reference_root.value() == finality_mroot, "cannot link proof to proven merkle root"); + } + else { + proofs_table _proofs_table(get_self(), get_self().value); + auto merkle_index = _proofs_table.get_index<"merkleroot"_n>(); + auto itr = merkle_index.find(finality_mroot); + check(itr!= merkle_index.end(), "cannot link proof to proven merkle root"); + } + block_data target_block = std::get(proof.target); + if (target_block.finality_data.active_finalizer_policy.has_value()){ + _maybe_set_finalizer_policy(target_block.finality_data.active_finalizer_policy.value(), target_block.dynamic_data.block_num); + } +} + +void svnn_ibc::_check_finality_proof(const finality_proof& finality_proof, const proof_of_inclusion& target_block_proof_of_inclusion){ + //temporarilly disabled : skip qc verification + //if QC is valid, it means that we have reaced finality on the block referenced by the finality_mroot + //_check_qc(finality_proof.qc, finality_proof.qc_block.finality_digest(), finality_proof.qc_block.finalizer_policy_generation); + + //check if the target proof of inclusion correctly resolves to the root of the finality proof + _check_target_block_proof_of_inclusion(target_block_proof_of_inclusion, finality_proof.qc_block.finality_mroot); + + //if proof of inclusion was successful, the target block and its dynamic data have been validated as final and correct + block_data target_block = std::get(target_block_proof_of_inclusion.target); + + //if the finality_mroot we just proven is more recent than the last root we have stored, store it + _maybe_add_proven_root(target_block.dynamic_data.block_num, finality_proof.qc_block.finality_mroot); +} + +ACTION svnn_ibc::setfpolicy(const fpolicy& policy, const uint32_t from_block_num){ + + //can only be called with account authority + require_auth(get_self()); + + policies_table _policies_table(get_self(), get_self().value); + + //can only be used once for the initilization of the contract + check(_policies_table.begin() == _policies_table.end(), "can only set finalizer policy manually for initialization"); + + _maybe_set_finalizer_policy(policy, from_block_num); +} + +ACTION svnn_ibc::checkproof(const proof& proof){ + + //if we have a finality proof, we execute the "heavy" code path + if (proof.finality_proof.has_value()){ + _check_finality_proof(proof.finality_proof.value(), proof.target_block_proof_of_inclusion); + } + else { + //if we only have a proof of inclusion of the target block, we execute the "light" code path + _check_target_block_proof_of_inclusion(proof.target_block_proof_of_inclusion, std::nullopt); + } +} + +//temporary : reset the state +ACTION svnn_ibc::clear(){ + require_auth(get_self()); + proofs_table _proofs_table(get_self(), get_self().value); + policies_table _policies_table(get_self(), get_self().value); + auto fp_itr = _proofs_table.begin(); + while (fp_itr!= _proofs_table.end()){ + fp_itr = _proofs_table.erase(fp_itr); + } + auto p_itr = _policies_table.begin(); + while (p_itr!= _policies_table.end()){ + p_itr = _policies_table.erase(p_itr); + } +} \ No newline at end of file diff --git a/unittests/test-contracts/svnn_ibc/svnn_ibc.hpp b/unittests/test-contracts/svnn_ibc/svnn_ibc.hpp new file mode 100644 index 0000000000..6ac6d8597b --- /dev/null +++ b/unittests/test-contracts/svnn_ibc/svnn_ibc.hpp @@ -0,0 +1,414 @@ +#include +#include +#include +#include +#include + +#include "bitset.hpp" + +#include +#include + +using namespace eosio; + +CONTRACT svnn_ibc : public contract { + public: + using contract::contract; + + using bls_public_key = std::vector; + using bls_signature = std::vector; + + const uint32_t POLICY_CACHE_EXPIRY = 600; //10 minutes for testing + const uint32_t PROOF_CACHE_EXPIRY = 600; //10 minutes for testing + + //Compute the maximum number of layers of a merkle tree for a given number of leaves + uint64_t calculate_max_depth(uint64_t node_count) { + if(node_count <= 1) + return node_count; + return 64 - __builtin_clzll(2 << (64 - 1 - __builtin_clzll ((node_count - 1)))); + } + + static uint32_t reverse_bytes(const uint32_t input){ + uint32_t output = (input>>24 & 0xff)|(input>>8 & 0xff00)|(input<<8 & 0xff0000)|(input<<24 & 0xff000000); + return output; + } + + static checksum256 hash_pair(const std::pair p){ + std::array arr1 = p.first.extract_as_byte_array(); + std::array arr2 = p.second.extract_as_byte_array(); + std::array result; + std::copy (arr1.cbegin(), arr1.cend(), result.begin()); + std::copy (arr2.cbegin(), arr2.cend(), result.begin() + 32); + checksum256 hash = sha256(reinterpret_cast(result.data()), 64); + return hash; + } + + static time_point add_time(const time_point& time, const uint32_t seconds ){ + int64_t total_seconds = (static_cast(time.sec_since_epoch()) + static_cast(seconds)); + microseconds ms = microseconds(total_seconds * 1000000); + time_point tp = time_point(ms); + return tp; + } + + /* + + //discuss : compute merkle branch direction vs providing them as part of the proof + + static std::vector _get_directions(const uint64_t index, const uint64_t last_node_index){ + + std::vector proof; + + uint64_t c_index = index; + uint64_t layers_depth = calculate_max_depth(last_node_index) -1; + uint64_t c_last_node_index = last_node_index; + + for (uint64_t i = 0; i < layers_depth; i++) { + if (c_last_node_index % 2) c_last_node_index+=1; + bool isLeft = c_index % 2 == 0 ? 0 : 1; + uint64_t pairIndex = isLeft ? c_index - 1 : + c_index == last_node_index - 1 && i < layers_depth - 1 ? c_index : + c_index + 1; + c_last_node_index/=2; + if (pairIndex < last_node_index) proof.push_back(isLeft); + c_index = c_index / 2; + } + return proof; + } + + */ + + struct merkle_branch { + uint8_t direction; + checksum256 hash; + }; + + + //compute the merkle root of target node and vector of merkle branches + static checksum256 _compute_root(const std::vector proof_nodes, const checksum256& target){ + checksum256 hash = target; + for (int i = 0 ; i < proof_nodes.size() ; i++){ + const checksum256 node = proof_nodes[i].hash; + std::array arr = node.extract_as_byte_array(); + if (proof_nodes[i].direction == 0){ + hash = hash_pair(std::make_pair(hash, node)); + } + else { + hash = hash_pair(std::make_pair(node, hash)); + } + } + return hash; + } + + struct quorum_certificate { + std::vector finalizers; + bls_signature signature; + }; + + struct finalizer_authority { + std::string description; + uint64_t fweight = 0; + bls_public_key public_key; + }; + + struct fpolicy { + + uint32_t generation = 0; ///< sequentially incrementing version number + uint64_t fthreshold = 0; ///< vote fweight threshold to finalize blocks + std::vector finalizers; ///< Instant Finality voter set + + checksum256 digest() const { + std::vector serialized = pack(*this); + return sha256(serialized.data(), serialized.size()); + } + + }; + + //finalizer policy augmented with contextually-relevant data + TABLE storedpolicy : fpolicy { + + uint32_t last_block_num = 0; //last block number where this policy is in force + + time_point cache_expiry; //cache expiry + + uint64_t primary_key() const {return generation;} + uint64_t by_cache_expiry()const { return cache_expiry.sec_since_epoch(); } + + EOSLIB_SERIALIZE( storedpolicy, (generation)(fthreshold)(finalizers)(last_block_num)(cache_expiry)) + + }; + + TABLE lastproof { + + uint64_t id; + + uint32_t block_num; + + checksum256 finality_mroot; + + time_point cache_expiry; + + uint64_t primary_key()const { return id; } + uint64_t by_block_num()const { return block_num; } + uint64_t by_cache_expiry()const { return cache_expiry.sec_since_epoch(); } + checksum256 by_merkle_root()const { return finality_mroot; } + + }; + + struct authseq { + name account; + uint64_t sequence; + + EOSLIB_SERIALIZE( authseq, (account)(sequence) ) + + }; + + struct r_action_base { + name account; + name name; + std::vector authorization; + + }; + + struct r_action : r_action_base { + std::vector data; + std::vector returnvalue; + + checksum256 digest() const { + checksum256 hashes[2]; + const r_action_base* base = this; + const auto action_input_size = pack_size(data); + const auto return_value_size = pack_size(returnvalue); + const auto rhs_size = action_input_size + return_value_size; + const auto serialized_base = pack(*base); + const auto serialized_data = pack(data); + const auto serialized_output = pack(returnvalue); + hashes[0] = sha256(serialized_base.data(), serialized_base.size()); + std::vector data_digest(action_input_size); + std::vector output_digest(return_value_size); + std::vector h1_result(rhs_size); + std::copy (serialized_data.cbegin(), serialized_data.cend(), h1_result.begin()); + std::copy (serialized_output.cbegin(), serialized_output.cend(), h1_result.begin() + action_input_size); + hashes[1] = sha256(reinterpret_cast(h1_result.data()), rhs_size); + std::array arr1 = hashes[0].extract_as_byte_array(); + std::array arr2 = hashes[1].extract_as_byte_array(); + std::array result; + std::copy (arr1.cbegin(), arr1.cend(), result.begin()); + std::copy (arr2.cbegin(), arr2.cend(), result.begin() + 32); + checksum256 final_hash = sha256(reinterpret_cast(result.data()), 64); + return final_hash; + } + + EOSLIB_SERIALIZE( r_action, (account)(name)(authorization)(data)(returnvalue)) + + }; + + struct action_receipt { + + name receiver; + + //act_digest is provided instead by obtaining the action digest. Implementation depends on the activation of action_return_value feature + //checksum256 act_digest; + + uint64_t global_sequence = 0; + uint64_t recv_sequence = 0; + + std::vector auth_sequence; + unsigned_int code_sequence = 0; + unsigned_int abi_sequence = 0; + + EOSLIB_SERIALIZE( action_receipt, (receiver)(global_sequence)(recv_sequence)(auth_sequence)(code_sequence)(abi_sequence) ) + + }; + + struct proof_of_inclusion; + + struct dynamic_data_v0 { + + //block_num is always present + uint32_t block_num; + + //can include any number of action_proofs and / or state_proofs pertaining to a given block + //all action_proofs must resolve to the same action_mroot + std::vector action_proofs; + + //can be used instead of providing action_proofs. Useful for proving finalizer policy changes + std::optional action_mroot; + + checksum256 get_action_mroot() const { + if (action_mroot.has_value()) return action_mroot.value(); + else { + check(action_proofs.size()>0, "must have at least one action proof"); + checksum256 root = checksum256(); + for (auto ap : action_proofs){ + if (root == checksum256()) root = ap.root(); + else check(ap.root() == root, "all action proofs must resolve to the same merkle root"); + } + return root; + } + }; + }; + + struct block_finality_data { + + //major_version for this block + uint32_t major_version; + + //minor_version for this block + uint32_t minor_version; + + //finalizer_policy_generation for this block + uint32_t finalizer_policy_generation; + + //if the block to prove contains a finalizer policy change, it can be provided + std::optional active_finalizer_policy; + + //if a finalizer policy is present, witness_hash should be the base_digest. Otherwise, witness_hash should be the static_data_digest + checksum256 witness_hash; + + //final_on_qc for this block + checksum256 finality_mroot; + + //returns hash of digest of active_finalizer_policy + witness_hash if active_finalizer_policy is present, otherwise returns witness_hash + checksum256 resolve_witness() const { + if (active_finalizer_policy.has_value()){ + std::vector serialized_policy = pack(active_finalizer_policy.value()); + checksum256 policy_digest = sha256(serialized_policy.data(), serialized_policy.size()); + checksum256 base_fpolicy_digest = hash_pair( std::make_pair( policy_digest, witness_hash) ); + return base_fpolicy_digest; + } + else { + return witness_hash; + } + }; + + //returns hash of major_version + minor_version + finalizer_policy_generation + resolve_witness() + finality_mroot + checksum256 finality_digest() const { + std::array result; + memcpy(&result[0], (uint8_t *)&major_version, 4); + memcpy(&result[4], (uint8_t *)&minor_version, 4); + memcpy(&result[8], (uint8_t *)&finalizer_policy_generation, 4); + std::array arr1 = finality_mroot.extract_as_byte_array(); + std::array arr2 = resolve_witness().extract_as_byte_array(); + std::copy (arr1.cbegin(), arr1.cend(), result.begin() + 12); + std::copy (arr2.cbegin(), arr2.cend(), result.begin() + 44); + checksum256 hash = sha256(reinterpret_cast(result.data()), 76); + return hash; + }; + + }; + + struct block_data { + + //finality data + block_finality_data finality_data; + + //dynamic_data to be verified + dynamic_data_v0 dynamic_data; + + //returns hash of finality_digest() and dynamic_data_digest() + checksum256 digest() const { + checksum256 finality_digest = finality_data.finality_digest(); + checksum256 action_mroot = dynamic_data.get_action_mroot(); + std::array result; + memcpy(&result[0], (uint8_t *)&finality_data.major_version, 4); + memcpy(&result[4], (uint8_t *)&finality_data.minor_version, 4); + memcpy(&result[8], (uint8_t *)&dynamic_data.block_num, 4); + std::array arr1 = finality_digest.extract_as_byte_array(); + std::array arr2 = action_mroot.extract_as_byte_array(); + std::copy (arr1.cbegin(), arr1.cend(), result.begin() + 12); + std::copy (arr2.cbegin(), arr2.cend(), result.begin() + 44); + checksum256 hash = sha256(reinterpret_cast(result.data()), 76); + return hash; + }; + }; + + struct action_data { + + r_action action; //antelope action + checksum256 action_receipt_digest; //required witness hash, actual action_receipt is irrelevant to IBC + + std::vector return_value; //empty if no return value + + //returns the action digest + checksum256 action_digest() const { + return action.digest(); + }; + + //returns the receipt digest, composed of the action_digest() and action_receipt_digest witness hash + checksum256 digest() const { + checksum256 action_receipt_digest = hash_pair( std::make_pair( action_digest(), action_receipt_digest) ); + return action_receipt_digest; + }; + + }; + + using target_data = std::variant; + + + struct proof_of_inclusion { + + uint64_t target_node_index; + uint64_t last_node_index; + + target_data target; + + std::vector merkle_branches; + + //returns the merkle root obtained by hashing target.digest() with merkle_branches + checksum256 root() const { + auto call_digest = [](const auto& var) -> checksum256 { return var.digest(); }; + checksum256 digest = std::visit(call_digest, target); + checksum256 root = _compute_root(merkle_branches, digest); + return root; + }; + + }; + + struct finality_proof { + + //block finality data over which we validate a QC + block_finality_data qc_block; + + //signature over finality_digest() of qc_block. + quorum_certificate qc; + + }; + + struct proof { + + //valid configurations : + //1) finality_proof for a QC block, and proof_of_inclusion of a target block within the final_on_strong_qc block represented by the finality_mroot present in header + //2) only a proof_of_inclusion of a target block, which must be included in a merkle tree represented by a root stored in the contract's RAM + std::optional finality_proof; + proof_of_inclusion target_block_proof_of_inclusion; + + }; + + typedef eosio::multi_index< "policies"_n, storedpolicy, + indexed_by<"expiry"_n, const_mem_fun>> policies_table; + + typedef eosio::multi_index< "lastproofs"_n, lastproof, + indexed_by<"blocknum"_n, const_mem_fun>, + indexed_by<"merkleroot"_n, const_mem_fun>, + indexed_by<"expiry"_n, const_mem_fun>> proofs_table; + + std::vector _g1add(const std::vector& op1, const std::vector& op2); + + void _maybe_set_finalizer_policy(const fpolicy& policy, const uint32_t from_block_num); + void _maybe_add_proven_root(const uint32_t block_num, const checksum256& finality_mroot); + + void _garbage_collection(); + + void _verify(const std::vector& pk, const std::vector& sig, std::vector& msg); + void _check_qc(const quorum_certificate& qc, const checksum256& finality_mroot, const uint64_t finalizer_policy_generation); + + void _check_finality_proof(const finality_proof& finality_proof, const proof_of_inclusion& target_block_proof_of_inclusion); + void _check_target_block_proof_of_inclusion(const proof_of_inclusion& proof, const std::optional reference_root); + + ACTION setfpolicy(const fpolicy& policy, const uint32_t from_block_num); //set finality policy + ACTION checkproof(const proof& proof); + + //clearing function, to be removed for production version + ACTION clear(); + +}; \ No newline at end of file diff --git a/unittests/test-contracts/svnn_ibc/svnn_ibc.wasm b/unittests/test-contracts/svnn_ibc/svnn_ibc.wasm new file mode 100755 index 0000000000..8f5d1276fd Binary files /dev/null and b/unittests/test-contracts/svnn_ibc/svnn_ibc.wasm differ diff --git a/unittests/test_contracts.hpp.in b/unittests/test_contracts.hpp.in index 7523a54f2b..ae110b75ca 100644 --- a/unittests/test_contracts.hpp.in +++ b/unittests/test_contracts.hpp.in @@ -50,6 +50,7 @@ namespace eosio { MAKE_READ_WASM_ABI(bls_primitives_test, bls_primitives_test, test-contracts) MAKE_READ_WASM_ABI(get_block_num_test, get_block_num_test, test-contracts) MAKE_READ_WASM_ABI(nested_container_multi_index, nested_container_multi_index, test-contracts) + MAKE_READ_WASM_ABI(svnn_ibc, svnn_ibc, test-contracts) }; } /// eosio::testing diff --git a/unittests/test_utils.hpp b/unittests/test_utils.hpp index 974fee3927..be389d5af8 100644 --- a/unittests/test_utils.hpp +++ b/unittests/test_utils.hpp @@ -147,7 +147,10 @@ void activate_protocol_features_set_bios_contract(appbase::scoped_app& app, chai builtin_protocol_feature_t::get_sender, builtin_protocol_feature_t::ram_restrictions, builtin_protocol_feature_t::webauthn_key, - builtin_protocol_feature_t::wtmsig_block_signatures }; + builtin_protocol_feature_t::wtmsig_block_signatures, + builtin_protocol_feature_t::bls_primitives, + builtin_protocol_feature_t::action_return_value, + builtin_protocol_feature_t::instant_finality}; for (const auto t : pfs) { auto feature_digest = pfm.get_builtin_digest(t); BOOST_CHECK( feature_digest ); diff --git a/unittests/unapplied_transaction_queue_tests.cpp b/unittests/unapplied_transaction_queue_tests.cpp index 5d91202698..56cdad886f 100644 --- a/unittests/unapplied_transaction_queue_tests.cpp +++ b/unittests/unapplied_transaction_queue_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -63,10 +64,12 @@ auto create_test_block_state( deque trx_metas ) { producer_authority_schedule schedule = { 0, { producer_authority{block->producer, block_signing_authority_v0{ 1, {{pub_key, 1}} } } } }; pbhs.active_schedule = schedule; pbhs.valid_block_signing_authority = block_signing_authority_v0{ 1, {{pub_key, 1}} }; + std::optional action_receipt_digests; auto bsp = std::make_shared( std::move( pbhs ), std::move( block ), std::move( trx_metas ), + std::move( action_receipt_digests ), protocol_feature_set(), []( block_timestamp_type timestamp, const flat_set& cur_features, @@ -78,6 +81,20 @@ auto create_test_block_state( deque trx_metas ) { return bsp; } +using branch_legacy_t = fork_database_legacy_t::branch_t; + +template +void add_forked( unapplied_transaction_queue& queue, const BRANCH_TYPE& forked_branch ) { + // forked_branch is in reverse order + for( auto ritr = forked_branch.rbegin(), rend = forked_branch.rend(); ritr != rend; ++ritr ) { + const auto& bsptr = *ritr; + for( auto itr = bsptr->trxs_metas().begin(), end = bsptr->trxs_metas().end(); itr != end; ++itr ) { + const auto& trx = *itr; + queue.add_forked(trx); + } + } +} + // given a current itr make sure expected number of items are iterated over void verify_order( unapplied_transaction_queue& q, unapplied_transaction_queue::iterator itr, size_t expected ) { size_t size = 0; @@ -136,7 +153,7 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { auto bs1 = create_test_block_state( { trx1, trx2 } ); auto bs2 = create_test_block_state( { trx3, trx4, trx5 } ); auto bs3 = create_test_block_state( { trx6 } ); - q.add_forked( { bs3, bs2, bs1, bs1 } ); // bs1 duplicate ignored + add_forked( q, branch_legacy_t{ bs3, bs2, bs1, bs1 } ); // bs1 duplicate ignored BOOST_CHECK( q.size() == 6u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 5u ); @@ -155,9 +172,9 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { // fifo forked auto bs4 = create_test_block_state( { trx7 } ); - q.add_forked( { bs1 } ); - q.add_forked( { bs3, bs2 } ); - q.add_forked( { bs4 } ); + add_forked( q, branch_legacy_t{ bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2 } ); + add_forked( q, branch_legacy_t{ bs4 } ); BOOST_CHECK( q.size() == 7u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 6u ); @@ -189,10 +206,10 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { // fifo forked, multi forks auto bs5 = create_test_block_state( { trx11, trx12, trx13 } ); auto bs6 = create_test_block_state( { trx11, trx15 } ); - q.add_forked( { bs3, bs2, bs1 } ); - q.add_forked( { bs4 } ); - q.add_forked( { bs3, bs2 } ); // dups ignored - q.add_forked( { bs6, bs5 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs4 } ); + add_forked( q, branch_legacy_t{ bs3, bs2 } ); // dups ignored + add_forked( q, branch_legacy_t{ bs6, bs5 } ); BOOST_CHECK_EQUAL( q.size(), 11u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 10u ); @@ -220,10 +237,10 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { BOOST_CHECK( q.empty() ); // altogether, order fifo: forked, aborted - q.add_forked( { bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); q.add_aborted( { trx9, trx14 } ); q.add_aborted( { trx18, trx19 } ); - q.add_forked( { bs6, bs5, bs4 } ); + add_forked( q, branch_legacy_t{ bs6, bs5, bs4 } ); // verify order verify_order( q, q.begin(), 15 ); // verify type order @@ -289,7 +306,7 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { BOOST_REQUIRE( next( q ) == trx22 ); BOOST_CHECK( q.empty() ); - q.add_forked( { bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); q.add_aborted( { trx9, trx11 } ); q.clear(); BOOST_CHECK( q.empty() ); diff --git a/unittests/whitelist_blacklist_tests.cpp b/unittests/whitelist_blacklist_tests.cpp index ca4ea88dfb..519682246f 100644 --- a/unittests/whitelist_blacklist_tests.cpp +++ b/unittests/whitelist_blacklist_tests.cpp @@ -492,7 +492,7 @@ BOOST_AUTO_TEST_CASE( actor_blacklist_inline_deferred ) { try { } }; - auto c1 = tester1.chain->control->applied_transaction.connect( log_trxs ); + auto c1 = tester1.chain->control->applied_transaction().connect( log_trxs ); // Disallow inline actions authorized by actor in blacklist BOOST_CHECK_EXCEPTION( tester1.chain->push_action( "alice"_n, "inlinecall"_n, "alice"_n, mvo() @@ -537,7 +537,7 @@ BOOST_AUTO_TEST_CASE( actor_blacklist_inline_deferred ) { try { tester1.actor_blacklist = {"bob"_n, "charlie"_n}; tester1.init(false); - auto c2 = tester1.chain->control->applied_transaction.connect( log_trxs ); + auto c2 = tester1.chain->control->applied_transaction().connect( log_trxs ); num_deferred = tester1.chain->control->db().get_index().size(); BOOST_REQUIRE_EQUAL(1u, num_deferred);