Ton

Форк
0
/
block-db.cpp 
843 строки · 28.8 Кб
1
/*
2
    This file is part of TON Blockchain Library.
3

4
    TON Blockchain Library is free software: you can redistribute it and/or modify
5
    it under the terms of the GNU Lesser General Public License as published by
6
    the Free Software Foundation, either version 2 of the License, or
7
    (at your option) any later version.
8

9
    TON Blockchain Library is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
    GNU Lesser General Public License for more details.
13

14
    You should have received a copy of the GNU Lesser General Public License
15
    along with TON Blockchain Library.  If not, see <http://www.gnu.org/licenses/>.
16

17
    Copyright 2017-2020 Telegram Systems LLP
18
*/
19
#include "block-db.h"
20
#include "block-db-impl.h"
21
#include "block-binlog.h"
22
#include "td/utils/common.h"
23
#include "td/utils/crypto.h"
24
#include "td/utils/format.h"
25
#include "td/utils/misc.h"
26
#include "td/utils/port/FileFd.h"
27
#include "td/utils/port/path.h"
28
#include "td/utils/filesystem.h"
29
#include "vm/cellslice.h"
30
#include "vm/boc.h"
31
#include "vm/db/StaticBagOfCellsDb.h"
32

33
#include <limits>
34

35
namespace block {
36

37
//static constexpr std::string default_binlog_name = "blockdb";
38
//static constexpr std::string default_binlog_suffix = ".bin";
39

40
bool parse_hash_string(std::string arg, RootHash& res) {
41
  if (arg.size() != 64) {
42
    res.set_zero();
43
    return false;
44
  }
45
  int f = 1;
46
  unsigned char* ptr = res.data();
47
  for (char c : arg) {
48
    f <<= 4;
49
    if (c >= '0' && c <= '9') {
50
      f += c - '0';
51
    } else {
52
      c |= 0x20;
53
      if (c >= 'a' && c <= 'f') {
54
        f += c - ('a' - 10);
55
      } else {
56
        res.set_zero();
57
        return false;
58
      }
59
    }
60
    if (f >= 0x100) {
61
      *ptr++ = (unsigned char)f;
62
      f = 1;
63
    }
64
  }
65
  return true;
66
}
67

68
td::Result<td::BufferSlice> load_binary_file(std::string filename, td::int64 max_size) {
69
  //TODO: use td::read_file
70
  auto res = [&]() -> td::Result<td::BufferSlice> {
71
    TRY_RESULT(fd, td::FileFd::open(filename, td::FileFd::Read));
72
    TRY_RESULT(stat, fd.stat());
73
    if (!stat.is_reg_) {
74
      return td::Status::Error("file is not regular");
75
    }
76
    td::int64 size = stat.size_;
77
    if (!size) {
78
      return td::Status::Error("file is empty");
79
    }
80
    if ((max_size && size > max_size) || static_cast<td::uint64>(size) > std::numeric_limits<std::size_t>::max()) {
81
      return td::Status::Error("file is too long");
82
    }
83
    td::BufferSlice res(td::narrow_cast<std::size_t>(size));
84
    TRY_RESULT(r, fd.read(res.as_slice()));
85
    if (r != static_cast<td::uint64>(size)) {
86
      return td::Status::Error(PSLICE() << "read " << r << " bytes out of " << size);
87
    }
88
    return std::move(res);
89
  }();
90
  LOG_IF(ERROR, res.is_error()) << "error reading file `" << filename << "` : " << res.error();
91
  return res;
92
}
93

94
td::Status save_binary_file(std::string filename, const td::BufferSlice& data, unsigned long long max_size) {
95
  //TODO: use td::write_file
96
  auto status = [&]() {
97
    if (max_size && data.size() > max_size) {
98
      return td::Status::Error("contents too long");
99
    }
100
    auto size = data.size();
101
    TRY_RESULT(to_file, td::FileFd::open(filename, td::FileFd::CreateNew | td::FileFd::Write));
102
    TRY_RESULT(written, to_file.write(data));
103
    if (written != static_cast<size_t>(size)) {
104
      return td::Status::Error(PSLICE() << "written " << written << " bytes instead of " << size);
105
    }
106
    to_file.close();
107
    return td::Status::OK();
108
  }();
109
  LOG_IF(ERROR, status.is_error()) << "error writing new file `" << filename << "` : " << status;
110
  return status;
111
}
112

113
FileHash compute_file_hash(const td::BufferSlice& data) {
114
  ton::Bits256 data_hash;
115
  td::sha256(data, td::MutableSlice{data_hash.data(), 32});
116
  return data_hash;
117
}
118

119
FileHash compute_file_hash(td::Slice data) {
120
  ton::Bits256 data_hash;
121
  td::sha256(data, td::MutableSlice{data_hash.data(), 32});
122
  return data_hash;
123
}
124

125
/*
126
 * 
127
 *   ZEROSTATE CONFIGURATION
128
 * 
129
 */
130

131
td::Status ZerostateInfo::base_check() {
132
  if (!has_data()) {
133
    return td::Status::OK();
134
  }
135
  auto data_hash = compute_file_hash(data);
136
  if (!has_file_hash()) {
137
    file_hash = data_hash;
138
  } else if (file_hash != data_hash) {
139
    return td::Status::Error("zerostate file hash mismatch");
140
  }
141
  vm::BagOfCells boc;
142
  auto res = boc.deserialize(data);
143
  if (!res.is_ok() || boc.get_root_count() != 1) {
144
    return td::Status::Error("zerostate is not a valid bag of cells");  // not a valid bag-of-Cells
145
  }
146
  data_hash = boc.get_root_cell()->get_hash().bits();
147
  if (!has_root_hash()) {
148
    root_hash = data_hash;
149
  } else if (root_hash != data_hash) {
150
    return td::Status::Error("zerostate root hash mismatch");
151
  }
152
  return td::Status::OK();
153
}
154

155
/*
156
 * 
157
 *   BLOCK DATABASE 
158
 * 
159
 */
160

161
std::string compute_db_filename(std::string base_dir, const FileHash& file_hash, int depth) {
162
  static const char hex_digits[] = "0123456789ABCDEF";
163
  assert(depth >= 0 && depth <= 8);
164
  std::string res = std::move(base_dir);
165
  res.reserve(res.size() + 32 + depth * 3 + 4);
166
  for (int i = 0; i < depth; i++) {
167
    unsigned u = file_hash.data()[i];
168
    res.push_back(hex_digits[u >> 4]);
169
    res.push_back(hex_digits[u & 15]);
170
    res.push_back('/');
171
  }
172
  for (int i = 0; i < 32; i++) {
173
    unsigned u = file_hash.data()[i];
174
    res.push_back(hex_digits[u >> 4]);
175
    res.push_back(hex_digits[u & 15]);
176
  }
177
  res += ".boc";
178
  return res;
179
}
180

181
std::string BlockDbImpl::compute_db_filename(const FileHash& file_hash) const {
182
  return block::compute_db_filename(base_dir, file_hash, depth);
183
}
184

185
std::string compute_db_tmp_filename(std::string base_dir, const FileHash& file_hash, int i, bool makedirs, int depth) {
186
  static const char hex_digits[] = "0123456789ABCDEF";
187
  assert(depth >= 0 && depth <= 8);
188
  std::string res = std::move(base_dir);
189
  res.reserve(res.size() + 32 + depth * 3 + 4);
190
  for (int j = 0; j < depth; j++) {
191
    unsigned u = file_hash.data()[j];
192
    res.push_back(hex_digits[u >> 4]);
193
    res.push_back(hex_digits[u & 15]);
194
    res.push_back('/');
195
    if (makedirs) {
196
      td::mkdir(res, 0755).ignore();
197
    }
198
  }
199
  for (int j = 0; j < 32; j++) {
200
    unsigned u = file_hash.data()[j];
201
    res.push_back(hex_digits[u >> 4]);
202
    res.push_back(hex_digits[u & 15]);
203
  }
204
  res += ".tmp";
205
  if (i > 0) {
206
    if (i < 10) {
207
      res.push_back((char)('0' + i));
208
    } else {
209
      res.push_back((char)('0' + i / 10));
210
      res.push_back((char)('0' + i % 10));
211
    }
212
  }
213
  return res;
214
}
215

216
std::string BlockDbImpl::compute_db_tmp_filename(const FileHash& file_hash, int i, bool makedirs) const {
217
  return block::compute_db_tmp_filename(base_dir, file_hash, i, makedirs, depth);
218
}
219

220
bool BlockDbImpl::file_cache_insert(const FileHash& file_hash, const td::BufferSlice& data, int mode) {
221
  auto it = file_cache.find(file_hash);
222
  if (it != file_cache.end()) {
223
    // found
224
    return true;
225
  }
226
  auto res = file_cache.emplace(file_hash, data.clone());
227
  return res.second;
228
}
229

230
td::Status BlockDbImpl::save_db_file(const FileHash& file_hash, const td::BufferSlice& data, int fmode) {
231
  if (fmode & FMode::chk_file_hash && file_hash != compute_file_hash(data)) {
232
    return td::Status::Error("file hash passed for creation of a new file does not match contents");
233
  }
234
  std::string filename = compute_db_filename(file_hash);
235
  bool overwrite = false;
236
  auto r_stat = td::stat(filename);
237
  if (r_stat.is_ok()) {
238
    auto stat = r_stat.move_as_ok();
239
    // file exists
240
    if (fmode & FMode::fail_if_exists) {
241
      return td::Status::Error(PSLICE() << "file " << filename << " cannot be created, it already exists");
242
    }
243
    if (!(fmode & (FMode::chk_if_exists | FMode::overwrite))) {
244
      file_cache_insert(file_hash, data);
245
      return td::Status::OK();
246
    }
247
    if (fmode & FMode::chk_if_exists) {
248
      if (stat.size_ != (long long)data.size()) {
249
        LOG(ERROR) << "file " << filename << " already exists with wrong content";
250
        if (!(fmode & FMode::overwrite)) {
251
          return td::Status::Error(PSLICE() << "file " << filename << " already exists with wrong content");
252
        }
253
      } else if (fmode & FMode::chk_size_only) {
254
        file_cache_insert(file_hash, data);
255
        return td::Status::OK();
256
      } else {
257
        auto res = load_binary_file(filename);
258
        if (res.is_error()) {
259
          return res.move_as_error();
260
        }
261
        auto old_contents = res.move_as_ok();
262
        if (old_contents.size() != data.size() || old_contents.as_slice() != data.as_slice()) {
263
          LOG(ERROR) << "file " << filename << " already exists with wrong content";
264
          if (!(fmode & FMode::overwrite)) {
265
            return td::Status::Error(PSLICE() << "file " << filename << " already exists with wrong content");
266
          }
267
        } else {
268
          file_cache_insert(file_hash, data);
269
          return td::Status::OK();
270
        }
271
      }
272
    }
273
    overwrite = true;
274
  }
275
  std::string tmp_filename;
276
  for (int i = 0; i < 10; i++) {
277
    tmp_filename = compute_db_tmp_filename(file_hash, i, true);
278
    auto res = save_binary_file(tmp_filename, data);
279
    if (res.is_ok()) {
280
      break;
281
    }
282
    if (i == 9) {
283
      return res;
284
    }
285
  }
286
  auto rename_status = td::rename(tmp_filename, filename);
287
  if (rename_status.is_error()) {
288
    td::unlink(tmp_filename).ignore();
289
    LOG(ERROR) << rename_status;
290
    return rename_status;
291
  }
292
  if (overwrite) {
293
    LOG(DEBUG) << "database file `" << filename << "` overwritten, " << data.size() << " bytes";
294
  } else {
295
    LOG(DEBUG) << "new database file `" << filename << "` created, " << data.size() << " bytes";
296
  }
297
  file_cache_insert(file_hash, data);
298
  return td::Status::OK();
299
}
300

301
td::Result<td::actor::ActorOwn<BlockDb>> BlockDb::create_block_db(std::string base_dir,
302
                                                                  std::unique_ptr<ZerostateInfo> zstate,
303
                                                                  bool allow_uninit, int depth,
304
                                                                  std::string binlog_name) {
305
  using td::actor::ActorId;
306
  using td::actor::ActorOwn;
307
  td::Result<int> res;
308
  ActorOwn<BlockDbImpl> actor =
309
      td::actor::create_actor<BlockDbImpl>(td::actor::ActorOptions().with_name("BlockDB"), res, base_dir,
310
                                           std::move(zstate), allow_uninit, depth, binlog_name);
311
  if (res.is_error()) {
312
    return std::move(res).move_as_error();
313
  } else {
314
    return std::move(actor);
315
  }
316
}
317

318
BlockDbImpl::BlockDbImpl(td::Result<int>& _res, std::string _base_dir, std::unique_ptr<ZerostateInfo> _zstate,
319
                         bool _allow_uninit, int _depth, std::string _binlog_name)
320
    : status(0)
321
    , allow_uninit(_allow_uninit)
322
    , created(false)
323
    , depth(_depth)
324
    , zstate(std::move(_zstate))
325
    , base_dir(_base_dir)
326
    , binlog_name(_binlog_name)
327
    , bb(std::unique_ptr<BinlogCallback>(new BlockBinlogCallback(*this)))
328
    , created_at(0) {
329
  auto res = do_init();
330
  status = (res.is_ok() && res.ok() > 0 ? res.ok() : -1);
331
  if (res.is_error()) {
332
    _res = std::move(res);
333
  } else {
334
    _res = res.move_as_ok();
335
  }
336
}
337

338
td::Result<int> BlockDbImpl::do_init() {
339
  if (base_dir.empty()) {
340
    return td::Status::Error("block database cannot have empty base directory");
341
  }
342
  if (depth < 0 || depth >= 8) {
343
    return td::Status::Error("block database directory tree depth must be in range 0..8");
344
  }
345
  if (base_dir.back() != '/') {
346
    base_dir.push_back('/');
347
  }
348
  if (binlog_name.empty()) {
349
    binlog_name = default_binlog_name;
350
  }
351
  bool f = true;
352
  for (char c : binlog_name) {
353
    if (c == '.') {
354
      f = false;
355
    } else if (c == '/') {
356
      f = true;
357
    }
358
  }
359
  if (f) {
360
    binlog_name += default_binlog_suffix;
361
  }
362
  if (binlog_name.at(0) != '/') {
363
    binlog_name = base_dir + binlog_name;
364
  }
365
  if (zstate) {
366
    if (!zstate->has_data() && zstate->has_filename()) {
367
      auto data = load_binary_file(zstate->filename, 1 << 20);
368
      if (data.is_error()) {
369
        return data.move_as_error();
370
      }
371
      zstate->data = data.move_as_ok();
372
    }
373
    auto res = zstate->base_check();
374
    if (res.is_error()) {
375
      return res;
376
    }
377
  }
378
  try {
379
    auto res = bb.set_binlog(binlog_name, allow_uninit ? 3 : 1);
380
    if (res.is_error()) {
381
      return res;
382
    }
383
  } catch (BinlogBuffer::BinlogError& err) {
384
    return td::Status::Error(-2, std::string{"error while initializing block database binlog: "} + err.msg);
385
  } catch (BinlogBuffer::InterpretError& err) {
386
    return td::Status::Error(-3, std::string{"error while interpreting block database binlog: "} + err.msg);
387
  }
388
  return created;
389
}
390

391
BlockDbImpl::~BlockDbImpl() {
392
}
393

394
td::Status BlockDbImpl::init_from_zstate() {
395
  if (!zstate) {
396
    return td::Status::Error("no zero state provided, cannot initialize from scratch");
397
  }
398
  if (!zstate->has_data()) {
399
    if (zstate->has_filename() || zstate->has_file_hash()) {
400
      if (!zstate->has_filename()) {
401
        zstate->filename = compute_db_filename(zstate->file_hash);
402
      }
403
      auto res = load_binary_file(zstate->filename, 1 << 20);
404
      if (res.is_error()) {
405
        return res.move_as_error();
406
      }
407
      zstate->data = res.move_as_ok();
408
    } else {
409
      return td::Status::Error("cannot load zero state for block DB creation");
410
    }
411
  }
412
  auto res = zstate->base_check();
413
  if (res.is_error()) {
414
    return res;
415
  }
416
  assert(zstate->has_file_hash() && zstate->has_root_hash());
417
  res = save_db_file(zstate->file_hash, zstate->data, FMode::chk_if_exists | FMode::chk_file_hash);
418
  if (res.is_error()) {
419
    return res;
420
  }
421
  return res;
422
}
423

424
td::Status BlockBinlogCallback::init_new_binlog(BinlogBuffer& bb) {
425
  auto res = db.init_from_zstate();
426
  if (res.is_error()) {
427
    return res;
428
  }
429
  auto lev = bb.alloc<log::Start>(db.zstate->root_hash);
430
  assert(!lev.get_log_pos());
431
  auto lev2 = bb.alloc<log::SetZeroState>(db.zstate->root_hash, db.zstate->file_hash, db.zstate->data.size());
432
  lev.commit();
433
  lev2.commit();  // TODO: introduce multi-commit bb.commit(lev, lev2)
434
  bb.flush(3);
435
  db.created = true;
436
  return td::Status::OK();
437
}
438

439
#define REPLAY_CASE(__T) \
440
  case __T::tag:         \
441
    return try_interpret<__T>(ptr, len, log_pos);
442

443
int BlockBinlogCallback::replay_log_event(BinlogBuffer& bb, const unsigned* ptr, std::size_t len,
444
                                          unsigned long long log_pos) {
445
  assert(len >= 4);
446
  LOG(DEBUG) << "replay_log_event(" << len << ", " << log_pos << ", " << *ptr << ")";
447
  switch (*ptr) {
448
    REPLAY_CASE(log::Start);
449
    REPLAY_CASE(log::SetZeroState);
450
    REPLAY_CASE(log::NewBlock);
451
    REPLAY_CASE(log::NewState);
452
  }
453
  std::ostringstream ss;
454
  ss << "unknown binlog event 0x" << std::hex << *ptr << std::dec;
455
  LOG(ERROR) << ss.str() << " at position " << log_pos;
456
  throw BinlogBuffer::InterpretError{ss.str()};
457
}
458

459
#undef REPLAY_CASE
460

461
int BlockBinlogCallback::replay(const log::Start& lev, unsigned long long log_pos) const {
462
  LOG(DEBUG) << "in replay(Start{" << lev.tag_field << ", " << lev.type_field << ", " << lev.created_at << "})";
463
  if (lev.type_field != lev.log_type) {
464
    throw BinlogBuffer::InterpretError{(PSLICE() << "unsupported binlog type " << lev.type_field).str()};
465
  }
466
  if (log_pos) {
467
    throw BinlogBuffer::InterpretError{"LEV_START can only be the very first record in a binlog"};
468
  }
469
  db.zstate_rhash = lev.zerostate_root_hash;
470
  db.created_at = lev.created_at;
471
  if (db.zstate) {
472
    if (!db.zstate->has_root_hash()) {
473
      db.zstate->root_hash = db.zstate_rhash;
474
    } else if (db.zstate->root_hash != db.zstate_rhash) {
475
      throw BinlogBuffer::InterpretError{PSTRING() << "zerostate hash mismatch: in binlog " << db.zstate_rhash.to_hex()
476
                                                   << ", required " << db.zstate->root_hash.to_hex()};
477
    }
478
  }
479
  return 0;  // ok
480
}
481

482
int BlockBinlogCallback::replay(const log::SetZeroState& lev, unsigned long long log_pos) const {
483
  LOG(DEBUG) << "in replay(SetZeroState)";
484
  // LOG(DEBUG) << "db.zstate_rhash = " << db.zstate_rhash.to_hex();
485
  if (db.zstate_rhash != td::ConstBitPtr{lev.root_hash}) {
486
    throw BinlogBuffer::InterpretError{std::string{"SetZeroState: zerostate root hash mismatch: in binlog "} +
487
                                       ton::Bits256{lev.root_hash}.to_hex() + ", required " + db.zstate_rhash.to_hex()};
488
  }
489
  db.zerostate = td::Ref<FileInfo>{true,
490
                                   FileType::state,
491
                                   ton::BlockId{ton::masterchainId, 1ULL << 63, 0},
492
                                   0,
493
                                   td::as<FileHash>(lev.file_hash),
494
                                   td::as<RootHash>(lev.root_hash),
495
                                   lev.file_size};
496
  return 0;  // ok
497
}
498

499
int BlockBinlogCallback::replay(const log::NewBlock& lev, unsigned long long log_pos) const {
500
  LOG(DEBUG) << "in replay(NewBlock)";
501
  if (!lev.seqno || lev.workchain == ton::workchainInvalid) {
502
    return -1;
503
  }
504
  ton::BlockId blkid{lev.workchain, lev.shard, lev.seqno};
505
  auto blk_info = td::Ref<FileInfo>{true,
506
                                    FileType::block,
507
                                    blkid,
508
                                    lev.flags & 0xff,
509
                                    td::as<FileHash>(lev.file_hash),
510
                                    td::as<RootHash>(lev.root_hash),
511
                                    lev.file_size};
512
  auto res = db.update_block_info(blk_info);
513
  if (res.is_error()) {
514
    LOG(ERROR) << "cannot update block information in the local DB: " << res.to_string();
515
    return -1;
516
  } else {
517
    return 0;  // ok
518
  }
519
}
520

521
int BlockBinlogCallback::replay(const log::NewState& lev, unsigned long long log_pos) const {
522
  LOG(DEBUG) << "in replay(NewState)";
523
  if (!lev.seqno || lev.workchain == ton::workchainInvalid) {
524
    return -1;
525
  }
526
  ton::BlockId id{lev.workchain, lev.shard, lev.seqno};
527
  auto state_info = td::Ref<FileInfo>{true,
528
                                      FileType::state,
529
                                      id,
530
                                      lev.flags & 0xff,
531
                                      td::as<FileHash>(lev.file_hash),
532
                                      td::as<RootHash>(lev.root_hash),
533
                                      lev.file_size};
534
  auto res = db.update_state_info(state_info);
535
  if (res.is_error()) {
536
    LOG(ERROR) << "cannot update shardchain state information in the local DB: " << res.to_string();
537
    return -1;
538
  } else {
539
    return 0;  // ok
540
  }
541
}
542

543
td::Status BlockDbImpl::update_block_info(Ref<FileInfo> blk_info) {
544
  auto it = block_info.find(blk_info->blk.id);
545
  if (it != block_info.end()) {
546
    // already exists
547
    if (it->second->blk.file_hash != blk_info->blk.file_hash || it->second->blk.root_hash != blk_info->blk.root_hash) {
548
      return td::Status::Error(-666, std::string{"fatal error in block DB: block "} + blk_info->blk.id.to_str() +
549
                                         " has two records with different file or root hashes");
550
    } else {
551
      return td::Status::OK();
552
    }
553
  } else {
554
    auto id = blk_info->blk.id;
555
    auto res = block_info.emplace(std::move(id), std::move(blk_info));
556
    if (res.second) {
557
      return td::Status::OK();
558
    } else {
559
      return td::Status::Error(-666, "cannot insert block information into DB");
560
    }
561
  }
562
}
563

564
td::Status BlockDbImpl::update_state_info(Ref<FileInfo> state) {
565
  auto it = state_info.find(state->blk.id);
566
  if (it != state_info.end()) {
567
    // already exists
568
    if (it->second->blk.root_hash != state->blk.root_hash) {
569
      return td::Status::Error(-666, std::string{"fatal error in block DB: state for block "} + state->blk.id.to_str() +
570
                                         " has two records with different root hashes");
571
    } else {
572
      return td::Status::OK();
573
    }
574
  } else {
575
    auto id = state->blk.id;
576
    auto res = state_info.emplace(std::move(id), std::move(state));
577
    if (res.second) {
578
      return td::Status::OK();
579
    } else {
580
      return td::Status::Error(-666, "cannot insert state information into DB");
581
    }
582
  }
583
}
584

585
void BlockDbImpl::get_top_block_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) {
586
  LOG(DEBUG) << "in BlockDb::get_top_block_id()";
587
  auto it = block_info.upper_bound(ton::BlockId{shard, std::numeric_limits<td::uint32>::max()});
588
  if (it != block_info.begin() && ton::ShardIdFull{(--it)->first} == shard) {
589
    promise(it->second->blk);
590
    return;
591
  }
592
  if (shard.is_masterchain()) {
593
    promise(ton::BlockIdExt{ton::BlockId{ton::masterchainId, 1ULL << 63, 0}});
594
    return;
595
  }
596
  promise(td::Status::Error(-666));
597
}
598

