/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2017-2019 Telegram Systems LLP */ #include "ltdb.hpp" #include "rootdb.hpp" #include "auto/tl/ton_api.h" #include "ton/ton-tl.hpp" #include "ton/ton-io.hpp" #include "td/db/RocksDb.h" namespace ton { namespace validator { void LtDb::add_new_block(BlockIdExt block_id, LogicalTime lt, UnixTime ts, td::Promise promise) { auto key = get_desc_key(block_id.shard_full()); std::string value; auto R = kv_->get(key.as_slice(), value); R.ensure(); tl_object_ptr v; bool add_shard = false; if (R.move_as_ok() == td::KeyValue::GetStatus::Ok) { auto F = fetch_tl_object(td::BufferSlice{value}, true); F.ensure(); v = F.move_as_ok(); } else { v = create_tl_object(1, 1, 0, 0, 0); add_shard = true; } if (block_id.id.seqno <= static_cast(v->last_seqno_) || lt <= static_cast(v->last_lt_) || ts <= static_cast(v->last_ts_)) { promise.set_value(td::Unit()); return; } auto db_value = create_serialize_tl_object(create_tl_block_id(block_id), lt, ts); auto db_key = get_el_key(block_id.shard_full(), v->last_idx_++); auto status_key = create_serialize_tl_object(); v->last_seqno_ = block_id.id.seqno; v->last_lt_ = lt; v->last_ts_ = ts; td::uint32 idx = 0; if (add_shard) { auto G = kv_->get(status_key.as_slice(), value); G.ensure(); if (G.move_as_ok() == td::KeyValue::GetStatus::NotFound) { idx = 0; } else { auto F = fetch_tl_object(value, true); F.ensure(); auto f = F.move_as_ok(); idx = f->total_shards_; } } kv_->begin_transaction().ensure(); kv_->set(key, serialize_tl_object(v, true)).ensure(); kv_->set(db_key, db_value.as_slice()).ensure(); if (add_shard) { auto shard_key = create_serialize_tl_object(idx); auto shard_value = create_serialize_tl_object(block_id.id.workchain, block_id.id.shard); kv_->set(status_key.as_slice(), create_serialize_tl_object(idx + 1)).ensure(); kv_->set(shard_key.as_slice(), shard_value.as_slice()).ensure(); } kv_->commit_transaction().ensure(); promise.set_value(td::Unit()); } void LtDb::get_block_common(AccountIdPrefixFull account_id, std::function compare_desc, std::function compare, bool exact, td::Promise promise) { bool f = false; BlockIdExt block_id; td::uint32 ls = 0; for (td::uint32 len = 0; len <= 60; len++) { auto s = shard_prefix(account_id, len); auto key = get_desc_key(s); std::string value; auto F = kv_->get(key, value); F.ensure(); if (F.move_as_ok() == td::KeyValue::GetStatus::NotFound) { if (!f) { continue; } else { break; } } f = true; auto G = fetch_tl_object(td::BufferSlice{value}, true); G.ensure(); auto g = G.move_as_ok(); if (compare_desc(*g.get()) > 0) { continue; } td::uint32 l = g->first_idx_ - 1; BlockIdExt lseq; td::uint32 r = g->last_idx_; BlockIdExt rseq; while (r - l > 1) { auto x = (r + l) / 2; auto db_key = get_el_key(s, x); F = kv_->get(db_key, value); F.ensure(); CHECK(F.move_as_ok() == td::KeyValue::GetStatus::Ok); auto E = fetch_tl_object(td::BufferSlice{value}, true); E.ensure(); auto e = E.move_as_ok(); int cmp_val = compare(*e.get()); if (cmp_val < 0) { rseq = create_block_id(e->id_); r = x; } else if (cmp_val > 0) { lseq = create_block_id(e->id_); l = x; } else { promise.set_value(create_block_id(e->id_)); return; } } if (rseq.is_valid()) { if (!block_id.is_valid()) { block_id = rseq; } else if (block_id.id.seqno > rseq.id.seqno) { block_id = rseq; } } if (lseq.is_valid()) { if (ls < lseq.id.seqno) { ls = lseq.id.seqno; } } if (block_id.is_valid() && ls + 1 == block_id.id.seqno) { if (!exact) { promise.set_value(std::move(block_id)); } else { promise.set_error(td::Status::Error(ErrorCode::notready, "ltdb: block not found")); } return; } } if (!exact && block_id.is_valid()) { promise.set_value(std::move(block_id)); } else { promise.set_error(td::Status::Error(ErrorCode::notready, "ltdb: block not found")); } } void LtDb::get_block_by_lt(AccountIdPrefixFull account_id, LogicalTime lt, td::Promise promise) { return get_block_common( account_id, [lt](ton_api::db_lt_desc_value &w) { return lt > static_cast(w.last_lt_) ? 1 : lt == static_cast(w.last_lt_) ? 0 : -1; }, [lt](ton_api::db_lt_el_value &w) { return lt > static_cast(w.lt_) ? 1 : lt == static_cast(w.lt_) ? 0 : -1; }, false, std::move(promise)); } void LtDb::get_block_by_seqno(AccountIdPrefixFull account_id, BlockSeqno seqno, td::Promise promise) { return get_block_common( account_id, [seqno](ton_api::db_lt_desc_value &w) { return seqno > static_cast(w.last_seqno_) ? 1 : seqno == static_cast(w.last_seqno_) ? 0 : -1; }, [seqno](ton_api::db_lt_el_value &w) { return seqno > static_cast(w.id_->seqno_) ? 1 : seqno == static_cast(w.id_->seqno_) ? 0 : -1; }, true, std::move(promise)); } void LtDb::get_block_by_unix_time(AccountIdPrefixFull account_id, UnixTime ts, td::Promise promise) { return get_block_common( account_id, [ts](ton_api::db_lt_desc_value &w) { return ts > static_cast(w.last_ts_) ? 1 : ts == static_cast(w.last_ts_) ? 0 : -1; }, [ts](ton_api::db_lt_el_value &w) { return ts > static_cast(w.ts_) ? 1 : ts == static_cast(w.ts_) ? 0 : -1; }, false, std::move(promise)); } td::BufferSlice LtDb::get_desc_key(ShardIdFull shard) { return create_serialize_tl_object(shard.workchain, shard.shard); } td::BufferSlice LtDb::get_el_key(ShardIdFull shard, td::uint32 idx) { return create_serialize_tl_object(shard.workchain, shard.shard, idx); } void LtDb::start_up() { kv_ = std::make_shared(td::RocksDb::open(db_path_).move_as_ok()); } void LtDb::truncate_workchain(ShardIdFull shard, td::Ref state) { auto key = get_desc_key(shard); std::string value; auto R = kv_->get(key, value); R.ensure(); CHECK(R.move_as_ok() == td::KeyValue::GetStatus::Ok); auto F = fetch_tl_object(td::BufferSlice{value}, true); F.ensure(); auto f = F.move_as_ok(); auto shards = state->get_shards(); BlockSeqno seqno = 0; if (shard.is_masterchain()) { seqno = state->get_seqno(); } else { for (auto s : shards) { if (shard_intersects(s->shard(), shard)) { seqno = s->top_block_id().seqno(); break; } } } while (f->last_idx_ > f->first_idx_) { auto db_key = get_el_key(shard, f->last_idx_ - 1); R = kv_->get(db_key, value); R.ensure(); CHECK(R.move_as_ok() == td::KeyValue::GetStatus::Ok); auto E = fetch_tl_object(td::BufferSlice{value}, true); E.ensure(); auto e = E.move_as_ok(); bool to_delete = static_cast(e->id_->seqno_) > seqno; if (!to_delete) { break; } else { f->last_idx_--; kv_->erase(db_key).ensure(); } } if (f->first_idx_ == f->last_idx_) { f->last_ts_ = 0; f->last_lt_ = 0; f->last_seqno_ = 0; } kv_->set(key, serialize_tl_object(f, true)).ensure(); } void LtDb::truncate(td::Ref state, td::Promise promise) { auto status_key = create_serialize_tl_object(); td::Result R; td::uint32 total_shards = 0; { std::string value; R = kv_->get(status_key.as_slice(), value); R.ensure(); if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) { promise.set_value(td::Unit()); return; } auto F = fetch_tl_object(value, true); F.ensure(); auto f = F.move_as_ok(); total_shards = f->total_shards_; if (total_shards == 0) { promise.set_value(td::Unit()); return; } } kv_->begin_transaction().ensure(); for (td::uint32 idx = 0; idx < total_shards; idx++) { auto shard_key = create_serialize_tl_object(idx); std::string value; R = kv_->get(shard_key.as_slice(), value); R.ensure(); CHECK(R.move_as_ok() == td::KeyValue::GetStatus::Ok); auto F = fetch_tl_object(value, true); F.ensure(); auto f = F.move_as_ok(); truncate_workchain(ShardIdFull{f->workchain_, static_cast(f->shard_)}, state); } kv_->commit_transaction().ensure(); promise.set_value(td::Unit()); } } // namespace validator } // namespace ton