Skip to content

Commit d3cec64

Browse files
committed
Actually deserialize split states
We don't yet store state parts in DB as this requires yet another instance of "change every single actor in the observable universe".
1 parent 09dbf72 commit d3cec64

File tree

6 files changed

+257
-13
lines changed

6 files changed

+257
-13
lines changed

crypto/vm/cells/MerkleProof.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Copyright 2017-2020 Telegram Systems LLP
1818
*/
1919
#include "vm/cells/MerkleProof.h"
20+
#include "td/utils/Status.h"
2021
#include "vm/cells/CellBuilder.h"
2122
#include "vm/cells/CellSlice.h"
2223
#include "vm/boc.h"
@@ -152,6 +153,11 @@ Ref<Cell> MerkleProof::virtualize(Ref<Cell> cell, int virtualization) {
152153
return virtualize_raw(r_raw.move_as_ok(), {0 /*level*/, static_cast<td::uint8>(virtualization)});
153154
}
154155

156+
td::Result<Ref<Cell>> MerkleProof::try_virtualize(Ref<Cell> cell, int virtualization) {
157+
TRY_RESULT(unpacked_cell, unpack_proof(std::move(cell)));
158+
return unpacked_cell->virtualize({0 /*level*/, static_cast<td::uint8>(virtualization)});
159+
}
160+
155161
class MerkleProofCombineFast {
156162
public:
157163
MerkleProofCombineFast(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {

crypto/vm/cells/MerkleProof.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class MerkleProof {
3636

3737
// cell must have zero level and must be a MerkleProof
3838
static Ref<Cell> virtualize(Ref<Cell> cell, int virtualization);
39+
static td::Result<Ref<Cell>> try_virtualize(Ref<Cell> cell, int virtualization = 1);
3940

4041
static Ref<Cell> combine(Ref<Cell> a, Ref<Cell> b);
4142
static td::Result<Ref<Cell>> combine_status(Ref<Cell> a, Ref<Cell> b);

validator/downloaders/download-state.cpp

Lines changed: 231 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,103 @@
2121
#include "common/checksum.h"
2222
#include "common/delay.h"
2323
#include "ton/ton-io.hpp"
24+
#include "vm/cells/MerkleProof.h"
25+
#include "crypto/block/block-auto.h"
26+
#include "crypto/block/block-parse.h"
2427

2528
namespace ton {
2629

2730
namespace validator {
2831

32+
class SplitStateDeserializer {
33+
public:
34+
td::Result<std::vector<SplitStatePart>> get_effective_shards_from_header(ShardId shard_id, RootHash root_hash,
35+
td::Ref<vm::Cell> wrapped_header,
36+
td::uint32 split_depth) {
37+
int shard_prefix_length = shard_pfx_len(shard_id);
38+
CHECK(split_depth <= 63 && shard_prefix_length < static_cast<int>(split_depth));
39+
40+
try {
41+
TRY_RESULT(header, vm::MerkleProof::try_virtualize(wrapped_header));
42+
43+
if (RootHash{header->get_hash().bits()} != root_hash) {
44+
return td::Status::Error("Hash mismatch in split state header");
45+
}
46+
47+
auto shard_state_cs = vm::load_cell_slice(header);
48+
bool rc = block::gen::t_ShardStateUnsplit.unpack(shard_state_cs, shard_state_);
49+
if (!rc) {
50+
return td::Status::Error("Cannot deserialize ShardStateUnsplit");
51+
}
52+
53+
vm::AugmentedDictionary accounts{
54+
vm::load_cell_slice_ref(shard_state_.accounts),
55+
256,
56+
block::tlb::aug_ShardAccounts,
57+
false,
58+
};
59+
60+
std::vector<SplitStatePart> parts;
61+
62+
// The following loop is the same as in state-serializer.cpp.
63+
ShardId effective_shard = shard_id ^ (1ULL << (63 - shard_prefix_length)) ^ (1ULL << (63 - split_depth));
64+
ShardId increment = 1ULL << (64 - split_depth);
65+
66+
for (int i = 0; i < (1 << (split_depth - shard_prefix_length)); ++i, effective_shard += increment) {
67+
td::BitArray<64> prefix;
68+
prefix.store_ulong(effective_shard);
69+
auto account_dict_part = accounts;
70+
account_dict_part.cut_prefix_subdict(prefix.bits(), split_depth);
71+
72+
if (!account_dict_part.is_empty()) {
73+
parts.push_back({effective_shard, account_dict_part.get_wrapped_dict_root()->get_hash()});
74+
}
75+
}
76+
77+
// Now check that header does not contain pruned cells outside of accounts dict. For that, we
78+
// just replace account dict with an empty cell and see if header remains virtualized or not.
79+
shard_state_.accounts = vm::DataCell::create("", 0, {}, false).move_as_ok();
80+
81+
vm::CellBuilder cb;
82+
block::gen::t_ShardStateUnsplit.pack(cb, shard_state_);
83+
if (cb.finalize()->get_virtualization() > 0) {
84+
return td::Status::Error("State headers is pruned outside of account dict");
85+
}
86+
87+
return parts;
88+
} catch (vm::VmVirtError const&) {
89+
return td::Status::Error("Insufficient number of cells in split state header");
90+
}
91+
}
92+
93+
td::Ref<vm::Cell> merge(std::vector<td::Ref<vm::Cell>> const& parts) {
94+
vm::AugmentedDictionary accounts{256, block::tlb::aug_ShardAccounts};
95+
for (auto const& part_root : parts) {
96+
vm::AugmentedDictionary part{
97+
vm::load_cell_slice_ref(part_root),
98+
256,
99+
block::tlb::aug_ShardAccounts,
100+
false,
101+
};
102+
bool rc = accounts.combine_with(part);
103+
LOG_CHECK(rc) << "Split state parts have been validated but merging them still resulted in a conflict";
104+
}
105+
106+
CHECK(accounts.is_valid());
107+
108+
shard_state_.accounts = accounts.get_wrapped_dict_root();
109+
110+
vm::CellBuilder cb;
111+
block::gen::t_ShardStateUnsplit.pack(cb, shard_state_);
112+
auto state_root = cb.finalize();
113+
CHECK(state_root->get_virtualization() == 0);
114+
return state_root;
115+
}
116+
117+
private:
118+
block::gen::ShardStateUnsplit::Record shard_state_;
119+
};
120+
29121
DownloadShardState::DownloadShardState(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::uint32 split_depth,
30122
td::uint32 priority, td::actor::ActorId<ValidatorManager> manager,
31123
td::Timestamp timeout, td::Promise<td::Ref<ShardState>> promise)
@@ -42,8 +134,13 @@ DownloadShardState::DownloadShardState(BlockIdExt block_id, BlockIdExt mastercha
42134
if (shard_prefix_length >= static_cast<int>(split_depth_)) {
43135
split_depth_ = 0;
44136
}
137+
138+
LOG(INFO) << "requested to download state of " << block_id.to_str() << " referenced by "
139+
<< masterchain_block_id.to_str() << " with split depth " << split_depth;
45140
}
46141

142+
DownloadShardState::~DownloadShardState() = default;
143+
47144
void DownloadShardState::start_up() {
48145
status_ = ProcessStatus(manager_, "process.download_state");
49146
alarm_timestamp() = timeout_;
@@ -71,6 +168,8 @@ void DownloadShardState::got_block_handle(BlockHandle handle) {
71168
}
72169

73170
void DownloadShardState::retry() {
171+
deserializer_ = {};
172+
parts_.clear();
74173
download_state();
75174
}
76175

@@ -119,20 +218,35 @@ void DownloadShardState::checked_proof_link() {
119218
}
120219
});
121220
td::actor::send_closure(manager_, &ValidatorManager::try_get_static_file, block_id_.file_hash, std::move(P));
221+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : downloading zero state");
122222
} else {
123-
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::BufferSlice> R) {
124-
if (R.is_error()) {
125-
fail_handler(SelfId, R.move_as_error());
126-
} else {
127-
td::actor::send_closure(SelfId, &DownloadShardState::downloaded_shard_state, R.move_as_ok());
128-
}
129-
});
130223
CHECK(masterchain_block_id_.is_valid());
131224
CHECK(masterchain_block_id_.is_masterchain());
132-
td::actor::send_closure(manager_, &ValidatorManager::send_get_persistent_state_request, block_id_,
133-
masterchain_block_id_, UnsplitStateType{}, priority_, std::move(P));
225+
226+
if (split_depth_ == 0) {
227+
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::BufferSlice> R) {
228+
if (R.is_error()) {
229+
fail_handler(SelfId, R.move_as_error());
230+
} else {
231+
td::actor::send_closure(SelfId, &DownloadShardState::downloaded_shard_state, R.move_as_ok());
232+
}
233+
});
234+
td::actor::send_closure(manager_, &ValidatorManager::send_get_persistent_state_request, block_id_,
235+
masterchain_block_id_, UnsplitStateType{}, priority_, std::move(P));
236+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : downloading state");
237+
} else {
238+
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::BufferSlice> R) {
239+
if (R.is_error()) {
240+
fail_handler(SelfId, R.move_as_error());
241+
} else {
242+
td::actor::send_closure(SelfId, &DownloadShardState::downloaded_split_state_header, R.move_as_ok());
243+
}
244+
});
245+
td::actor::send_closure(manager_, &ValidatorManager::send_get_persistent_state_request, block_id_,
246+
masterchain_block_id_, SplitPersistentStateType{}, priority_, std::move(P));
247+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : downloading state header");
248+
}
134249
}
135-
status_.set_status(PSTRING() << block_id_.id.to_str() << " : downloading state");
136250
}
137251