599
void BlockDbImpl::get_top_block_state_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) {
600
  LOG(DEBUG) << "in BlockDb::get_top_block_state_id()";
601
  auto it = state_info.upper_bound(ton::BlockId{shard, std::numeric_limits<td::uint32>::max()});
602
  if (it != state_info.begin() && ton::ShardIdFull{(--it)->first} == shard) {
603
    promise(it->second->blk);
604
    return;
605
  }
606
  if (shard.is_masterchain() && zerostate.not_null()) {
607
    promise(zerostate->blk);
608
    return;
609
  }
610
  promise(td::Status::Error(-666, "no state for given workchain found in database"));
611
}
612

613
void BlockDbImpl::get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) {
614
  LOG(DEBUG) << "in BlockDb::get_block_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", " << blk_id.seqno
615
             << "}, " << need_data << ")";
616
  auto it = block_info.find(blk_id);
617
  if (it != block_info.end()) {
618
    if (need_data && it->second->data.is_null()) {
619
      LOG(DEBUG) << "loading data for block " << blk_id.to_str();
620
      auto res = load_data(it->second.write());
621
      if (res.is_error()) {
622
        promise(std::move(res));
623
        return;
624
      }
625
    }
626
    promise(it->second);
627
    return;
628
  }
629
  promise(td::Status::Error(-666, "block not found in database"));
630
}
631

