diff --git a/libraries/chain/include/eosio/chain/account_object.hpp b/libraries/chain/include/eosio/chain/account_object.hpp index d10f2da6537..d56bc8dce26 100644 --- a/libraries/chain/include/eosio/chain/account_object.hpp +++ b/libraries/chain/include/eosio/chain/account_object.hpp @@ -80,5 +80,4 @@ namespace eosio { namespace chain { CHAINBASE_SET_INDEX_TYPE(eosio::chain::account_object, eosio::chain::account_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::account_sequence_object, eosio::chain::account_sequence_index) - -FC_REFLECT(eosio::chain::account_object, (name)(vm_type)(vm_version)(code_version)(code)(creation_date)) +FC_REFLECT(eosio::chain::account_object, (name)(vm_type)(vm_version)(privileged)(last_code_update)(code_version)(creation_date)(code)(abi)) diff --git a/libraries/chain/include/eosio/chain/contract_table_objects.hpp b/libraries/chain/include/eosio/chain/contract_table_objects.hpp index bc2fff140c4..51f2adb90e6 100644 --- a/libraries/chain/include/eosio/chain/contract_table_objects.hpp +++ b/libraries/chain/include/eosio/chain/contract_table_objects.hpp @@ -216,5 +216,8 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::index256_object, eosio::chain::index256_i CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_double_object, eosio::chain::index_double_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_long_double_object, eosio::chain::index_long_double_index) -FC_REFLECT(eosio::chain::table_id_object, (id)(code)(scope)(table) ) +FC_REFLECT(chainbase::oid, (_id)) +FC_REFLECT(eosio::chain::table_id_object, (id)(code)(scope)(table)(payer)(count) ) + +FC_REFLECT(chainbase::oid, (_id)) FC_REFLECT(eosio::chain::key_value_object, (id)(t_id)(primary_key)(value)(payer) ) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f3c0d05d85d..0d7e8814c00 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(db_size_api_plugin) #add_subdirectory(faucet_testnet_plugin) #add_subdirectory(mongo_db_plugin) #add_subdirectory(sql_db_plugin) +add_subdirectory(snapshot_plugin) # Forward variables to top level so packaging picks them up set(CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_DEPENDS} PARENT_SCOPE) diff --git a/plugins/snapshot_plugin/CMakeLists.txt b/plugins/snapshot_plugin/CMakeLists.txt new file mode 100644 index 00000000000..a7ef7273207 --- /dev/null +++ b/plugins/snapshot_plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB HEADERS "include/eosio/snapshot_plugin/*.hpp") +add_library( snapshot_plugin + snapshot_plugin.cpp + ${HEADERS} ) + +target_link_libraries( snapshot_plugin appbase fc chain_plugin) +target_include_directories( snapshot_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/snapshot_plugin/include/eosio/snapshot_plugin/snapshot_plugin.hpp b/plugins/snapshot_plugin/include/eosio/snapshot_plugin/snapshot_plugin.hpp new file mode 100644 index 00000000000..ba9d59c2bd5 --- /dev/null +++ b/plugins/snapshot_plugin/include/eosio/snapshot_plugin/snapshot_plugin.hpp @@ -0,0 +1,46 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#pragma once +#include + +#include +#include + +namespace eosio { + +using namespace appbase; + +class snapshot_plugin : public appbase::plugin { +public: + snapshot_plugin(); + virtual ~snapshot_plugin(); + + APPBASE_PLUGIN_REQUIRES((chain_plugin)) + virtual void set_program_options(options_description&, options_description& cfg) override; + + void plugin_initialize(const variables_map& options); + void plugin_startup(); + void plugin_shutdown(); + + /** + * snapshot_version must be changed anytime there is a change + * on any of the following types: + * - eosio::types::chain_id_type + * - eosio::chain::genesis_state + * - eosio::chain::block_header_state + * - eosio::chain::account_object + * - eosio::chain::permission_object + * - eosio::chain::table_id_object + * - eosio::chain::key_value_object + */ + uint16_t snapshot_version = 0x0001; + +private: + std::unique_ptr my; + fc::optional m_irreversible_block_connection; + fc::optional m_accepted_block_connection; +}; + +} diff --git a/plugins/snapshot_plugin/snapshot_plugin.cpp b/plugins/snapshot_plugin/snapshot_plugin.cpp new file mode 100644 index 00000000000..3a89c35b164 --- /dev/null +++ b/plugins/snapshot_plugin/snapshot_plugin.cpp @@ -0,0 +1,158 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace chainbase; +using namespace fc; +using namespace std; + +struct permission { + struct name perm_name; + struct name parent; + authority required_auth; +}; +FC_REFLECT( permission, (perm_name)(parent)(required_auth) ) + +template +void iterate_all_tables(const database& db, Function f) +{ + const auto &idx = db.get_index(); + auto lower = idx.begin(); + auto upper = idx.end(); + + for (auto itr = lower; itr != upper; ++itr) { + f(*itr); + } +} + +template +void iterate_all_rows(const database& db, const table_id_object& t_id, Function f) +{ + const auto &idx = db.get_index(); + decltype(t_id.id) next_tid(t_id.id._id + 1); + auto lower = idx.lower_bound(boost::make_tuple(t_id.id)); + auto upper = idx.lower_bound(boost::make_tuple(next_tid)); + + for (auto itr = lower; itr != upper; ++itr) { + f(*itr); + } +} + +namespace eosio { + static appbase::abstract_plugin& _snapshot_plugin = app().register_plugin(); + +class snapshot_plugin_impl { + public: +}; + +snapshot_plugin::snapshot_plugin():my(new snapshot_plugin_impl()){} +snapshot_plugin::~snapshot_plugin(){} + +void snapshot_plugin::set_program_options(options_description&, options_description& cfg) { + cfg.add_options() + ("snapshot-at-block", bpo::value()->default_value(""), "Block hash at which to take the snapshot") + ("snapshot-to", bpo::value()->default_value("snapshot.json"), "Pathname of JSON file where to store the snapshot") + ; +} + +void snapshot_plugin::plugin_initialize(const variables_map& options) { + + auto snapshot_at_block = fc::json::from_string(options.at("snapshot-at-block").as()).as(); + auto snapshot_to = options.at("snapshot-to").as(); + + if( snapshot_at_block != block_id_type() ) { + + chain_plugin* chain_plug = app().find_plugin(); + FC_ASSERT(chain_plug); + auto& chain = chain_plug->chain(); + auto& db = chain.db(); + const auto& config = chain_plug->chain_config(); + + // Take snapshot after block is applied + m_accepted_block_connection.emplace(chain.accepted_block.connect([this, snapshot_at_block, snapshot_to, &db, &config, &chain](const chain::block_state_ptr& b) { + + if( b->id == snapshot_at_block ) { + + ilog("Taking snapshot at block ${n} (${h})...",("h",snapshot_at_block)("n",b->block_num)); + + std::ofstream out; + try { + out.open(snapshot_to); + fc::raw::pack(out, snapshot_version); + fc::raw::pack(out, chain.get_chain_id()); + fc::raw::pack(out, config.genesis); + fc::raw::pack(out, static_cast(*b)); + + const auto& account_idx = db.get_index(); + uint32_t total_accounts = std::distance(account_idx.begin(), account_idx.end()); + fc::raw::pack(out, total_accounts); + for (auto accnt = account_idx.begin(); accnt != account_idx.end(); ++accnt) { + fc::raw::pack(out, *accnt); + } + + const auto& permissions_idx = db.get_index(); + uint32_t total_perms = std::distance(permissions_idx.begin(), permissions_idx.end()); + fc::raw::pack(out, total_perms); + for (auto perm = permissions_idx.begin(); perm != permissions_idx.end(); ++perm) { + fc::raw::pack(out, *perm); + } + + const auto &table_idx = db.get_index(); + uint32_t total_tables = std::distance(table_idx.begin(), table_idx.end()); + fc::raw::pack(out, total_tables); + iterate_all_tables(db, [&](const table_id_object& t_id) { + + uint32_t cnt = 0; + iterate_all_rows(db, t_id, [&cnt](const key_value_object& row) { cnt++; }); + + fc::raw::pack(out, t_id.id); + fc::raw::pack(out, t_id.code); + fc::raw::pack(out, t_id.scope); + fc::raw::pack(out, t_id.table); + fc::raw::pack(out, t_id.payer); + fc::raw::pack(out, cnt); + + //fc::raw::pack(out, t_id); + //t_id.count = cnt; + + iterate_all_rows(db, t_id, [&](const key_value_object& row) { + fc::raw::pack(out, row); + }); + }); + + out.flush(); + out.close(); + ilog( "Snapshot saved in ${s}",("s",snapshot_to)); + } catch ( ... ) { + try { fc::remove( snapshot_to); } catch (...) {} + wlog( "Failed to take snapshot"); + return; + } + } + + })); + } +} + +void snapshot_plugin::plugin_startup() { + // Make the magic happen +} + +void snapshot_plugin::plugin_shutdown() { + // OK, that's enough magic +} + +} diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index bc3872e2c1b..d798b0f7038 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -56,6 +56,7 @@ target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} txn_test_gen_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} db_size_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} producer_api_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${whole_archive_flag} snapshot_plugin -Wl,${no_whole_archive_flag} PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 42a6e37b5a0..d7b53b9bc2c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -14,3 +14,14 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/eosiocpp DESTINATION ${CMAKE_INSTALL_ add_executable( print_floats print_floats.cpp ) target_include_directories( print_floats PRIVATE ${Boost_INCLUDE_DIR} ) target_link_libraries( print_floats PRIVATE ${Boost_LIBRARIES} ) + +add_executable( snap2json snap2json.cpp ) +target_include_directories( snap2json PRIVATE ${Boost_INCLUDE_DIR} ) +target_link_libraries( snap2json PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + +install( TARGETS + snap2json + RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} +) diff --git a/tools/snap2json.cpp b/tools/snap2json.cpp new file mode 100644 index 00000000000..490fc50ab95 --- /dev/null +++ b/tools/snap2json.cpp @@ -0,0 +1,184 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: find a better way to instantiate chainbase objects +#undef OBJECT_CTOR +#define OBJECT_CTOR(...) public: +#define shared_string vector +#define shared_authority authority +namespace eosio { namespace chain { namespace config { + template<> + constexpr uint64_t billable_size_v = 1; +}}} + +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace chainbase; +using namespace fc; +using namespace std; + +#include + +namespace po = boost::program_options; + +int main(int argc, const char **argv) { + + try { + + po::options_description desc("Convert a snapshot taken with the snapshot_plugin to json"); + desc.add_options() + ("help,h", "Print this help message and exit") + ("in", po::value(), "Pathname of the input binary snapshot") + ("out", po::value(), "Pathname of the output JSON file") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + string in_file; + if(vm.count("in")) { + in_file = vm.at("in").as(); + } + + if( vm.count("help") || !in_file.size() ) { + std::cout << desc << std::endl; + return 1; + } + + if(!fc::exists(in_file)) { + cout << "Input file does not exists: " << in_file << endl; + return 1; + } + + std::ifstream ifs(in_file); + + std::streambuf *buf; + std::ofstream ofs; + + if(vm.count("out")) { + ofs.open(vm.at("out").as()); + buf = ofs.rdbuf(); + } else { + buf = std::cout.rdbuf(); + } + + std::ostream out(buf); + + uint16_t version; + fc::raw::unpack(ifs, version); + out << "{\"version\":" << fc::json::to_string(version); + + chain_id_type chain_id(sha256("")); + fc::raw::unpack(ifs, chain_id); + out << ",\"chain_id\":" << fc::json::to_string(chain_id); + + genesis_state gs; + fc::raw::unpack(ifs, gs); + out << ",\"genesis_state\":" << fc::json::to_string(gs); + + block_header_state bhs; + fc::raw::unpack(ifs, bhs); + out << ",\"block_header_state\":" << fc::json::to_string(bhs); + + uint32_t total_accounts; + fc::raw::unpack(ifs, total_accounts); + + map contract_abi; + + out << ",\"accounts\":["; + for(uint32_t i=0; i0) out << ","; + account_object accnt; + fc::raw::unpack(ifs, accnt); + + fc::variant v; + fc::to_variant(accnt, v); + fc::mutable_variant_object mvo(v); + + abi_def abi; + if( abi_serializer::to_abi(accnt.abi, abi) ) { + contract_abi[accnt.name] = abi; + fc::variant vabi; + fc::to_variant(abi, vabi); + mvo["abi"] = vabi; + } + + out << fc::json::to_string(mvo); + } + out << "]"; + + out << ",\"permissions\":["; + uint32_t total_perms; + fc::raw::unpack(ifs, total_perms); + for(uint32_t i=0; i0) out << ","; + permission_object perm; + fc::raw::unpack(ifs, perm); + out << fc::json::to_string(perm); + } + out << "]"; + + out << ",\"tables\":["; + uint32_t total_tables; + fc::raw::unpack(ifs, total_tables); + for(uint32_t i=0; i0) out << ","; + table_id_object t_id; + fc::raw::unpack(ifs, t_id); + out << "{"; + out << "\"tid\":" << fc::json::to_string(t_id); + out << ",\"rows\":{"; + + const auto& abi = contract_abi[t_id.code]; + abi_serializer abis(abi); + string table_type = abis.get_table_type(t_id.table); + vector data; + + for(int j=0; j0) out << ","; + key_value_object row; + fc::raw::unpack(ifs, row); + out << "\"" << row.primary_key << "\":"; + if(!table_type.size()) { + out << "{\"hex_data\":\"" << fc::to_hex(row.value.data(), row.value.size()) << "\"}"; + } else { + data.resize( row.value.size() ); + memcpy(data.data(), row.value.data(), row.value.size()); + out << "{\"data\":" + << fc::json::to_string( abis.binary_to_variant(table_type, data) ) + << "}"; + } + } + out << "}}"; //table + } + out << "]"; //tables + + out << "}"; //main object + + ifs.close(); + ofs.close(); + + return 0; + + } FC_CAPTURE_AND_LOG(()); + return 1; +}