Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for sub_chain_plugin #3

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("00000002f270fc046e055a4e09be02ed581c1b34429cfc39cc1c07f474acdb6f", 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("00000002f270fc046e055a4e09be02ed581c1b34429cfc39cc1c07f474acdb6f", 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
Loading