Skip to content

Commit dfe997e

Browse files
Storage stat cache (#1724)
Co-authored-by: SpyCheese <[email protected]>
1 parent 95acdb1 commit dfe997e

File tree

14 files changed

+282
-11
lines changed

14 files changed

+282
-11
lines changed

crypto/block/account-storage-stat.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class AccountStorageStat {
5757
return root.is_null() ? td::Bits256::zero() : td::Bits256{root->get_hash().bits()};
5858
}
5959

60+
bool is_dict_ready() const {
61+
return dict_up_to_date_;
62+
}
63+
6064
void apply_child_stat(AccountStorageStat &&child);
6165

6266
static constexpr int errorcode_limits_exceeded = 999;

crypto/block/transaction.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,44 @@ bool Account::init_new(ton::UnixTime now) {
561561
return true;
562562
}
563563

564+
/**
565+
* Initializes account_storage_stat of the account using the existing dict_root.
566+
* This is not strictly necessary, as the storage stat is recomputed in Transaction.
567+
* However, it can be used to optimize cell usage.
568+
* This requires storage_dict_hash to be set, as it guarantees that the stored storage_used was computed recently
569+
* (in older versions it included extra currency balance, in newer versions it does not).
570+
*
571+
* @param dict_root Root of the storage dictionary.
572+
*
573+
* @returns Status of the operation.
574+
*/
575+
td::Status Account::init_account_storage_stat(Ref<vm::Cell> dict_root) {
576+
if (storage.is_null()) {
577+
if (dict_root.not_null()) {
578+
return td::Status::Error("storage is null, but dict_root is not null");
579+
}
580+
account_storage_stat = {};
581+
return td::Status::OK();
582+
}
583+
if (!storage_dict_hash) {
584+
return td::Status::Error("cannot init storage dict: storage_dict_hash is not set");
585+
}
586+
// Root of AccountStorage is not counted in AccountStorageStat
587+
if (storage_used.cells < 1 || storage_used.bits < storage->size()) {
588+
return td::Status::Error(PSTRING() << "storage_used is too small: cells=" << storage_used.cells
589+
<< " bits=" << storage_used.bits << " storage_root_bits=" << storage->size());
590+
}
591+
AccountStorageStat new_stat(std::move(dict_root), storage->prefetch_all_refs(), storage_used.cells - 1,
592+
storage_used.bits - storage->size());
593+
TRY_RESULT(root_hash, new_stat.get_dict_hash());
594+
if (storage_dict_hash.value() != root_hash) {
595+
return td::Status::Error(PSTRING() << "invalid storage dict hash: computed " << root_hash.to_hex() << ", found "
596+
<< storage_dict_hash.value().to_hex());
597+
}
598+
account_storage_stat = std::move(new_stat);
599+
return td::Status::OK();
600+
}
601+
564602
/**
565603
* Resets the fixed prefix length of the account.
566604
*

crypto/block/transaction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ struct Account {
298298
bool set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr);
299299
bool unpack(Ref<vm::CellSlice> account, ton::UnixTime now, bool special);
300300
bool init_new(ton::UnixTime now);
301+
td::Status init_account_storage_stat(Ref<vm::Cell> dict_root);
301302
bool deactivate();
302303
bool recompute_tmp_addr(Ref<vm::CellSlice>& tmp_addr, int fixed_prefix_length, td::ConstBitPtr orig_addr_rewrite) const;
303304
td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const;

tdutils/td/utils/LRUCache.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ namespace td {
2626
template <typename K, typename V>
2727
class LRUCache {
2828
public:
29-
explicit LRUCache(size_t max_size) : max_size_(max_size) {
30-
CHECK(max_size_ > 0);
29+
explicit LRUCache(uint64 max_size) : max_size_(max_size) {
3130
}
3231
LRUCache(const LRUCache&) = delete;
3332
LRUCache& operator=(const LRUCache&) = delete;
@@ -49,13 +48,14 @@ class LRUCache {
4948
return cache_.contains(key);
5049
}
5150

52-
bool put(const K& key, V value, bool update = true) {
51+
bool put(const K& key, V value, bool update = true, uint64 weight = 1) {
5352
bool added = false;
5453
auto it = cache_.find(key);
5554
if (it == cache_.end()) {
5655
update = true;
57-
it = cache_.insert(std::make_unique<Entry>(key, std::move(value))).first;
56+
it = cache_.insert(std::make_unique<Entry>(key, std::move(value), weight)).first;
5857
added = true;
58+
total_weight_ += weight;
5959
} else {
6060
(*it)->value = std::move(value);
6161
if (update) {
@@ -69,11 +69,12 @@ class LRUCache {
6969
return added;
7070
}
7171

72-
V& get(const K& key, bool update = true) {
72+
V& get(const K& key, bool update = true, uint64 weight = 1) {
7373
auto it = cache_.find(key);
7474
if (it == cache_.end()) {
7575
update = true;
76-
it = cache_.insert(std::make_unique<Entry>(key)).first;
76+
it = cache_.insert(std::make_unique<Entry>(key, weight)).first;
77+
total_weight_ += weight;
7778
} else if (update) {
7879
(*it)->remove();
7980
}
@@ -87,12 +88,13 @@ class LRUCache {
8788

8889
private:
8990
struct Entry : ListNode {
90-
explicit Entry(K key) : key(std::move(key)) {
91+
Entry(K key, uint64 weight) : key(std::move(key)), weight(weight) {
9192
}
92-
Entry(K key, V value) : key(std::move(key)), value(std::move(value)) {
93+
Entry(K key, V value, uint64 weight) : key(std::move(key)), value(std::move(value)), weight(weight) {
9394
}
9495
K key;
9596
V value;
97+
uint64 weight;
9698
};
9799
struct Cmp {
98100
using is_transparent = void;
@@ -108,13 +110,15 @@ class LRUCache {
108110
};
109111
std::set<std::unique_ptr<Entry>, Cmp> cache_;
110112
ListNode lru_;
111-
size_t max_size_;
113+
uint64 max_size_;
114+
uint64 total_weight_ = 0;
112115

113116
void cleanup() {
114-
while (cache_.size() > max_size_) {
117+
while (total_weight_ > max_size_ && cache_.size() > 1) {
115118
auto to_remove = (Entry*)lru_.get();
116119
CHECK(to_remove);
117120
to_remove->remove();
121+
total_weight_ -= to_remove->weight;
118122
cache_.erase(cache_.find(to_remove->key));
119123
}
120124
}

validator/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ set(VALIDATOR_HEADERS
5959
import-db-slice-local.hpp
6060
queue-size-counter.hpp
6161
validator-telemetry.hpp
62+
storage-stat-cache.hpp
6263

6364
manager-disk.h
6465
manager-disk.hpp
@@ -87,6 +88,7 @@ set(VALIDATOR_SOURCE
8788
validator-options.cpp
8889
queue-size-counter.cpp
8990
validator-telemetry.cpp
91+
storage-stat-cache.cpp
9092

9193
downloaders/wait-block-data.cpp
9294
downloaders/wait-block-state.cpp

validator/impl/collator-impl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ class Collator final : public td::actor::Actor {
227227
bool deferring_messages_enabled_ = false;
228228
bool store_out_msg_queue_size_ = false;
229229

230+
std::function<td::Ref<vm::Cell>(const td::Bits256&)> storage_stat_cache_;
231+
std::vector<std::pair<td::Ref<vm::Cell>, td::uint32>> storage_stat_cache_update_;
232+
230233
td::PerfWarningTimer perf_timer_;
231234
//
232235
block::Account* lookup_account(td::ConstBitPtr addr) const;
@@ -247,6 +250,7 @@ class Collator final : public td::actor::Actor {
247250
void after_get_shard_state(int idx, td::Result<Ref<ShardState>> res);
248251
void after_get_block_data(int idx, td::Result<Ref<BlockData>> res);
249252
void after_get_shard_blocks(td::Result<std::vector<Ref<ShardTopBlockDescription>>> res);
253+
void after_get_storage_stat_cache(td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res);
250254
bool preprocess_prev_mc_state();
251255
bool register_mc_state(Ref<MasterchainStateQ> other_mc_state);
252256
bool request_aux_mc_state(BlockSeqno seqno, Ref<MasterchainStateQ>& state);

validator/impl/collator.cpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,16 @@ void Collator::start_up() {
277277
td::actor::send_closure_later(std::move(self), &Collator::after_get_shard_blocks, std::move(res));
278278
});
279279
}
280-
// 6. set timeout
280+
// 6. get storage stat cache
281+
++pending;
282+
LOG(DEBUG) << "sending get_storage_stat_cache() query to Manager";
283+
td::actor::send_closure_later(
284+
manager, &ValidatorManager::get_storage_stat_cache,
285+
[self = get_self()](td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res) {
286+
LOG(DEBUG) << "got answer to get_storage_stat_cache() query";
287+
td::actor::send_closure_later(std::move(self), &Collator::after_get_storage_stat_cache, std::move(res));
288+
});
289+
// 7. set timeout
281290
alarm_timestamp() = timeout;
282291
CHECK(pending);
283292
}
@@ -686,6 +695,22 @@ void Collator::after_get_shard_blocks(td::Result<std::vector<Ref<ShardTopBlockDe
686695
check_pending();
687696
}
688697

698+
/**
699+
* Callback function called after retrieving storage stat cache.
700+
*
701+
* @param res The retrieved storage stat cache.
702+
*/
703+
void Collator::after_get_storage_stat_cache(td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res) {
704+
--pending;
705+
if (res.is_error()) {
706+
LOG(INFO) << "after_get_storage_stat_cache : " << res.error();
707+
} else {
708+
LOG(DEBUG) << "after_get_storage_stat_cache : OK";
709+
storage_stat_cache_ = res.move_as_ok();
710+
}
711+
check_pending();
712+
}
713+
689714
/**
690715
* Unpacks the last masterchain state and initializes the Collator object with the extracted configuration.
691716
*
@@ -2448,6 +2473,20 @@ std::unique_ptr<block::Account> Collator::make_account_from(td::ConstBitPtr addr
24482473
return nullptr;
24492474
}
24502475
ptr->block_lt = start_lt;
2476+
if (storage_stat_cache_ && ptr->storage_dict_hash) {
2477+
auto dict_root = storage_stat_cache_(ptr->storage_dict_hash.value());
2478+
if (dict_root.not_null()) {
2479+
auto S = ptr->init_account_storage_stat(dict_root);
2480+
if (S.is_error()) {
2481+
fatal_error(S.move_as_error_prefix(PSTRING() << "failed to init storage stat from cache for account "
2482+
<< addr.to_hex(256) << ": "));
2483+
return nullptr;
2484+
}
2485+
LOG(DEBUG) << "Inited storage stat from cache for account " << addr.to_hex(256) << " (" << ptr->storage_used.cells
2486+
<< " cells)";
2487+
storage_stat_cache_update_.emplace_back(dict_root, ptr->storage_used.cells);
2488+
}
2489+
}
24512490
return ptr;
24522491
}
24532492

@@ -2589,6 +2628,10 @@ bool Collator::combine_account_transactions() {
25892628
}
25902629
}
25912630
}
2631+
if (acc.storage_dict_hash && acc.account_storage_stat && acc.account_storage_stat.value().is_dict_ready()) {
2632+
storage_stat_cache_update_.emplace_back(acc.account_storage_stat.value().get_dict_root().move_as_ok(),
2633+
acc.storage_used.cells);
2634+
}
25922635
} else {
25932636
if (acc.total_state->get_hash() != acc.orig_total_state->get_hash()) {
25942637
return fatal_error(std::string{"total state of account "} + z.first.to_hex() +
@@ -5685,6 +5728,7 @@ bool Collator::create_block_candidate() {
56855728
stats_.cat_lt_delta = block_limit_status_->limits.classify_lt(block_limit_status_->cur_lt);
56865729
td::actor::send_closure(manager, &ValidatorManager::record_collate_query_stats, block_candidate->id, work_time,
56875730
cpu_work_time, std::move(stats_));
5731+
td::actor::send_closure(manager, &ValidatorManager::update_storage_stat_cache, std::move(storage_stat_cache_update_));
56885732
return true;
56895733
}
56905734

validator/impl/validate-query.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,15 @@ void ValidateQuery::start_up() {
381381
return;
382382
}
383383
}
384+
// 5. get storage stat cache
385+
++pending;
386+
LOG(DEBUG) << "sending get_storage_stat_cache() query to Manager";
387+
td::actor::send_closure_later(
388+
manager, &ValidatorManager::get_storage_stat_cache,
389+
[self = get_self()](td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res) {
390+
LOG(DEBUG) << "got answer to get_storage_stat_cache() query";
391+
td::actor::send_closure_later(std::move(self), &ValidateQuery::after_get_storage_stat_cache, std::move(res));
392+
});
384393
// ...
385394
CHECK(pending);
386395
}
@@ -749,6 +758,26 @@ void ValidateQuery::got_mc_handle(td::Result<BlockHandle> res) {
749758
});
750759
}
751760

761+
/**
762+
* Callback function called after retrieving storage stat cache.
763+
*
764+
* @param res The retrieved storage stat cache.
765+
*/
766+
void ValidateQuery::after_get_storage_stat_cache(td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res) {
767+
--pending;
768+
if (res.is_error()) {
769+
LOG(INFO) << "after_get_storage_stat_cache : " << res.error();
770+
} else {
771+
LOG(DEBUG) << "after_get_storage_stat_cache : OK";
772+
storage_stat_cache_ = res.move_as_ok();
773+
}
774+
if (!pending) {
775+
if (!try_validate()) {
776+
fatal_error("cannot validate new block");
777+
}
778+
}
779+
}
780+
752781
/**
753782
* Callback function called after retrieving the shard state for a previous block.
754783
*
@@ -5133,6 +5162,20 @@ std::unique_ptr<block::Account> ValidateQuery::make_account_from(td::ConstBitPtr
51335162
return nullptr;
51345163
}
51355164
ptr->block_lt = start_lt_;
5165+
if (storage_stat_cache_ && ptr->storage_dict_hash) {
5166+
auto dict_root = storage_stat_cache_(ptr->storage_dict_hash.value());
5167+
if (dict_root.not_null()) {
5168+
auto S = ptr->init_account_storage_stat(dict_root);
5169+
if (S.is_error()) {
5170+
fatal_error(S.move_as_error_prefix(PSTRING() << "failed to init storage stat from cache for account "
5171+
<< addr.to_hex(256) << ": "));
5172+
return nullptr;
5173+
}
5174+
LOG(DEBUG) << "Inited storage stat from cache for account " << addr.to_hex(256) << " (" << ptr->storage_used.cells
5175+
<< " cells)";
5176+
storage_stat_cache_update_.emplace_back(dict_root, ptr->storage_used.cells);
5177+
}
5178+
}
51365179
return ptr;
51375180
}
51385181

@@ -5746,6 +5789,11 @@ bool ValidateQuery::check_account_transactions(const StdSmcAddress& acc_addr, Re
57465789
})) {
57475790
return reject_query("at least one Transaction of account "s + acc_addr.to_hex() + " is invalid");
57485791
}
5792+
if (account.storage_dict_hash && account.account_storage_stat &&
5793+
account.account_storage_stat.value().is_dict_ready()) {
5794+
storage_stat_cache_update_.emplace_back(account.account_storage_stat.value().get_dict_root().move_as_ok(),
5795+
account.storage_used.cells);
5796+
}
57495797
if (is_masterchain() && account.libraries_changed()) {
57505798
return scan_account_libraries(account.orig_library, account.library, acc_addr);
57515799
} else {
@@ -6923,6 +6971,7 @@ bool ValidateQuery::save_candidate() {
69236971

69246972
td::actor::send_closure(manager, &ValidatorManager::set_block_candidate, id_, block_candidate.clone(),
69256973
validator_set_->get_catchain_seqno(), validator_set_->get_validator_set_hash(), std::move(P));
6974+
td::actor::send_closure(manager, &ValidatorManager::update_storage_stat_cache, std::move(storage_stat_cache_update_));
69266975
return true;
69276976
}
69286977

validator/impl/validate-query.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ class ValidateQuery : public td::actor::Actor {
236236
std::set<StdSmcAddress> account_expected_defer_all_messages_;
237237
td::uint64 old_out_msg_queue_size_ = 0, new_out_msg_queue_size_ = 0;
238238

239+
std::function<td::Ref<vm::Cell>(const td::Bits256&)> storage_stat_cache_;
240+
std::vector<std::pair<td::Ref<vm::Cell>, td::uint32>> storage_stat_cache_update_;
241+
239242
bool msg_metadata_enabled_ = false;
240243
bool deferring_messages_enabled_ = false;
241244
bool store_out_msg_queue_size_ = false;
@@ -289,6 +292,7 @@ class ValidateQuery : public td::actor::Actor {
289292
void after_get_latest_mc_state(td::Result<std::pair<Ref<MasterchainState>, BlockIdExt>> res);
290293
void after_get_mc_state(td::Result<Ref<ShardState>> res);
291294
void got_mc_handle(td::Result<BlockHandle> res);
295+
void after_get_storage_stat_cache(td::Result<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> res);
292296
void after_get_shard_state(int idx, td::Result<Ref<ShardState>> res);
293297
bool process_mc_state(Ref<MasterchainState> mc_state);
294298
bool try_unpack_mc_state();

validator/interfaces/validator-manager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ class ValidatorManager : public ValidatorManagerInterface {
221221

222222
virtual void add_persistent_state_description(td::Ref<PersistentStateDescription> desc) = 0;
223223

224+
virtual void get_storage_stat_cache(td::Promise<std::function<td::Ref<vm::Cell>(const td::Bits256&)>> promise) {
225+
promise.set_error(td::Status::Error("not implemented"));
226+
}
227+
virtual void update_storage_stat_cache(std::vector<std::pair<td::Ref<vm::Cell>, td::uint32>> data) {
228+
// not implemented
229+
}
230+
224231
static bool is_persistent_state(UnixTime ts, UnixTime prev_ts) {
225232
return ts / (1 << 17) != prev_ts / (1 << 17);
226233
}

0 commit comments

Comments
 (0)