From b94f35691b28612602d1a50bae864d102dfaaae6 Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Sat, 7 Dec 2024 05:49:31 -0500 Subject: [PATCH] Add unit tests for sub_chain_plugin --- libraries/testing/contracts.hpp.in | 1 + plugins/sub_chain_plugin/CMakeLists.txt | 2 + plugins/sub_chain_plugin/sub_chain_plugin.cpp | 2 +- plugins/sub_chain_plugin/test/CMakeLists.txt | 4 + .../sub_chain_plugin/test/test_sub_chain.cpp | 249 ++++++++++++++++++ unittests/test-contracts/CMakeLists.txt | 1 + .../test-contracts/dancer/CMakeLists.txt | 6 + unittests/test-contracts/dancer/dancer.abi | 59 +++++ unittests/test-contracts/dancer/dancer.cpp | 32 +++ unittests/test-contracts/dancer/dancer.hpp | 24 ++ unittests/test-contracts/dancer/dancer.wasm | Bin 0 -> 4359 bytes 11 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 plugins/sub_chain_plugin/test/CMakeLists.txt create mode 100644 plugins/sub_chain_plugin/test/test_sub_chain.cpp create mode 100644 unittests/test-contracts/dancer/CMakeLists.txt create mode 100644 unittests/test-contracts/dancer/dancer.abi create mode 100644 unittests/test-contracts/dancer/dancer.cpp create mode 100644 unittests/test-contracts/dancer/dancer.hpp create mode 100755 unittests/test-contracts/dancer/dancer.wasm diff --git a/libraries/testing/contracts.hpp.in b/libraries/testing/contracts.hpp.in index f94abcaf71..d3b2a65e79 100644 --- a/libraries/testing/contracts.hpp.in +++ b/libraries/testing/contracts.hpp.in @@ -73,6 +73,7 @@ namespace sysio { MAKE_READ_WASM_ABI(params_test, params_test, test-contracts) MAKE_READ_WASM_ABI(crypto_primitives_test,crypto_primitives_test,test-contracts) MAKE_READ_WASM_ABI(get_block_num_test, get_block_num_test, test-contracts) + MAKE_READ_WASM_ABI(dancer, dancer, test-contracts) }; } /// sysio::testing } /// sysio diff --git a/plugins/sub_chain_plugin/CMakeLists.txt b/plugins/sub_chain_plugin/CMakeLists.txt index c0f8ce21aa..a6a6760720 100644 --- a/plugins/sub_chain_plugin/CMakeLists.txt +++ b/plugins/sub_chain_plugin/CMakeLists.txt @@ -5,3 +5,5 @@ add_library( sub_chain_plugin target_link_libraries( sub_chain_plugin appbase fc chain_plugin sysio_chain http_plugin ) target_include_directories( sub_chain_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +add_subdirectory( test ) \ No newline at end of file diff --git a/plugins/sub_chain_plugin/sub_chain_plugin.cpp b/plugins/sub_chain_plugin/sub_chain_plugin.cpp index 5025620434..63c485f676 100644 --- a/plugins/sub_chain_plugin/sub_chain_plugin.cpp +++ b/plugins/sub_chain_plugin/sub_chain_plugin.cpp @@ -18,7 +18,7 @@ sub_chain_plugin::~sub_chain_plugin() {} void sub_chain_plugin::set_program_options(options_description&, options_description& cfg) { cfg.add_options() ("s-chain-contract", bpo::value()->default_value("settle.wns"), "Contract name for identifying relevant S-transactions.") - ("s-chain-actions", bpo::value>()->composing(), "List of action names for relevant S-transactions for a given s-chain-contract"); + ("s-chain-actions", bpo::value>()->multitoken()->composing(), "List of action names for relevant S-transactions for a given s-chain-contract"); } void sub_chain_plugin::plugin_initialize(const variables_map& options) { try { diff --git a/plugins/sub_chain_plugin/test/CMakeLists.txt b/plugins/sub_chain_plugin/test/CMakeLists.txt new file mode 100644 index 0000000000..ebd0a6843e --- /dev/null +++ b/plugins/sub_chain_plugin/test/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_sub_chain test_sub_chain.cpp ) +target_link_libraries( test_sub_chain sub_chain_plugin sysio_testing ) + +add_test(NAME test_sub_chain COMMAND plugins/sub_chain_plugin/test/test_sub_chain WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/plugins/sub_chain_plugin/test/test_sub_chain.cpp b/plugins/sub_chain_plugin/test/test_sub_chain.cpp new file mode 100644 index 0000000000..92f35929d1 --- /dev/null +++ b/plugins/sub_chain_plugin/test/test_sub_chain.cpp @@ -0,0 +1,249 @@ +#define BOOST_TEST_MODULE sub_chain +#include + +#include + +#include + +#include "contracts.hpp" + +namespace { + +using namespace sysio; +using namespace sysio::chain; +using namespace sysio::testing; + +BOOST_AUTO_TEST_SUITE( sub_chain ) + +BOOST_AUTO_TEST_CASE( sub_chain_sanity ) { try { + + BOOST_CHECK_EQUAL( 1, 1 ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( sub_chain_init_without_config ) { try { + + sub_chain_plugin sub_chain; + variables_map options; + + BOOST_CHECK_EXCEPTION( sub_chain.initialize(options), fc::exception, [] (const fc::exception& e) { + return e.to_detail_string().find("s-chain-contract") != std::string::npos; + }); + +} FC_LOG_AND_RETHROW() } + +auto test_options(int argc, const char * argv[]) { + bpo::options_description option_desc; + sub_chain_plugin plugin; + plugin.set_program_options(option_desc, option_desc); + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv).options(option_desc).run(); + bpo::variables_map parsed_options; + bpo::store(parsed, parsed_options); + bpo::notify(parsed_options); + return parsed_options; +} + +BOOST_AUTO_TEST_CASE( sub_chain_init_with_config ) { try { + + // init a new sub chain plugin + sub_chain_plugin sub_chain; + + // setup the command line options + const char *argv[] = {"sub_chain", "--s-chain-contract", "fizzbop", "--s-chain-actions", "fizz", "bop"}; + auto parsed_options = test_options(6, argv); + + BOOST_CHECK_EQUAL("fizzbop", parsed_options.at("s-chain-contract").as()); + BOOST_CHECK_EQUAL(2, parsed_options.at("s-chain-actions").as>().size()); + BOOST_CHECK_EQUAL("fizz", parsed_options.at("s-chain-actions").as>().at(0)); + BOOST_CHECK_EQUAL("bop", parsed_options.at("s-chain-actions").as>().at(1)); + + // Now we should initialize without error + sub_chain.initialize(parsed_options); + + BOOST_CHECK_EQUAL("fizzbop"_n, sub_chain.get_contract_name()); + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE( bad_config ) { try { + + const char *bad_argvs[][5] = { + {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "areallylongactionname"}, + {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "bad,format"}, + {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "bad format"}, + {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "$%^&*mat"}, + }; + + auto bad_options = std::vector(); + for (auto argv : bad_argvs) { + bad_options.push_back(test_options(5, argv)); + } + + for (auto options : bad_options) { + sub_chain_plugin sub_chain; + wlog(options.at("s-chain-actions").as>().at(0).c_str()); + BOOST_CHECK_EXCEPTION( sub_chain.initialize(options), fc::exception, [] (const fc::exception& e) { + return e.to_detail_string().find("Invalid name") != std::string::npos; + }); + } + + sub_chain_plugin plugin; + const char *good_and_bad[] = {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "good", "b,a,d"}; + BOOST_CHECK_EXCEPTION( plugin.initialize(test_options(6, good_and_bad)), fc::exception, [] (const fc::exception& e) { + return e.to_detail_string().find("Invalid name") != std::string::npos; + }); + + sub_chain_plugin plugin2; + const char *many_args[] = {"sub_chain", "--s-chain-contract", "something", "--s-chain-actions", "good", "better", "--s-chain-actions", "brok en", "--s-chain-actions", "best"}; + BOOST_CHECK_EXCEPTION( plugin2.initialize(test_options(10, many_args)), fc::exception, [] (const fc::exception& e) { + return e.to_detail_string().find("Invalid name") != std::string::npos; + }); + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE( nothing_to_report ) { try { + + tester chain; + chain.create_account("abbie"_n); + chain.produce_block(); + + sub_chain_plugin sub_chain; + const char *argv[] = {"sub_chain", "--s-chain-contract", "abbie", "--s-chain-actions", "dance"}; + sub_chain.initialize(test_options(5, argv)); + + chain.produce_block(); + chain.produce_block(); + + // Verify no transactions found when there are none to find + BOOST_CHECK_EQUAL(0, sub_chain.find_relevant_transactions(*chain.control.get()).size()); + + // Verify default prev_s_id is empty / zero + BOOST_CHECK_EQUAL(checksum256_type(), sub_chain.get_prev_s_id()); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( find_relevant_txns ) { try { + + tester chain; + chain.create_account("abbie"_n); + chain.set_code("abbie"_n, contracts::dancer_wasm()); + chain.set_abi("abbie"_n, contracts::dancer_abi().data()); + chain.create_account("darcy"_n); + chain.set_code("darcy"_n, contracts::dancer_wasm()); + chain.set_abi("darcy"_n, contracts::dancer_abi().data()); + chain.produce_block(); + + // setup sub chain to watch abbie dance + sub_chain_plugin sub_chain; + const char *argv[] = {"sub_chain", "--s-chain-contract", "abbie", "--s-chain-actions", "dance"}; + sub_chain.initialize(test_options(5, argv)); + + // Verify no transactions found when there are none to find + BOOST_CHECK_EQUAL(0, sub_chain.find_relevant_transactions(*chain.control.get()).size()); + + // Create a transaction that should be found + chain.push_action("abbie"_n, "dance"_n, "abbie"_n, mutable_variant_object()); + // Create a transaction that should be ignored + chain.push_action("darcy"_n, "dance"_n, "darcy"_n, mutable_variant_object()); + + // sub chain should find the transaction in the current queue + BOOST_CHECK_EQUAL(1, sub_chain.find_relevant_transactions(*chain.control.get()).size()); + BOOST_CHECK_EQUAL(checksum256_type(), sub_chain.get_prev_s_id()); + + chain.produce_block(); + // once the block is produced, sub chain no longer sees the transaction + BOOST_CHECK_EQUAL(0, sub_chain.find_relevant_transactions(*chain.control.get()).size()); + +} FC_LOG_AND_RETHROW() } + +void producer_api_flow(tester* chain, sub_chain_plugin* sub_chain) { + + // Step 1: Get the relevant transactions + const auto& relevant_s_transactions = sub_chain->find_relevant_transactions(*chain->control.get()); + if (relevant_s_transactions.empty()) { + return; + } + // Step 2: Calculate the S-Root + checksum256_type s_root = sub_chain->calculate_s_root(relevant_s_transactions); + // Step 4: S-Root is hashed with the previous S-ID using SHA-256 + // Step 5: Takes the 32 least-significant bits from the previous S-ID to get the previous S-Block number, and increment to get the new S-Block number + // Step 6: Hashes the S-Root with the previous S-ID with SHA-256, then replace the 32 least significant bits with the new S-Block number to produce the new S-ID + checksum256_type curr_s_id = sub_chain->compute_curr_s_id(s_root); + // Prepare the s_header for the current block to be added to the header extension + s_header s_header { + sub_chain->get_contract_name(), + sub_chain->get_prev_s_id(), + curr_s_id, + s_root + }; + // Set the s_root in the chain controller for the building block state + auto& controller = *chain->control.get(); + controller.set_s_header( s_header ); + sub_chain->update_prev_s_id(curr_s_id); +} + +BOOST_AUTO_TEST_CASE( track_s_id ) { try { + + tester chain; + chain.create_account("candice"_n); + chain.set_code("candice"_n, contracts::dancer_wasm()); + chain.set_abi("candice"_n, contracts::dancer_abi().data()); + chain.create_account("fiona"_n); + chain.set_code("fiona"_n, contracts::dancer_wasm()); + chain.set_abi("fiona"_n, contracts::dancer_abi().data()); + + chain.produce_block(); + + sub_chain_plugin sub_chain; + const char *argv[] = {"sub_chain", "--s-chain-contract", "fiona", "--s-chain-actions", "dance"}; + sub_chain.initialize(test_options(5, argv)); + + producer_api_flow(&chain, &sub_chain); + + // Current s_id should still be zero + BOOST_CHECK_EQUAL(checksum256_type(), sub_chain.get_prev_s_id()); + chain.produce_block(); + BOOST_CHECK_EQUAL(checksum256_type(), sub_chain.get_prev_s_id()); + + // Push ignored transaction + chain.push_action("candice"_n, "dance"_n, "candice"_n, mutable_variant_object()); + producer_api_flow(&chain, &sub_chain); + chain.produce_block(); + BOOST_CHECK_EQUAL(checksum256_type(), sub_chain.get_prev_s_id()); + + // now push a tracked transaction and sroot should update + chain.push_action("fiona"_n, "dance"_n, "fiona"_n, mutable_variant_object()); + producer_api_flow(&chain, &sub_chain); + chain.produce_block(); + auto last_s_id = sub_chain.get_prev_s_id(); + BOOST_CHECK_NE(checksum256_type(), sub_chain.get_prev_s_id()); + + // no transactions, sroot should stay the same + producer_api_flow(&chain, &sub_chain); + chain.produce_block(); + BOOST_CHECK_EQUAL(last_s_id, sub_chain.get_prev_s_id()); + + // push a few more transactions and sroot should update + chain.push_action("candice"_n, "dance"_n, "candice"_n, mutable_variant_object()); + chain.push_action("fiona"_n, "dance"_n, "fiona"_n, mutable_variant_object()); + + producer_api_flow(&chain, &sub_chain); + chain.produce_block(); + BOOST_CHECK_NE(last_s_id, sub_chain.get_prev_s_id()); + BOOST_CHECK_EQUAL("0000000256a91f0ea6732096a9ba5c358f5f1dd0b43532ef5be164a464c299d9", sub_chain.get_prev_s_id().str()); + + // push a non-tracked action on the tracked contract + chain.push_action("fiona"_n, "stop"_n, "fiona"_n, mutable_variant_object()); + producer_api_flow(&chain, &sub_chain); + chain.produce_block(); + + // sroot should not change + BOOST_CHECK_EQUAL("0000000256a91f0ea6732096a9ba5c358f5f1dd0b43532ef5be164a464c299d9", sub_chain.get_prev_s_id().str()); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace diff --git a/unittests/test-contracts/CMakeLists.txt b/unittests/test-contracts/CMakeLists.txt index cc1a270000..8f478a93f3 100644 --- a/unittests/test-contracts/CMakeLists.txt +++ b/unittests/test-contracts/CMakeLists.txt @@ -28,3 +28,4 @@ add_subdirectory( wasm_config_bios ) add_subdirectory( params_test ) add_subdirectory( crypto_primitives_test ) add_subdirectory( get_block_num_test ) +add_subdirectory( dancer ) diff --git a/unittests/test-contracts/dancer/CMakeLists.txt b/unittests/test-contracts/dancer/CMakeLists.txt new file mode 100644 index 0000000000..a984222cb6 --- /dev/null +++ b/unittests/test-contracts/dancer/CMakeLists.txt @@ -0,0 +1,6 @@ +if( SYSIO_COMPILE_TEST_CONTRACTS ) + add_contract( dancer dancer dancer.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/dancer.wasm ${CMAKE_CURRENT_BINARY_DIR}/dancer.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/dancer.abi ${CMAKE_CURRENT_BINARY_DIR}/dancer.abi COPYONLY ) +endif() diff --git a/unittests/test-contracts/dancer/dancer.abi b/unittests/test-contracts/dancer/dancer.abi new file mode 100644 index 0000000000..234855b3b1 --- /dev/null +++ b/unittests/test-contracts/dancer/dancer.abi @@ -0,0 +1,59 @@ +{ + "____comment": "This file was generated with sysio-abigen. DO NOT EDIT ", + "version": "sysio::abi/1.2", + "types": [], + "structs": [ + { + "name": "dance", + "base": "", + "fields": [] + }, + { + "name": "step", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "count", + "type": "uint64" + }, + { + "name": "is_dancing", + "type": "bool" + } + ] + }, + { + "name": "stop", + "base": "", + "fields": [] + } + ], + "actions": [ + { + "name": "dance", + "type": "dance", + "ricardian_contract": "" + }, + { + "name": "stop", + "type": "stop", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "steps", + "type": "step", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/unittests/test-contracts/dancer/dancer.cpp b/unittests/test-contracts/dancer/dancer.cpp new file mode 100644 index 0000000000..dad55c8ede --- /dev/null +++ b/unittests/test-contracts/dancer/dancer.cpp @@ -0,0 +1,32 @@ +#include "dancer.hpp" + +using namespace sysio; + +void dancer::dance() { + print("Dancer is dancing"); + steps stepsTable(_self, _self.value); + auto lastStepMaybe = stepsTable.find(_self.value); + if(lastStepMaybe == stepsTable.end()) { + stepsTable.emplace(_self, [&]( auto& obj ) { + obj.id = _self.value; + obj.count = 1; + obj.is_dancing = true; + }); + } else { + const auto& lastStep = *lastStepMaybe; + stepsTable.modify(lastStep, _self, [&]( auto& obj ) { + obj.count = lastStep.count + 1; + obj.is_dancing = true; + }); + } +} + +void dancer::stop() { + print("Dancer is stopping"); + steps stepsTable(_self, _self.value); + const auto& lastStep = stepsTable.get(_self.value, "no dance to stop!"); + stepsTable.modify(lastStep, _self, [&]( auto& obj ) { + obj.count = 0; + obj.is_dancing = false; + }); +} diff --git a/unittests/test-contracts/dancer/dancer.hpp b/unittests/test-contracts/dancer/dancer.hpp new file mode 100644 index 0000000000..01dd4a8233 --- /dev/null +++ b/unittests/test-contracts/dancer/dancer.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +class [[sysio::contract]] dancer : public sysio::contract { +public: + using sysio::contract::contract; + + [[sysio::action]] + void dance(); + + [[sysio::action]] + void stop(); + + struct [[sysio::table("steps")]] step { + uint64_t id; + uint64_t count; + bool is_dancing; + uint64_t primary_key() const { return id; } + }; + + typedef sysio::multi_index< "steps"_n, step > steps; + +}; diff --git a/unittests/test-contracts/dancer/dancer.wasm b/unittests/test-contracts/dancer/dancer.wasm new file mode 100755 index 0000000000000000000000000000000000000000..4c42e30799088945dfcb59dd076b80ab0ef34ebe GIT binary patch literal 4359 zcmc&%QHW$m6}?sUUU%=j8K-75*|fW(srNEMM+ht`J+MD^EoOJ4f<{FA>*?u!yVE^0 z-EH^VH7mM32|*Bo2>RvM3jSn8e-s1}+{B;woh*q6LGe#mT=CB@&#Bil)624&;D-a# zuj*FSt$WWs_uluUGd)luqJ~dwi)^czY)7~7r)JxFhKZN*h@WFR2W1v90?B|#CFIucIfY!(hWYW9!>Ke2fgcE=#?evSU&Q%=pW>z zuj^Dx18?LDyMwA)iD!Giv}SD)zjk-j%O#=GCBQ)?1p1VDa$S~Jl&mQA5v6LY#;Uc` zr%e>Au~I5FN>t^HnJIsGSCX0d+rK}4R8Oh6Gam2XlqZ(He|9|*dnWmz5-0YoJ?+G) z7o*n2L;l_V$=iPrr!lA4J`t*f^px>@%h>w>PiW>U2 ztxP|^qMTk6S(YSml2o9?KAFT~BT1I9%gL6x+oBZ@5J}```44j*$X=#4#lH?q5ewfK z&^U}P;)ugzwq@!Lw?Atn(_6-3j7Lqk2!zw~$h~_9k+Ui@UaqVNLGZPKGxlSdX=t3s z>{3AFeu)Uxr5d_SQ=gx2hC1cX5A4U66`bIPh<8}?VmSGiH;AQg*ryN^Dr`cr@#=zD zXD%w(-YK4(xgwd6iZu)sTnG-peAS zfTR_AfE(=ESp*vcfD3ZD2KOo4oWWKKh9B83{*6$cqWAQlc=5Ep|u*-rp2 z1%8w(;I50wo+|SU@o8$%o#;07My3IaBMRgI@`83~hpiwv!%(O36nu7L5ZSVF_H$Ew+UR6zbdVd!xg2m12oV87S)}UIsCgfnTfh( z%X}--^L`K_>LDR<1B?d}@DS_|+{G}hxX5o02sglpLVj9~2yl4R@F)?iW2EM&azvyz zDI=;eQH_b}(==@Za6p@_&{q}#%iI&>UWK_=DRa+~2y`>~hf>kJ7=w+3TvAhdDOuFe zHHV)li}!rBWYUl5T@a>mpuMlzrL&+2_XX2HWDNgzE${|~5B@-1?KzPPvI@gaY?3`M zkJyA1BoRPnwO|%I6Ep!sxcpz=P4AYstd{`H z5w=DCojdnFiM|JnkwisSbyZh+;apZ_wxw14N2FoZ*Z1vHX%y-LV;*V`2*`--XSG3A z(u1T{PJ+AF8^jtYf@Vg)7r)xDpYS6P$$CLa!Mj7!0#Zukl&}Rz4Gt-*-3CSt9L z9;!9a7HSO|!9uNxS!?3sS`(FyeOKXi6Wz}})ctHVi$c~A`haWX57DDwD_T@&S}bYv z_JktEb`{y$S(a*4c}$SqY|87Hgb^R?R!6Twd=D}f?FRPa{QudG^vI5Xe(>5GUa|eJ z*~O1*Z)mU!MUJ5g#q6=Q_d{u^rE$sl#XdmtB0UxiVK@*RaQ)GO8$11+ta`b8L0FVS z^-W$kCVUPgpSs%V@8>;Nj9h0QUy%3;b>q&pd@7&o4DqGs`cv1#s6V_WM}PiJd~Ch1 z%jwBBeAbS~+&3JBBRQ2)n968pkar6x#>aBFtB;S<4nBk1*n%5|qr&xv?%?`<(Qo4` zJAX;?$z(Kf{N9C1ceG#lK|C!WK5(6)$PdOvsgN6~zi`EF?($(zx}71_cCiAVN(-jE zrRePJLvYn~u)YfyyQ5(-!8hw;m49=B`ybZ+h(`ybUjORN$2c>$WxnSF*@6fs(d|^^ E-)is^5&!@I literal 0 HcmV?d00001