Skip to content

Commit

Permalink
Merge pull request #573 from eosnetworkfoundation/block_replay_test
Browse files Browse the repository at this point in the history
[3.2] Add block replay test
  • Loading branch information
ClaytonCalabrese authored Jul 1, 2022
2 parents 0d47fe7 + 905f689 commit a055cd2
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 11 deletions.
3 changes: 1 addition & 2 deletions libraries/testing/include/eosio/testing/tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,7 @@ namespace eosio { namespace testing {
void execute_setup_policy(const setup_policy policy);

void close();
template <typename Lambda>
void open( protocol_feature_set&& pfs, std::optional<chain_id_type> expected_chain_id, Lambda lambda );
void open( protocol_feature_set&& pfs, std::optional<chain_id_type> expected_chain_id, const std::function<void()>& lambda );
void open( protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot );
void open( protocol_feature_set&& pfs, const genesis_state& genesis );
void open( protocol_feature_set&& pfs, std::optional<chain_id_type> expected_chain_id = {} );
Expand Down
5 changes: 2 additions & 3 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,7 @@ namespace eosio { namespace testing {
open( make_protocol_feature_set(), expected_chain_id );
}

template <typename Lambda>
void base_tester::open( protocol_feature_set&& pfs, std::optional<chain_id_type> expected_chain_id, Lambda lambda ) {
void base_tester::open( protocol_feature_set&& pfs, std::optional<chain_id_type> expected_chain_id, const std::function<void()>& lambda ) {
if( !expected_chain_id ) {
expected_chain_id = controller::extract_chain_id_from_db( cfg.state_dir );
if( !expected_chain_id ) {
Expand All @@ -283,7 +282,7 @@ namespace eosio { namespace testing {

control.reset( new controller(cfg, std::move(pfs), *expected_chain_id) );
control->add_indices();
lambda();
if (lambda) lambda();
chain_transactions.clear();
control->accepted_block.connect([this]( const block_state_ptr& block_state ){
FC_ASSERT( block_state->block );
Expand Down
169 changes: 163 additions & 6 deletions unittests/restart_chain_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,66 @@ void remove_existing_blocks(controller::config& config) {

void block_log_set_buff_len(uint64_t len);

void remove_existing_states(controller::config& config) {
auto state_path = config.state_dir;
remove_all(state_path);
fc::create_directories(state_path);
}

struct dummy_action {
static eosio::chain::name get_name() { return "dummyaction"_n; }
static eosio::chain::name get_account() { return "testapi"_n; }

char a; // 1
uint64_t b; // 8
int32_t c; // 4
};

struct cf_action {
static eosio::chain::name get_name() { return "cfaction"_n; }
static eosio::chain::name get_account() { return "testapi"_n; }

uint32_t payload = 100;
uint32_t cfd_idx = 0; // context free data index
};

FC_REFLECT(dummy_action, (a)(b)(c))
FC_REFLECT(cf_action, (payload)(cfd_idx))

#define DUMMY_ACTION_DEFAULT_A 0x45
#define DUMMY_ACTION_DEFAULT_B 0xab11cd1244556677
#define DUMMY_ACTION_DEFAULT_C 0x7451ae12

class replay_tester : public base_tester {
public:
template <typename OnAppliedTrx>
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->startup( [](){}, []() { return false; }, genesis );
});
}
using base_tester::produce_block;

signed_block_ptr produce_block(fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms)) override {
return _produce_block(skip_time, false);
}

signed_block_ptr
produce_empty_block(fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms)) override {
unapplied_transactions.add_aborted(control->abort_block());
return _produce_block(skip_time, true);
}

signed_block_ptr finish_block() override { return _finish_block(); }

bool validate() { return true; }
};

BOOST_AUTO_TEST_SUITE(restart_chain_tests)

BOOST_AUTO_TEST_CASE(test_existing_state_without_block_log)
{
BOOST_AUTO_TEST_CASE(test_existing_state_without_block_log) {
tester chain;

std::vector<signed_block_ptr> blocks;
Expand Down Expand Up @@ -57,8 +113,7 @@ BOOST_AUTO_TEST_CASE(test_existing_state_without_block_log)
}
}

BOOST_AUTO_TEST_CASE(test_restart_with_different_chain_id)
{
BOOST_AUTO_TEST_CASE(test_restart_with_different_chain_id) {
tester chain;

std::vector<signed_block_ptr> blocks;
Expand All @@ -75,9 +130,111 @@ BOOST_AUTO_TEST_CASE(test_restart_with_different_chain_id)
other.close();
genesis_state genesis;
genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:01.000");
genesis.initial_key = eosio::testing::base_tester::get_public_key( config::system_account_name, "active" );
genesis.initial_key = eosio::testing::base_tester::get_public_key(config::system_account_name, "active");
std::optional<chain_id_type> chain_id = genesis.compute_chain_id();
BOOST_REQUIRE_EXCEPTION(other.open(chain_id), chain_id_type_exception, fc_exception_message_starts_with("chain ID in state "));
BOOST_REQUIRE_EXCEPTION(other.open(chain_id), chain_id_type_exception,
fc_exception_message_starts_with("chain ID in state "));
}

BOOST_AUTO_TEST_CASE(test_restart_from_block_log) {
tester chain;

chain.create_account("replay1"_n);
chain.produce_blocks(1);
chain.create_account("replay2"_n);
chain.produce_blocks(1);
chain.create_account("replay3"_n);
chain.produce_blocks(1);

BOOST_REQUIRE_NO_THROW(chain.control->get_account("replay1"_n));
BOOST_REQUIRE_NO_THROW(chain.control->get_account("replay2"_n));
BOOST_REQUIRE_NO_THROW(chain.control->get_account("replay3"_n));

chain.close();

controller::config copied_config = chain.get_config();
auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir);
BOOST_REQUIRE(genesis);

// remove the state files to make sure we are starting from block log
remove_existing_states(copied_config);

tester from_block_log_chain(copied_config, *genesis);

BOOST_REQUIRE_NO_THROW(from_block_log_chain.control->get_account("replay1"_n));
BOOST_REQUIRE_NO_THROW(from_block_log_chain.control->get_account("replay2"_n));
BOOST_REQUIRE_NO_THROW(from_block_log_chain.control->get_account("replay3"_n));
}

BOOST_AUTO_TEST_CASE(test_light_validation_restart_from_block_log) {
tester chain(setup_policy::full);

chain.create_account("testapi"_n);
chain.create_account("dummy"_n);
chain.produce_block();
chain.set_code("testapi"_n, contracts::test_api_wasm());
chain.produce_block();

cf_action cfa;
signed_transaction trx;
action act({}, cfa);
trx.context_free_actions.push_back(act);
trx.context_free_data.emplace_back(fc::raw::pack<uint32_t>(100)); // verify payload matches context free data
trx.context_free_data.emplace_back(fc::raw::pack<uint32_t>(200));
// add a normal action along with cfa
dummy_action da = {DUMMY_ACTION_DEFAULT_A, DUMMY_ACTION_DEFAULT_B, DUMMY_ACTION_DEFAULT_C};
action act1(vector<permission_level>{{"testapi"_n, config::active_name}}, da);
trx.actions.push_back(act1);
chain.set_transaction_headers(trx);
// run normal passing case
auto sigs = trx.sign(chain.get_private_key("testapi"_n, "active"), chain.control->get_chain_id());
auto trace = chain.push_transaction(trx);
chain.produce_block();

BOOST_REQUIRE(trace->receipt);
BOOST_CHECK_EQUAL(trace->receipt->status, transaction_receipt::executed);
BOOST_CHECK_EQUAL(2, trace->action_traces.size());

BOOST_CHECK(trace->action_traces.at(0).context_free); // cfa
BOOST_CHECK_EQUAL("test\n", trace->action_traces.at(0).console); // cfa executed

BOOST_CHECK(!trace->action_traces.at(1).context_free); // non-cfa
BOOST_CHECK_EQUAL("", trace->action_traces.at(1).console);

chain.close();

controller::config copied_config = chain.get_config();
auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir);
BOOST_REQUIRE(genesis);

// remove the state files to make sure we are starting from block log
remove_existing_states(copied_config);
transaction_trace_ptr other_trace;

replay_tester from_block_log_chain(copied_config, *genesis,
[&](std::tuple<const transaction_trace_ptr&, const packed_transaction_ptr&> x) {
auto& t = std::get<0>(x);
if (t && t->id == trace->id) {
other_trace = t;
}
});


BOOST_REQUIRE(other_trace);
BOOST_REQUIRE(other_trace->receipt);
BOOST_CHECK_EQUAL(other_trace->receipt->status, transaction_receipt::executed);
BOOST_CHECK(*trace->receipt == *other_trace->receipt);
BOOST_CHECK_EQUAL(2, other_trace->action_traces.size());

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());

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());
}

namespace{
Expand Down

0 comments on commit a055cd2

Please sign in to comment.