src/blockchain/impl/key_value_block_storage.cpp¶
Namespaces¶
| Name |
|---|
| sgns |
| sgns::blockchain |
Types¶
| Name | |
|---|---|
| using boost::variant< BlockHash, BlockNumber > | BlockId Block id is the variant over BlockHash and BlockNumber. |
Functions¶
| Name | |
|---|---|
| OUTCOME_CPP_DEFINE_CATEGORY_3(sgns::blockchain , KeyValueBlockStorage::Error , e ) |
Types Documentation¶
using BlockId¶
Block id is the variant over BlockHash and BlockNumber.
Functions Documentation¶
function OUTCOME_CPP_DEFINE_CATEGORY_3¶
Source code¶
#include "blockchain/impl/key_value_block_storage.hpp"
#include <utility>
#include "blockchain/impl/common.hpp"
#include "blockchain/impl/storage_util.hpp"
#include "storage/database_error.hpp"
#include "blockchain/impl/proto/SGBlocks.pb.h"
#include "storage/predefined_keys.hpp"
OUTCOME_CPP_DEFINE_CATEGORY_3( sgns::blockchain, KeyValueBlockStorage::Error, e )
{
using E = sgns::blockchain::KeyValueBlockStorage::Error;
switch ( e )
{
case E::BLOCK_EXISTS:
return "Block already exists on the chain";
case E::BODY_DOES_NOT_EXIST:
return "Block body was not found";
case E::JUSTIFICATION_DOES_NOT_EXIST:
return "Justification was not found";
case E::GENESIS_BLOCK_ALREADY_EXISTS:
return "Genesis block already exists";
case E::FINALIZED_BLOCK_NOT_FOUND:
return "Finalized block not found. Possible storage corrupted";
case E::GENESIS_BLOCK_NOT_FOUND:
return "Genesis block not found exists";
}
return "Unknown error";
}
namespace sgns::blockchain
{
using primitives::BlockId;
using Buffer = base::Buffer;
using Prefix = prefix::Prefix;
KeyValueBlockStorage::KeyValueBlockStorage( std::shared_ptr<crdt::GlobalDB> db,
std::shared_ptr<crypto::Hasher> hasher,
std::shared_ptr<KeyValueBlockHeaderRepository> header_repo ) :
db_{ std::move( db ) },
hasher_{ std::move( hasher ) },
logger_{ base::createLogger( "Block Storage:" ) },
header_repo_{ std::move( header_repo ) }
{
}
outcome::result<std::shared_ptr<KeyValueBlockStorage>> KeyValueBlockStorage::create(
base::Buffer state_root,
const std::shared_ptr<crdt::GlobalDB> &db,
const std::shared_ptr<crypto::Hasher> &hasher,
const std::shared_ptr<KeyValueBlockHeaderRepository> &header_repo,
const BlockHandler &on_finalized_block_found )
{
auto block_storage = std::make_shared<KeyValueBlockStorage>( KeyValueBlockStorage( db, hasher, header_repo ) );
auto last_finalized_block_hash_res = block_storage->getLastFinalizedBlockHash();
if ( last_finalized_block_hash_res.has_value() )
{
return loadExisting( db, hasher, header_repo, on_finalized_block_found );
}
if ( last_finalized_block_hash_res == outcome::failure( Error::FINALIZED_BLOCK_NOT_FOUND ) )
{
return createWithGenesis( std::move( state_root ), db, hasher, header_repo, on_finalized_block_found );
}
return last_finalized_block_hash_res.error();
}
outcome::result<std::shared_ptr<KeyValueBlockStorage>> KeyValueBlockStorage::loadExisting(
std::shared_ptr<crdt::GlobalDB> db,
std::shared_ptr<crypto::Hasher> hasher,
std::shared_ptr<KeyValueBlockHeaderRepository> header_repo,
const BlockHandler &on_finalized_block_found )
{
auto block_storage = std::make_shared<KeyValueBlockStorage>(
KeyValueBlockStorage( std::move( db ), std::move( hasher ), std::move( header_repo ) ) );
OUTCOME_TRY( ( auto &&, last_finalized_block_hash ), block_storage->getLastFinalizedBlockHash() );
OUTCOME_TRY( ( auto &&, block_header ), block_storage->getBlockHeader( last_finalized_block_hash ) );
primitives::Block finalized_block;
finalized_block.header = block_header;
on_finalized_block_found( finalized_block );
return block_storage;
}
outcome::result<std::shared_ptr<KeyValueBlockStorage>> KeyValueBlockStorage::createWithGenesis(
base::Buffer state_root,
const std::shared_ptr<crdt::GlobalDB> &db,
std::shared_ptr<crypto::Hasher> hasher,
std::shared_ptr<KeyValueBlockHeaderRepository> header_repo,
const BlockHandler &on_genesis_created )
{
auto block_storage = std::make_shared<KeyValueBlockStorage>(
KeyValueBlockStorage( db, std::move( hasher ), std::move( header_repo ) ) );
BOOST_OUTCOME_TRYV2( auto &&, block_storage->ensureGenesisNotExists() );
// state root type is Hash256, however for consistency with spec root hash
// returns buffer. So we need this conversion
OUTCOME_TRY( ( auto &&, state_root_blob ), base::Hash256::fromSpan( state_root.toVector() ) );
auto extrinsics_root_buf = trieRoot( {} );
// same reason for conversion as few lines above
OUTCOME_TRY( ( auto &&, extrinsics_root ), base::Hash256::fromSpan( extrinsics_root_buf.toVector() ) );
// genesis block initialization
primitives::Block genesis_block;
genesis_block.header.number = 0;
genesis_block.header.extrinsics_root = extrinsics_root;
genesis_block.header.state_root = state_root_blob;
// the rest of the fields have default value
OUTCOME_TRY( ( auto &&, genesis_block_hash ), block_storage->putBlock( genesis_block ) );
BOOST_OUTCOME_TRYV2( auto &&,
db->Put( { storage::GetGenesisBlockHashLookupKey() }, Buffer{ genesis_block_hash }, { "topic" } ) );
BOOST_OUTCOME_TRYV2( auto &&, block_storage->setLastFinalizedBlockHash( genesis_block_hash ) );
on_genesis_created( genesis_block );
return block_storage;
}
outcome::result<primitives::BlockHeader> KeyValueBlockStorage::getBlockHeader( const primitives::BlockId &id ) const
{
return header_repo_->getBlockHeader( id );
}
outcome::result<primitives::BlockBody> KeyValueBlockStorage::getBlockBody( const primitives::BlockId &id ) const
{
OUTCOME_TRY( ( auto &&, block_data ), getBlockData( id ) );
if ( block_data.body )
{
return block_data.body.value();
}
return Error::BODY_DOES_NOT_EXIST;
}
outcome::result<primitives::BlockData> KeyValueBlockStorage::getBlockData( const primitives::BlockId &id ) const
{
OUTCOME_TRY( ( auto &&, encoded_block_data ), GetRawBlock( id ) );
auto block_data = GetBlockDataFromSerialized( encoded_block_data.toVector() );
return block_data;
}
outcome::result<primitives::Justification> KeyValueBlockStorage::getJustification(
const primitives::BlockId &block ) const
{
OUTCOME_TRY( ( auto &&, block_data ), getBlockData( block ) );
if ( block_data.justification )
{
return block_data.justification.value();
}
return Error::JUSTIFICATION_DOES_NOT_EXIST;
}
outcome::result<primitives::BlockHash> KeyValueBlockStorage::putBlockHeader( const primitives::BlockHeader &header )
{
return header_repo_->putBlockHeader( header );
}
outcome::result<void> KeyValueBlockStorage::putBlockData( primitives::BlockNumber block_number,
const primitives::BlockData &block_data )
{
primitives::BlockData to_insert;
// if block data does not exist, put a new one. Otherwise get the old one
// and merge with the new one. During the merge new block data fields have
// higher priority over the old ones (old ones should be rewritten)
auto existing_block_data_res = getBlockData( block_data.hash );
if ( !existing_block_data_res )
{
to_insert = block_data;
}
else
{
auto existing_data = existing_block_data_res.value();
// add all the fields from the new block_data
to_insert.header = block_data.header ? block_data.header : existing_data.header;
to_insert.body = block_data.body ? block_data.body : existing_data.body;
to_insert.justification = block_data.justification ? block_data.justification : existing_data.justification;
to_insert.message_queue = block_data.message_queue ? block_data.message_queue : existing_data.message_queue;
to_insert.receipt = block_data.receipt ? block_data.receipt : existing_data.receipt;
}
//OUTCOME_TRY((auto &&, encoded_block_data), scale::encode(to_insert));
auto encoded_block_data = GetSerializedBlockData( to_insert );
OUTCOME_TRY( ( auto &&, id_string ), idToStringKey( *db_, block_number ) );
//TODO - For now one block data per block header. Revisit this
BOOST_OUTCOME_TRYV2(
auto &&,
db_->Put( { header_repo_->GetHeaderPath() + id_string + "/tx/0" },
Buffer{ encoded_block_data },
{ "topic" } ) );
//BOOST_OUTCOME_TRYV2(auto &&, putWithPrefix(*db_,
// Prefix::BLOCK_DATA,
// block_number,
// block_data.hash,
// Buffer{encoded_block_data}));
return outcome::success();
}
outcome::result<primitives::BlockHash> KeyValueBlockStorage::putBlock( const primitives::Block &block )
{
// TODO(xDimon): Need to implement mechanism for wipe out orphan blocks
// (in side-chains whom rejected by finalization)
// for avoid leaks of storage space
auto block_hash = hasher_->blake2b_256( header_repo_->GetHeaderSerializedData( block.header ) );
//auto block_in_storage_res =
// getWithPrefix(*db_, Prefix::HEADER, block_hash);
auto block_in_storage_res = header_repo_->getBlockHeader( block_hash );
if ( block_in_storage_res.has_value() )
{
return Error::BLOCK_EXISTS;
}
if ( block_in_storage_res != outcome::failure( blockchain::Error::BLOCK_NOT_FOUND ) )
{
return block_in_storage_res.error();
}
// insert our block's parts into the database-
BOOST_OUTCOME_TRYV2( auto &&, putBlockHeader( block.header ) );
primitives::BlockData block_data;
block_data.hash = block_hash;
block_data.header = block.header;
block_data.body = block.body;
BOOST_OUTCOME_TRYV2( auto &&, putBlockData( block.header.number, block_data ) );
logger_->info( "Added block. Number: {}. Hash: {}. State root: {}",
block.header.number,
block_hash.toHex(),
block.header.state_root.toHex() );
return block_hash;
}
outcome::result<void> KeyValueBlockStorage::putJustification( const primitives::Justification &j,
const primitives::BlockHash &hash,
const primitives::BlockNumber &block_number )
{
// insert justification into the database as a part of BlockData
// primitives::BlockData block_data{.hash = hash, .justification = j};
primitives::BlockData block_data;
block_data.hash = hash;
block_data.justification = j;
BOOST_OUTCOME_TRYV2( auto &&, putBlockData( block_number, block_data ) );
return outcome::success();
}
outcome::result<void> KeyValueBlockStorage::removeBlock( const primitives::BlockHash &hash,
const primitives::BlockNumber &number )
{
auto header_rm_res = header_repo_->removeBlockHeader( number );
if ( header_rm_res.has_failure() )
{
return header_rm_res;
}
OUTCOME_TRY( ( auto &&, key ), idToBufferKey( *db_, number ) );
//TODO - For now one block data per block header. Revisit this
OUTCOME_TRY(db_->Remove( { header_repo_->GetHeaderPath() + std::string( key.toString() ) + "/tx/0" }, { "topic" } ) );
return outcome::success();
}
outcome::result<primitives::BlockHash> KeyValueBlockStorage::getGenesisBlockHash() const
{
auto hash_res = db_->Get( { storage::GetGenesisBlockHashLookupKey() } );
if ( hash_res.has_value() )
{
primitives::BlockHash hash;
std::copy( hash_res.value().begin(), hash_res.value().end(), hash.begin() );
return hash;
}
if ( hash_res == outcome::failure( storage::DatabaseError::NOT_FOUND ) )
{
return Error::GENESIS_BLOCK_NOT_FOUND;
}
return hash_res.as_failure();
}
outcome::result<primitives::BlockHash> KeyValueBlockStorage::getLastFinalizedBlockHash() const
{
auto hash_res = db_->Get( { storage::GetLastFinalizedBlockHashLookupKey() } );
if ( hash_res.has_value() )
{
primitives::BlockHash hash;
SGBlocks::BlockHashData hash_proto;
if ( !hash_proto.ParseFromArray( hash_res.value().data(), hash_res.value().size() ) )
{
std::cerr << "Failed to parse BlockPayloadData from array." << std::endl;
}
hash = ( base::Hash256::fromReadableString( hash_proto.hash() ) ).value();
return hash;
}
if ( hash_res == outcome::failure( storage::DatabaseError::NOT_FOUND ) )
{
return Error::FINALIZED_BLOCK_NOT_FOUND;
}
return hash_res.as_failure();
}
outcome::result<void> KeyValueBlockStorage::setLastFinalizedBlockHash( const primitives::BlockHash &hash )
{
SGBlocks::BlockHashData hash_proto;
hash_proto.set_hash( hash.toReadableString() );
size_t size = hash_proto.ByteSizeLong();
std::vector<uint8_t> serialized_proto( size );
hash_proto.SerializeToArray( serialized_proto.data(), serialized_proto.size() );
BOOST_OUTCOME_TRYV2(
auto &&,
db_->Put( { storage::GetLastFinalizedBlockHashLookupKey() }, Buffer{ serialized_proto }, { "topic" } ) );
return outcome::success();
}
outcome::result<void> KeyValueBlockStorage::ensureGenesisNotExists() const
{
auto res = getLastFinalizedBlockHash();
if ( res.has_value() )
{
return Error::GENESIS_BLOCK_ALREADY_EXISTS;
}
return outcome::success();
}
std::vector<uint8_t> KeyValueBlockStorage::GetSerializedBlockData( const primitives::BlockData &block_data )
{
SGBlocks::BlockPayloadData data_proto;
SGBlocks::BlockHeaderData *header_data = data_proto.mutable_header();
if ( block_data.header )
{
header_data->set_parent_hash( block_data.header.value().parent_hash.toReadableString() );
header_data->set_block_number( block_data.header.value().number );
header_data->set_state_root( block_data.header.value().state_root.toReadableString() );
header_data->set_extrinsics_root( block_data.header.value().extrinsics_root.toReadableString() );
}
data_proto.set_hash( block_data.hash.toReadableString() );
if ( block_data.body )
{
for ( const auto &body_data : block_data.body.value() )
{
data_proto.add_block_body( std::string( body_data.data.toString() ) );
}
}
size_t size = data_proto.ByteSizeLong();
std::vector<uint8_t> serialized_proto( size );
data_proto.SerializeToArray( serialized_proto.data(), serialized_proto.size() );
return serialized_proto;
}
primitives::BlockData KeyValueBlockStorage::GetBlockDataFromSerialized(
const std::vector<uint8_t> &serialized_data ) const
{
primitives::BlockData block_data;
SGBlocks::BlockPayloadData data_proto;
if ( !data_proto.ParseFromArray( serialized_data.data(), serialized_data.size() ) )
{
std::cerr << "Failed to parse BlockPayloadData from array." << std::endl;
}
primitives::BlockHeader header;
header.parent_hash = ( base::Hash256::fromReadableString( data_proto.header().parent_hash() ) ).value();
header.number = data_proto.header().block_number();
header.state_root = ( base::Hash256::fromReadableString( data_proto.header().state_root() ) ).value();
header.extrinsics_root = ( base::Hash256::fromReadableString( data_proto.header().extrinsics_root() ) ).value();
block_data.header = header;
block_data.hash = ( base::Hash256::fromReadableString( data_proto.hash() ) ).value();
primitives::BlockBody body;
for ( int i = 0; i < data_proto.block_body_size(); ++i )
{
const std::string &tmp = data_proto.block_body( i );
primitives::Extrinsic curr = { base::Buffer{}.put( tmp ) };
body.push_back( curr );
}
block_data.body = body;
return block_data;
}
outcome::result<base::Buffer> KeyValueBlockStorage::GetRawBlock( const primitives::BlockId &id ) const
{
OUTCOME_TRY( ( auto &&, key ), idToBufferKey( *db_, id ) );
//TODO - For now one block data per block header. Revisit this
OUTCOME_TRY( ( auto &&, encoded_block_data ),
db_->Get( { header_repo_->GetHeaderPath() + std::string( key.toString() ) + "/tx/0" } ) );
return encoded_block_data;
}
}
Updated on 2026-03-04 at 13:10:44 -0800