632
void BlockDbImpl::get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) {
633
  LOG(DEBUG) << "in BlockDb::get_state_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", " << blk_id.seqno
634
             << "}, " << need_data << ")";
635
  auto it = state_info.find(blk_id);
636
  if (it != state_info.end()) {
637
    if (need_data && it->second->data.is_null()) {
638
      LOG(DEBUG) << "loading data for state " << blk_id.to_str();
639
      auto res = load_data(it->second.write());
640
      if (res.is_error()) {
641
        promise(std::move(res));
642
        return;
643
      }
644
    }
645
    promise(it->second);
646
    return;
647
  }
648
  if (zerostate.not_null() && blk_id == zerostate->blk.id) {
649
    LOG(DEBUG) << "get_state_by_id(): zerostate requested";
650
    if (need_data && zerostate->data.is_null()) {
651
      LOG(DEBUG) << "loading data for zerostate";
652
      auto res = load_data(zerostate.write());
653
      if (res.is_error()) {
654
        promise(std::move(res));
655
        return;
656
      }
657
    }
658
    promise(zerostate);
659
    return;
660
  }
661
  promise(td::Status::Error(-666, "requested state not found in database"));
662
}
663

664
void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promise<td::Ref<OutputQueueInfoDescr>> promise) {
665
  LOG(DEBUG) << "in BlockDb::get_out_queue_info_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", "
666
             << blk_id.seqno << ")";
667
  auto it = state_info.find(blk_id);
668
  if (it == state_info.end()) {
669
    promise(td::Status::Error(
670
        -666, std::string{"cannot obtain output queue info for block "} + blk_id.to_str() + " : cannot load state"));
671
    return;
672
  }
673
  if (it->second->data.is_null()) {
674
    LOG(DEBUG) << "loading data for state " << blk_id.to_str();
675
    auto res = load_data(it->second.write());
676
    if (res.is_error()) {
677
      promise(std::move(res));
678
      return;
679
    }
680
  }
681
  auto it2 = block_info.find(blk_id);
682
  if (it2 == block_info.end()) {
683
    promise(td::Status::Error(-666, std::string{"cannot obtain output queue info for block "} + blk_id.to_str() +
684
                                        " : cannot load block description"));
685
    return;
686
  }
687
  vm::StaticBagOfCellsDbLazy::Options options;
688
  auto res = vm::StaticBagOfCellsDbLazy::create(it->second->data.clone(), options);
689
  if (res.is_error()) {
690
    td::Status err = res.move_as_error();
691
    LOG(ERROR) << "cannot deserialize state for block " << blk_id.to_str() << " : " << err.to_string();
692
    promise(std::move(err));
693
    return;
694
  }
695
  auto static_boc = res.move_as_ok();
696
  auto rc = static_boc->get_root_count();
697
  if (rc.is_error()) {
698
    promise(rc.move_as_error());
699
    return;
700
  }
701
  if (rc.move_as_ok() != 1) {
702
    promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid"));
703
    return;
704
  }
705
  auto res3 = static_boc->get_root_cell(0);
706
  if (res3.is_error()) {
707
    promise(res3.move_as_error());
708
    return;
709
  }
710
  auto state_root = res3.move_as_ok();
711
  if (it->second->blk.root_hash != state_root->get_hash().bits()) {
712
    promise(td::Status::Error(
713
        -668, std::string{"state for block "} + blk_id.to_str() + " is invalid : state root hash mismatch"));
714
    return;
715
  }
716
  vm::CellSlice cs = vm::load_cell_slice(state_root);
717
  if (!cs.have(64, 1) || cs.prefetch_ulong(32) != 0x9023afde) {
718
    promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid"));
719
    return;
720
  }
721
  auto out_queue_info = cs.prefetch_ref();
722
  promise(Ref<OutputQueueInfoDescr>{true, blk_id, it2->second->blk.root_hash.cbits(), state_root->get_hash().bits(),
723
                                    std::move(out_queue_info)});
724
}
725

