diff --git a/common/global-version.h b/common/global-version.h index 2308ce3e..b54a3bdc 100644 --- a/common/global-version.h +++ b/common/global-version.h @@ -19,6 +19,6 @@ namespace ton { // See doc/GlobalVersions.md -constexpr int SUPPORTED_VERSION = 10; +constexpr int SUPPORTED_VERSION = 11; } diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 06908338..59b43d13 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -214,6 +214,8 @@ set(BLOCK_SOURCE block/mc-config.cpp block/output-queue-merger.cpp block/transaction.cpp + block/account-storage-stat.h + block/account-storage-stat.cpp block/precompiled-smc/PrecompiledSmartContract.cpp ${TLB_BLOCK_AUTO} diff --git a/crypto/block/account-storage-stat.cpp b/crypto/block/account-storage-stat.cpp new file mode 100644 index 00000000..8e8b6f7e --- /dev/null +++ b/crypto/block/account-storage-stat.cpp @@ -0,0 +1,172 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . +*/ +#include "account-storage-stat.h" + +namespace block { + +AccountStorageStat::AccountStorageStat() : AccountStorageStat({}, {}, 0, 0) { +} + +AccountStorageStat::AccountStorageStat(Ref dict_root, std::vector> roots, + td::uint64 total_cells, td::uint64 total_bits) + : dict_(std::move(dict_root), 256), roots_(std::move(roots)), total_cells_(total_cells), total_bits_(total_bits) { +} + +AccountStorageStat::AccountStorageStat(const AccountStorageStat& other) + : AccountStorageStat(other.dict_.get_root_cell(), other.roots_, other.total_cells_, other.total_bits_) { +} + +AccountStorageStat::AccountStorageStat(AccountStorageStat&& other) + : AccountStorageStat(other.dict_.get_root_cell(), std::move(other.roots_), other.total_cells_, other.total_bits_) { + cache_ = std::move(other.cache_); +} + +AccountStorageStat& AccountStorageStat::operator=(const AccountStorageStat& other) { + dict_ = other.dict_; + total_cells_ = other.total_cells_; + total_bits_ = other.total_bits_; + roots_ = other.roots_; + cache_ = {}; + return *this; +} + +AccountStorageStat& AccountStorageStat::operator=(AccountStorageStat&& other) { + dict_ = std::move(other.dict_); + total_cells_ = other.total_cells_; + total_bits_ = other.total_bits_; + roots_ = std::move(other.roots_); + cache_ = std::move(other.cache_); + return *this; +} + +td::Result AccountStorageStat::add_root(const Ref& cell) { + roots_.push_back(cell); + return add_cell(cell); +} + +td::Status AccountStorageStat::remove_root(const Ref& cell) { + auto it = std::find_if(roots_.begin(), roots_.end(), + [&](const Ref& c) { return c->get_hash() == cell->get_hash(); }); + if (it == roots_.end()) { + return td::Status::Error(PSTRING() << "no such root " << cell->get_hash().to_hex()); + } + roots_.erase(it); + return remove_cell(cell); +} + +td::Result AccountStorageStat::replace_roots(std::vector> new_roots) { + std::vector> old_roots = roots_; + td::uint32 max_merkle_depth = 0; + for (const Ref& root : new_roots) { + if (root.is_null()) { + continue; + } + TRY_RESULT(info, add_root(root)); + max_merkle_depth = std::max(max_merkle_depth, info.max_merkle_depth); + } + for (const Ref& root : old_roots) { + TRY_STATUS(remove_root(root)); + } + return CellInfo{max_merkle_depth}; +} + +td::Result AccountStorageStat::add_cell(const Ref& cell) { + Entry& e = get_entry(cell); + ++e.refcnt; + if (e.refcnt == 0) { + return td::Status::Error(PSTRING() << "cell " << cell->get_hash().to_hex() << ": refcnt overflow"); + } + if (e.refcnt != 1) { + update_dict(e); + return CellInfo{e.max_merkle_depth}; + } + td::uint32 max_merkle_depth = 0; + vm::CellSlice cs{vm::NoVm{}, cell}; + ++total_cells_; + total_bits_ += cs.size(); + for (unsigned i = 0; i < cs.size_refs(); ++i) { + TRY_RESULT(info, add_cell(cs.prefetch_ref(i))); + max_merkle_depth = std::max(max_merkle_depth, info.max_merkle_depth); + } + if (cs.special_type() == vm::CellTraits::SpecialType::MerkleProof || + cs.special_type() == vm::CellTraits::SpecialType::MerkleUpdate) { + ++max_merkle_depth; + } + max_merkle_depth = std::min(max_merkle_depth, MERKLE_DEPTH_LIMIT); + Entry& e2 = get_entry(cell); + e2.max_merkle_depth = max_merkle_depth; + update_dict(e2); + return CellInfo{max_merkle_depth}; +} + +td::Status AccountStorageStat::remove_cell(const Ref& cell) { + Entry& e = get_entry(cell); + if (e.refcnt == 0) { + return td::Status::Error(PSTRING() << "cell " << cell->get_hash().to_hex() << " is not in the dict"); + } + --e.refcnt; + update_dict(e); + if (e.refcnt != 0) { + return td::Status::OK(); + } + vm::CellSlice cs{vm::NoVm{}, std::move(cell)}; + if (total_cells_ == 0 || total_bits_ < cs.size()) { + return td::Status::Error("total_cell/total_bits becomes negative"); + } + --total_cells_; + total_bits_ -= cs.size(); + for (unsigned i = 0; i < cs.size_refs(); ++i) { + TRY_STATUS(remove_cell(cs.prefetch_ref(i))); + } + return td::Status::OK(); +} + +bool AccountStorageStat::Entry::serialize(vm::CellBuilder& cb) const { + return cb.store_long_bool(refcnt, 32) && cb.store_long_bool(max_merkle_depth, 2); +} + +void AccountStorageStat::Entry::fetch(Ref cs) { + if (cs.is_null()) { + refcnt = max_merkle_depth = 0; + } else { + refcnt = (td::uint32)cs.write().fetch_ulong(32); + max_merkle_depth = (td::uint32)cs.write().fetch_ulong(2); + } +} + +AccountStorageStat::Entry& AccountStorageStat::get_entry(const Ref& cell) { + return cache_.apply(cell->get_hash().as_slice(), [&](Entry& e) { + if (e.inited) { + return; + } + e.inited = true; + e.hash = cell->get_hash(); + e.fetch(dict_.lookup(e.hash.as_bitslice())); + }); +} + +void AccountStorageStat::update_dict(const Entry& e) { + if (e.refcnt == 0) { + dict_.lookup_delete(e.hash.as_bitslice()); + } else { + vm::CellBuilder cb; + CHECK(e.serialize(cb)); + dict_.set_builder(e.hash.as_bitslice(), cb); + } +} + +} // namespace block \ No newline at end of file diff --git a/crypto/block/account-storage-stat.h b/crypto/block/account-storage-stat.h new file mode 100644 index 00000000..894192d4 --- /dev/null +++ b/crypto/block/account-storage-stat.h @@ -0,0 +1,119 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU 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 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "vm/dict.h" +#include "ton/ton-types.h" +#include "ton/ton-shard.h" +#include "common/bitstring.h" +#include "block.h" +#include "vm/db/CellHashTable.h" + +namespace block { +using td::Ref; + +class AccountStorageStat { + public: + struct CellInfo { + td::uint32 max_merkle_depth = 0; + }; + + AccountStorageStat(); + AccountStorageStat(Ref dict_root, std::vector> roots, td::uint64 total_cells, + td::uint64 total_bits); + AccountStorageStat(const AccountStorageStat &other); + AccountStorageStat(AccountStorageStat &&other); + ~AccountStorageStat() = default; + + AccountStorageStat &operator=(const AccountStorageStat &other); + AccountStorageStat &operator=(AccountStorageStat &&other); + + td::uint64 get_total_cells() const { + return total_cells_; + } + + td::uint64 get_total_bits() const { + return total_bits_; + } + + Ref get_dict_root() const { + return dict_.get_root_cell(); + } + + td::Bits256 get_dict_hash() const { + return dict_.is_empty() ? td::Bits256::zero() : td::Bits256{dict_.get_root_cell()->get_hash().bits()}; + } + + td::Result add_root(const Ref &cell); + td::Status remove_root(const Ref &cell); + td::Result replace_roots(std::vector> new_roots); + + private: + vm::Dictionary dict_; + td::uint64 total_cells_, total_bits_; + std::vector> roots_; + + td::Result add_cell(const Ref &cell); + td::Status remove_cell(const Ref &cell); + + struct Entry { + bool inited = false; + vm::Cell::Hash hash; + td::uint32 refcnt = 0; + td::uint32 max_merkle_depth = 0; + + void fetch(Ref cs); + bool serialize(vm::CellBuilder &cb) const; + + vm::Cell::Hash key() const { + return hash; + } + bool operator<(const Entry &other) const { + return key() < other.key(); + } + struct Eq { + using is_transparent = void; // Pred to use + bool operator()(const Entry &info, const Entry &other_info) const { + return info.key() == other_info.key(); + } + bool operator()(const Entry &info, td::Slice hash) const { + return info.key().as_slice() == hash; + } + bool operator()(td::Slice hash, const Entry &info) const { + return info.key().as_slice() == hash; + } + }; + struct Hash { + using is_transparent = void; // Pred to use + using transparent_key_equal = Eq; + size_t operator()(td::Slice hash) const { + return cell_hash_slice_hash(hash); + } + size_t operator()(const Entry &info) const { + return cell_hash_slice_hash(info.key().as_slice()); + } + }; + }; + vm::CellHashTable cache_; + + Entry &get_entry(const Ref &cell); + void update_dict(const Entry &e); + + static constexpr td::uint32 MERKLE_DEPTH_LIMIT = 3; +}; + +} // namespace block diff --git a/crypto/block/block-parse.cpp b/crypto/block/block-parse.cpp index 50851c79..6d645ac7 100644 --- a/crypto/block/block-parse.cpp +++ b/crypto/block/block-parse.cpp @@ -961,40 +961,33 @@ const RefTo t_Ref_MsgEnvelope; bool StorageUsed::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { return t_VarUInteger_7.validate_skip(ops, cs, weak) // cells:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(ops, cs, weak) // bits:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(ops, cs, weak); // public_cells:(VarUInteger 7) + && t_VarUInteger_7.validate_skip(ops, cs, weak); // bits:(VarUInteger 7) } bool StorageUsed::skip(vm::CellSlice& cs) const { return t_VarUInteger_7.skip(cs) // cells:(VarUInteger 7) - && t_VarUInteger_7.skip(cs) // bits:(VarUInteger 7) - && t_VarUInteger_7.skip(cs); // public_cells:(VarUInteger 7) + && t_VarUInteger_7.skip(cs); // bits:(VarUInteger 7) } const StorageUsed t_StorageUsed; -bool StorageUsedShort::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { - return t_VarUInteger_7.validate_skip(ops, cs, weak) // cells:(VarUInteger 7) - && t_VarUInteger_7.validate_skip(ops, cs, weak); // bits:(VarUInteger 7) -} - -bool StorageUsedShort::skip(vm::CellSlice& cs) const { - return t_VarUInteger_7.skip(cs) // cells:(VarUInteger 7) - && t_VarUInteger_7.skip(cs); // bits:(VarUInteger 7) -} - -const StorageUsedShort t_StorageUsedShort; - const Maybe t_Maybe_Grams; bool StorageInfo::skip(vm::CellSlice& cs) const { - return t_StorageUsed.skip(cs) // used:StorageUsed - && cs.advance(32) // last_paid:uint32 - && t_Maybe_Grams.skip(cs); // due_payment:(Maybe Grams) + int extra_tag = 0; + return t_StorageUsed.skip(cs) // used:StorageUsed + && cs.fetch_uint_to(3, extra_tag) // storage_extra:StorageExtraInfo + && (extra_tag == 0 || cs.advance(256)) // storage_extra_info$001 dict_hash:uint256 = StorageExtraInfo; + && cs.advance(32) // last_paid:uint32 + && t_Maybe_Grams.skip(cs); // due_payment:(Maybe Grams) } bool StorageInfo::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { - return t_StorageUsed.validate_skip(ops, cs, weak) // used:StorageUsed + int extra_tag = 0; + return t_StorageUsed.validate_skip(ops, cs, weak) // used:StorageUsed + && cs.fetch_uint_to(3, extra_tag) // storage_extra:StorageExtraInfo + && (extra_tag == 0 || + (extra_tag == 1 && cs.advance(256))) // storage_extra_info$001 dict_hash:uint256 = StorageExtraInfo; && cs.advance(32) // last_paid:uint32 && t_Maybe_Grams.validate_skip(ops, cs, weak); // due_payment:(Maybe Grams) } @@ -1368,7 +1361,7 @@ bool TrActionPhase::skip(vm::CellSlice& cs) const { && cs.advance(16 * 4 + 256) // tot_actions:uint16 spec_actions:uint16 // skipped_actions:uint16 msgs_created:uint16 // action_list_hash:uint256 - && t_StorageUsedShort.skip(cs); // tot_msg_size:StorageUsedShort + && t_StorageUsed.skip(cs); // tot_msg_size:StorageUsed } bool TrActionPhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const { @@ -1381,7 +1374,7 @@ bool TrActionPhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const && cs.advance(16 * 4 + 256) // tot_actions:uint16 spec_actions:uint16 // skipped_actions:uint16 msgs_created:uint16 // action_list_hash:uint256 - && t_StorageUsedShort.validate_skip(ops, cs, weak); // tot_msg_size:StorageUsed + && t_StorageUsed.validate_skip(ops, cs, weak); // tot_msg_size:StorageUsed } const TrActionPhase t_TrActionPhase; @@ -1392,11 +1385,11 @@ bool TrBouncePhase::skip(vm::CellSlice& cs) const { return cs.advance(2); // tr_phase_bounce_negfunds$00 case tr_phase_bounce_nofunds: return cs.advance(2) // tr_phase_bounce_nofunds$01 - && t_StorageUsedShort.skip(cs) // msg_size:StorageUsedShort + && t_StorageUsed.skip(cs) // msg_size:StorageUsed && t_Grams.skip(cs); // req_fwd_fees:Grams case tr_phase_bounce_ok: return cs.advance(1) // tr_phase_bounce_ok$1 - && t_StorageUsedShort.skip(cs) // msg_size:StorageUsedShort + && t_StorageUsed.skip(cs) // msg_size:StorageUsed && t_Grams.skip(cs) // msg_fees:Grams && t_Grams.skip(cs); // fwd_fees:Grams } @@ -1409,11 +1402,11 @@ bool TrBouncePhase::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const return cs.advance(2); // tr_phase_bounce_negfunds$00 case tr_phase_bounce_nofunds: return cs.advance(2) // tr_phase_bounce_nofunds$01 - && t_StorageUsedShort.validate_skip(ops, cs, weak) // msg_size:StorageUsedShort + && t_StorageUsed.validate_skip(ops, cs, weak) // msg_size:StorageUsed && t_Grams.validate_skip(ops, cs, weak); // req_fwd_fees:Grams case tr_phase_bounce_ok: return cs.advance(1) // tr_phase_bounce_ok$1 - && t_StorageUsedShort.validate_skip(ops, cs, weak) // msg_size:StorageUsedShort + && t_StorageUsed.validate_skip(ops, cs, weak) // msg_size:StorageUsed && t_Grams.validate_skip(ops, cs, weak) // msg_fees:Grams && t_Grams.validate_skip(ops, cs, weak); // fwd_fees:Grams } diff --git a/crypto/block/block-parse.h b/crypto/block/block-parse.h index 65f8b91f..fd17c657 100644 --- a/crypto/block/block-parse.h +++ b/crypto/block/block-parse.h @@ -493,13 +493,6 @@ struct StorageUsed final : TLB_Complex { extern const StorageUsed t_StorageUsed; -struct StorageUsedShort final : TLB_Complex { - bool skip(vm::CellSlice& cs) const override; - bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; -}; - -extern const StorageUsedShort t_StorageUsedShort; - struct StorageInfo final : TLB_Complex { bool skip(vm::CellSlice& cs) const override; bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override; diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index 4a8bbc06..5cba3c69 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -246,14 +246,13 @@ out_msg_queue_extra#0 dispatch_queue:DispatchQueue out_queue_size:(Maybe uint48) _ out_queue:OutMsgQueue proc_info:ProcessedInfo extra:(Maybe OutMsgQueueExtra) = OutMsgQueueInfo; -// -storage_used$_ cells:(VarUInteger 7) bits:(VarUInteger 7) - public_cells:(VarUInteger 7) = StorageUsed; -storage_used_short$_ cells:(VarUInteger 7) - bits:(VarUInteger 7) = StorageUsedShort; +storage_extra_none$000 = StorageExtraInfo; +storage_extra_info$001 dict_hash:uint256 = StorageExtraInfo; -storage_info$_ used:StorageUsed last_paid:uint32 +storage_used$_ cells:(VarUInteger 7) bits:(VarUInteger 7) = StorageUsed; + +storage_info$_ used:StorageUsed storage_extra:StorageExtraInfo last_paid:uint32 due_payment:(Maybe Grams) = StorageInfo; account_none$0 = Account; @@ -341,13 +340,13 @@ tr_phase_action$_ success:Bool valid:Bool no_funds:Bool total_fwd_fees:(Maybe Grams) total_action_fees:(Maybe Grams) result_code:int32 result_arg:(Maybe int32) tot_actions:uint16 spec_actions:uint16 skipped_actions:uint16 msgs_created:uint16 - action_list_hash:bits256 tot_msg_size:StorageUsedShort + action_list_hash:bits256 tot_msg_size:StorageUsed = TrActionPhase; tr_phase_bounce_negfunds$00 = TrBouncePhase; -tr_phase_bounce_nofunds$01 msg_size:StorageUsedShort +tr_phase_bounce_nofunds$01 msg_size:StorageUsed req_fwd_fees:Grams = TrBouncePhase; -tr_phase_bounce_ok$1 msg_size:StorageUsedShort +tr_phase_bounce_ok$1 msg_size:StorageUsed msg_fees:Grams fwd_fees:Grams = TrBouncePhase; // trans_ord$0000 credit_first:Bool diff --git a/crypto/block/create-state.cpp b/crypto/block/create-state.cpp index c8c8b970..4a74ac0f 100644 --- a/crypto/block/create-state.cpp +++ b/crypto/block/create-state.cpp @@ -338,10 +338,11 @@ td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref code, R PDO(cb.store_long_rchk_bool(workchain_id, ctor == 2 ? 8 : 32) && cb.store_bits_bool(addr.cbits(), 256)); THRERR("Cannot serialize addr:MsgAddressInt of the new smart contract"); // storage_stat:StorageInfo -> storage_stat.used:StorageUsed - PDO(block::store_UInt7(cb, stats.cells) // cells:(VarUInteger 7) - && block::store_UInt7(cb, stats.bits) // bits:(VarUInteger 7) - && block::store_UInt7(cb, stats.public_cells)); // public_cells:(VarUInteger 7) + PDO(block::store_UInt7(cb, stats.cells) // cells:(VarUInteger 7) + && block::store_UInt7(cb, stats.bits)) // bits:(VarUInteger 7) THRERR("Cannot serialize used:StorageUsed of the new smart contract"); + PDO(cb.store_zeroes_bool(3)); // extra:StorageExtraInfo + THRERR("Cannot serialize storage_extra:StorageExtraInfo of the new smart contract"); PDO(cb.store_long_bool(0, 33)); // last_paid:uint32 due_payment:(Maybe Grams) PDO(cb.append_data_cell_bool(storage)); // storage:AccountStorage THRERR("Cannot create Account of the new smart contract"); diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 98e6a26d..bb4825ea 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -156,6 +156,10 @@ class McShardHashI : public td::CntObject { virtual bool before_merge() const = 0; }; +struct StorageUsed { + td::uint64 cells = 0, bits = 0; +}; + struct McShardHash : public McShardHashI { ton::BlockIdExt blk_; ton::LogicalTime start_lt_, end_lt_; @@ -336,7 +340,7 @@ struct StoragePrices { , mc_cell_price(_mc_cprice) { } static td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector& pricing, - const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid, + const StorageUsed& storage_used, ton::UnixTime last_paid, bool is_special, bool is_masterchain); }; diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index 34d23511..4e92b8f6 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -257,6 +257,11 @@ bool Account::unpack_storage_info(vm::CellSlice& cs) { return false; } last_paid = info.last_paid; + if (info.storage_extra.write().fetch_long(3) == 1) { + info.storage_extra->prefetch_bits_to(storage_dict_hash.value_force()); + } else { + storage_dict_hash = {}; + } if (info.due_payment->prefetch_ulong(1) == 1) { vm::CellSlice& cs2 = info.due_payment.write(); cs2.advance(1); @@ -268,11 +273,9 @@ bool Account::unpack_storage_info(vm::CellSlice& cs) { due_payment = td::zero_refint(); } unsigned long long u = 0; - u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells); - u |= storage_stat.bits = block::tlb::t_VarUInteger_7.as_uint(*used.bits); - u |= storage_stat.public_cells = block::tlb::t_VarUInteger_7.as_uint(*used.public_cells); - LOG(DEBUG) << "last_paid=" << last_paid << "; cells=" << storage_stat.cells << " bits=" << storage_stat.bits - << " public_cells=" << storage_stat.public_cells; + u |= storage_used.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells); + u |= storage_used.bits = block::tlb::t_VarUInteger_7.as_uint(*used.bits); + LOG(DEBUG) << "last_paid=" << last_paid << "; cells=" << storage_used.cells << " bits=" << storage_used.bits; return (u != std::numeric_limits::max()); } @@ -527,7 +530,8 @@ bool Account::init_new(ton::UnixTime now) { last_trans_hash_.set_zero(); now_ = now; last_paid = 0; - storage_stat.clear(); + storage_used = {}; + storage_dict_hash = {}; due_payment = td::zero_refint(); balance.set_zero(); if (my_addr_exact.is_null()) { @@ -617,12 +621,12 @@ bool Account::belongs_to_shard(ton::ShardIdFull shard) const { * @param payment The total sum to be updated. * @param delta The time delta for which the payment is calculated. * @param prices The storage prices. - * @param storage Account storage statistics. + * @param storage_used Account storage statistics. * @param is_mc A flag indicating whether the account is in the masterchain. */ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, const block::StoragePrices& prices, - const vm::CellStorageStat& storage, bool is_mc) { - td::BigInt256 c{(long long)storage.cells}, b{(long long)storage.bits}; + const StorageUsed& storage_used, bool is_mc) { + td::BigInt256 c{(long long)storage_used.cells}, b{(long long)storage_used.bits}; if (is_mc) { // storage.cells * prices.mc_cell_price + storage.bits * prices.mc_bit_price; c.mul_short(prices.mc_cell_price); @@ -643,7 +647,7 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co * * @param now The current Unix time. * @param pricing The vector of storage prices. - * @param storage_stat Account storage statistics. + * @param storage_used Account storage statistics. * @param last_paid The Unix time when the last payment was made. * @param is_special A flag indicating if the account is special. * @param is_masterchain A flag indicating if the account is in the masterchain. @@ -651,7 +655,7 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co * @returns The computed storage fees as RefInt256. */ td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing, - const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid, + const StorageUsed& storage_used, ton::UnixTime last_paid, bool is_special, bool is_masterchain) { if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) { return td::zero_refint(); @@ -669,7 +673,7 @@ td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std:: ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now); if (upto < valid_until) { assert(upto >= pricing[i].valid_since); - add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain); + add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_used, is_masterchain); } upto = valid_until; } @@ -685,7 +689,7 @@ td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std:: * @returns The computed storage fees as RefInt256. */ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { - return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain()); + return StoragePrices::compute_storage_fees(now, pricing, storage_used, last_paid, is_special, is_masterchain()); } namespace transaction { @@ -1848,7 +1852,7 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { if (S.is_error()) { // Rollback changes to state, fail action phase LOG(INFO) << "Account state size exceeded limits: " << S.move_as_error(); - new_storage_stat.clear(); + new_account_storage_stat = {}; new_code = old_code; new_data = old_data; new_library = old_library; @@ -2104,7 +2108,7 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c LOG(DEBUG) << "added " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex(); } new_library = std::move(dict).extract_root_cell(); - } catch (vm::VmError& vme) { + } catch (vm::VmError&) { return 42; } ap.spec_actions++; @@ -2931,7 +2935,7 @@ static td::uint32 get_public_libraries_diff_count(const td::Ref& old_l * This function is not called for special accounts. * * @param size_limits The size limits configuration. - * @param update_storage_stat Store storage stat in the Transaction's CellStorageStat. + * @param update_storage_stat Store storage stat in the Transaction's AccountStorageStat. * * @returns A `td::Status` indicating the result of the check. * - If the state limits are within the allowed range, returns OK. @@ -2939,60 +2943,47 @@ static td::uint32 get_public_libraries_diff_count(const td::Ref& old_l */ td::Status Transaction::check_state_limits(const SizeLimitsConfig& size_limits, bool update_storage_stat) { auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool { - if (a.is_null()) { - return b.is_null(); - } - if (b.is_null()) { - return false; - } - return a->get_hash() == b->get_hash(); + return a.is_null() || b.is_null() ? a.is_null() == b.is_null() : a->get_hash() == b->get_hash(); }; if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) && cell_equal(account.library, new_library)) { return td::Status::OK(); } - vm::CellStorageStat storage_stat; - storage_stat.limit_cells = size_limits.max_acc_state_cells; - storage_stat.limit_bits = size_limits.max_acc_state_bits; + AccountStorageStat storage_stat; + if (update_storage_stat && account.account_storage_stat) { + storage_stat = account.account_storage_stat.value(); + } { TD_PERF_COUNTER(transaction_storage_stat_a); td::Timer timer; - auto add_used_storage = [&](const td::Ref& cell) -> td::Status { - if (cell.not_null()) { - TRY_RESULT(res, storage_stat.add_used_storage(cell)); - if (res.max_merkle_depth > max_allowed_merkle_depth) { - return td::Status::Error("too big merkle depth"); - } - } - return td::Status::OK(); - }; - TRY_STATUS(add_used_storage(new_code)); - TRY_STATUS(add_used_storage(new_data)); - TRY_STATUS(add_used_storage(new_library)); + TRY_RESULT(info, storage_stat.replace_roots({new_code, new_data, new_library})); + if (info.max_merkle_depth > max_allowed_merkle_depth) { + return td::Status::Error("too big Merkle depth"); + } if (timer.elapsed() > 0.1) { - LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; + LOG(INFO) << "Compute used storage (1) took " << timer.elapsed() << "s"; } } - if (acc_status == Account::acc_active) { - storage_stat.clear_limit(); - } else { - storage_stat.clear(); + if (storage_stat.get_total_cells() > size_limits.max_acc_state_cells || + storage_stat.get_total_bits() > size_limits.max_acc_state_bits) { + return td::Status::Error(PSTRING() << "account state is too big: cells=" << storage_stat.get_total_cells() + << ", bits=" << storage_stat.get_total_bits() + << " (max cells=" << size_limits.max_acc_state_cells + << ", max bits=" << size_limits.max_acc_state_bits << ")"); } - td::Status res; - if (storage_stat.cells > size_limits.max_acc_state_cells || storage_stat.bits > size_limits.max_acc_state_bits) { - res = td::Status::Error(PSTRING() << "account state is too big"); - } else if (account.is_masterchain() && !cell_equal(account.library, new_library) && - get_public_libraries_count(new_library) > size_limits.max_acc_public_libraries) { - res = td::Status::Error("too many public libraries"); - } else { - res = td::Status::OK(); + if (account.is_masterchain() && !cell_equal(account.library, new_library)) { + auto libraries_count = get_public_libraries_count(new_library); + if (libraries_count > size_limits.max_acc_public_libraries) { + return td::Status::Error(PSTRING() << "too many public libraries: " << libraries_count << " (max " + << size_limits.max_acc_public_libraries << ")"); + } } if (update_storage_stat) { // storage_stat will be reused in compute_state() - new_storage_stat = std::move(storage_stat); + new_account_storage_stat.value_force() = std::move(storage_stat); } - return res; + return td::Status::OK(); } /** @@ -3140,44 +3131,6 @@ bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const { return cb.store_long_bool(v, 2); } -/** - * Tries to update the storage statistics based on the old storage statistics and old account state without fully recomputing it. - * - * It succeeds if only root cell of AccountStorage is changed. - * old_cs and new_cell are AccountStorage without extra currencies (if global_version >= 10). - * - * @param old_stat The old storage statistics. - * @param old_cs The old AccountStorage. - * @param new_cell The new AccountStorage. - * - * @returns An optional value of type vm::CellStorageStat. If the update is successful, it returns the new storage statistics. Otherwise, it returns an empty optional. - */ -static td::optional try_update_storage_stat(const vm::CellStorageStat& old_stat, - td::Ref old_cs, - td::Ref new_cell) { - if (old_stat.cells == 0 || old_cs.is_null()) { - return {}; - } - vm::CellSlice new_cs = vm::CellSlice(vm::NoVm(), new_cell); - if (old_cs->size_refs() != new_cs.size_refs()) { - return {}; - } - for (unsigned i = 0; i < old_cs->size_refs(); ++i) { - if (old_cs->prefetch_ref(i)->get_hash() != new_cs.prefetch_ref(i)->get_hash()) { - return {}; - } - } - if (old_stat.bits < old_cs->size()) { - return {}; - } - - vm::CellStorageStat new_stat; - new_stat.cells = old_stat.cells; - new_stat.bits = old_stat.bits - old_cs->size() + new_cs.size(); - new_stat.public_cells = old_stat.public_cells; - return new_stat; -} - /** * Removes extra currencies dict from AccountStorage. * @@ -3185,9 +3138,9 @@ static td::optional try_update_storage_stat(const vm::CellS * * @param storage_cs AccountStorage as CellSlice. * - * @returns AccountStorage without extra currencies as Cell. + * @returns AccountStorage without extra currencies as CellSlice. */ -static td::Ref storage_without_extra_currencies(td::Ref storage_cs) { +static td::Ref storage_without_extra_currencies(td::Ref storage_cs) { block::gen::AccountStorage::Record rec; if (!block::gen::csr_unpack(storage_cs, rec)) { LOG(ERROR) << "failed to unpack AccountStorage"; @@ -3205,18 +3158,20 @@ static td::Ref storage_without_extra_currencies(td::Ref return {}; } } - td::Ref cell; - if (!block::gen::pack_cell(cell, rec)) { + td::Ref result; + if (!block::gen::csr_pack(result, rec)) { LOG(ERROR) << "failed to pack AccountStorage"; return {}; } - return cell; + return result; } namespace transaction { /** * Computes the new state of the account. * + * @param cfg The configuration for the serialization phase. + * * @returns True if the state computation is successful, false otherwise. */ bool Transaction::compute_state(const SerializeConfig& cfg) { @@ -3290,45 +3245,82 @@ bool Transaction::compute_state(const SerializeConfig& cfg) { } else { new_inner_state.clear(); } - vm::CellStorageStat& stats = new_storage_stat; + td::Ref old_storage_for_stat = account.storage; - td::Ref new_storage_for_stat = storage; + td::Ref new_storage_for_stat = new_storage; if (cfg.extra_currency_v2) { new_storage_for_stat = storage_without_extra_currencies(new_storage); if (new_storage_for_stat.is_null()) { return false; } if (old_storage_for_stat.not_null()) { - old_storage_for_stat = vm::load_cell_slice_ref(storage_without_extra_currencies(old_storage_for_stat)); + old_storage_for_stat = storage_without_extra_currencies(old_storage_for_stat); if (old_storage_for_stat.is_null()) { return false; } } + } else if (cfg.store_storage_dict_hash) { + LOG(ERROR) << "unsupported store_storage_dict_hash=true, extra_currency_v2=false"; + return false; } - auto new_stats = try_update_storage_stat(account.storage_stat, old_storage_for_stat, storage); - if (new_stats) { - stats = new_stats.unwrap(); + + bool storage_refs_changed = false; + if (old_storage_for_stat.is_null() || new_storage_for_stat->size_refs() != old_storage_for_stat->size_refs()) { + storage_refs_changed = true; } else { - TD_PERF_COUNTER(transaction_storage_stat_b); - td::Timer timer; - stats.add_used_storage(new_storage_for_stat).ensure(); - if (timer.elapsed() > 0.1) { - LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s"; + for (unsigned i = 0; i < new_storage_for_stat->size_refs(); i++) { + if (new_storage_for_stat->prefetch_ref(i)->get_hash() != old_storage_for_stat->prefetch_ref(i)->get_hash()) { + storage_refs_changed = true; + break; + } } } - CHECK(cb.store_long_bool(1, 1) // account$1 - && cb.append_cellslice_bool(account.my_addr) // addr:MsgAddressInt - && block::store_UInt7(cb, stats.cells) // storage_used$_ cells:(VarUInteger 7) - && block::store_UInt7(cb, stats.bits) // bits:(VarUInteger 7) - && block::store_UInt7(cb, stats.public_cells) // public_cells:(VarUInteger 7) - && cb.store_long_bool(last_paid, 32)); // last_paid:uint32 + + if (storage_refs_changed || (cfg.store_storage_dict_hash && !account.storage_dict_hash)) { + TD_PERF_COUNTER(transaction_storage_stat_b); + td::Timer timer; + if (!new_account_storage_stat && account.account_storage_stat) { + new_account_storage_stat = account.account_storage_stat; + } + AccountStorageStat& stats = new_account_storage_stat.value_force(); + // Don't check Merkle depth and size here - they were checked in check_state_limits + auto S = stats.replace_roots(new_storage->prefetch_all_refs()).move_as_status(); + if (S.is_error()) { + LOG(ERROR) << "Cannot recompute storage stats for account " << account.addr.to_hex() << ": " << S.move_as_error(); + return false; + } + new_storage_dict_hash = stats.get_dict_hash(); + // Root of AccountStorage is not counted in AccountStorageStat + new_storage_used.cells = stats.get_total_cells() + 1; + new_storage_used.bits = stats.get_total_bits() + new_storage->size(); + if (timer.elapsed() > 0.1) { + LOG(INFO) << "Compute used storage (2) took " << timer.elapsed() << "s"; + } + } else { + new_storage_used = account.storage_used; + new_storage_used.bits -= old_storage_for_stat->size(); + new_storage_used.bits += new_storage_for_stat->size(); + new_storage_dict_hash = account.storage_dict_hash; + new_account_storage_stat = account.account_storage_stat; + } + if (!cfg.store_storage_dict_hash) { + new_storage_dict_hash = {}; + } + + CHECK(cb.store_long_bool(1, 1) // account$1 + && cb.append_cellslice_bool(account.my_addr) // addr:MsgAddressInt + && block::store_UInt7(cb, new_storage_used.cells) // storage_used$_ cells:(VarUInteger 7) + && block::store_UInt7(cb, new_storage_used.bits) // bits:(VarUInteger 7) + && cb.store_long_bool(new_storage_dict_hash ? 1 : 0, 3) // extra:StorageExtraInfo + && (!new_storage_dict_hash || cb.store_bits_bool(new_storage_dict_hash.value())) // dict_hash:uint256 + && cb.store_long_bool(last_paid, 32)); // last_paid:uint32 if (due_payment.not_null() && td::sgn(due_payment) != 0) { CHECK(cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, due_payment)); // due_payment:(Maybe Grams) } else { CHECK(cb.store_long_bool(0, 1)); } - CHECK(cb.append_data_cell_bool(std::move(storage))); + CHECK(cb.append_cellslice_bool(new_storage)); new_total_state = cb.finalize(); if (verbosity > 2) { FLOG(INFO) { @@ -3345,6 +3337,8 @@ bool Transaction::compute_state(const SerializeConfig& cfg) { * * Updates root. * + * @param cfg The configuration for the serialization. + * * @returns True if the serialization is successful, False otherwise. */ bool Transaction::serialize(const SerializeConfig& cfg) { @@ -3688,7 +3682,9 @@ Ref Transaction::commit(Account& acc) { acc.last_trans_end_lt_ = end_lt; acc.last_trans_hash_ = root->get_hash().bits(); acc.last_paid = last_paid; - acc.storage_stat = new_storage_stat; + acc.storage_used = new_storage_used; + acc.account_storage_stat = std::move(new_account_storage_stat); + acc.storage_dict_hash = new_storage_dict_hash; acc.storage = new_storage; acc.balance = std::move(balance); acc.due_payment = std::move(due_payment); @@ -3936,6 +3932,7 @@ td::Status FetchConfigParams::fetch_config_params( } { serialize_cfg->extra_currency_v2 = config.get_global_version() >= 10; + serialize_cfg->store_storage_dict_hash = config.get_global_version() >= 11; } { // fetch block_grams_created diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 8e612e6a..aa08719a 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -17,6 +17,7 @@ Copyright 2017-2020 Telegram Systems LLP */ #pragma once +#include "account-storage-stat.h" #include "common/refcnt.hpp" #include "common/refint.h" #include "vm/cells.h" @@ -179,6 +180,7 @@ struct ActionPhaseConfig { struct SerializeConfig { bool extra_currency_v2{false}; + bool store_storage_dict_hash{false}; }; struct CreditPhase { @@ -266,8 +268,12 @@ struct Account { ton::LogicalTime last_trans_lt_; ton::Bits256 last_trans_hash_; ton::LogicalTime block_lt; + ton::UnixTime last_paid; - vm::CellStorageStat storage_stat; + StorageUsed storage_used; + td::optional storage_dict_hash; + td::optional account_storage_stat; + block::CurrencyCollection balance; td::RefInt256 due_payment; Ref orig_total_state; // ^Account @@ -377,7 +383,9 @@ struct Transaction { std::unique_ptr compute_phase; std::unique_ptr action_phase; std::unique_ptr bounce_phase; - vm::CellStorageStat new_storage_stat; + StorageUsed new_storage_used; + td::optional new_account_storage_stat; + td::optional new_storage_dict_hash; bool gas_limit_overridden{false}; Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg = {}); diff --git a/crypto/vm/boc.h b/crypto/vm/boc.h index 17e7eb69..3a7ddc48 100644 --- a/crypto/vm/boc.h +++ b/crypto/vm/boc.h @@ -113,21 +113,19 @@ class NewCellStorageStat { struct CellStorageStat { unsigned long long cells; unsigned long long bits; - unsigned long long public_cells; struct CellInfo { td::uint32 max_merkle_depth = 0; }; td::HashMap seen; - CellStorageStat() : cells(0), bits(0), public_cells(0) { + CellStorageStat() : cells(0), bits(0) { } - explicit CellStorageStat(unsigned long long limit_cells) - : cells(0), bits(0), public_cells(0), limit_cells(limit_cells) { + explicit CellStorageStat(unsigned long long limit_cells) : cells(0), bits(0), limit_cells(limit_cells) { } void clear_seen() { seen.clear(); } void clear() { - cells = bits = public_cells = 0; + cells = bits = 0; clear_limit(); clear_seen(); } diff --git a/crypto/vm/cells/CellSlice.cpp b/crypto/vm/cells/CellSlice.cpp index 9cd3e931..bea20f95 100644 --- a/crypto/vm/cells/CellSlice.cpp +++ b/crypto/vm/cells/CellSlice.cpp @@ -773,6 +773,14 @@ bool CellSlice::prefetch_maybe_ref(Ref& res) const { } } +std::vector> CellSlice::prefetch_all_refs() const { + std::vector> res(size_refs()); + for (unsigned i = 0; i < size_refs(); ++i) { + res[i] = prefetch_ref(i); + } + return res; +} + bool CellSlice::fetch_maybe_ref(Ref& res) { auto z = prefetch_ulong(1); if (!z) { diff --git a/crypto/vm/cells/CellSlice.h b/crypto/vm/cells/CellSlice.h index ecce30f5..7525272b 100644 --- a/crypto/vm/cells/CellSlice.h +++ b/crypto/vm/cells/CellSlice.h @@ -190,6 +190,7 @@ class CellSlice : public td::CntObject { } bool fetch_maybe_ref(Ref& ref); bool prefetch_maybe_ref(Ref& ref) const; + std::vector> prefetch_all_refs() const; td::BitSlice fetch_bits(unsigned bits); td::BitSlice prefetch_bits(unsigned bits) const; td::Ref fetch_subslice(unsigned bits, unsigned refs = 0); diff --git a/doc/GlobalVersions.md b/doc/GlobalVersions.md index 77963e95..c0be0b10 100644 --- a/doc/GlobalVersions.md +++ b/doc/GlobalVersions.md @@ -156,3 +156,10 @@ Example: if the last masterchain block seqno is `19071` then the list contains b ### TVM changes - `SENDMSG` calculates messages size and fees without extra currencies, uses new +64 and +128 mode behavior. - `SENDMSG` does not check the number of extra currencies. + +## Version 11 +### New account storage stat +Along with the storage stat (cells and bits count), each account now stores the hash of the **storage dict**. + +**Storage dict** is the dictionary that stores refcnt for each cell in the account state. +This is required to help computing storage stats in the future, after collator-validator separation. \ No newline at end of file diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index d73e715c..f91f2bf1 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -180,7 +180,7 @@ struct RawAccountState { td::Ref extra_currencies; ton::UnixTime storage_last_paid{0}; - vm::CellStorageStat storage_stat; + block::StorageUsed storage_used; td::Ref code; td::Ref data; @@ -1036,7 +1036,7 @@ class Query { TRY_RESULT(basechain_msg_prices, cfg->get_msg_prices(false)); block::MsgPrices* msg_prices[2] = {&basechain_msg_prices, &masterchain_msg_prices}; auto storage_fee_256 = block::StoragePrices::compute_storage_fees( - raw_.source->get_sync_time(), storage_prices, raw_.source->raw().storage_stat, + raw_.source->get_sync_time(), storage_prices, raw_.source->raw().storage_used, raw_.source->raw().storage_last_paid, false, is_masterchain); auto storage_fee = storage_fee_256.is_null() ? 0 : storage_fee_256->to_long(); @@ -1085,7 +1085,7 @@ class Query { TRY_RESULT(dest_gas_limits_prices, cfg->get_gas_limits_prices(dest_is_masterchain)); auto dest_storage_fee_256 = destination ? block::StoragePrices::compute_storage_fees( - destination->get_sync_time(), storage_prices, destination->raw().storage_stat, + destination->get_sync_time(), storage_prices, destination->raw().storage_used, destination->raw().storage_last_paid, false, is_masterchain) : td::make_refint(0); Fee dst_fee; @@ -1399,17 +1399,16 @@ class GetRawAccountState : public td::actor::Actor { return td::Status::Error("Failed to unpack StorageInfo"); } unsigned long long u = 0; - vm::CellStorageStat storage_stat; + block::StorageUsed storage_stat; u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.cells); u |= storage_stat.bits = block::tlb::t_VarUInteger_7.as_uint(*storage_used.bits); - u |= storage_stat.public_cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.public_cells); //LOG(DEBUG) << "last_paid=" << res.storage_last_paid << "; cells=" << storage_stat.cells - //<< " bits=" << storage_stat.bits << " public_cells=" << storage_stat.public_cells; + //<< " bits=" << storage_stat.bits; if (u == std::numeric_limits::max()) { return td::Status::Error("Failed to unpack StorageStat"); } - res.storage_stat = storage_stat; + res.storage_used = storage_stat; } block::gen::AccountStorage::Record storage; @@ -2089,7 +2088,7 @@ class RunEmulator : public TonlibQueryActor { raw.balance = balance.grams->to_long(); raw.extra_currencies = balance.extra; raw.storage_last_paid = std::move(account.last_paid); - raw.storage_stat = std::move(account.storage_stat); + raw.storage_used = account.storage_used; raw.code = std::move(account.code); raw.data = std::move(account.data); raw.state = std::move(account.total_state); diff --git a/validator/impl/validate-query.cpp b/validator/impl/validate-query.cpp index 90966d82..5f2ba661 100644 --- a/validator/impl/validate-query.cpp +++ b/validator/impl/validate-query.cpp @@ -1008,6 +1008,7 @@ bool ValidateQuery::fetch_config_params() { } { serialize_cfg_.extra_currency_v2 = config_->get_global_version() >= 10; + serialize_cfg_.store_storage_dict_hash = config_->get_global_version() >= 11; } { // fetch block_grams_created