Introducing ledger shards and new ledger syncing logic. (#247)

- The ledgers are stored in a sqlite database in ledger file system.
- Ledgers are organized in shards inside primary folder.
- Raw inputs are saved as shards inside blob folder. No input files are created if neither inputs nor outputs are available.
- Both last primary shard hash and last blob shard hashes are subjected to consensus and necessary sync operations are performed if out of sync.
- Hpfs sync support setting single sync targets from both ends of the list. (Targets set from front are prioritized).
- Contract and ledger syncs and serving are performed independently.
This commit is contained in:
Savinda Senevirathne
2021-02-18 11:24:05 +05:30
committed by GitHub
parent e394497698
commit 16c5b3fae2
53 changed files with 1917 additions and 2317 deletions

527
src/ledger/ledger.cpp Normal file
View File

@@ -0,0 +1,527 @@
#include "ledger.hpp"
#include "../crypto.hpp"
#include "../conf.hpp"
#include "../util/util.hpp"
#include "../msg/fbuf/ledger_helpers.hpp"
#include "../msg/fbuf/common_helpers.hpp"
#include "ledger_serve.hpp"
#define LEDGER_CREATE_ERROR \
{ \
if (db != NULL) \
sqlite::close_db(&db); \
ledger_fs.release_rw_session(); \
return -1; \
}
namespace ledger
{
ledger_context ctx;
constexpr uint32_t LEDGER_FS_ID = 1;
ledger::ledger_mount ledger_fs; // Global ledger file system instance.
ledger::ledger_sync ledger_sync_worker; // Global ledger file system sync instance.
ledger::ledger_serve ledger_server; // Ledger file server instance.
std::shared_mutex primary_index_file_mutex;
/**
* Perform ledger related initializations.
*/
int init()
{
if (ledger_fs.init(LEDGER_FS_ID, conf::ctx.ledger_hpfs_dir, conf::ctx.ledger_hpfs_mount_dir, conf::ctx.ledger_hpfs_rw_dir, conf::cfg.node.full_history) == -1)
{
LOG_ERROR << "Ledger file system initialization failed.";
return -1;
}
if (ledger_server.init("ledger", &ledger_fs) == -1)
{
LOG_ERROR << "Ledger file system serve worker initialization failed.";
return -1;
}
if (ledger_sync_worker.init("ledger", &ledger_fs) == -1)
{
LOG_ERROR << "Ledger file system sync worker initialization failed.";
return -1;
}
if (get_last_ledger_and_update_context() == -1)
{
LOG_ERROR << "Getting last ledger faild.";
return -1;
}
return 0;
}
/**
* Perform deinit tasks related to ledger.
*/
void deinit()
{
ledger_sync_worker.deinit();
ledger_server.deinit();
ledger_fs.deinit();
}
/**
* Create and save ledger record from the given proposal message.
* @param proposal Consensus-reached Stage 3 proposal.
* @param candidate_user_inputs Raw inputs received in this consensus round.
* @param generated_user_outputs Generated raw outputs in this consensus round.
* @return Returns 0 on success -1 on error.
*/
int save_ledger(const p2p::proposal &proposal, const std::map<std::string, consensus::candidate_user_input> &candidate_user_inputs,
const std::map<std::string, consensus::generated_user_output> &generated_user_outputs)
{
uint64_t seq_no = 0;
std::string prev_ledger_hash_hex;
if (extract_lcl(proposal.lcl, seq_no, prev_ledger_hash_hex) == -1)
{
// lcl records should follow [ledger sequnce numer]-[lcl hex] format.
LOG_ERROR << "Invalid lcl name: " << proposal.lcl << " when saving ledger.";
return -1;
}
seq_no++; // New lcl sequence number.
// Aqure hpfs rw session before accessing shards and insert ledger records.
if (ledger_fs.acquire_rw_session() == -1)
return -1;
sqlite3 *db = NULL;
// Prepare shard folders and database and get the primary shard sequence number.
uint64_t primary_shard_seq_no;
if (prepare_shard(&db, primary_shard_seq_no, seq_no) == -1)
LEDGER_CREATE_ERROR;
// Combined binary hash of consensus user binary pub keys.
const std::string user_hash = crypto::get_hash(proposal.users);
// Combined binary hash of consensus input hashes.
const std::string input_hash = crypto::get_hash(proposal.input_hashes);
uint8_t seq_no_byte_str[8], time_byte_str[8];
util::uint64_to_bytes(seq_no_byte_str, seq_no);
util::uint64_to_bytes(time_byte_str, proposal.time);
// Contruct binary string for data hash.
std::string data;
data.reserve(sizeof(seq_no_byte_str) + sizeof(time_byte_str) + (sizeof(util::h32) * 5));
data.append((char *)seq_no_byte_str);
data.append((char *)time_byte_str);
data.append(proposal.state_hash.to_string_view());
data.append(proposal.patch_hash.to_string_view());
data.append(user_hash);
data.append(input_hash);
data.append(proposal.output_hash);
// Combined binary hash of data fields. blake3(seq_no + time + state_hash + patch_hash + user_hash + input_hash + output_hash)
const std::string data_hash = crypto::get_hash(data);
const std::string prev_ledger_hash = util::to_bin(prev_ledger_hash_hex);
// Ledger hash is the combined hash of previous ledger hash and the new data hash.
const std::string ledger_hash = crypto::get_hash(prev_ledger_hash, data_hash);
const std::string ledger_hash_hex = util::to_hex(ledger_hash);
// Construct ledger struct.
// Hashes are stored as hex string;
const sqlite::ledger ledger(
seq_no,
proposal.time,
ledger_hash_hex,
prev_ledger_hash_hex,
util::to_hex(data_hash),
util::to_hex(proposal.state_hash.to_string_view()),
util::to_hex(proposal.patch_hash.to_string_view()),
util::to_hex(user_hash),
util::to_hex(input_hash),
util::to_hex(proposal.output_hash)); // Merkle root output hash.
if (sqlite::insert_ledger_row(db, ledger) == -1)
{
LOG_ERROR << errno << ": Error creating the ledger, shard: " << std::to_string(primary_shard_seq_no);
LEDGER_CREATE_ERROR;
}
if ((!candidate_user_inputs.empty() || !generated_user_outputs.empty()) && save_ledger_blob(ledger_hash, candidate_user_inputs, generated_user_outputs) == -1)
{
LOG_ERROR << errno << ": Error saving the raw inputs/outputs, shard: " << std::to_string(primary_shard_seq_no);
LEDGER_CREATE_ERROR;
}
// Update the seq_no and lcl when ledger is created.
const std::string new_lcl = std::string(std::to_string(seq_no)).append("-").append(ledger_hash_hex);
ctx.set_lcl(seq_no, new_lcl);
const std::string shard_vpath = std::string(ledger::PRIMARY_DIR).append("/").append(std::to_string(primary_shard_seq_no));
util::h32 last_primary_shard_hash;
if (ledger_fs.get_hash(last_primary_shard_hash, hpfs::RW_SESSION_NAME, shard_vpath) == -1)
{
LOG_ERROR << errno << ": Error reading shard hash: " << std::to_string(primary_shard_seq_no);
LEDGER_CREATE_ERROR;
}
// Update the last shard hash and shard seqence number tracker when a new ledger is created.
ctx.set_last_primary_shard_id(p2p::sequence_hash{primary_shard_seq_no, last_primary_shard_hash});
//Remove old shards that exceeds max shard range.
if (conf::cfg.node.max_shards > 0 && primary_shard_seq_no >= conf::cfg.node.max_shards)
{
remove_old_shards(primary_shard_seq_no - conf::cfg.node.max_shards + 1, PRIMARY_DIR);
}
sqlite::close_db(&db);
return ledger_fs.release_rw_session();
}
/**
* Opens a db connection to a shard and populates the shard_seq_no.
* @param db Database connection to be openned.
* @param ledger_seq_no Ledger sequence number.
* @return Returns 0 on success -1 on failure.
*/
int prepare_shard(sqlite3 **db, uint64_t &shard_seq_no, const uint64_t ledger_seq_no)
{
// Construct shard path.
shard_seq_no = (ledger_seq_no - 1) / PRIMARY_SHARD_SIZE;
const std::string shard_path = ledger_fs.physical_path(hpfs::RW_SESSION_NAME, std::string(ledger::PRIMARY_DIR).append("/").append(std::to_string(shard_seq_no)));
// If (seq_no - 1) % PRIMARY_SHARD_SIZE == 0 means this is the first ledger of the shard.
// So create the shard folder and ledger table.
if ((ledger_seq_no - 1) % PRIMARY_SHARD_SIZE == 0)
{
// Creating the directory.
if (util::create_dir_tree_recursive(shard_path) == -1)
{
LOG_ERROR << errno << ": Error creating the shard, shard: " << std::to_string(shard_seq_no);
return -1;
}
// Creating ledger database and open a database connection.
if (sqlite::open_db(shard_path + "/" + DATEBASE, db) == -1)
{
LOG_ERROR << errno << ": Error openning the shard database, shard: " << std::to_string(shard_seq_no);
return -1;
}
// Creating the ledger table.
if (sqlite::create_ledger_table(*db) == -1)
{
LOG_ERROR << errno << ": Error creating the shard table, shard: " << std::to_string(shard_seq_no);
return -1;
}
util::h32 prev_shard_hash;
if (shard_seq_no > 0)
{
const std::string prev_shard_vpath = std::string(ledger::PRIMARY_DIR).append("/").append(std::to_string(shard_seq_no - 1));
if (ledger_fs.get_hash(prev_shard_hash, hpfs::RW_SESSION_NAME, prev_shard_vpath) < 1)
{
LOG_ERROR << errno << ": Error getting shard hash in vpath: " << prev_shard_vpath << " for previous shard hash.";
return -1;
}
}
// Write the prev_shard.hash to the new folder.
const std::string shard_hash_file_path = shard_path + PREV_SHARD_HASH_FILENAME;
const int fd = open(shard_hash_file_path.data(), O_CREAT | O_RDWR, FILE_PERMS);
if (fd == -1)
{
LOG_ERROR << errno << ": Error creating prev_shard.hash file in shard " << std::to_string(shard_seq_no);
return -1;
}
if (write(fd, &prev_shard_hash, sizeof(util::h32)) == -1)
{
LOG_ERROR << errno << ": Error writing to " << shard_hash_file_path << ".";
close(fd);
return -1;
}
close(fd);
}
else if (sqlite::open_db(shard_path + "/" + DATEBASE, db) == -1)
{
LOG_ERROR << errno << ": Error openning the shard database, shard: " << std::to_string(shard_seq_no);
return -1;
}
return 0;
}
/**
* Remove old shards that exceeds max shard range from file system.
* @param led_shard_no minimum shard number to be in history.
*/
void remove_old_shards(const uint64_t led_shard_no, std::string_view shard_parent_dir)
{
// Remove old shards if full history mode is not enabled.
if (!conf::cfg.node.full_history)
{
const std::string shard_dir_path = ledger_fs.physical_path(hpfs::RW_SESSION_NAME, shard_parent_dir);
std::list<std::string> shards = util::fetch_dir_entries(shard_dir_path);
for (const std::string shard : shards)
{
uint64_t primary_shard_seq_no;
util::stoull(shard, primary_shard_seq_no);
if (primary_shard_seq_no < led_shard_no)
{
const std::string shard_path = std::string(shard_dir_path).append("/").append(shard);
if (util::is_dir_exists(shard_path) && util::remove_directory_recursively(shard_path) == -1)
{
LOG_ERROR << errno << ": Error deleting shard: " << shard;
}
}
}
}
}
/**
* Extract seq_no and hash from lcl.
* @param lcl lcl to be extracted.
* @param seq_no Extracted sequence number.
* @param hash Extracted hash.
* @return Returns 0 on success -1 on error.
*/
int extract_lcl(const std::string &lcl, uint64_t &seq_no, std::string &hash)
{
if (lcl == GENESIS_LEDGER)
{
seq_no = 0;
hash = lcl.substr(2);
return 0;
}
const size_t pos = lcl.find("-");
if (pos == std::string::npos)
return -1;
if (util::stoull(lcl.substr(0, pos), seq_no) == -1)
return -1;
hash = lcl.substr(pos + 1);
if (hash.size() != 64)
return -1;
return 0;
}
/**
* Save raw data from the consensused proposal. A blob file is only created if there is any user inputs or contract outputs
* to save disk space.
* @param ledger_hash Hash of this ledger we are saving.
* @param candidate_user_inputs Raw inputs received in this consensus round.
* @param generated_user_outputs Generated raw outputs in this consensus round.
* @return Returns 0 on success -1 on error.
*/
int save_ledger_blob(std::string_view ledger_hash, const std::map<std::string, consensus::candidate_user_input> &candidate_user_inputs,
const std::map<std::string, consensus::generated_user_output> &generated_user_outputs)
{
// Construct shard path.
uint64_t last_blob_shard_seq_no = ctx.get_last_blob_shard_id().seq_no;
std::string shard_vpath = std::string(ledger::BLOB_DIR).append("/").append(std::to_string(last_blob_shard_seq_no));
std::string shard_path = ledger_fs.physical_path(hpfs::RW_SESSION_NAME, shard_vpath);
bool should_create_folder = false;
if (util::is_dir_exists(shard_path))
{
if ((util::fetch_dir_entries(shard_path).size() - 1) >= BLOB_SHARD_SIZE)
{
should_create_folder = true;
last_blob_shard_seq_no++;
shard_vpath = std::string(ledger::BLOB_DIR).append("/").append(std::to_string(last_blob_shard_seq_no));
shard_path = ledger_fs.physical_path(hpfs::RW_SESSION_NAME, shard_vpath);
}
}
else
{
should_create_folder = true;
}
// Create the required shard folder if not already existing.
if (should_create_folder)
{
// Creating the directory.
if (util::create_dir_tree_recursive(shard_path) == -1)
{
LOG_ERROR << errno << ": Error creating the blob shard, shard: " << std::to_string(last_blob_shard_seq_no);
ledger_fs.release_rw_session();
return -1;
}
util::h32 prev_shard_hash;
if (last_blob_shard_seq_no > 0)
{
const std::string prev_shard_vpath = std::string(ledger::BLOB_DIR).append("/").append(std::to_string(last_blob_shard_seq_no - 1));
if (ledger_fs.get_hash(prev_shard_hash, hpfs::RW_SESSION_NAME, prev_shard_vpath) < 1)
{
LOG_ERROR << errno << ": Error getting blob shard hash in vpath: " << prev_shard_vpath << " for previous shard hash.";
ledger_fs.release_rw_session();
return -1;
}
}
// Write the prev_shard.hash to the new folder.
const std::string shard_hash_file_path = shard_path + PREV_SHARD_HASH_FILENAME;
const int fd = open(shard_hash_file_path.data(), O_CREAT | O_RDWR, FILE_PERMS);
if (fd == -1)
{
LOG_ERROR << errno << ": Error creating prev_shard.hash file in blob shard " << std::to_string(last_blob_shard_seq_no);
return -1;
}
if (write(fd, &prev_shard_hash, sizeof(util::h32)) == -1)
{
LOG_ERROR << errno << ": Error writing to " << shard_hash_file_path << ".";
close(fd);
return -1;
}
close(fd);
}
ledger_blob blob;
blob.ledger_hash = ledger_hash;
for (const auto &[hash, user_input] : candidate_user_inputs)
{
std::string input;
if (usr::input_store.read_buf(user_input.input, input) != -1)
{
const auto itr = blob.inputs.find(user_input.userpubkey);
if (itr == blob.inputs.end())
blob.inputs.emplace(user_input.userpubkey, std::vector<std::string>());
blob.inputs[user_input.userpubkey].push_back(input);
}
}
for (const auto &[hash, user_output] : generated_user_outputs)
{
std::vector<std::string> outputs;
for (const auto &output : user_output.outputs)
{
outputs.push_back(output.message);
}
blob.outputs.emplace(user_output.userpubkey, outputs);
}
flatbuffers::FlatBufferBuilder builder(1024);
msg::fbuf::ledgermsg::create_ledger_blob_msg_from_ledger_blob(builder, blob);
const std::string file_path = shard_path + "/" + util::to_hex(ledger_hash) + ".blob";
const int fd = open(file_path.data(), O_CREAT | O_RDWR, FILE_PERMS);
if (fd == -1)
{
LOG_ERROR << errno << ": Error creating ledger blob file. " << file_path;
return -1;
}
if (write(fd, builder.GetBufferPointer(), builder.GetSize()) == -1)
{
LOG_ERROR << errno << ": Error writing to ledger blob file. " << file_path;
close(fd);
return -1;
}
close(fd);
util::h32 last_shard_hash;
if (ledger_fs.get_hash(last_shard_hash, hpfs::RW_SESSION_NAME, shard_vpath) == -1)
{
LOG_ERROR << errno << ": Error reading blob shard hash: " << std::to_string(last_blob_shard_seq_no);
ledger_fs.release_rw_session();
return -1;
}
// Update the last blob shard hash and blob shard seqence number tracker when a new ledger is created.
ctx.set_last_blob_shard_id(p2p::sequence_hash{last_blob_shard_seq_no, last_shard_hash});
//Remove old shards that exceeds max shard range.
if (last_blob_shard_seq_no >= MAX_BLOB_SHARDS)
{
remove_old_shards(last_blob_shard_seq_no - MAX_BLOB_SHARDS + 1, BLOB_DIR);
}
return 0;
}
/**
* Get last ledger and update the context.
* @return Returns 0 on success -1 on error.
*/
int get_last_ledger_and_update_context()
{
// Aquire hpfs rw session before accessing shards and insert ledger records.
if (ledger_fs.acquire_rw_session() == -1)
return -1;
const std::string shard_dir_path = ledger_fs.physical_path(hpfs::RW_SESSION_NAME, ledger::PRIMARY_DIR);
std::list<std::string> shards = util::fetch_dir_entries(shard_dir_path);
if (shards.size() == 0)
{
ledger_fs.release_rw_session();
ctx.set_lcl(0, GENESIS_LEDGER);
}
else
{
sqlite3 *db = NULL;
shards.sort([](std::string &a, std::string &b) {
uint64_t seq_no_a, seq_no_b;
util::stoull(a, seq_no_a);
util::stoull(b, seq_no_b);
return seq_no_a > seq_no_b;
});
uint64_t last_primary_shard_seq_no;
util::stoull(shards.front(), last_primary_shard_seq_no);
const std::string shard_path = std::string(shard_dir_path).append("/").append(shards.front());
// Open a database connection.
if (sqlite::open_db(shard_path + "/" + DATEBASE, &db) == -1)
{
LOG_ERROR << errno << ": Error openning the shard database, shard: " << shards.front();
ledger_fs.release_rw_session();
return -1;
}
sqlite::ledger last_ledger = sqlite::get_last_ledger(db);
sqlite::close_db(&db);
ctx.set_lcl(last_ledger.seq_no, std::to_string(last_ledger.seq_no) + "-" + last_ledger.ledger_hash_hex);
ledger_fs.release_rw_session();
}
return 0;
}
/**
* Get the hash and shard sequence number of the last shard in the ledger primary directory.
* @param session_name Hpfs session name.
* @param last_shard_id Struct which holds last shard data. (sequence number and hash).
* @param shard_parent_dir Parent director vpath of the shards.
* @return
*/
int get_last_shard_info(std::string_view session_name, p2p::sequence_hash &last_shard_id, std::string_view shard_parent_dir)
{
const std::string shard_dir_path = ledger_fs.physical_path(session_name, shard_parent_dir);
std::list<std::string> shards = util::fetch_dir_entries(shard_dir_path);
if (shards.size() > 0)
{
shards.sort([](std::string &a, std::string &b) {
uint64_t seq_no_a, seq_no_b;
util::stoull(a, seq_no_a);
util::stoull(b, seq_no_b);
return seq_no_a > seq_no_b;
});
const std::string shard_path = std::string(shard_parent_dir).append("/").append(shards.front());
if (ledger_fs.get_hash(last_shard_id.hash, session_name, shard_path) == -1 || util::stoull(shards.front(), last_shard_id.seq_no) == -1)
{
LOG_ERROR << "Error reading last shard hash in " << shard_path;
return -1;
}
}
return 0;
}
} // namespace ledger