138252
void DownloadShardState::download_zero_state() {
@@ -200,6 +314,113 @@ void DownloadShardState::checked_shard_state() {
200314
}
201315
}
202316

317+
void DownloadShardState::downloaded_split_state_header(td::BufferSlice data) {
318+
LOG(INFO) << "processing state header";
319+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : processing state header");
320+
321+
deserializer_ = std::make_unique<SplitStateDeserializer>();
322+
323+
auto maybe_header = vm::std_boc_deserialize(data);
324+
if (maybe_header.is_error()) {
325+
fail_handler(actor_id(this), maybe_header.move_as_error());
326+
return;
327+
}
328+
329+
auto maybe_parts = deserializer_->get_effective_shards_from_header(block_id_.shard_full().shard, handle_->state(),
330+
maybe_header.move_as_ok(), split_depth_);
331+
if (maybe_parts.is_error()) {
332+
fail_handler(actor_id(this), maybe_parts.move_as_error());
333+
return;
334+
}
335+
336+
parts_ = maybe_parts.move_as_ok();
337+
338+
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
339+
R.ensure();
340+
td::actor::send_closure(SelfId, &DownloadShardState::written_split_state_file);
341+
});
342+
td::actor::send_closure(manager_, &ValidatorManager::store_persistent_state_file, block_id_, masterchain_block_id_,
343+
SplitPersistentStateType{}, std::move(data), std::move(P));
344+
}
345+
346+
namespace {
347+
348+
void retry_part_download(td::actor::ActorId<DownloadShardState> SelfId, td::Status error) {
349+
LOG(WARNING) << "failed to download state part : " << error;
350+
delay_action([=]() { td::actor::send_closure(SelfId, &DownloadShardState::written_split_state_file); },
351+
td::Timestamp::in(1.0));
352+
}
353+
354+
} // namespace
355+
356+
void DownloadShardState::written_split_state_file() {
357+
if (stored_parts_.size() == parts_.size()) {
358+
auto state_root = deserializer_->merge(stored_parts_);
359+
auto maybe_state = create_shard_state(block_id_, state_root);
360+
361+
// We cannot rollback database changes here without significant elbow grease.
362+
maybe_state.ensure();
363+
state_ = maybe_state.move_as_ok();
364+
CHECK(state_->root_hash() == handle_->state());
365+
366+
written_shard_state_file();
367+
return;
368+
}
369+
370+
size_t idx = stored_parts_.size();
371+
372+
LOG(INFO) << "downloading state part " << idx + 1 << " out of " << parts_.size();
373+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : downloading state part (part " << idx + 1 << " out of "
374+
<< parts_.size() << ")");
375+
376+
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::BufferSlice> R) {
377+
if (R.is_error()) {
378+
retry_part_download(SelfId, R.move_as_error());
379+
} else {
380+
td::actor::send_closure(SelfId, &DownloadShardState::downloaded_state_part, R.move_as_ok());
381+
}
382+
});
383+
td::actor::send_closure(manager_, &ValidatorManager::send_get_persistent_state_request, block_id_,
384+
masterchain_block_id_, SplitAccountStateType{parts_[idx].effective_shard}, priority_,
385+
std::move(P));
386+
}
387+
388+
void DownloadShardState::downloaded_state_part(td::BufferSlice data) {
389+
size_t idx = stored_parts_.size();
390+
391+
LOG(INFO) << "processing state part " << idx + 1 << " out of " << parts_.size();
392+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : processing state part (part " << idx + 1 << " out of "
393+
<< parts_.size() << ")");
394+
395+
auto maybe_part = vm::std_boc_deserialize(data);
396+
if (maybe_part.is_error()) {
397+
retry_part_download(actor_id(this), maybe_part.move_as_error());
398+
return;
399+
}
400+
401+
auto root = maybe_part.move_as_ok();
402+
if (root->get_hash() != parts_[idx].root_hash) {
403+
auto error_message =
404+
"Hash mismatch for part " +
405+
persistent_state_type_to_string(block_id_.shard_full(), SplitAccountStateType{parts_[idx].effective_shard});
406+
retry_part_download(actor_id(this), td::Status::Error(error_message));
407+
return;
408+
}
409+
410+
stored_parts_.push_back(root);
411+
412+
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
413+
R.ensure();
414+
td::actor::send_closure(SelfId, &DownloadShardState::written_split_state_file);
415+
});
416+
td::actor::send_closure(manager_, &ValidatorManager::store_persistent_state_file, block_id_, masterchain_block_id_,
417+
SplitAccountStateType{parts_[idx].effective_shard}, std::move(data), std::move(P));
418+
419+
LOG(INFO) << "storing state part to file " << idx + 1 << " out of " << parts_.size();
420+
status_.set_status(PSTRING() << block_id_.id.to_str() << " : storing state part to file (part " << idx + 1
421+
<< " out of " << parts_.size() << ")");
422+
}
423+
203424
void DownloadShardState::written_shard_state_file() {
204425
status_.set_status(PSTRING() << block_id_.id.to_str() << " : storing state to celldb");
205426
LOG(WARNING) << "written shard state file " << block_id_.to_str();

validator/downloaders/download-state.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,19 @@ namespace ton {
2525

2626
namespace validator {
2727

28+
class SplitStateDeserializer;
29+
30+
struct SplitStatePart {
31+
ShardId effective_shard;
32+
vm::CellHash root_hash;
33+
};
34+
2835
class DownloadShardState : public td::actor::Actor {
2936
public:
3037
DownloadShardState(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::uint32 split_depth, td::uint32 priority,
3138
td::actor::ActorId<ValidatorManager> manager, td::Timestamp timeout,
3239
td::Promise<td::Ref<ShardState>> promise);
40+
~DownloadShardState();
3341

3442
void start_up() override;
3543
void got_block_handle(BlockHandle handle);
@@ -43,8 +51,12 @@ class DownloadShardState : public td::actor::Actor {
4351
void downloaded_zero_state(td::BufferSlice data);
4452

4553
void downloaded_shard_state(td::BufferSlice data);
46-
4754
void checked_shard_state();
55+
56+
void downloaded_split_state_header(td::BufferSlice data);
57+
void written_split_state_file();
58+
void downloaded_state_part(td::BufferSlice data);
59+
4860
void written_shard_state_file();
4961
void written_shard_state(td::Ref<ShardState> state);
5062
void written_block_handle();
@@ -67,6 +79,10 @@ class DownloadShardState : public td::actor::Actor {
6779
td::Timestamp timeout_;
6880
td::Promise<td::Ref<ShardState>> promise_;
6981

82+
std::unique_ptr<SplitStateDeserializer> deserializer_;
83+
std::vector<SplitStatePart> parts_;
84+
std::vector<Ref<vm::Cell>> stored_parts_;
85+
7086
td::BufferSlice data_;
7187
td::Ref<ShardState> state_;
7288

validator/fabric.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ td::Result<td::Ref<Proof>> create_proof(BlockIdExt masterchain_block_id, td::Buf
3939
td::Result<td::Ref<ProofLink>> create_proof_link(BlockIdExt block_id, td::BufferSlice proof);
4040
td::Result<td::Ref<BlockSignatureSet>> create_signature_set(td::BufferSlice sig_set);
4141
td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::BufferSlice data);
42-
td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::Ref<vm::DataCell> root_cell);
42+
td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::Ref<vm::Cell> root_cell);
4343
td::Result<BlockHandle> create_block_handle(td::BufferSlice data);
4444
td::Result<BlockHandle> create_block_handle(td::Slice data);
4545
td::Result<ConstBlockHandle> create_temp_block_handle(td::BufferSlice data);

validator/impl/fabric.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::Buff
8484
}
8585
}
8686

87-
td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::Ref<vm::DataCell> root_cell) {
87+
td::Result<td::Ref<ShardState>> create_shard_state(BlockIdExt block_id, td::Ref<vm::Cell> root_cell) {
8888
auto res = ShardStateQ::fetch(block_id, {}, std::move(root_cell));
8989
if (res.is_error()) {
9090
return res.move_as_error();

0 commit comments

Comments
 (0)