726
void BlockDbImpl::get_object_by_file_hash(FileHash file_hash, bool need_data, bool force_file_load,
727
                                          td::Promise<td::Ref<FileInfo>> promise) {
728
  if (zerostate.not_null() && zerostate->blk.file_hash == file_hash) {
729
    if (need_data && zerostate->data.is_null()) {
730
      auto res = load_data(zerostate.write());
731
      if (res.is_error()) {
732
        promise(std::move(res));
733
        return;
734
      }
735
    }
736
    promise(zerostate);
737
    return;
738
  }
739
  promise(td::Status::Error(-666));
740
}
741

742
void BlockDbImpl::get_object_by_root_hash(RootHash root_hash, bool need_data, bool force_file_load,
743
                                          td::Promise<td::Ref<FileInfo>> promise) {
744
  if (zerostate.not_null() && zerostate->blk.root_hash == root_hash) {
745
    if (need_data && zerostate->data.is_null()) {
746
      auto res = load_data(zerostate.write());
747
      if (res.is_error()) {
748
        promise(std::move(res));
749
        return;
750
      }
751
    }
752
    promise(zerostate);
753
    return;
754
  }
755
  promise(td::Status::Error(-666));
756
}
757

758
void BlockDbImpl::save_new_block(ton::BlockIdExt id, td::BufferSlice data, int authority,
759
                                 td::Promise<td::Unit> promise) {
760
  // TODO: add verification that data is a BoC with correct root hash, and that it is a Block corresponding to blk_id
761
  // ...
762
  // TODO: check whether we already have a block with blk_id
763
  // ...
764
  auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash);
