Files
2025-07-09 10:18:06 -05:00

1382 lines
54 KiB
C++

#include <boost/test/unit_test.hpp>
#include <fc/io/cfile.hpp>
#include <eosio/trace_api/test_common.hpp>
#include <eosio/trace_api/store_provider.hpp>
using namespace eosio;
using namespace eosio::trace_api;
using namespace eosio::trace_api::test_common;
using open_state = slice_directory::open_state;
namespace {
struct test_fixture {
std::vector<action_trace_v1> actions = {
{
{
1,
"receiver"_n, "contract"_n, "action"_n,
{{ "alice"_n, "active"_n }},
{ 0x01, 0x01, 0x01, 0x01 }
},
{ 0x05, 0x05, 0x05, 0x05 }
},
{
{
0,
"receiver"_n, "contract"_n, "action"_n,
{{ "alice"_n, "active"_n }},
{ 0x00, 0x00, 0x00, 0x00 }
},
{ 0x04, 0x04, 0x04, 0x04}
},
{
{
2,
"receiver"_n, "contract"_n, "action"_n,
{{ "alice"_n, "active"_n }},
{ 0x02, 0x02, 0x02, 0x02 }
},
{ 0x06, 0x06, 0x06, 0x06 }
}
};
transaction_trace_v2 transaction_trace {
"0000000000000000000000000000000000000000000000000000000000000001"_h,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
{ chain::signature_type() },
{ chain::time_point_sec(), 1, 0, 100, 50, 0 }
};
block_trace_v2 block_trace1_v2 {
"b000000000000000000000000000000000000000000000000000000000000001"_h,
1,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"bp.one"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
transaction_trace
}
};
block_trace_v2 block_trace2_v2 {
"b000000000000000000000000000000000000000000000000000000000000001"_h,
5,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"bp.one"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
transaction_trace
}
};
const block_trace_v1 bt_v1 {
{
"0000000000000000000000000000000000000000000000000000000000000001"_h,
1,
"0000000000000000000000000000000000000000000000000000000000000003"_h,
chain::block_timestamp_type(1),
"bp.one"_n,
{}
},
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
{
{
{
"0000000000000000000000000000000000000000000000000000000000000001"_h,
{
{
0,
"eosio.token"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
},
{
1,
"alice"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
},
{
2,
"bob"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
}
}
},
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
std::vector<chain::signature_type>{chain::signature_type()},
chain::transaction_header{chain::time_point_sec(), 1, 0, 100, 50, 0}
}
}
};
const block_trace_v1 bt2_v1 {
{
"0000000000000000000000000000000000000000000000000000000000000002"_h,
5,
"0000000000000000000000000000000000000000000000000000000000000005"_h,
chain::block_timestamp_type(2),
"bp.two"_n
},
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
{
{
{
"f000000000000000000000000000000000000000000000000000000000000004"_h,
{}
},
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
std::vector<chain::signature_type>{chain::signature_type()},
chain::transaction_header{chain::time_point_sec(), 1, 0, 100, 50, 0}
}
}
};
const block_trace_v0 bt_v0 {
"0000000000000000000000000000000000000000000000000000000000000001"_h,
1,
"0000000000000000000000000000000000000000000000000000000000000003"_h,
chain::block_timestamp_type(1),
"bp.one"_n,
{
{
"0000000000000000000000000000000000000000000000000000000000000001"_h,
{
{
0,
"eosio.token"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
},
{
1,
"alice"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
},
{
2,
"bob"_n, "eosio.token"_n, "transfer"_n,
{{ "alice"_n, "active"_n }},
make_transfer_data( "alice"_n, "bob"_n, "0.0001 SYS"_t, "Memo!" )
}
}
}
}
};
const metadata_log_entry be1 { block_entry_v0 {
"b000000000000000000000000000000000000000000000000000000000000001"_h, 5, 0
} };
const metadata_log_entry le1 { lib_entry_v0 { 4 } };
const metadata_log_entry be2 { block_entry_v0 {
"b000000000000000000000000000000000000000000000000000000000000002"_h, 7, 0
} };
const metadata_log_entry le2 { lib_entry_v0 { 5 } };
bool create_non_empty_trace_slice( slice_directory& sd, uint32_t slice_number, fc::cfile& file) {
const uint8_t bad_which = 0x7F;
if (!sd.find_or_create_trace_slice(slice_number, open_state::write, file)) {
file.write(reinterpret_cast<const char*>(&bad_which), sizeof(uint8_t));
file.close();
return sd.find_or_create_trace_slice(slice_number, open_state::read, file);
}
return false;
}
};
struct test_store_provider : public store_provider {
test_store_provider(const std::filesystem::path& slice_dir, uint32_t width, std::optional<uint32_t> minimum_irreversible_history_blocks = std::optional<uint32_t>(), std::optional<uint32_t> minimum_uncompressed_irreversible_history_blocks = std::optional<uint32_t>(), size_t compression_seek_point_stride = 0)
: store_provider(slice_dir, width, minimum_irreversible_history_blocks, minimum_uncompressed_irreversible_history_blocks, compression_seek_point_stride) {
}
using store_provider::scan_metadata_log_from;
using store_provider::read_data_log;
};
class vslice_datastream;
struct vslice {
enum mode { read_mode, write_mode};
vslice(mode m = write_mode) : _mode(m) {}
unsigned long tellp() const {
return _pos;
}
void seek( unsigned long loc ) {
if (_mode == read_mode) {
if (loc > _buffer.size()) {
throw std::ios_base::failure( "read vslice unable to seek to: " + std::to_string(loc) + ", end is at: " + std::to_string(_buffer.size()));
}
}
_pos = loc;
}
void seek_end( long loc ) {
_pos = _buffer.size();
}
void read( char* d, size_t n ) {
if( _pos + n > _buffer.size() ) {
throw std::ios_base::failure( "vslice unable to read " + std::to_string( n ) + " bytes; only can read " + std::to_string( _buffer.size() - _pos ) );
}
std::memcpy( d, _buffer.data() + _pos, n);
_pos += n;
}
void write( const char* d, size_t n ) {
if (_mode == read_mode) {
throw std::ios_base::failure( "read vslice should not have write called" );
}
if (_buffer.size() < _pos + n) {
_buffer.resize(_pos + n);
}
std::memcpy( _buffer.data() + _pos, d, n);
_pos += n;
}
void flush() {
_flush = true;
}
void sync() {
_sync = true;
}
vslice_datastream create_datastream();
std::vector<char> _buffer;
mode _mode = write_mode;
unsigned long _pos = 0lu;
bool _flush = false;
bool _sync = false;
};
class vslice_datastream {
public:
explicit vslice_datastream( vslice& vs ) : _vs(vs) {}
void skip( size_t s ) {
std::vector<char> d( s );
read( &d[0], s );
}
bool read( char* d, size_t s ) {
_vs.read( d, s );
return true;
}
bool get( unsigned char& c ) { return get( *(char*)&c ); }
bool get( char& c ) { return read(&c, 1); }
private:
vslice& _vs;
};
inline vslice_datastream vslice::create_datastream() {
return vslice_datastream(*this);
}
}
BOOST_AUTO_TEST_SUITE(slice_tests)
BOOST_FIXTURE_TEST_CASE(write_data_trace, test_fixture)
{
vslice vs;
const auto offset = append_store(bt_v1, vs );
BOOST_REQUIRE_EQUAL(offset,0u);
const auto offset2 = append_store(bt2_v1, vs );
BOOST_REQUIRE(offset < offset2);
vs._pos = offset;
const auto bt_returned = extract_store<block_trace_v1>( vs );
BOOST_REQUIRE(bt_returned == bt_v1);
vs._pos = offset2;
const auto bt_returned2 = extract_store<block_trace_v1>( vs );
BOOST_REQUIRE(bt_returned2 == bt2_v1);
}
BOOST_FIXTURE_TEST_CASE(write_data_multi_trace_version, test_fixture)
{
vslice vs;
const auto offset = append_store(bt_v0, vs );
BOOST_REQUIRE_EQUAL(offset,0u);
const auto offset2 = append_store(bt_v1, vs );
BOOST_REQUIRE(offset < offset2);
const auto offset3 = append_store(block_trace1_v2, vs );
BOOST_REQUIRE(offset2 < offset3);
vs._pos = offset;
const auto bt_returned = extract_store<block_trace_v0>( vs );
BOOST_REQUIRE(bt_returned == bt_v0);
vs._pos = offset2;
const auto bt_returned2 = extract_store<block_trace_v1>( vs );
BOOST_REQUIRE(bt_returned2 == bt_v1);
vs._pos = offset3;
const auto bt_returned3 = extract_store<block_trace_v2>( vs );
BOOST_REQUIRE(bt_returned3 == block_trace1_v2);
}
BOOST_FIXTURE_TEST_CASE(write_metadata_trace, test_fixture)
{
vslice vs;
const auto offset = append_store( be1, vs );
auto next_offset = vs._pos;
BOOST_REQUIRE(offset < next_offset);
const auto offset2 = append_store( le1, vs );
BOOST_REQUIRE(next_offset <= offset2);
BOOST_REQUIRE(offset2 < vs._pos);
next_offset = vs._pos;
const auto offset3 = append_store( be2, vs );
BOOST_REQUIRE(next_offset <= offset3);
BOOST_REQUIRE(offset3 < vs._pos);
next_offset = vs._pos;
const auto offset4 = append_store( le2, vs );
BOOST_REQUIRE(next_offset <= offset4);
BOOST_REQUIRE(offset4 < vs._pos);
vs._pos = offset;
const auto be_returned1 = extract_store<metadata_log_entry>( vs );
BOOST_REQUIRE(std::holds_alternative<block_entry_v0>(be_returned1));
const auto real_be_returned1 = std::get<block_entry_v0>(be_returned1);
const auto real_be1 = std::get<block_entry_v0>(be1);
BOOST_REQUIRE(real_be_returned1 == real_be1);
vs._pos = offset2;
const auto le_returned1 = extract_store<metadata_log_entry>( vs );
BOOST_REQUIRE(std::holds_alternative<lib_entry_v0>(le_returned1));
const auto real_le_returned1 = std::get<lib_entry_v0>(le_returned1);
const auto real_le1 = std::get<lib_entry_v0>(le1);
BOOST_REQUIRE(real_le_returned1 == real_le1);
vs._pos = offset3;
const auto be_returned2 = extract_store<metadata_log_entry>( vs );
BOOST_REQUIRE(std::holds_alternative<block_entry_v0>(be_returned2));
const auto real_be_returned2 = std::get<block_entry_v0>(be_returned2);
const auto real_be2 = std::get<block_entry_v0>(be2);
BOOST_REQUIRE(real_be_returned2 == real_be2);
vs._pos = offset4;
const auto le_returned2 = extract_store<metadata_log_entry>( vs );
BOOST_REQUIRE(std::holds_alternative<lib_entry_v0>(le_returned2));
const auto real_le_returned2 = std::get<lib_entry_v0>(le_returned2);
const auto real_le2 = std::get<lib_entry_v0>(le2);
BOOST_REQUIRE(real_le_returned2 == real_le2);
}
BOOST_FIXTURE_TEST_CASE(slice_number, test_fixture)
{
fc::temp_directory tempdir;
slice_directory sd(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
BOOST_REQUIRE_EQUAL(sd.slice_number(99), 0u);
BOOST_REQUIRE_EQUAL(sd.slice_number(100), 1u);
BOOST_REQUIRE_EQUAL(sd.slice_number(1599), 15u);
slice_directory sd2(tempdir.path(), 0x10, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
BOOST_REQUIRE_EQUAL(sd2.slice_number(0xf), 0u);
BOOST_REQUIRE_EQUAL(sd2.slice_number(0x100), 0x10u);
BOOST_REQUIRE_EQUAL(sd2.slice_number(0x233), 0x23u);
}
BOOST_FIXTURE_TEST_CASE(slice_file, test_fixture)
{
fc::temp_directory tempdir;
slice_directory sd(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
fc::cfile slice;
// create trace slices
for (uint i = 0; i < 9; ++i) {
bool found = sd.find_or_create_trace_slice(i, open_state::write, slice);
BOOST_REQUIRE(!found);
std::filesystem::path fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
const std::string expected_filename = "trace_0000000" + std::to_string(i) + "00-0000000" + std::to_string(i+1) + "00.log";
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), 0u);
BOOST_REQUIRE_EQUAL(slice.tellp(), 0u);
slice.close();
}
// create trace index slices
for (uint i = 0; i < 9; ++i) {
bool found = sd.find_or_create_index_slice(i, open_state::write, slice);
BOOST_REQUIRE(!found);
std::filesystem::path fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
const std::string expected_filename = "trace_index_0000000" + std::to_string(i) + "00-0000000" + std::to_string(i+1) + "00.log";
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
slice_directory::index_header h;
const auto data = fc::raw::pack(h);
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), data.size());
BOOST_REQUIRE_EQUAL(slice.tellp(), data.size());
slice.close();
}
// reopen trace slice for append
bool found = sd.find_or_create_trace_slice(0, open_state::write, slice);
BOOST_REQUIRE(found);
std::filesystem::path fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
std::string expected_filename = "trace_0000000000-0000000100.log";
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), 0u);
BOOST_REQUIRE_EQUAL(slice.tellp(), 0u);
uint64_t offset = append_store(bt_v1, slice);
BOOST_REQUIRE_EQUAL(offset, 0u);
auto data = fc::raw::pack(bt_v1);
BOOST_REQUIRE(slice.tellp() > 0u);
BOOST_REQUIRE_EQUAL(data.size(), slice.tellp());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), slice.tellp());
uint64_t trace_file_size = std::filesystem::file_size(fp);
slice.close();
// open same file for read
found = sd.find_or_create_trace_slice(0, open_state::read, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), trace_file_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), 0u);
slice.close();
// open same file for append again
found = sd.find_or_create_trace_slice(0, open_state::write, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), trace_file_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), trace_file_size);
slice.close();
// reopen trace index slice for append
found = sd.find_or_create_index_slice(1, open_state::write, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
expected_filename = "trace_index_0000000100-0000000200.log";
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
slice_directory::index_header h;
data = fc::raw::pack(h);
const uint64_t header_size = data.size();
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), header_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), header_size);
offset = append_store(be1, slice);
BOOST_REQUIRE_EQUAL(offset, header_size);
data = fc::raw::pack(be1);
const auto be1_size = data.size();
BOOST_REQUIRE_EQUAL(header_size + be1_size, slice.tellp());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), slice.tellp());
slice.close();
found = sd.find_or_create_index_slice(1, open_state::read, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), header_size + be1_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), header_size);
slice.close();
found = sd.find_or_create_index_slice(1, open_state::write, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), header_size + be1_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), header_size + be1_size);
offset = append_store(le1, slice);
BOOST_REQUIRE_EQUAL(offset, header_size + be1_size);
data = fc::raw::pack(le1);
const auto le1_size = data.size();
BOOST_REQUIRE_EQUAL(header_size + be1_size + le1_size, slice.tellp());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), slice.tellp());
slice.close();
found = sd.find_or_create_index_slice(1, open_state::read, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), header_size + be1_size + le1_size);
BOOST_REQUIRE_EQUAL(slice.tellp(), header_size);
slice.close();
}
BOOST_FIXTURE_TEST_CASE(slice_file_find_test, test_fixture)
{
fc::temp_directory tempdir;
slice_directory sd(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
fc::cfile slice;
// create trace slice
bool found = sd.find_or_create_trace_slice(1, open_state::write, slice);
BOOST_REQUIRE(!found);
std::filesystem::path fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
const std::string expected_filename = "trace_0000000100-0000000200.log";
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), 0u);
BOOST_REQUIRE_EQUAL(slice.tellp(), 0u);
slice.close();
// find trace slice (and open)
found = sd.find_trace_slice(1, open_state::write, slice);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), 0u);
slice.close();
// find trace slice (and don't open)
found = sd.find_trace_slice(1, open_state::write, slice, false);
BOOST_REQUIRE(found);
fp = slice.get_file_path();
BOOST_REQUIRE_EQUAL(fp.parent_path().generic_string(), tempdir.path().generic_string());
BOOST_REQUIRE_EQUAL(fp.filename().generic_string(), expected_filename);
BOOST_REQUIRE(!slice.is_open());
BOOST_REQUIRE_EQUAL(std::filesystem::file_size(fp), 0u);
slice.close();
}
void verify_directory_contents(const std::filesystem::path& tempdir, std::set<std::filesystem::path> expected_files) {
std::set<std::filesystem::path> unexpected_files;
for (std::filesystem::directory_iterator itr(tempdir); itr != std::filesystem::directory_iterator(); ++itr) {
const auto filename = itr->path().filename();
if (expected_files.erase(filename) < 1) {
unexpected_files.insert(filename);
}
}
if (expected_files.size() + unexpected_files.size() == 0u)
return;
std::string msg;
if (expected_files.size()) {
msg += " Expected the following files to be present, but were not:";
}
bool comma = false;
for(auto file : expected_files) {
if (comma)
msg += ",";
msg += " " + file.generic_string();
}
if (unexpected_files.size()) {
msg += " Did not expect the following files to be present, but they were:";
}
for(auto file : expected_files) {
if (comma)
msg += ",";
msg += " " + file.generic_string();
}
BOOST_FAIL(msg);
}
BOOST_FIXTURE_TEST_CASE(slice_dir_cleanup_height_less_than_width, test_fixture)
{
fc::temp_directory tempdir;
const uint32_t width = 10;
const uint32_t min_saved_blocks = 5;
slice_directory sd(tempdir.path(), width, std::optional<uint32_t>(min_saved_blocks), std::optional<uint32_t>(), 0);
fc::cfile file;
// verify it cleans up when there is just an index file, just a trace file, or when both are there
// verify it cleans up all slices that need to be cleaned
std::set<std::filesystem::path> files;
BOOST_REQUIRE(!sd.find_or_create_index_slice(0, open_state::read, file));
files.insert(file.get_file_path().filename());
verify_directory_contents(tempdir.path(), files);
BOOST_REQUIRE(!sd.find_or_create_trace_slice(0, open_state::read, file));
files.insert(file.get_file_path().filename());
BOOST_REQUIRE(!sd.find_or_create_index_slice(1, open_state::read, file));
files.insert(file.get_file_path().filename());
BOOST_REQUIRE(!sd.find_or_create_trace_slice(2, open_state::read, file));
files.insert(file.get_file_path().filename());
BOOST_REQUIRE(!sd.find_or_create_index_slice(3, open_state::read, file));
files.insert(file.get_file_path().filename());
BOOST_REQUIRE(!sd.find_or_create_index_slice(4, open_state::read, file));
const auto index4 = file.get_file_path().filename();
files.insert(index4);
BOOST_REQUIRE(!sd.find_or_create_trace_slice(4, open_state::read, file));
const auto trace4 = file.get_file_path().filename();
files.insert(trace4);
BOOST_REQUIRE(!sd.find_or_create_index_slice(5, open_state::read, file));
const auto index5 = file.get_file_path().filename();
files.insert(index5);
BOOST_REQUIRE(!sd.find_or_create_trace_slice(6, open_state::read, file));
const auto trace6 = file.get_file_path().filename();
files.insert(trace6);
verify_directory_contents(tempdir.path(), files);
// verify that the current_slice and the previous are maintained as long as lib - min_saved_blocks is part of previous slice
uint32_t current_slice = 6;
uint32_t lib = current_slice * width;
sd.run_maintenance_tasks(lib, {});
std::set<std::filesystem::path> files2;
files2.insert(index5);
files2.insert(trace6);
verify_directory_contents(tempdir.path(), files2);
// saved blocks still in previous slice
lib += min_saved_blocks - 1; // current_slice * width + min_saved_blocks - 1
sd.run_maintenance_tasks(lib, {});
verify_directory_contents(tempdir.path(), files2);
// now all saved blocks in current slice
lib += 1; // current_slice * width + min_saved_blocks
sd.run_maintenance_tasks(lib, {});
std::set<std::filesystem::path> files3;
files3.insert(trace6);
verify_directory_contents(tempdir.path(), files3);
// moving lib into next slice, so 1 saved blocks still in 6th slice
lib += width - 1;
sd.run_maintenance_tasks(lib, {});
verify_directory_contents(tempdir.path(), files3);
// moved last saved block out of 6th slice, so 6th slice is cleaned up
lib += 1;
sd.run_maintenance_tasks(lib, {});
verify_directory_contents(tempdir.path(), std::set<std::filesystem::path>());
}
BOOST_FIXTURE_TEST_CASE(slice_dir_compress, test_fixture)
{
fc::temp_directory tempdir;
const uint32_t width = 10;
const uint32_t min_uncompressed_blocks = 5;
slice_directory sd(tempdir.path(), width, std::optional<uint32_t>(), std::optional<uint32_t>(min_uncompressed_blocks), 8);
fc::cfile file;
using file_vector_t = std::vector<std::tuple<std::filesystem::path, std::filesystem::path, std::filesystem::path>>;
file_vector_t file_paths;
for (int i = 0; i < 7 ; i++) {
BOOST_REQUIRE(!sd.find_or_create_index_slice(i, open_state::read, file));
auto index_name = file.get_file_path().filename();
BOOST_REQUIRE(create_non_empty_trace_slice(sd, i, file));
auto trace_name = file.get_file_path().filename();
auto compressed_trace_name = trace_name;
compressed_trace_name.replace_extension(".clog");
file_paths.emplace_back(index_name, trace_name, compressed_trace_name);
}
// initial set is only indices and uncompressed traces
std::set<std::filesystem::path> files;
for (const auto& e: file_paths) {
files.insert(std::get<0>(e));
files.insert(std::get<1>(e));
}
verify_directory_contents(tempdir.path(), files);
// verify no change up to the last block before a slice becomes compressible
sd.run_maintenance_tasks(14, {});
verify_directory_contents(tempdir.path(), files);
for (std::size_t reps = 0; reps < file_paths.size(); reps++) {
// leading edge,
// compresses one slice
files.erase(std::get<1>(file_paths.at(reps)));
files.insert(std::get<2>(file_paths.at(reps)));
sd.run_maintenance_tasks(15 + (reps * width), {});
verify_directory_contents(tempdir.path(), files);
// trailing edge, no change
sd.run_maintenance_tasks(24 + (reps * width), {});
verify_directory_contents(tempdir.path(), files);
}
// make sure the test is correct and and no uncompressed files remain
for (const auto& e: file_paths) {
BOOST_REQUIRE_EQUAL(files.count(std::get<0>(e)), 1u);
BOOST_REQUIRE_EQUAL(files.count(std::get<1>(e)), 0u);
BOOST_REQUIRE_EQUAL(files.count(std::get<2>(e)), 1u);
}
}
BOOST_FIXTURE_TEST_CASE(slice_dir_compress_and_delete, test_fixture)
{
fc::temp_directory tempdir;
const uint32_t width = 10;
const uint32_t min_uncompressed_blocks = 5;
const uint32_t min_saved_blocks = min_uncompressed_blocks + width;
slice_directory sd(tempdir.path(), width, std::optional<uint32_t>(min_saved_blocks), std::optional<uint32_t>(min_uncompressed_blocks), 8);
fc::cfile file;
using file_vector_t = std::vector<std::tuple<std::filesystem::path, std::filesystem::path, std::filesystem::path>>;
file_vector_t file_paths;
for (int i = 0; i < 7 ; i++) {
BOOST_REQUIRE(!sd.find_or_create_index_slice(i, open_state::read, file));
auto index_name = file.get_file_path().filename();
BOOST_REQUIRE(create_non_empty_trace_slice(sd, i, file));
auto trace_name = file.get_file_path().filename();
auto compressed_trace_name = trace_name;
compressed_trace_name.replace_extension(".clog");
file_paths.emplace_back(index_name, trace_name, compressed_trace_name);
}
// initial set is only indices and uncompressed traces
std::set<std::filesystem::path> files;
for (const auto& e: file_paths) {
files.insert(std::get<0>(e));
files.insert(std::get<1>(e));
}
verify_directory_contents(tempdir.path(), files);
// verify no change up to the last block before a slice becomes compressible
sd.run_maintenance_tasks(14, {});
verify_directory_contents(tempdir.path(), files);
for (std::size_t reps = 0; reps < file_paths.size() + 1; reps++) {
// leading edge,
// compresses one slice IF its not past the end of our test,
if (reps < file_paths.size()) {
files.erase(std::get<1>(file_paths.at(reps)));
files.insert(std::get<2>(file_paths.at(reps)));
}
// removes one IF its not the first
if (reps > 0) {
files.erase(std::get<0>(file_paths.at(reps-1)));
files.erase(std::get<2>(file_paths.at(reps-1)));
}
sd.run_maintenance_tasks(15 + (reps * width), {});
verify_directory_contents(tempdir.path(), files);
// trailing edge, no change
sd.run_maintenance_tasks(24 + (reps * width), {});
verify_directory_contents(tempdir.path(), files);
}
// make sure the test is correct and ran through the permutations
BOOST_REQUIRE_EQUAL(files.size(), 0u);
}
BOOST_FIXTURE_TEST_CASE(store_provider_write_read_v1, test_fixture)
{
fc::temp_directory tempdir;
test_store_provider sp(tempdir.path(), 100);
sp.append(bt_v1);
sp.append_lib(54);
sp.append(bt2_v1);
const uint32_t bt_bn = bt_v1.number;
bool found_block = false;
bool lib_seen = false;
const uint64_t first_offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
if (block.number == bt_bn) {
BOOST_REQUIRE(!found_block);
found_block = true;
}
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
return false;
}
return true;
}, []() {});
BOOST_REQUIRE(found_block);
BOOST_REQUIRE(lib_seen);
std::vector<uint32_t> block_nums;
std::vector<uint64_t> block_offsets;
lib_seen = false;
uint64_t offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
block_nums.push_back(block.number);
block_offsets.push_back(block.offset);
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
}
return true;
}, []() {});
BOOST_REQUIRE(lib_seen);
BOOST_REQUIRE_EQUAL(block_nums.size(), 2u);
BOOST_REQUIRE_EQUAL(block_nums[0], bt_v1.number);
BOOST_REQUIRE_EQUAL(block_nums[1], bt2_v1.number);
BOOST_REQUIRE_EQUAL(block_offsets.size(), 2u);
BOOST_REQUIRE(block_offsets[0] < block_offsets[1]);
BOOST_REQUIRE(first_offset < offset);
std::optional<data_log_entry> bt_data = sp.read_data_log(block_nums[0], block_offsets[0]);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v1>(*bt_data), bt_v1);
bt_data = sp.read_data_log(block_nums[1], block_offsets[1]);
BOOST_REQUIRE(bt_data);
auto v = std::variant<block_trace_v0, block_trace_v1, block_trace_v2>(*bt_data);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v1>(v), bt2_v1);
block_nums.clear();
block_offsets.clear();
lib_seen = false;
int counter = 0;
try {
offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
block_nums.push_back(block.number);
block_offsets.push_back(block.offset);
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
}
return true;
}, [&counter]() {
if( ++counter == 3 ) {
throw yield_exception("");
}
});
BOOST_FAIL("Should not have completed scan");
} catch (const yield_exception& ex) {
}
BOOST_REQUIRE(lib_seen);
BOOST_REQUIRE_EQUAL(block_nums.size(), 1u);
BOOST_REQUIRE_EQUAL(block_nums[0], bt_v1.number);
BOOST_REQUIRE_EQUAL(block_offsets.size(), 1u);
BOOST_REQUIRE(first_offset < offset);
}
BOOST_FIXTURE_TEST_CASE(store_provider_write_read_v2, test_fixture)
{
fc::temp_directory tempdir;
test_store_provider sp(tempdir.path(), 100);
sp.append(block_trace1_v2);
sp.append_lib(54);
sp.append(block_trace2_v2);
const uint32_t bt_bn = block_trace1_v2.number;
bool found_block = false;
bool lib_seen = false;
const uint64_t first_offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
if (block.number == bt_bn) {
BOOST_REQUIRE(!found_block);
found_block = true;
}
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
return false;
}
return true;
}, []() {});
BOOST_REQUIRE(found_block);
BOOST_REQUIRE(lib_seen);
std::vector<uint32_t> block_nums;
std::vector<uint64_t> block_offsets;
lib_seen = false;
uint64_t offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
block_nums.push_back(block.number);
block_offsets.push_back(block.offset);
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
}
return true;
}, []() {});
BOOST_REQUIRE(lib_seen);
BOOST_REQUIRE_EQUAL(block_nums.size(), 2u);
BOOST_REQUIRE_EQUAL(block_nums[0], block_trace1_v2.number);
BOOST_REQUIRE_EQUAL(block_nums[1], block_trace2_v2.number);
BOOST_REQUIRE_EQUAL(block_offsets.size(), 2u);
BOOST_REQUIRE(block_offsets[0] < block_offsets[1]);
BOOST_REQUIRE(first_offset < offset);
std::optional<data_log_entry> bt_data = sp.read_data_log(block_nums[0], block_offsets[0]);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v2>(*bt_data), block_trace1_v2);
bt_data = sp.read_data_log(block_nums[1], block_offsets[1]);
BOOST_REQUIRE(bt_data);
auto v = data_log_entry(*bt_data);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v2>(v), block_trace2_v2);
block_nums.clear();
block_offsets.clear();
lib_seen = false;
int counter = 0;
try {
offset = sp.scan_metadata_log_from(9, 0, [&](const metadata_log_entry& e) -> bool {
if (std::holds_alternative<block_entry_v0>(e)) {
const auto& block = std::get<block_entry_v0>(e);
block_nums.push_back(block.number);
block_offsets.push_back(block.offset);
} else if (std::holds_alternative<lib_entry_v0>(e)) {
auto best_lib = std::get<lib_entry_v0>(e);
BOOST_REQUIRE(!lib_seen);
BOOST_REQUIRE_EQUAL(best_lib.lib, 54u);
lib_seen = true;
}
return true;
}, [&counter]() {
if( ++counter == 3 ) {
throw yield_exception("");
}
});
BOOST_FAIL("Should not have completed scan");
} catch (const yield_exception& ex) {
}
BOOST_REQUIRE(lib_seen);
BOOST_REQUIRE_EQUAL(block_nums.size(), 1u);
BOOST_REQUIRE_EQUAL(block_nums[0], block_trace1_v2.number);
BOOST_REQUIRE_EQUAL(block_offsets.size(), 1u);
BOOST_REQUIRE(first_offset < offset);
}
BOOST_FIXTURE_TEST_CASE(test_get_block_v1, test_fixture)
{
fc::temp_directory tempdir;
store_provider sp(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
sp.append(bt_v1);
sp.append_lib(1);
sp.append(bt2_v1);
int count = 0;
get_block_t block1 = sp.get_block(1, [&count]() {
if (++count >= 3) {
throw yield_exception("");
}
});
BOOST_REQUIRE(block1);
BOOST_REQUIRE(std::get<1>(*block1));
const auto block1_bt = std::get<0>(*block1);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v1>(block1_bt), bt_v1);
count = 0;
get_block_t block2 = sp.get_block(5, [&count]() {
if (++count >= 4) {
throw yield_exception("");
}
});
BOOST_REQUIRE(block2);
BOOST_REQUIRE(!std::get<1>(*block2));
const auto block2_bt = std::get<0>(*block2);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v1>(block2_bt), bt2_v1);
count = 0;
try {
sp.get_block(5,[&count]() {
if (++count >= 3) {
throw yield_exception("");
}
});
BOOST_FAIL("Should not have completed scan");
} catch (const yield_exception& ex) {
}
count = 0;
block2 = sp.get_block(2,[&count]() {
if (++count >= 4) {
throw yield_exception("");
}
});
BOOST_REQUIRE(!block2);
}
BOOST_FIXTURE_TEST_CASE(test_get_block_v2, test_fixture)
{
fc::temp_directory tempdir;
store_provider sp(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
sp.append(block_trace1_v2);
sp.append_lib(1);
sp.append(block_trace2_v2);
int count = 0;
get_block_t block1 = sp.get_block(1, [&count]() {
if (++count >= 3) {
throw yield_exception("");
}
});
BOOST_REQUIRE(block1);
BOOST_REQUIRE(std::get<1>(*block1));
const auto block1_bt = std::get<0>(*block1);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v2>(block1_bt), block_trace1_v2);
count = 0;
get_block_t block2 = sp.get_block(5, [&count]() {
if (++count >= 4) {
throw yield_exception("");
}
});
BOOST_REQUIRE(block2);
BOOST_REQUIRE(!std::get<1>(*block2));
const auto block2_bt = std::get<0>(*block2);
BOOST_REQUIRE_EQUAL(std::get<block_trace_v2>(block2_bt), block_trace2_v2);
count = 0;
try {
sp.get_block(5,[&count]() {
if (++count >= 3) {
throw yield_exception("");
}
});
BOOST_FAIL("Should not have completed scan");
} catch (const yield_exception& ex) {
}
count = 0;
block2 = sp.get_block(2,[&count]() {
if (++count >= 4) {
throw yield_exception("");
}
});
BOOST_REQUIRE(!block2);
}
// Verify basics of get_trx_block_number()
BOOST_FIXTURE_TEST_CASE(test_get_trx_block_number_basic, test_fixture)
{
chain::transaction_id_type trx_id1 = "0000000000000000000000000000000000000000000000000000000000000001"_h;
chain::transaction_id_type trx_id2 = "0000000000000000000000000000000000000000000000000000000000000002"_h;
uint32_t block_num1 = 1;
uint32_t block_num2 = 2;
transaction_trace_v2 trx_trace1 {
trx_id1,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
{ chain::signature_type() },
{ chain::time_point_sec(), 1, 0, 100, 50, 0 }
};
transaction_trace_v2 trx_trace2 {
trx_id2,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
{ chain::signature_type() },
{ chain::time_point_sec(), 1, 0, 100, 50, 0 }
};
// block 1 includes trx_trace1
block_trace_v2 block_trace1 {
"b000000000000000000000000000000000000000000000000000000000000001"_h,
block_num1,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
trx_trace1
}
};
// block 2 includes trx_trace2
block_trace_v2 block_trace2 {
"b000000000000000000000000000000000000000000000000000000000000003"_h,
block_num2,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
trx_trace2
}
};
block_trxs_entry block_trxs_entry1 {
.ids = {trx_id1},
.block_num = block_num1
};
block_trxs_entry block_trxs_entry2 {
.ids = {trx_id2},
.block_num = block_num2
};
fc::temp_directory tempdir;
store_provider sp(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
// on_accepted_block of block 1
sp.append(block_trace1);
sp.append_trx_ids(block_trxs_entry1);
// block 1 is reversible and get_trx_block_number should find trx_id1 in block 1
get_block_n block_num = sp.get_trx_block_number(trx_id1, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num1);
// block 1 becomes final
sp.append_lib(block_num1);
// get_trx_block_number should find trx_id1 in block 1
block_num = sp.get_trx_block_number(trx_id1, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num1);
// on_accepted_block of block 2
sp.append(block_trace2);
sp.append_trx_ids(block_trxs_entry2);
// get_trx_block_number should find both trx_id1 and trx_id2
block_num = sp.get_trx_block_number(trx_id1, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num1);
block_num = sp.get_trx_block_number(trx_id2, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num2);
// block 2 becomes final
sp.append_lib(block_num2);
// get_trx_block_number should still find both trx_id1 and trx_id2
block_num = sp.get_trx_block_number(trx_id1, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num1);
block_num = sp.get_trx_block_number(trx_id2, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, block_num2);
}
// This test verifies the bug reported by https://github.com/AntelopeIO/spring/issues/942
// is fixed. The bug was if the block containing a transaction forked out,
// get_trx_block_number() always returned the latest block whose block number was
// the same as the initial block's, but this latest block might not include the
// transaction anymore.
BOOST_FIXTURE_TEST_CASE(test_get_trx_block_number_forked, test_fixture)
{
chain::transaction_id_type target_trx_id = "0000000000000000000000000000000000000000000000000000000000000001"_h;
uint32_t initial_block_num = 1;
uint32_t final_block_num = 3;
transaction_trace_v2 trx_trace1 {
target_trx_id,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
{ chain::signature_type() },
{ chain::time_point_sec(), 1, 0, 100, 50, 0 }
};
transaction_trace_v2 trx_trace2 {
"0000000000000000000000000000000000000000000000000000000000000002"_h,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{chain::transaction_receipt_header::status_enum::executed},
10,
5,
{ chain::signature_type() },
{ chain::time_point_sec(), 1, 0, 100, 50, 0 }
};
// Initial block including trx_trace1
block_trace_v2 initial_block_trace {
"b000000000000000000000000000000000000000000000000000000000000001"_h,
initial_block_num,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
trx_trace1
}
};
// Initial block is forked. The original trx_trace1 is forked out and
// replaced by trx_trace2.
block_trace_v2 forked_block_trace = initial_block_trace;
forked_block_trace.transactions = std::vector<transaction_trace_v2> { trx_trace2 };
// Final block including original trx_trace1
block_trace_v2 final_block_trace {
"b000000000000000000000000000000000000000000000000000000000000003"_h,
final_block_num,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
trx_trace1
}
};
block_trxs_entry initial_block_trxs_entry {
.ids = {target_trx_id},
.block_num = initial_block_num
};
block_trxs_entry forked_block_trxs_entry {
.ids = {trx_trace2.id},
.block_num = initial_block_num
};
block_trxs_entry final_block_trxs_entry {
.ids = {target_trx_id},
.block_num = final_block_num
};
fc::temp_directory tempdir;
store_provider sp(tempdir.path(), 100, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
// on_accepted_block of the initial block
sp.append(initial_block_trace); // block 1
sp.append_trx_ids(initial_block_trxs_entry); // block 1
// target trx is in the first block (is still reversible)
get_block_n block_num = sp.get_trx_block_number(target_trx_id, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, initial_block_num);
// initial block forks out
sp.append(forked_block_trace); // block 1
sp.append_trx_ids(forked_block_trxs_entry); // block 1
// forked block becomes final
sp.append_lib(initial_block_num); // block 1
// target trx is forked out. block 1 does not include
// target_trx (trx_trace1) but trx_trace2;
// therefore no block is found for target_trx_id.
block_num = sp.get_trx_block_number(target_trx_id, {});
BOOST_REQUIRE(!block_num);
// on_accepted_block of the final block
sp.append(final_block_trace); // block 3
sp.append_trx_ids(final_block_trxs_entry); // block 3
// final block becomes final
sp.append_lib(final_block_num); // block 3
block_num = sp.get_trx_block_number(target_trx_id, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, final_block_num); // target trx is in final block
}
// This test verifies the bug reported by https://github.com/AntelopeIO/spring/issues/1693
// is fixed. The issue was that when starting from a snapshot not all stride files were
// available. When it couldn't find the first stride file it reported trx not found.
BOOST_FIXTURE_TEST_CASE(test_get_trx_when_missing_strides, test_fixture)
{
chain::transaction_id_type target_trx_id = "0000000000000000000000000000000000000000000000000000000000000001"_h;
uint32_t trx_block_num = 10;
std::vector<block_trace_v2> empty_blocks;
for (uint32_t i = 1; i < trx_block_num; ++i) {
empty_blocks.push_back({
"b000000000000000000000000000000000000000000000000000000000000001"_h,
i, // block_num
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2>{
}
});
}
transaction_trace_v2 trx_trace1 {
target_trx_id,
actions,
fc::enum_type<uint8_t, chain::transaction_receipt_header::status_enum>{
chain::transaction_receipt_header::status_enum::executed},
10,
5,
{chain::signature_type()},
{chain::time_point_sec(), 1, 0, 100, 50, 0}
};
// Block including trx_trace1
block_trace_v2 block_trace {
"b000000000000000000000000000000000000000000000000000000000000010"_h,
trx_block_num,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
chain::block_timestamp_type(0),
"test"_n,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
"0000000000000000000000000000000000000000000000000000000000000000"_h,
0,
std::vector<transaction_trace_v2> {
trx_trace1
}
};
block_trxs_entry initial_block_trxs_entry {
.ids = {target_trx_id},
.block_num = trx_block_num
};
fc::temp_directory tempdir;
store_provider sp(tempdir.path(), 5, std::optional<uint32_t>(), std::optional<uint32_t>(), 0);
// store some empty blocks which will create a stride
for (const auto& bt : empty_blocks) {
sp.append(bt);
sp.append_trx_ids({.block_num = bt.number});
}
// on_accepted_block of the initial block
sp.append(block_trace); // block 10
sp.append_trx_ids(initial_block_trxs_entry); // block 10
get_block_n block_num = sp.get_trx_block_number(target_trx_id, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, trx_block_num); // target trx is in final block
// remove first stride file and verify we can still find trx
BOOST_REQUIRE(std::filesystem::exists(tempdir.path() / "trace_trx_id_0000000000-0000000005.log"));
std::filesystem::remove(tempdir.path() / "trace_trx_id_0000000000-0000000005.log");
// verify we can still find the trx
block_num = sp.get_trx_block_number(target_trx_id, {});
BOOST_REQUIRE(block_num);
BOOST_REQUIRE_EQUAL(*block_num, trx_block_num); // target trx is in final block
}
BOOST_AUTO_TEST_SUITE_END()