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
2528namespace ton {
2629
2730namespace 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+
29121DownloadShardState::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+
47144void 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
73170void 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
138252void 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+
203424void 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 ();
0 commit comments