765
  if (save_res.is_error()) {
766
    promise(std::move(save_res));
767
    return;
768
  }
769
  auto sz = data.size();
770
  auto lev = bb.alloc<log::NewBlock>(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff);
771
  if (sz <= 8) {
772
    std::memcpy(lev->last_bytes, data.data(), sz);
773
  } else {
774
    std::memcpy(lev->last_bytes, data.data() + sz - 8, 8);
775
  }
776
  lev.commit();
777
  bb.flush();
778
  promise(td::Unit{});
779
}
780

781
void BlockDbImpl::save_new_state(ton::BlockIdExt id, td::BufferSlice data, int authority,
782
                                 td::Promise<td::Unit> promise) {
783
  // TODO: add verification that data is a BoC with correct root hash, and that it is a Block corresponding to blk_id
784
  // ...
785
  // TODO: check whether we already have a block with blk_id
786
  // ...
787
  auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash);
788
  if (save_res.is_error()) {
789
    promise(std::move(save_res));
790
    return;
791
  }
792
  auto sz = data.size();
793
  auto lev = bb.alloc<log::NewState>(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff);
794
  if (sz <= 8) {
795
    std::memcpy(lev->last_bytes, data.data(), sz);
796
  } else {
797
    std::memcpy(lev->last_bytes, data.data() + sz - 8, 8);
798
  }
