Skip to content

Commit

Permalink
Add unit tests for sub_chain_plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
valthon committed Dec 9, 2024
1 parent 07ce02d commit b94f356
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 1 deletion.
1 change: 1 addition & 0 deletions libraries/testing/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions plugins/sub_chain_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
2 changes: 1 addition & 1 deletion plugins/sub_chain_plugin/sub_chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>()->default_value("settle.wns"), "Contract name for identifying relevant S-transactions.")
("s-chain-actions", bpo::value<std::vector<std::string>>()->composing(), "List of action names for relevant S-transactions for a given s-chain-contract");
("s-chain-actions", bpo::value<std::vector<std::string>>()->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 {
Expand Down
4 changes: 4 additions & 0 deletions plugins/sub_chain_plugin/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
249 changes: 249 additions & 0 deletions plugins/sub_chain_plugin/test/test_sub_chain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#define BOOST_TEST_MODULE sub_chain
#include <boost/test/included/unit_test.hpp>

#include <sysio/sub_chain_plugin/sub_chain_plugin.hpp>

#include <sysio/testing/tester.hpp>

#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<std::string>());
BOOST_CHECK_EQUAL(2, parsed_options.at("s-chain-actions").as<std::vector<std::string>>().size());
BOOST_CHECK_EQUAL("fizz", parsed_options.at("s-chain-actions").as<std::vector<std::string>>().at(0));
BOOST_CHECK_EQUAL("bop", parsed_options.at("s-chain-actions").as<std::vector<std::string>>().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<variables_map>();
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<std::vector<std::string>>().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
1 change: 1 addition & 0 deletions unittests/test-contracts/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
6 changes: 6 additions & 0 deletions unittests/test-contracts/dancer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
59 changes: 59 additions & 0 deletions unittests/test-contracts/dancer/dancer.abi
Original file line number Diff line number Diff line change
@@ -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": []
}
32 changes: 32 additions & 0 deletions unittests/test-contracts/dancer/dancer.cpp
Original file line number Diff line number Diff line change
@@ -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;
});
}
Loading

0 comments on commit b94f356

Please sign in to comment.