diff --git a/.cicd/defaults.json b/.cicd/defaults.json index ff3a3dbc..136b822d 100644 --- a/.cicd/defaults.json +++ b/.cicd/defaults.json @@ -1,10 +1,10 @@ { "leap-dev":{ - "target":"3", + "target":"hotstuff_integration", "prerelease":false }, "cdt":{ - "target":"3", + "target":"hotstuff_integration", "prerelease":false } } diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1b211e8a..9530f87f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -62,7 +62,7 @@ jobs: echo cdt-prerelease=${{inputs.override-cdt-prerelease}} >> $GITHUB_OUTPUT fi - name: Download cdt - uses: AntelopeIO/asset-artifact-download-action@v2 + uses: AntelopeIO/asset-artifact-download-action@v3 with: owner: AntelopeIO repo: cdt @@ -70,9 +70,8 @@ jobs: target: '${{steps.versions.outputs.cdt-target}}' prereleases: ${{fromJSON(steps.versions.outputs.cdt-prerelease)}} artifact-name: cdt_ubuntu_package_amd64 - token: ${{github.token}} - name: Download leap-dev - uses: AntelopeIO/asset-artifact-download-action@v2 + uses: AntelopeIO/asset-artifact-download-action@v3 with: owner: AntelopeIO repo: leap @@ -81,9 +80,9 @@ jobs: prereleases: ${{fromJSON(steps.versions.outputs.leap-dev-prerelease)}} artifact-name: leap-dev-ubuntu20-amd64 container-package: experimental-binaries - token: ${{github.token}} - name: Install packages run: | + sudo apt-get update && sudo apt-get upgrade -y sudo apt install ./*.deb sudo apt-get install cmake rm ./*.deb diff --git a/contracts/eosio.bios/include/eosio.bios/eosio.bios.hpp b/contracts/eosio.bios/include/eosio.bios/eosio.bios.hpp index 643e562c..7ee9e45a 100644 --- a/contracts/eosio.bios/include/eosio.bios/eosio.bios.hpp +++ b/contracts/eosio.bios/include/eosio.bios/eosio.bios.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace eosiobios { @@ -66,6 +67,38 @@ namespace eosiobios { (schedule_version)(new_producers)) }; + /** + * finalizer_authority + * + * The public bls key and proof of possession of private key signature, + * and vote weight of a finalizer. + */ + constexpr size_t max_finalizers = 64*1024; + constexpr size_t max_finalizer_description_size = 256; + + 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)) + }; + + /** + * 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)); + }; + /** * The `eosio.bios` is the first sample of system contract provided by `block.one` through the EOSIO platform. It is a minimalist system contract because it only supplies the actions that are absolutely critical to bootstrap a chain and nothing more. This allows for a chain agnostic approach to bootstrapping a chain. * @@ -190,6 +223,15 @@ namespace eosiobios { [[eosio::action]] void onerror( ignore sender_id, ignore> sent_trx ); + /** + * 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 privilege action allows to set privilege status for an account (turn it on/off). * @param account - the account to set the privileged status for. diff --git a/contracts/eosio.bios/src/eosio.bios.cpp b/contracts/eosio.bios/src/eosio.bios.cpp index 69d6758f..a1a51e71 100644 --- a/contracts/eosio.bios/src/eosio.bios.cpp +++ b/contracts/eosio.bios/src/eosio.bios.cpp @@ -1,4 +1,7 @@ #include +#include + +#include namespace eosiobios { @@ -17,6 +20,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 not started with PUB_BLS"); + check(f.pop.substr(0, sig_prefix.length()) == sig_prefix, "proof of possession signature not started 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 cannot be met by finalizer weights"); + + set_finalizers(std::move(abi_finalizer_policy)); +} + void bios::onerror( ignore, ignore> ) { check( false, "the onerror action cannot be called directly" ); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8c0ab125..86cb70a7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) set(EOSIO_VERSION_MIN "3.1") -set(EOSIO_VERSION_SOFT_MAX "4.1") +set(EOSIO_VERSION_SOFT_MAX "5.0") # set(EOSIO_VERSION_HARD_MAX "") find_package(leap) diff --git a/tests/eosio.bios_inst_fin_tests.cpp b/tests/eosio.bios_inst_fin_tests.cpp new file mode 100644 index 00000000..625e6398 --- /dev/null +++ b/tests/eosio.bios_inst_fin_tests.cpp @@ -0,0 +1,220 @@ +#include +#include +#include + +#include + +#include "contracts.hpp" + +using namespace eosio::testing; +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +using mvo = fc::mutable_variant_object; + +// tests for instant finality actions +class eosio_bios_if_tester : public tester { +public: + + eosio_bios_if_tester() { + create_accounts( { "iftester"_n } ); + produce_block(); + + // instant finality actions are privileged + base_tester::push_action(config::system_account_name, "setpriv"_n, + config::system_account_name, mutable_variant_object() + ("account", "iftester") + ("is_priv", 1) + ); + + set_code( "iftester"_n, contracts::bios_wasm() ); + set_abi( "iftester"_n, contracts::bios_abi().data() ); + produce_block(); + } +}; + +BOOST_AUTO_TEST_SUITE(eosio_bios_if_tests) + +// one finalizer in finalizer policy +BOOST_FIXTURE_TEST_CASE( set_1_finalizer, eosio_bios_if_tester ) try { + // public_key and pop are generated by leap-util + push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo() + ("threshold", 2) + ("finalizers", std::vector{ + mvo() + ("description", "set_1_finalizer") + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")}))); + + signed_block_ptr cur_block = produce_block(); + fc::variant pretty_output; + abi_serializer::to_variant( *cur_block, pretty_output, get_resolver(), fc::microseconds::maximum() ); + + BOOST_REQUIRE(pretty_output.get_object().contains("proposed_finalizer_policy")); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["generation"], 1); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["fthreshold"], 2); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"].size(), 1u); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(0)]["description"], "set_1_finalizer"); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(0)]["fweight"], 1); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(0)]["public_key"], "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ=="); +} FC_LOG_AND_RETHROW() + +// two finalizers in finalizer policy +BOOST_FIXTURE_TEST_CASE( set_2_finalizers, eosio_bios_if_tester ) try { + // public_key and pop are generated by leap-util + push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo() + ("threshold", 4) + ("finalizers", std::vector{ + mvo() + ("description", "set_2_finalizer_1") + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg=="), + mvo() + ("description", "set_2_finalizer_2") + ("weight", 2) + ("public_key", "PUB_BLS_UGcXVpLNrhdODrbI9Geaswu8wFnL+WMnphfTaCgehRxol5wI1qiU5zq6qHp9+CkFnmm2XWCcM/YEtqZYL6uTM1TXTpTm3LODI0s/ULO4iKSNYclsmDdh5cFSMmKKnloHudh3Zw==") + ("pop", "SIG_BLS_bFLCSmXqVrFqWo2mqxCeuLO5iAevMu/8qxpS7HkXSMmXuVLEytt+shDHn8FpcrUMlMHjxEAHOyRVi0ckvXYrCpHMx6floRvljJ1vV2FphGqgm24DxuB1BE3E21Okc0QS3GH6UCISfVBXuqoK+EfmoSGz3ssi0kzevE8MQitzGgK9EW3zTlZtvdRryI1OTUoUXkjTo1Q2VQIbqJZ55W7SNHUcIBZO5Ih4AXc7usjWUBXv1BK0NNcort4EAfOXnkIN47iRLQ==") + }))); + + signed_block_ptr cur_block = produce_block(); + fc::variant pretty_output; + abi_serializer::to_variant( *cur_block, pretty_output, get_resolver(), fc::microseconds::maximum() ); + + BOOST_REQUIRE(pretty_output.get_object().contains("proposed_finalizer_policy")); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["generation"], 1); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["fthreshold"], 4); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"].size(), 2u); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(1)]["description"], "set_2_finalizer_2"); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(1)]["fweight"], 2); + BOOST_REQUIRE_EQUAL(pretty_output["proposed_finalizer_policy"]["finalizers"][size_t(1)]["public_key"], "PUB_BLS_UGcXVpLNrhdODrbI9Geaswu8wFnL+WMnphfTaCgehRxol5wI1qiU5zq6qHp9+CkFnmm2XWCcM/YEtqZYL6uTM1TXTpTm3LODI0s/ULO4iKSNYclsmDdh5cFSMmKKnloHudh3Zw=="); +} FC_LOG_AND_RETHROW() + +// finalizer cannot be empty +BOOST_FIXTURE_TEST_CASE( empty_finalizers, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{}))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// public_key must start with PUB_BLS +BOOST_FIXTURE_TEST_CASE( public_key_not_started_with_PUB_BLS, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{mvo() + ("description", "set_1_finalizer") + ("weight", 1) + // PUB_BLS to UB_BLS + ("public_key", "UB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")}))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// pop signature must start with SIG_BLS +BOOST_FIXTURE_TEST_CASE( signature_not_started_with_SIG_BLS, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{mvo() + ("description", "set_1_finalizer") + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + // SIG_BLS changed to SIG_BL + ("pop", "SIG_BL_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")}))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// test to verify POP verification failure +BOOST_FIXTURE_TEST_CASE( pop_failed, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{mvo() + ("description", "set_1_finalizer") + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + // SIG_BLS_q changed to SIG_BLS_j + ("pop", "SIG_BLS_jxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")}))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// Verifies threshold must be greater than half the sum of the weights +BOOST_FIXTURE_TEST_CASE( threshold_equal_to_half_weights, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo() + ("threshold", 5) + ("finalizers", std::vector{ + mvo() + ("description", "set_2_finalizer_1") + ("weight", 5) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg=="), + mvo() + ("description", "set_2_finalizer_2") + ("weight", 5) + ("public_key", "PUB_BLS_UGcXVpLNrhdODrbI9Geaswu8wFnL+WMnphfTaCgehRxol5wI1qiU5zq6qHp9+CkFnmm2XWCcM/YEtqZYL6uTM1TXTpTm3LODI0s/ULO4iKSNYclsmDdh5cFSMmKKnloHudh3Zw==") + ("pop", "SIG_BLS_bFLCSmXqVrFqWo2mqxCeuLO5iAevMu/8qxpS7HkXSMmXuVLEytt+shDHn8FpcrUMlMHjxEAHOyRVi0ckvXYrCpHMx6floRvljJ1vV2FphGqgm24DxuB1BE3E21Okc0QS3GH6UCISfVBXuqoK+EfmoSGz3ssi0kzevE8MQitzGgK9EW3zTlZtvdRryI1OTUoUXkjTo1Q2VQIbqJZ55W7SNHUcIBZO5Ih4AXc7usjWUBXv1BK0NNcort4EAfOXnkIN47iRLQ==") + }))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// Verifies threshold greater by one than half of the sum of the weights works +BOOST_FIXTURE_TEST_CASE( threshold_greater_than_by_one_half_weights, eosio_bios_if_tester ) try { + BOOST_REQUIRE_NO_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo() + ("threshold", 6) + ("finalizers", std::vector{ + mvo() + ("description", "set_2_finalizer_1") + ("weight", 5) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg=="), + mvo() + ("description", "set_2_finalizer_2") + ("weight", 5) + ("public_key", "PUB_BLS_UGcXVpLNrhdODrbI9Geaswu8wFnL+WMnphfTaCgehRxol5wI1qiU5zq6qHp9+CkFnmm2XWCcM/YEtqZYL6uTM1TXTpTm3LODI0s/ULO4iKSNYclsmDdh5cFSMmKKnloHudh3Zw==") + ("pop", "SIG_BLS_bFLCSmXqVrFqWo2mqxCeuLO5iAevMu/8qxpS7HkXSMmXuVLEytt+shDHn8FpcrUMlMHjxEAHOyRVi0ckvXYrCpHMx6floRvljJ1vV2FphGqgm24DxuB1BE3E21Okc0QS3GH6UCISfVBXuqoK+EfmoSGz3ssi0kzevE8MQitzGgK9EW3zTlZtvdRryI1OTUoUXkjTo1Q2VQIbqJZ55W7SNHUcIBZO5Ih4AXc7usjWUBXv1BK0NNcort4EAfOXnkIN47iRLQ==") + })))); +} FC_LOG_AND_RETHROW() + +// Verifies no duplicate keys are allowed +BOOST_FIXTURE_TEST_CASE( duplicate_pub_keys, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo() + ("threshold", 10) + ("finalizers", std::vector{ + mvo() + ("description", "set_2_finalizer_1") + ("weight", 5) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg=="), + mvo() + ("description", "set_2_finalizer_2") + ("weight", 5) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg=="), + }))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// Verifies description cannot exceed maximum allowed size (256 chars) +BOOST_FIXTURE_TEST_CASE( long_description, eosio_bios_if_tester ) try { + BOOST_REQUIRE_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{mvo() + ("description", std::string(257, 'a')) + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")}))), eosio_assert_message_exception); +} FC_LOG_AND_RETHROW() + +// Verifies description equal to maximum allowed size (256 chars) +BOOST_FIXTURE_TEST_CASE( description_size_equal_to_max, eosio_bios_if_tester ) try { + BOOST_REQUIRE_NO_THROW(push_action("iftester"_n, "setfinalizer"_n, "iftester"_n, mvo() + ("finalizer_policy", mvo()("threshold", 2) + ("finalizers", std::vector{mvo() + ("description", std::string(256, 'a')) + ("weight", 1) + ("public_key", "PUB_BLS_jpMTmybJZvf4k6fR7Bgu7wrNjwQLK/IBdjhZHWTjoohgdUMi8VpTGsyYpzP1+ekMzUuZ8LqFcnfO0myTwH9Y2YeabhhowSp7nzJJhgO4XWCpcGCLssOjVWh3/D9wMIISVUwfsQ==") + ("pop", "SIG_BLS_qxozxdQngA4iDidRNJXwKML7VhawRi6XGMXeBc55MzDaAyixR5D3Ys7d72IiwroUkWDqEVQrPq+u/ukICWD9g+LeE9JNxn8IBMLpotXu728ezyal6g5tMoDf8PQuZSEP6yPSMGo7ajbHVe+ehcgWs+/zpxWH1WgCTgU3Bc5Qy32z6L0ztK3WLuW25OmK3EQLbIP5sPMv07gMWP4aDNLAor6IzQYMvxFaibiWsSqMt4YxB6eONetmdftCn5Om3NcHwW7Ueg==")})))); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END()