799
  lev.commit();
800
  bb.flush();
801
  promise(td::Unit{});
802
}
803

804
td::Status BlockDbImpl::load_data(FileInfo& file_info, bool force) {
805
  if (!file_info.data.is_null() && !force) {
806
    return td::Status::OK();
807
  }
808
  if (file_info.blk.file_hash.is_zero()) {
809
    return td::Status::Error("cannot load a block file without knowing its file hash");
810
  }
811
  auto it = file_cache.find(file_info.blk.file_hash);
812
  if (it != file_cache.end() && !force) {
813
    file_info.data = it->second.clone();
814
    return td::Status::OK();
815
  }
816
  std::string filename = compute_db_filename(file_info.blk.file_hash);
817
  auto res = load_binary_file(filename);
818
  if (res.is_error()) {
819
    return res.move_as_error();
820
  }
821
  file_info.data = res.move_as_ok();
822
  file_cache_insert(file_info.blk.file_hash, file_info.data);
823
  return td::Status::OK();
824
}
825

826
FileInfo FileInfo::clone() const {
827
  return FileInfo{*this};
828
}
829

830
FileInfo::FileInfo(const FileInfo& other)
831
    : td::CntObject()
832
    , blk(other.blk)
833
    , type(other.type)
834
    , status(other.status)
835
    , file_size(other.file_size)
836
    , data(other.data.clone()) {
837
}
838

839
FileInfo* FileInfo::make_copy() const {
840
  return new FileInfo(*this);
841
}
842

843
}  // namespace block
844

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.