diff --git a/nil/internal/collate/block_listener.go b/nil/internal/collate/block_listener.go index 06091fe68..8b6f96818 100644 --- a/nil/internal/collate/block_listener.go +++ b/nil/internal/collate/block_listener.go @@ -175,6 +175,22 @@ func unmarshalBlock(pbBlock *pb.RawFullBlock) (*types.BlockWithExtractedData, er return raw.DecodeBytes() } +type blockByNumberCache struct { + blockByHash *execution.BlockByHashAccessor + blockHashByNumber *execution.BlockHashByNumberAccessor +} + +func (b blockByNumberCache) Get( + tx db.RoTx, shardId types.ShardId, num types.BlockNumber, +) (*types.RawBlockWithExtractedData, error) { + hash, err := b.blockHashByNumber.Get(tx, shardId, num) + if err != nil { + return nil, err + } + + return b.blockByHash.Get(tx, shardId, hash) +} + func SetBlockRequestHandler( ctx context.Context, networkManager network.Manager, shardId types.ShardId, database db.DB, logger logging.Logger, ) { @@ -183,8 +199,10 @@ func SetBlockRequestHandler( return } - // Sharing accessor between all handlers enables caching. - accessor := execution.NewStateAccessor(128, 0) + accessor := &blockByNumberCache{ + blockByHash: execution.NewBlockByHashAccessor(128), + blockHashByNumber: execution.NewBlockHashByNumberAccessor(128), + } handler := func(s network.Stream) { if err := s.SetDeadline(time.Now().Add(requestTimeout)); err != nil { return @@ -212,15 +230,8 @@ func SetBlockRequestHandler( } defer tx.Rollback() - acc := accessor.RawAccess(tx, shardId). - GetBlock(). - WithOutTransactions(). - WithInTransactions(). - WithChildBlocks(). - WithConfig() - for id := blockReq.GetId(); ; id++ { - resp, err := acc.ByNumber(types.BlockNumber(id)) + resp, err := accessor.Get(tx, shardId, types.BlockNumber(id)) if err != nil { if !errors.Is(err, db.ErrKeyNotFound) { logError(logger, err, "DB error") @@ -229,11 +240,11 @@ func SetBlockRequestHandler( } b := &pb.RawFullBlock{ - BlockBytes: resp.Block(), - OutTransactionsBytes: resp.OutTransactions(), - InTransactionsBytes: resp.InTransactions(), - ChildBlocks: pb.PackHashes(resp.ChildBlocks()), - Config: resp.Config(), + BlockBytes: resp.Block, + OutTransactionsBytes: resp.OutTransactions, + InTransactionsBytes: resp.InTransactions, + ChildBlocks: pb.PackHashes(resp.ChildBlocks), + Config: resp.Config, } if err := writeBlockToStream(s, b); err != nil { diff --git a/nil/internal/collate/proposer.go b/nil/internal/collate/proposer.go index c008156db..6ca18ae89 100644 --- a/nil/internal/collate/proposer.go +++ b/nil/internal/collate/proposer.go @@ -73,11 +73,10 @@ func (p *proposer) GenerateProposal(ctx context.Context, txFabric db.DB) (*execu if err != nil { return nil, fmt.Errorf("failed to fetch previous block: %w", err) } - data, err := p.params.StateAccessor.Access(tx, p.params.ShardId).GetBlock().ByHash(prevBlockHash) + prevBlock, err := p.params.BlockAccessor.GetByHash(tx, p.params.ShardId, prevBlockHash) if err != nil { return nil, err } - prevBlock := data.Block() if prevBlock.PatchLevel > validatorPatchLevel { return nil, fmt.Errorf( @@ -94,7 +93,7 @@ func (p *proposer) GenerateProposal(ctx context.Context, txFabric db.DB) (*execu p.executionState, err = execution.NewExecutionState(tx, p.params.ShardId, execution.StateParams{ Block: prevBlock, ConfigAccessor: configAccessor, - StateAccessor: p.params.StateAccessor, + BlockAccessor: p.params.BlockAccessor, FeeCalculator: p.params.FeeCalculator, Mode: execution.ModeProposal, }) @@ -381,17 +380,14 @@ func (p *proposer) handleTransactionsFromNeighbors(tx db.RoTx) error { neighbor := &state.Neighbors[position] nextTx := p.executionState.InTxCounts[neighborId] - shardAccessor := p.params.StateAccessor.Access(tx, neighborId) - for checkLimits() { - data, err := shardAccessor.GetBlock().ByNumber(neighbor.BlockNumber) + block, err := p.params.BlockAccessor.GetByNumber(tx, neighborId, neighbor.BlockNumber) if errors.Is(err, db.ErrKeyNotFound) { break } if err != nil { return err } - block := data.Block() outTxnTrie := execution.NewDbTransactionTrieReader(tx, neighborId) if err := outTxnTrie.SetRootHash(block.OutTransactionsRoot); err != nil { diff --git a/nil/internal/collate/validator.go b/nil/internal/collate/validator.go index 62443131f..860beaa0d 100644 --- a/nil/internal/collate/validator.go +++ b/nil/internal/collate/validator.go @@ -112,11 +112,11 @@ func (s *Validator) getLastBlockUnlocked(ctx context.Context) (*types.Block, com return nil, common.EmptyHash, err } - block, err := s.params.StateAccessor.Access(tx, s.params.ShardId).GetBlock().ByHash(hash) + block, err := s.params.BlockAccessor.GetByHash(tx, s.params.ShardId, hash) if err != nil { return nil, common.EmptyHash, err } - return block.Block(), hash, nil + return block, hash, nil } func (s *Validator) GetLastBlock(ctx context.Context) (*types.Block, common.Hash, error) { @@ -140,11 +140,11 @@ func (s *Validator) getBlock(ctx context.Context, hash common.Hash) (*types.Bloc } defer tx.Rollback() - block, err := s.params.StateAccessor.Access(tx, s.params.ShardId).GetBlock().ByHash(hash) + block, err := s.params.BlockAccessor.GetByHash(tx, s.params.ShardId, hash) if err != nil { return nil, err } - return block.Block(), nil + return block, nil } func (s *Validator) TxPool() TxnPool { diff --git a/nil/internal/db/accessors.go b/nil/internal/db/accessors.go index 2a75b2d88..1c5a90bdf 100644 --- a/nil/internal/db/accessors.go +++ b/nil/internal/db/accessors.go @@ -211,3 +211,19 @@ func ReadBlockByNumber(tx RoTx, shardId types.ShardId, blockNumber types.BlockNu } return ReadBlock(tx, shardId, blockHash) } + +func ReadTxnNumberByHash( + tx RoTx, shardId types.ShardId, hash common.Hash, +) (BlockHashAndTransactionIndex, error) { + var idx BlockHashAndTransactionIndex + + value, err := GetFromShard(tx, shardId, BlockHashAndInTransactionIndexByTransactionHash, hash) + if err != nil { + return idx, err + } + + if err := idx.UnmarshalNil(value); err != nil { + return idx, err + } + return idx, nil +} diff --git a/nil/internal/execution/block_accessors.go b/nil/internal/execution/block_accessors.go new file mode 100644 index 000000000..0f307f8de --- /dev/null +++ b/nil/internal/execution/block_accessors.go @@ -0,0 +1,304 @@ +package execution + +import ( + "errors" + "fmt" + + "github.com/NilFoundation/nil/nil/common" + "github.com/NilFoundation/nil/nil/common/assert" + "github.com/NilFoundation/nil/nil/common/check" + "github.com/NilFoundation/nil/nil/internal/db" + "github.com/NilFoundation/nil/nil/internal/mpt" + "github.com/NilFoundation/nil/nil/internal/types" + lru "github.com/hashicorp/golang-lru/v2" +) + +type BlockAccessor struct { + headers *BlockHeaderByHashAccessor + hashByNumber *BlockHashByNumberAccessor +} + +func NewBlockAccessor(blocksLRUSize int) *BlockAccessor { + return &BlockAccessor{ + headers: NewBlockHeaderByHashAccessor(blocksLRUSize), + hashByNumber: NewBlockHashByNumberAccessor(blocksLRUSize), + } +} + +func (s BlockAccessor) GetByHash(tx db.RoTx, shardId types.ShardId, hash common.Hash) (*types.Block, error) { + return s.headers.Get(tx, shardId, hash) +} + +func (s BlockAccessor) GetByNumber(tx db.RoTx, shardId types.ShardId, num types.BlockNumber) (*types.Block, error) { + hash, err := s.hashByNumber.Get(tx, shardId, num) + if err != nil { + return nil, err + } + + return s.headers.Get(tx, shardId, hash) +} + +type headerWithRaw struct { + block *types.Block + raw []byte +} + +type shardAndBlockId struct { + shardId types.ShardId + blockId types.BlockNumber +} + +type BlockHeaderByHashAccessor struct { + cache *lru.Cache[common.Hash, headerWithRaw] +} + +func NewBlockHeaderByHashAccessor(blocksLRUSize int) *BlockHeaderByHashAccessor { + cache, err := lru.New[common.Hash, headerWithRaw](blocksLRUSize) + check.PanicIfErr(err) + + return &BlockHeaderByHashAccessor{ + cache: cache, + } +} + +type BlockByHashAccessor struct { + cache *lru.Cache[common.Hash, *types.RawBlockWithExtractedData] + + headersCache *BlockHeaderByHashAccessor +} + +func NewBlockByHashAccessor(blocksLRUSize int) *BlockByHashAccessor { + fc, err := lru.New[common.Hash, *types.RawBlockWithExtractedData](blocksLRUSize) + check.PanicIfErr(err) + + return &BlockByHashAccessor{ + cache: fc, + headersCache: NewBlockHeaderByHashAccessor(blocksLRUSize), + } +} + +type BlockHashByNumberAccessor struct { + cache *lru.Cache[shardAndBlockId, common.Hash] +} + +func NewBlockHashByNumberAccessor(blocksLRUSize int) *BlockHashByNumberAccessor { + cache, err := lru.New[shardAndBlockId, common.Hash](blocksLRUSize) + check.PanicIfErr(err) + + return &BlockHashByNumberAccessor{ + cache: cache, + } +} + +func (b *BlockHashByNumberAccessor) Get(tx db.RoTx, shardId types.ShardId, num types.BlockNumber) (common.Hash, error) { + key := shardAndBlockId{shardId, num} + // todo: use cache + // currently, the block can be somehow updated + + hash, err := db.ReadBlockHashByNumber(tx, shardId, num) + if err != nil { + return common.EmptyHash, err + } + + b.cache.Add(key, hash) + return hash, nil +} + +func (b *BlockHeaderByHashAccessor) Get(tx db.RoTx, shardId types.ShardId, hash common.Hash) (*types.Block, error) { + h, err := b.get(tx, shardId, hash) + if err != nil { + return nil, err + } + return h.block, nil +} + +func (b *BlockHeaderByHashAccessor) GetRaw(tx db.RoTx, shardId types.ShardId, hash common.Hash) ([]byte, error) { + h, err := b.get(tx, shardId, hash) + if err != nil { + return nil, err + } + return h.raw, nil +} + +func (b *BlockByHashAccessor) Get( + tx db.RoTx, shardId types.ShardId, hash common.Hash, +) (*types.RawBlockWithExtractedData, error) { + if rawBlockExt, ok := b.cache.Get(hash); ok { + return rawBlockExt, nil + } + + h, err := b.headersCache.get(tx, shardId, hash) + if err != nil { + return nil, err + } + + res := &types.RawBlockWithExtractedData{ + Block: h.raw, + } + + res.InTransactions, err = b.collectTxnIndexedEntities(tx, shardId, + db.TransactionTrieTable, h.block.InTransactionsRoot) + if err != nil { + return nil, err + } + res.InTxCounts, err = b.collectTxnCounts(tx, shardId, db.TransactionTrieTable, h.block.InTransactionsRoot) + if err != nil { + return nil, err + } + + res.OutTransactions, err = b.collectTxnIndexedEntities(tx, shardId, + db.TransactionTrieTable, h.block.OutTransactionsRoot) + if err != nil { + return nil, err + } + res.OutTxCounts, err = b.collectTxnCounts(tx, shardId, db.TransactionTrieTable, h.block.OutTransactionsRoot) + if err != nil { + return nil, err + } + + res.Receipts, err = b.collectTxnIndexedEntities(tx, shardId, db.ReceiptTrieTable, h.block.ReceiptsRoot) + if err != nil { + return nil, err + } + + res.ChildBlocks, err = b.collectChildBlocks(tx, shardId, h.block) + if err != nil { + return nil, err + } + + res.DbTimestamp, err = db.ReadBlockTimestamp(tx, shardId, hash) + if err != nil && !errors.Is(err, db.ErrKeyNotFound) { + return nil, err + } + + if shardId.IsMainShard() { + res.Config, err = b.collectConfig(tx, shardId, h.block) + if err != nil { + return nil, err + } + } + + return res, nil +} + +func (b *BlockHeaderByHashAccessor) get(tx db.RoTx, shardId types.ShardId, hash common.Hash) (headerWithRaw, error) { + if h, ok := b.cache.Get(hash); ok { + return h, nil + } + + raw, err := db.ReadBlockBytes(tx, shardId, hash) + if err != nil { + return headerWithRaw{}, err + } + + block := &types.Block{} + if err := block.UnmarshalNil(raw); err != nil { + return headerWithRaw{}, err + } + + if assert.Enable { + blockHash := block.Hash(shardId) + check.PanicIfNotf(blockHash == hash, "block hash mismatch: %s != %s", blockHash, hash) + } + + res := headerWithRaw{ + block: block, + raw: raw, + } + b.cache.Add(hash, res) + + return res, nil +} + +func (b *BlockByHashAccessor) collectTxnCounts( + tx db.RoTx, shardId types.ShardId, + tableName db.ShardedTableName, root common.Hash, +) ([][]byte, error) { + reader := mpt.NewDbReader(tx, shardId, tableName) + if err := reader.SetRootHash(root); err != nil { + return nil, err + } + + items := make([][]byte, 0, 16) + for k, v := range reader.Iterate() { + if len(k) != types.ShardIdSize { + continue + } + + var transactionIndex types.TransactionIndex + if err := transactionIndex.UnmarshalNil(v); err != nil { + return nil, fmt.Errorf("failed to unmarshal transaction index for shard %s: %w", k, err) + } + + txCount := &types.TxCount{ + ShardId: uint16(types.BytesToShardId(k)), + Count: transactionIndex, + } + + item, err := txCount.MarshalNil() + if err != nil { + return nil, err + } + items = append(items, item) + } + + return items, nil +} + +func (b *BlockByHashAccessor) collectTxnIndexedEntities( + tx db.RoTx, shardId types.ShardId, + tableName db.ShardedTableName, root common.Hash, +) ([][]byte, error) { + reader := mpt.NewDbReader(tx, shardId, tableName) + if err := reader.SetRootHash(root); err != nil { + return nil, err + } + + items := make([][]byte, 0, 1024) + for index := types.TransactionIndex(0); ; index++ { + entity, err := reader.Get(index.Bytes()) + if errors.Is(err, db.ErrKeyNotFound) { + break + } else if err != nil { + return nil, err + } + items = append(items, entity) + } + + return items, nil +} + +func (b *BlockByHashAccessor) collectChildBlocks( + tx db.RoTx, shardId types.ShardId, block *types.Block, +) ([]common.Hash, error) { + treeShards := NewDbShardBlocksTrieReader(tx, shardId, block.Id) + if err := treeShards.SetRootHash(block.ChildBlocksRootHash); err != nil { + return nil, err + } + + shards := make(map[types.ShardId]common.Hash) + for key, value := range treeShards.Iterate() { + shards[types.BytesToShardId(key)] = common.BytesToHash(value) + } + + values := make([]common.Hash, len(shards)) + for key, value := range shards { + values[key-1] = value // the main shard is omitted + } + return values, nil +} + +func (b *BlockByHashAccessor) collectConfig( + tx db.RoTx, shardId types.ShardId, block *types.Block, +) (map[string][]byte, error) { + res := mpt.NewDbReader(tx, shardId, db.ConfigTrieTable) + reader, err := res, res.SetRootHash(block.ConfigRoot) + if err != nil { + return nil, err + } + configMap := make(map[string][]byte) + for key, value := range reader.Iterate() { + configMap[string(key)] = value + } + return configMap, nil +} diff --git a/nil/internal/execution/block_cache.go b/nil/internal/execution/block_cache.go index caab8180d..e40736675 100644 --- a/nil/internal/execution/block_cache.go +++ b/nil/internal/execution/block_cache.go @@ -35,7 +35,7 @@ func getHashFn(es *ExecutionState, ref *types.Block) func(n uint64) (common.Hash lastKnownHash := cache[len(cache)-1] for { - data, err := es.shardAccessor.GetBlock().ByHash(lastKnownHash) + b, err := es.blockAccessor.GetByHash(es.tx, es.ShardId, lastKnownHash) if errors.Is(err, db.ErrKeyNotFound) { break } @@ -43,9 +43,9 @@ func getHashFn(es *ExecutionState, ref *types.Block) func(n uint64) (common.Hash return common.EmptyHash, err } - cache = append(cache, data.Block().PrevBlock) - lastKnownHash = data.Block().PrevBlock - lastKnownNumber := data.Block().Id.Uint64() - 1 + cache = append(cache, b.PrevBlock) + lastKnownHash = b.PrevBlock + lastKnownNumber := b.Id.Uint64() - 1 if n == lastKnownNumber { return lastKnownHash, nil } diff --git a/nil/internal/execution/block_generator.go b/nil/internal/execution/block_generator.go index c2eefdcf6..e9f2ec213 100644 --- a/nil/internal/execution/block_generator.go +++ b/nil/internal/execution/block_generator.go @@ -21,7 +21,7 @@ type BlockGeneratorParams struct { MainKeysPath string DisableConsensus bool ExecutionMode string - StateAccessor *StateAccessor + BlockAccessor *BlockAccessor FeeCalculator FeeCalculator EvmTracingHooks *tracing.Hooks } @@ -85,7 +85,7 @@ func NewBlockGenerator( executionState, err := NewExecutionState(rwTx, params.ShardId, StateParams{ Block: prevBlock, - StateAccessor: params.StateAccessor, + BlockAccessor: params.BlockAccessor, ConfigAccessor: configAccessor, FeeCalculator: params.FeeCalculator, Mode: params.ExecutionMode, diff --git a/nil/internal/execution/execution_state_test.go b/nil/internal/execution/execution_state_test.go index ba4fb21a9..e65943ed8 100644 --- a/nil/internal/execution/execution_state_test.go +++ b/nil/internal/execution/execution_state_test.go @@ -86,15 +86,13 @@ func (s *SuiteExecutionState) TestExecState() { }) s.Run("CheckTransactions", func() { - data, err := es.shardAccessor.GetBlock().ByHash(blockRes.BlockHash) + block, err := es.blockAccessor.GetByHash(es.tx, es.ShardId, blockRes.BlockHash) s.Require().NoError(err) - s.Require().NotNil(data) - s.Require().NotNil(data.Block()) transactionsRoot := NewDbTransactionTrieReader(tx, es.ShardId) - s.Require().NoError(transactionsRoot.SetRootHash(data.Block().InTransactionsRoot)) + s.Require().NoError(transactionsRoot.SetRootHash(block.InTransactionsRoot)) receiptsRoot := NewDbReceiptTrieReader(tx, es.ShardId) - s.Require().NoError(receiptsRoot.SetRootHash(data.Block().ReceiptsRoot)) + s.Require().NoError(receiptsRoot.SetRootHash(block.ReceiptsRoot)) var transactionIndex types.TransactionIndex for { diff --git a/nil/internal/execution/state.go b/nil/internal/execution/state.go index fc95faced..db76a0749 100644 --- a/nil/internal/execution/state.go +++ b/nil/internal/execution/state.go @@ -125,7 +125,7 @@ type ExecutionState struct { // Tracing hooks set for every EVM created during execution EvmTracingHooks *tracing.Hooks - shardAccessor *shardAccessor + blockAccessor *BlockAccessor // Pointer to currently executed VM evm *vm.EVM @@ -256,17 +256,15 @@ type revision struct { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(es *ExecutionState) (*vm.BlockContext, error) { - data, err := es.shardAccessor.GetBlock().ByHash(es.PrevBlock) + header, err := es.blockAccessor.GetByHash(es.tx, es.ShardId, es.PrevBlock) if err != nil && !errors.Is(err, db.ErrKeyNotFound) { return nil, err } currentBlockId := uint64(0) - var header *types.Block time := uint64(0) rollbackCounter := uint32(0) if err == nil { - header = data.Block() currentBlockId = header.Id.Uint64() + 1 // TODO: we need to use header.Timestamp instead of but it's always zero for now. // Let's return some kind of logical timestamp (monotonic increasing block number). @@ -292,7 +290,7 @@ type StateParams struct { // Required parameters ConfigAccessor config.ConfigAccessor - StateAccessor *StateAccessor + BlockAccessor *BlockAccessor // Optional parameters FeeCalculator FeeCalculator @@ -356,7 +354,7 @@ func NewExecutionState(tx db.RoTx, shardId types.ShardId, params StateParams) (* journal: newJournal(), transientStorage: newTransientStorage(), - shardAccessor: params.StateAccessor.Access(tx, shardId), + blockAccessor: params.BlockAccessor, configAccessor: params.ConfigAccessor, BaseFee: baseFeePerGas, @@ -1594,14 +1592,12 @@ func (es *ExecutionState) buildTransactionTrees(outTxnValues []*types.Transactio func (es *ExecutionState) buildInboundTransactionTree() (common.Hash, error) { inTxnKeys := make([]types.TransactionIndex, 0, len(es.InTransactions)) - inTxnValues := make([]*types.Transaction, 0, len(es.InTransactions)) - for i, txn := range es.InTransactions { + for i := range es.InTransactions { inTxnKeys = append(inTxnKeys, types.TransactionIndex(i)) - inTxnValues = append(inTxnValues, txn) } inTransactionTree := NewDbTransactionTrie(es.tx, es.ShardId) - if err := inTransactionTree.UpdateBatch(inTxnKeys, inTxnValues); err != nil { + if err := inTransactionTree.UpdateBatch(inTxnKeys, es.InTransactions); err != nil { return common.Hash{}, err } @@ -2086,16 +2082,11 @@ func (es *ExecutionState) resetVm() { } func (es *ExecutionState) MarshalJSON() ([]byte, error) { - prevBlockRes, err := es.shardAccessor.GetBlock().ByHash(es.PrevBlock) + prevBlock, err := es.blockAccessor.GetByHash(es.tx, es.ShardId, es.PrevBlock) if err != nil && !errors.Is(err, db.ErrKeyNotFound) { return nil, err } - var prevBlock *types.Block - if err == nil { - prevBlock = prevBlockRes.Block() - } - data := struct { ContractTreeRoot common.Hash `json:"contractTreeRoot"` ReceiptTreeRoot common.Hash `json:"receiptTreeRoot"` diff --git a/nil/internal/execution/state_accessor.go b/nil/internal/execution/state_accessor.go index dd22ba0b8..71786b1cb 100644 --- a/nil/internal/execution/state_accessor.go +++ b/nil/internal/execution/state_accessor.go @@ -1,9 +1,6 @@ package execution import ( - "errors" - "fmt" - "github.com/NilFoundation/nil/nil/common" "github.com/NilFoundation/nil/nil/common/assert" "github.com/NilFoundation/nil/nil/common/check" @@ -14,814 +11,241 @@ import ( lru "github.com/hashicorp/golang-lru/v2" ) -type fieldAccessor[T any] func() T - -func notInitialized[T any](name string) fieldAccessor[T] { - return func() T { panic(fmt.Sprintf("field not initialized : `%s`", name)) } -} - -func initWith[T any](val T) fieldAccessor[T] { - return func() T { return val } -} - /* -supposed usage is +StateAccessor supposed usage: -data, err := accessor.Access(tx, shardId).GetBlock().ByHash(hash) -block := data.Block +// Share accessor between goroutines, it is thread-safe. +accessor := execution.NewStateAccessor(1000, 2000) -data, err := accessor.Access(tx, shardId).GetBlock().ByIndex(index) -block := data.Block +rawHeader, err := accessor.Access(tx, shardId).GetRawBlockHeaderByHash(hash) -data, err := accessor.Access(tx, shardId).GetBlock().WithInTransactions().ByIndex(index) -block, txns := data.Block, data.InTransactions +data, err := accessor.Access(tx, shardId).GetFullBlockByNumber(index) +header, txns := data.Block, data.InTransactions ... */ + type StateAccessor struct { - cache *accessorCache - rawCache *rawAccessorCache + txnCache *txnCache + + blockByHash *BlockByHashAccessor + blockHashByNumber *BlockHashByNumberAccessor } func NewStateAccessor(blockLRUSize, txnLRUSize int) *StateAccessor { - if txnLRUSize <= 0 { - // todo: fix with rework of transaction accessors - txnLRUSize = blockLRUSize - } return &StateAccessor{ - cache: newAccessorCache(blockLRUSize, txnLRUSize), - rawCache: newRawAccessorCache(blockLRUSize, txnLRUSize), + txnCache: newTxnCache(txnLRUSize), + blockByHash: NewBlockByHashAccessor(blockLRUSize), + blockHashByNumber: NewBlockHashByNumberAccessor(blockLRUSize), } } -func (s *StateAccessor) Access(tx db.RoTx, shardId types.ShardId) *shardAccessor { - return &shardAccessor{s.RawAccess(tx, shardId)} -} - -func (s *StateAccessor) RawAccess(tx db.RoTx, shardId types.ShardId) *rawShardAccessor { - return &rawShardAccessor{ - cache: s.cache, - rawCache: s.rawCache, - tx: tx, - shardId: shardId, +func (s *StateAccessor) BlockAccessor() *BlockAccessor { + return &BlockAccessor{ + headers: s.blockByHash.headersCache, + hashByNumber: s.blockHashByNumber, } } -type accessorCache struct { - blocksLRU *lru.Cache[common.Hash, *types.Block] - inTransactionsLRU *lru.Cache[common.Hash, []*types.Transaction] - outTransactionsLRU *lru.Cache[common.Hash, []*types.Transaction] - receiptsLRU *lru.Cache[common.Hash, []*types.Receipt] +func (s *StateAccessor) Access(tx db.RoTx, shardId types.ShardId) *ShardAccessor { + return &ShardAccessor{ + cache: s.txnCache, + blockByHash: s.blockByHash, + blockHashByNumber: s.blockHashByNumber, + tx: tx, + shardId: shardId, + } } -func newAccessorCache(blockLRUSize, txnLRUSize int) *accessorCache { - blocksLRU, err := lru.New[common.Hash, *types.Block](blockLRUSize) - check.PanicIfErr(err) - - outTransactionsLRU, err := lru.New[common.Hash, []*types.Transaction](txnLRUSize) - check.PanicIfErr(err) +type ShardAccessor struct { + cache *txnCache - inTransactionsLRU, err := lru.New[common.Hash, []*types.Transaction](txnLRUSize) - check.PanicIfErr(err) + blockHashByNumber *BlockHashByNumberAccessor + blockByHash *BlockByHashAccessor - receiptsLRU, err := lru.New[common.Hash, []*types.Receipt](txnLRUSize) - check.PanicIfErr(err) - - return &accessorCache{ - blocksLRU: blocksLRU, - inTransactionsLRU: inTransactionsLRU, - outTransactionsLRU: outTransactionsLRU, - receiptsLRU: receiptsLRU, - } + tx db.RoTx + shardId types.ShardId } -type rawAccessorCache struct { - blocksLRU *lru.Cache[common.Hash, []byte] - inTransactionsLRU *lru.Cache[common.Hash, [][]byte] - inTxCountsLRU *lru.Cache[common.Hash, [][]byte] - outTransactionsLRU *lru.Cache[common.Hash, [][]byte] - outTxCountsLRU *lru.Cache[common.Hash, [][]byte] - receiptsLRU *lru.Cache[common.Hash, [][]byte] +type txnCache struct { + inTxnLRU *lru.Cache[db.BlockHashAndTransactionIndex, *Txn] + outTxnLRU *lru.Cache[db.BlockHashAndTransactionIndex, *Txn] + inTxnIndexLRU *lru.Cache[common.Hash, db.BlockHashAndTransactionIndex] } -func newRawAccessorCache(blockLRUSize, txnLRUSize int) *rawAccessorCache { - blocksLRU, err := lru.New[common.Hash, []byte](blockLRUSize) - check.PanicIfErr(err) - - outTransactionsLRU, err := lru.New[common.Hash, [][]byte](txnLRUSize) - check.PanicIfErr(err) - - outTxCountsLRU, err := lru.New[common.Hash, [][]byte](txnLRUSize) +func newTxnCache(txnLRUSize int) *txnCache { + inTxnLRU, err := lru.New[db.BlockHashAndTransactionIndex, *Txn](txnLRUSize) check.PanicIfErr(err) - inTransactionsLRU, err := lru.New[common.Hash, [][]byte](txnLRUSize) + outTxnLRU, err := lru.New[db.BlockHashAndTransactionIndex, *Txn](txnLRUSize) check.PanicIfErr(err) - inTxCountsLRU, err := lru.New[common.Hash, [][]byte](txnLRUSize) + inTxnIndexLRU, err := lru.New[common.Hash, db.BlockHashAndTransactionIndex](txnLRUSize) check.PanicIfErr(err) - receiptsLRU, err := lru.New[common.Hash, [][]byte](txnLRUSize) - check.PanicIfErr(err) - - return &rawAccessorCache{ - blocksLRU: blocksLRU, - inTransactionsLRU: inTransactionsLRU, - inTxCountsLRU: inTxCountsLRU, - outTransactionsLRU: outTransactionsLRU, - outTxCountsLRU: outTxCountsLRU, - receiptsLRU: receiptsLRU, + return &txnCache{ + inTxnLRU: inTxnLRU, + outTxnLRU: outTxnLRU, + inTxnIndexLRU: inTxnIndexLRU, } } -type shardAccessor struct { - *rawShardAccessor -} - -func collectSerializedShardCounts( - block common.Hash, sa *rawShardAccessor, cache *lru.Cache[common.Hash, [][]byte], - tableName db.ShardedTableName, rootHash common.Hash, res *fieldAccessor[[][]byte], -) { - if items, ok := cache.Get(block); ok { - *res = initWith(items) - return - } - root := mpt.NewDbReader(sa.tx, sa.shardId, tableName) - check.PanicIfErr(root.SetRootHash(rootHash)) - - items := make([][]byte, 0, 16) - for k, v := range root.Iterate() { - if len(k) != types.ShardIdSize { - continue - } - - shardId := types.BytesToShardId(k) - var transactionIndex types.TransactionIndex - check.PanicIfErr(transactionIndex.UnmarshalNil(v)) - - txCount := &types.TxCount{ - ShardId: uint16(shardId), - Count: transactionIndex, - } - - item, err := txCount.MarshalNil() - check.PanicIfErr(err) - items = append(items, item) - } - - *res = initWith(items) - cache.Add(block, items) -} - -func collectSerializedBlockEntities( - block common.Hash, - sa *rawShardAccessor, - cache *lru.Cache[common.Hash, [][]byte], - tableName db.ShardedTableName, - rootHash common.Hash, - res *fieldAccessor[[][]byte], -) error { - if items, ok := cache.Get(block); ok { - *res = initWith(items) - return nil - } - - root := mpt.NewDbReader(sa.tx, sa.shardId, tableName) - if err := root.SetRootHash(rootHash); err != nil { - return err - } - - items := make([][]byte, 0, 1024) - var index types.TransactionIndex - for { - entity, err := root.Get(index.Bytes()) - if errors.Is(err, db.ErrKeyNotFound) { - break - } else if err != nil { - return fmt.Errorf("failed to get from %v with index %v from trie: %w", tableName, index, err) - } - items = append(items, entity) - index++ - } - - *res = initWith(items) - cache.Add(block, items) - return nil -} - -func unmashalSerializedEntities[ - T interface { - ~*S - serialization.NilUnmarshaler - }, - S any, -](block common.Hash, raw [][]byte, cache *lru.Cache[common.Hash, []*S], res *fieldAccessor[[]*S]) error { - items, ok := cache.Get(block) - if !ok { - var err error - items, err = serialization.DecodeContainer[T](raw) - if err != nil { - return err - } - cache.Add(block, items) - } - - *res = initWith(items) - return nil -} - -func (s *shardAccessor) mptReader(tableName db.ShardedTableName, rootHash common.Hash) (*mpt.Reader, error) { - res := mpt.NewDbReader(s.tx, s.shardId, tableName) - return res, res.SetRootHash(rootHash) -} - -func (s *shardAccessor) GetBlock() blockAccessor { - return blockAccessor{rawBlockAccessor{rawShardAccessor: s.rawShardAccessor}} -} - -func (s *shardAccessor) GetInTransaction() inTransactionAccessor { - return inTransactionAccessor{shardAccessor: s} -} - -func (s *shardAccessor) GetOutTransaction() outTransactionAccessor { - return outTransactionAccessor{shardAccessor: s} -} - -//////// raw block accessor ////////// - -type rawShardAccessor struct { - cache *accessorCache - rawCache *rawAccessorCache - tx db.RoTx - shardId types.ShardId -} - -func (s *rawShardAccessor) GetBlock() rawBlockAccessor { - return rawBlockAccessor{rawShardAccessor: s} -} - -type rawBlockAccessorResult struct { - block fieldAccessor[[]byte] - inTransactions fieldAccessor[[][]byte] - inTxCounts fieldAccessor[[][]byte] - outTransactions fieldAccessor[[][]byte] - outTxCounts fieldAccessor[[][]byte] - receipts fieldAccessor[[][]byte] - childBlocks fieldAccessor[[]common.Hash] - dbTimestamp fieldAccessor[uint64] - config fieldAccessor[map[string][]byte] -} - -func (r rawBlockAccessorResult) Block() []byte { - return r.block() -} - -func (r rawBlockAccessorResult) InTransactions() [][]byte { - return r.inTransactions() -} - -func (r rawBlockAccessorResult) InTxCounts() [][]byte { - return r.inTxCounts() -} - -func (r rawBlockAccessorResult) OutTransactions() [][]byte { - return r.outTransactions() -} - -func (r rawBlockAccessorResult) OutTxCounts() [][]byte { - return r.outTxCounts() -} - -func (r rawBlockAccessorResult) Receipts() [][]byte { - return r.receipts() -} - -func (r rawBlockAccessorResult) ChildBlocks() []common.Hash { - return r.childBlocks() -} - -func (r rawBlockAccessorResult) DbTimestamp() uint64 { - return r.dbTimestamp() -} - -func (r rawBlockAccessorResult) Config() map[string][]byte { - return r.config() -} - -type rawBlockAccessor struct { - rawShardAccessor *rawShardAccessor - withInTransactions bool - withOutTransactions bool - withReceipts bool - withChildBlocks bool - withDbTimestamp bool - withConfig bool -} - -func (b rawBlockAccessor) WithChildBlocks() rawBlockAccessor { - b.withChildBlocks = true - return b -} - -func (b rawBlockAccessor) WithInTransactions() rawBlockAccessor { - b.withInTransactions = true - return b -} - -func (b rawBlockAccessor) WithOutTransactions() rawBlockAccessor { - b.withOutTransactions = true - return b -} - -func (b rawBlockAccessor) WithReceipts() rawBlockAccessor { - b.withReceipts = true - return b -} - -func (b rawBlockAccessor) WithDbTimestamp() rawBlockAccessor { - b.withDbTimestamp = true - return b -} - -func (b rawBlockAccessor) WithConfig() rawBlockAccessor { - b.withConfig = true - return b -} - -func (b rawBlockAccessor) decodeBlock(hash common.Hash, data []byte) (*types.Block, error) { - sa := b.rawShardAccessor - block, ok := sa.cache.blocksLRU.Get(hash) - if !ok { - block = &types.Block{} - if err := block.UnmarshalNil(data); err != nil { - return nil, err - } - sa.cache.blocksLRU.Add(hash, block) - } - return block, nil -} - -func (b rawBlockAccessor) ByHash(hash common.Hash) (rawBlockAccessorResult, error) { - sa := b.rawShardAccessor - - // Extract raw block - rawBlock, ok := sa.rawCache.blocksLRU.Get(hash) - if !ok { - var err error - rawBlock, err = db.ReadBlockBytes(sa.tx, sa.shardId, hash) - if err != nil { - return rawBlockAccessorResult{}, err - } - sa.rawCache.blocksLRU.Add(hash, rawBlock) - } - - // We need to decode some block data anyway - block, err := b.decodeBlock(hash, rawBlock) - if err != nil { - return rawBlockAccessorResult{}, err - } - - res := rawBlockAccessorResult{ - block: initWith(rawBlock), - inTransactions: notInitialized[[][]byte]("InTransactions"), - inTxCounts: notInitialized[[][]byte]("InTxCounts"), - outTransactions: notInitialized[[][]byte]("OutTransactions"), - outTxCounts: notInitialized[[][]byte]("OutTxCounts"), - receipts: notInitialized[[][]byte]("Receipts"), - childBlocks: notInitialized[[]common.Hash]("ChildBlocks"), - dbTimestamp: notInitialized[uint64]("DbTimestamp"), - config: notInitialized[map[string][]byte]("Config"), - } - - if b.withInTransactions { - if err := collectSerializedBlockEntities( - hash, - sa, - sa.rawCache.inTransactionsLRU, - db.TransactionTrieTable, - block.InTransactionsRoot, - &res.inTransactions, - ); err != nil { - return rawBlockAccessorResult{}, err - } - collectSerializedShardCounts( - hash, sa, sa.rawCache.inTxCountsLRU, db.TransactionTrieTable, - block.InTransactionsRoot, &res.inTxCounts, - ) - } - - if b.withOutTransactions { - if err := collectSerializedBlockEntities( - hash, - sa, - sa.rawCache.outTransactionsLRU, - db.TransactionTrieTable, - block.OutTransactionsRoot, - &res.outTransactions, - ); err != nil { - return rawBlockAccessorResult{}, err - } - collectSerializedShardCounts( - hash, sa, sa.rawCache.outTxCountsLRU, db.TransactionTrieTable, - block.OutTransactionsRoot, &res.outTxCounts, - ) - } - - if b.withReceipts { - if err := collectSerializedBlockEntities( - hash, - sa, - sa.rawCache.receiptsLRU, - db.ReceiptTrieTable, - block.ReceiptsRoot, - &res.receipts, - ); err != nil { - return rawBlockAccessorResult{}, err - } - } - - if b.withChildBlocks { - treeShards := NewDbShardBlocksTrieReader(sa.tx, sa.shardId, block.Id) - if err := treeShards.SetRootHash(block.ChildBlocksRootHash); err != nil { - return rawBlockAccessorResult{}, err - } - - shards := make(map[types.ShardId]common.Hash) - for key, value := range treeShards.Iterate() { - var hash common.Hash - - shardId := types.BytesToShardId(key) - hash.SetBytes(value) - shards[shardId] = hash - } - - values := make([]common.Hash, len(shards)) - for key, value := range shards { - values[key-1] = value // the main shard is omitted - } - res.childBlocks = initWith(values) - } - - if b.withDbTimestamp { - ts, err := db.ReadBlockTimestamp(sa.tx, sa.shardId, hash) - // This is needed for old blocks that don't have their timestamp stored - if errors.Is(err, db.ErrKeyNotFound) { - ts = types.InvalidDbTimestamp - } else if err != nil { - return rawBlockAccessorResult{}, err - } - - res.dbTimestamp = initWith(ts) - } +//////// block accessors ////////// - // config is included only for main shard, empty for others - if b.withConfig { - root := mpt.NewDbReader(sa.tx, sa.shardId, db.ConfigTrieTable) - if err := root.SetRootHash(block.ConfigRoot); err != nil { - return rawBlockAccessorResult{}, err - } - configMap := make(map[string][]byte) - for key, value := range root.Iterate() { - configMap[string(key)] = value - } - res.config = initWith(configMap) - } - - return res, nil -} - -func (b rawBlockAccessor) ByNumber(num types.BlockNumber) (rawBlockAccessorResult, error) { - sa := b.rawShardAccessor - hash, err := db.ReadBlockHashByNumber(sa.tx, sa.shardId, num) +func (s *ShardAccessor) GetRawBlockHeaderByHash(hash common.Hash) (serialization.EncodedData, error) { + h, err := s.blockByHash.headersCache.get(s.tx, s.shardId, hash) if err != nil { - return rawBlockAccessorResult{}, err + return nil, err } - return b.ByHash(hash) -} - -//////// block accessor ////////// - -type blockAccessorResult struct { - block fieldAccessor[*types.Block] - inTransactions fieldAccessor[[]*types.Transaction] - outTransactions fieldAccessor[[]*types.Transaction] - receipts fieldAccessor[[]*types.Receipt] - childBlocks fieldAccessor[[]common.Hash] - dbTimestamp fieldAccessor[uint64] - config fieldAccessor[map[string][]byte] -} - -func (r blockAccessorResult) Block() *types.Block { - return r.block() -} - -func (r blockAccessorResult) InTransactions() []*types.Transaction { - return r.inTransactions() -} - -func (r blockAccessorResult) OutTransactions() []*types.Transaction { - return r.outTransactions() -} - -func (r blockAccessorResult) Receipts() []*types.Receipt { - return r.receipts() + return h.raw, nil } -func (r blockAccessorResult) ChildBlocks() []common.Hash { - return r.childBlocks() +func (s *ShardAccessor) GetFullBlockByHash(hash common.Hash) (*types.RawBlockWithExtractedData, error) { + return s.blockByHash.Get(s.tx, s.shardId, hash) } -func (r blockAccessorResult) DbTimestamp() uint64 { - return r.dbTimestamp() +func (s *ShardAccessor) GetBlockHeaderByHash(hash common.Hash) (*types.Block, error) { + return s.blockByHash.headersCache.Get(s.tx, s.shardId, hash) } -func (r blockAccessorResult) Config() map[string][]byte { - return r.config() -} - -type blockAccessor struct { - rawBlockAccessor -} - -func (b blockAccessor) WithChildBlocks() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithChildBlocks()} -} +//////// transaction accessors ////////// -func (b blockAccessor) WithInTransactions() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithInTransactions()} -} +type Txn struct { + Block *types.BlockWithHash + Index types.TransactionIndex + Transaction *types.Transaction + RawTxn []byte -func (b blockAccessor) WithOutTransactions() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithOutTransactions()} + // Receipt is only set for incoming transactions (the shard does not contain receipts for outgoing transactions). + Receipt *types.Receipt + RawReceipt []byte } -func (b blockAccessor) WithReceipts() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithReceipts()} +func (s *ShardAccessor) GetOutTxnByIndex(idx types.TransactionIndex, block *types.BlockWithHash) (*Txn, error) { + return s.getTxnByIndex(false, idx, block) } -func (b blockAccessor) WithDbTimestamp() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithDbTimestamp()} +func (s *ShardAccessor) GetInTxnByIndex(idx types.TransactionIndex, block *types.BlockWithHash) (*Txn, error) { + return s.getTxnByIndex(true, idx, block) } -func (b blockAccessor) WithConfig() blockAccessor { - return blockAccessor{b.rawBlockAccessor.WithConfig()} -} - -func (b blockAccessor) ByHash(hash common.Hash) (blockAccessorResult, error) { - sa := b.rawShardAccessor - - raw, err := b.rawBlockAccessor.ByHash(hash) +func (s *ShardAccessor) GetInTxnByHash(hash common.Hash) (*Txn, error) { + idx, err := s.getInTxnIndexByHash(hash) if err != nil { - return blockAccessorResult{}, err + return nil, err } - block, err := b.decodeBlock(hash, raw.Block()) + block, err := s.GetBlockHeaderByHash(idx.BlockHash) if err != nil { - return blockAccessorResult{}, err - } - - res := blockAccessorResult{ - block: initWith(block), - inTransactions: notInitialized[[]*types.Transaction]("InTransactions"), - outTransactions: notInitialized[[]*types.Transaction]("OutTransactions"), - receipts: notInitialized[[]*types.Receipt]("Receipts"), - childBlocks: notInitialized[[]common.Hash]("ChildBlocks"), - dbTimestamp: notInitialized[uint64]("DbTimestamp"), - config: notInitialized[map[string][]byte]("Config"), - } - - if b.withInTransactions { - if err := unmashalSerializedEntities[*types.Transaction]( - hash, - raw.InTransactions(), - sa.cache.inTransactionsLRU, - &res.inTransactions, - ); err != nil { - return blockAccessorResult{}, err - } + return nil, err } - if b.withOutTransactions { - if err := unmashalSerializedEntities[*types.Transaction]( - hash, - raw.OutTransactions(), - sa.cache.outTransactionsLRU, - &res.outTransactions, - ); err != nil { - return blockAccessorResult{}, err - } - } - - if b.withReceipts { - if err := unmashalSerializedEntities[*types.Receipt]( - hash, raw.Receipts(), sa.cache.receiptsLRU, &res.receipts, - ); err != nil { - return blockAccessorResult{}, err - } - } - - if b.withChildBlocks { - res.childBlocks = initWith(raw.ChildBlocks()) - } - - if b.withDbTimestamp { - res.dbTimestamp = initWith(raw.DbTimestamp()) + res, err := s.getTxnByIndex(true, idx.TransactionIndex, types.NewBlockWithRawHash(block, idx.BlockHash)) + if err != nil { + return nil, err } - if b.withConfig { - res.config = initWith(raw.Config()) + if assert.Enable { + check.PanicIfNot(res.Transaction.Hash() == hash) } return res, nil } -func (b blockAccessor) ByNumber(num types.BlockNumber) (blockAccessorResult, error) { - sa := b.rawShardAccessor - hash, err := db.ReadBlockHashByNumber(sa.tx, sa.shardId, num) - if err != nil { - return blockAccessorResult{}, err +func (s *ShardAccessor) getInTxnIndexByHash(hash common.Hash) (db.BlockHashAndTransactionIndex, error) { + if idx, ok := s.cache.inTxnIndexLRU.Get(hash); ok { + return idx, nil } - return b.ByHash(hash) -} -//////// transaction accessors ////////// - -type transactionAccessorResult struct { - block fieldAccessor[*types.Block] - index fieldAccessor[types.TransactionIndex] - transaction fieldAccessor[*types.Transaction] -} - -func (r transactionAccessorResult) Block() *types.Block { - return r.block() -} - -func (r transactionAccessorResult) Index() types.TransactionIndex { - return r.index() -} - -func (r transactionAccessorResult) Transaction() *types.Transaction { - return r.transaction() -} - -func getBlockAndInTxnIndexByHash( - sa *shardAccessor, - incoming bool, - hash common.Hash, -) (*types.Block, db.BlockHashAndTransactionIndex, error) { - var idx db.BlockHashAndTransactionIndex - - table := db.BlockHashAndInTransactionIndexByTransactionHash - if !incoming { - table = db.BlockHashAndOutTransactionIndexByTransactionHash - } - - value, err := sa.tx.GetFromShard(sa.shardId, table, hash.Bytes()) - if err != nil { - return nil, idx, err - } - - if err = idx.UnmarshalNil(value); err != nil { - return nil, idx, err - } - - data, err := sa.GetBlock().ByHash(idx.BlockHash) + idx, err := db.ReadTxnNumberByHash(s.tx, s.shardId, hash) if err != nil { - return nil, idx, err + return idx, err } - return data.Block(), idx, nil + s.cache.inTxnIndexLRU.Add(hash, idx) + return idx, nil } -func baseGetTxnByHash(sa *shardAccessor, incoming bool, hash common.Hash) (transactionAccessorResult, error) { - block, idx, err := getBlockAndInTxnIndexByHash(sa, incoming, hash) - if err != nil { - return transactionAccessorResult{}, err +func (s *ShardAccessor) getTxnByIndex( + incoming bool, idx types.TransactionIndex, block *types.BlockWithHash, +) (*Txn, error) { + cache := s.cache.outTxnLRU + if incoming { + cache = s.cache.inTxnLRU } - data, err := baseGetTxnByIndex(sa, incoming, idx.TransactionIndex, block) - if err != nil { - return transactionAccessorResult{}, err + fullIdx := db.BlockHashAndTransactionIndex{ + BlockHash: block.Hash, + TransactionIndex: idx, } - if assert.Enable { - check.PanicIfNot(data.Transaction() == nil || data.Transaction().Hash() == hash) + if txn, ok := cache.Get(fullIdx); ok { + return txn, nil } - return data, nil -} -func baseGetTxnByIndex( - sa *shardAccessor, - incoming bool, - idx types.TransactionIndex, - block *types.Block, -) (transactionAccessorResult, error) { - root := block.InTransactionsRoot - if !incoming { - root = block.OutTransactionsRoot - } - txnTrie, err := sa.mptReader(db.TransactionTrieTable, root) - if err != nil { - return transactionAccessorResult{}, err - } - txn, err := mpt.GetEntity[*types.Transaction](txnTrie, idx.Bytes()) + res, err := s.makeTxn(incoming, idx, block) if err != nil { - return transactionAccessorResult{}, err + return nil, err } - return transactionAccessorResult{block: initWith(block), index: initWith(idx), transaction: initWith(txn)}, nil -} - -type outTransactionAccessorResult struct { - transactionAccessorResult -} - -type outTransactionAccessor struct { - shardAccessor *shardAccessor -} - -func (a outTransactionAccessor) ByHash(hash common.Hash) (outTransactionAccessorResult, error) { - data, err := baseGetTxnByHash(a.shardAccessor, false, hash) - return outTransactionAccessorResult{data}, err -} - -func (a outTransactionAccessor) ByIndex( - idx types.TransactionIndex, - block *types.Block, -) (outTransactionAccessorResult, error) { - data, err := baseGetTxnByIndex(a.shardAccessor, false, idx, block) - return outTransactionAccessorResult{data}, err -} - -type inTransactionAccessorResult struct { - transactionAccessorResult - receipt fieldAccessor[*types.Receipt] -} - -func (r inTransactionAccessorResult) Receipt() *types.Receipt { - return r.receipt() -} - -type inTransactionAccessor struct { - shardAccessor *shardAccessor - withReceipt bool -} - -func (a inTransactionAccessor) WithReceipt() inTransactionAccessor { - a.withReceipt = true - return a + cache.Add(fullIdx, res) + return res, nil } -func (a inTransactionAccessor) ByHash(hash common.Hash) (inTransactionAccessorResult, error) { - data, err := baseGetTxnByHash(a.shardAccessor, true, hash) - if err != nil { - return inTransactionAccessorResult{}, err +func (s *ShardAccessor) makeTxn(incoming bool, idx types.TransactionIndex, block *types.BlockWithHash) (*Txn, error) { + res := &Txn{ + Block: block, + Index: idx, } - res := inTransactionAccessorResult{ - transactionAccessorResult: data, - receipt: notInitialized[*types.Receipt]("Receipt"), + if cached, ok := s.blockByHash.cache.Get(block.Hash); ok { + if incoming { + res.RawTxn = cached.InTransactions[idx] + res.RawReceipt = cached.Receipts[idx] + } else { + res.RawTxn = cached.OutTransactions[idx] + } + } else if incoming { + if err := s.readTxn(block.InTransactionsRoot, res); err != nil { + return nil, err + } + if err := s.readReceipt(res); err != nil { + return nil, err + } + } else { + if err := s.readTxn(block.OutTransactionsRoot, res); err != nil { + return nil, err + } } - if a.withReceipt { - return a.addReceipt(res) + res.Transaction = &types.Transaction{} + if err := res.Transaction.UnmarshalNil(res.RawTxn); err != nil { + return nil, err + } + if len(res.RawReceipt) > 0 { + res.Receipt = &types.Receipt{} + if err := res.Receipt.UnmarshalNil(res.RawReceipt); err != nil { + return nil, err + } } return res, nil } -func (a inTransactionAccessor) ByIndex( - idx types.TransactionIndex, - block *types.Block, -) (inTransactionAccessorResult, error) { - data, err := baseGetTxnByIndex(a.shardAccessor, true, idx, block) +func (s *ShardAccessor) readTxn(root common.Hash, res *Txn) error { + txnTrie := mpt.NewDbReader(s.tx, s.shardId, db.TransactionTrieTable) + err := txnTrie.SetRootHash(root) if err != nil { - return inTransactionAccessorResult{}, err - } - - res := inTransactionAccessorResult{ - transactionAccessorResult: data, - receipt: notInitialized[*types.Receipt]("Receipt"), - } - - if a.withReceipt { - return a.addReceipt(res) + return err } - return res, nil + res.RawTxn, err = txnTrie.Get(res.Index.Bytes()) + return err } -func (a inTransactionAccessor) addReceipt( - accessResult inTransactionAccessorResult, -) (inTransactionAccessorResult, error) { - if accessResult.Block() == nil { - accessResult.receipt = initWith[*types.Receipt](nil) - return accessResult, nil - } - receiptTrie, err := a.shardAccessor.mptReader(db.ReceiptTrieTable, accessResult.Block().ReceiptsRoot) +func (s *ShardAccessor) readReceipt(res *Txn) error { + receiptTrie := mpt.NewDbReader(s.tx, s.shardId, db.ReceiptTrieTable) + err := receiptTrie.SetRootHash(res.Block.ReceiptsRoot) if err != nil { - return inTransactionAccessorResult{}, err - } - receipt, err := mpt.GetEntity[*types.Receipt](receiptTrie, accessResult.Index().Bytes()) - if err != nil { - return inTransactionAccessorResult{}, err + return err } - accessResult.receipt = initWith(receipt) - return accessResult, nil + res.RawReceipt, err = receiptTrie.Get(res.Index.Bytes()) + return err } diff --git a/nil/internal/execution/testaide.go b/nil/internal/execution/testaide.go index fcbb3d824..64aa31846 100644 --- a/nil/internal/execution/testaide.go +++ b/nil/internal/execution/testaide.go @@ -40,8 +40,8 @@ func NewTestExecutionState(t *testing.T, tx db.RoTx, shardId types.ShardId, para if params.ConfigAccessor == nil { params.ConfigAccessor = config.GetStubAccessor() } - if params.StateAccessor == nil { - params.StateAccessor = NewStateAccessor(32, 0) + if params.BlockAccessor == nil { + params.BlockAccessor = NewBlockAccessor(32) } es, err := NewExecutionState(tx, shardId, params) @@ -56,7 +56,7 @@ func NewTestExecutionState(t *testing.T, tx db.RoTx, shardId types.ShardId, para func NewTestBlockGeneratorParams(shardId types.ShardId, nShards uint32) BlockGeneratorParams { res := NewBlockGeneratorParams(shardId, nShards) - res.StateAccessor = NewStateAccessor(32, 0) + res.BlockAccessor = NewBlockAccessor(32) return res } diff --git a/nil/internal/mpt/mpt.go b/nil/internal/mpt/mpt.go index 640ffb9a1..c98612c9a 100644 --- a/nil/internal/mpt/mpt.go +++ b/nil/internal/mpt/mpt.go @@ -3,7 +3,6 @@ package mpt import ( "github.com/NilFoundation/nil/nil/common" "github.com/NilFoundation/nil/nil/internal/db" - "github.com/NilFoundation/nil/nil/internal/serialization" "github.com/NilFoundation/nil/nil/internal/types" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -122,21 +121,6 @@ func NewDbMPT(tx db.RwTx, shardId types.ShardId, name db.ShardedTableName) *Merk return NewMPT(NewDbSetter(tx, shardId, name), NewDbReader(tx, shardId, name)) } -func GetEntity[ - T interface { - ~*S - serialization.NilUnmarshaler - }, - S any, -](r *Reader, key []byte) (*S, error) { - data, err := r.Get(key) - if err != nil { - return nil, err - } - var s S - return &s, T(&s).UnmarshalNil(data) -} - // Implements database.NodeDatabase type nodeDatabaseWrapper struct { diff --git a/nil/internal/types/block.go b/nil/internal/types/block.go index 559c37fa8..8189fb60d 100644 --- a/nil/internal/types/block.go +++ b/nil/internal/types/block.go @@ -75,6 +75,26 @@ type Block struct { ConsensusParams } +type BlockWithHash struct { + *Block + + Hash common.Hash +} + +func NewBlockWithHash(b *Block, shardId ShardId) *BlockWithHash { + return &BlockWithHash{ + Block: b, + Hash: b.Hash(shardId), + } +} + +func NewBlockWithRawHash(b *Block, rawHash common.Hash) *BlockWithHash { + return &BlockWithHash{ + Block: b, + Hash: rawHash, + } +} + type RawBlockWithExtractedData struct { Block serialization.EncodedData InTransactions []serialization.EncodedData @@ -175,7 +195,7 @@ func (b *RawBlockWithExtractedData) DecodeBytes() (*BlockWithExtractedData, erro ChildBlocks: b.ChildBlocks, DbTimestamp: b.DbTimestamp, Config: common.TransformMap(b.Config, func(k string, v []byte) (string, hexutil.Bytes) { - return k, hexutil.Bytes(v) + return k, v }), }, nil } @@ -216,7 +236,7 @@ func (b *BlockWithExtractedData) EncodeToBytes() (*RawBlockWithExtractedData, er ChildBlocks: b.ChildBlocks, DbTimestamp: b.DbTimestamp, Config: common.TransformMap(b.Config, func(k string, v hexutil.Bytes) (string, []byte) { - return k, []byte(v) + return k, v }), }, nil } diff --git a/nil/internal/types/transaction.go b/nil/internal/types/transaction.go index f52c30807..056645acb 100644 --- a/nil/internal/types/transaction.go +++ b/nil/internal/types/transaction.go @@ -75,6 +75,10 @@ type TransactionIndex uint64 const TransactionIndexSize = 8 +func (ti TransactionIndex) String() string { + return strconv.FormatUint(uint64(ti), 10) +} + func (ti TransactionIndex) Bytes() []byte { res, err := ti.MarshalNil() check.PanicIfErr(err) diff --git a/nil/services/nilservice/config.go b/nil/services/nilservice/config.go index b79a10864..189fd4d2a 100644 --- a/nil/services/nilservice/config.go +++ b/nil/services/nilservice/config.go @@ -87,7 +87,7 @@ type Config struct { L1Fetcher rollup.L1BlockFetcher `yaml:"-"` - StateAccessor *execution.StateAccessor `yaml:"-"` + BlockAccessor *execution.BlockAccessor `yaml:"-"` NetworkManagerFactory func(ctx context.Context, cfg *Config, db db.DB) (network.Manager, error) `yaml:"-"` } @@ -227,6 +227,6 @@ func (c *Config) BlockGeneratorParams(shardId types.ShardId) execution.BlockGene EvmTracingHooks: verboseTracingHook, MainKeysPath: c.MainKeysPath, DisableConsensus: c.DisableConsensus, - StateAccessor: c.StateAccessor, + BlockAccessor: c.BlockAccessor, } } diff --git a/nil/services/nilservice/service.go b/nil/services/nilservice/service.go index 530728183..e859198b5 100644 --- a/nil/services/nilservice/service.go +++ b/nil/services/nilservice/service.go @@ -497,8 +497,8 @@ func CreateNode( return nil, err } - if cfg.StateAccessor == nil { - cfg.StateAccessor = execution.NewStateAccessor(1024, 0) + if cfg.BlockAccessor == nil { + cfg.BlockAccessor = execution.NewBlockAccessor(1024) } var txnPools map[types.ShardId]txnpool.Pool diff --git a/nil/services/rpc/rawapi/internal/local_account.go b/nil/services/rpc/rawapi/internal/local_account.go index 2209b0ef5..5dd9d163c 100644 --- a/nil/services/rpc/rawapi/internal/local_account.go +++ b/nil/services/rpc/rawapi/internal/local_account.go @@ -230,14 +230,10 @@ func (api *localShardApiRo) getSmartContract( blockReference rawapitypes.BlockReference, withProof bool, ) (*types.SmartContract, *mpt.Proof, error) { - rawBlock, err := api.getBlockByReference(tx, blockReference, false) + block, err := api.getHeaderByRef(tx, blockReference) if err != nil { return nil, nil, err } - var block types.Block - if err := block.UnmarshalNil(rawBlock.Block); err != nil { - return nil, nil, err - } reader := mpt.NewDbReader(tx, api.shardId(), db.ContractTrieTable) @@ -313,7 +309,7 @@ type GetRangeConfig struct { Max uint64 } -// GetContractRange returnes a range off accounts starting from the next after `start` address. +// GetContractRange returns a range off accounts starting from the next after `start` address. func (api *localShardApiRo) GetContractRange( ctx context.Context, blockReference rawapitypes.BlockReference, @@ -328,14 +324,10 @@ func (api *localShardApiRo) GetContractRange( } defer tx.Rollback() - rawBlock, err := api.getBlockByReference(tx, blockReference, false) + block, err := api.getHeaderByRef(tx, blockReference) if err != nil { return nil, err } - var block types.Block - if err := block.UnmarshalNil(rawBlock.Block); err != nil { - return nil, err - } conf := &GetRangeConfig{ WithCode: withCode, diff --git a/nil/services/rpc/rawapi/internal/local_block.go b/nil/services/rpc/rawapi/internal/local_block.go index a5157624c..4d5da3def 100644 --- a/nil/services/rpc/rawapi/internal/local_block.go +++ b/nil/services/rpc/rawapi/internal/local_block.go @@ -5,8 +5,6 @@ import ( "errors" "github.com/NilFoundation/nil/nil/common" - "github.com/NilFoundation/nil/nil/common/assert" - "github.com/NilFoundation/nil/nil/common/check" "github.com/NilFoundation/nil/nil/internal/db" "github.com/NilFoundation/nil/nil/internal/serialization" "github.com/NilFoundation/nil/nil/internal/types" @@ -23,11 +21,11 @@ func (api *localShardApiRo) GetBlockHeader( } defer tx.Rollback() - block, err := api.getBlockByReference(tx, blockReference, false) + block, err := api.getRawHeaderByRef(tx, blockReference) if err != nil { return nil, err } - return block.Block, nil + return block, nil } func (api *localShardApiRo) GetFullBlockData( @@ -39,7 +37,7 @@ func (api *localShardApiRo) GetFullBlockData( return nil, err } defer tx.Rollback() - return api.getBlockByReference(tx, blockReference, true) + return api.getFullBlockByRef(tx, blockReference) } func (api *localShardApiRo) GetBlockTransactionCount( @@ -52,7 +50,9 @@ func (api *localShardApiRo) GetBlockTransactionCount( } defer tx.Rollback() - res, err := api.getBlockByReference(tx, blockReference, true) + // We're caching blocks, so taking a full block is not a serious problem. + // (Unless we're attacked by transaction count lovers.) + res, err := api.getFullBlockByRef(tx, blockReference) if err != nil { return 0, err } @@ -67,25 +67,50 @@ func handleBlockFetchError(err error) error { } // getBlockByReference tries to fetch the block from db, if such block is not in the db, `rawapitypes.ErrBlockNotFound` -func (api *localShardApiRo) getBlockByReference( - tx db.RoTx, - blockReference rawapitypes.BlockReference, - withTransactions bool, +func (api *localShardApiRo) getRawHeaderByRef(tx db.RoTx, ref rawapitypes.BlockReference) ([]byte, error) { + hash, err := api.getBlockHashByRef(tx, ref) + if err != nil { + return nil, handleBlockFetchError(err) + } + + res, err := api.accessor.Access(tx, api.shardId()).GetRawBlockHeaderByHash(hash) + if err != nil { + return nil, handleBlockFetchError(err) + } + + return res, nil +} + +func (api *localShardApiRo) getHeaderByRef(tx db.RoTx, ref rawapitypes.BlockReference) (*types.Block, error) { + hash, err := api.getBlockHashByRef(tx, ref) + if err != nil { + return nil, handleBlockFetchError(err) + } + + res, err := api.accessor.Access(tx, api.shardId()).GetBlockHeaderByHash(hash) + if err != nil { + return nil, handleBlockFetchError(err) + } + return res, nil +} + +func (api *localShardApiRo) getFullBlockByRef( + tx db.RoTx, ref rawapitypes.BlockReference, ) (*types.RawBlockWithExtractedData, error) { - blockHash, err := api.getBlockHashByReference(tx, blockReference) + hash, err := api.getBlockHashByRef(tx, ref) if err != nil { return nil, handleBlockFetchError(err) } - block, err := api.getBlockByHash(tx, blockHash, withTransactions) + result, err := api.accessor.Access(tx, api.shardId()).GetFullBlockByHash(hash) if err != nil { return nil, handleBlockFetchError(err) } - return block, nil + return fixTxns(tx, result) } -func (api *localShardApiRo) getBlockHashByReference( +func (api *localShardApiRo) getBlockHashByRef( tx db.RoTx, blockReference rawapitypes.BlockReference, ) (common.Hash, error) { @@ -106,67 +131,27 @@ func (api *localShardApiRo) getBlockHashByReference( return common.EmptyHash, errors.New("unknown block reference type") } -func (api *localShardApiRo) getBlockByHash( - tx db.RoTx, - hash common.Hash, - withTransactions bool, -) (*types.RawBlockWithExtractedData, error) { - accessor := api.accessor.RawAccess(tx, api.shardId()).GetBlock() - if withTransactions { - accessor = accessor. - WithInTransactions(). - WithOutTransactions(). - WithReceipts(). - WithChildBlocks(). - WithDbTimestamp(). - WithConfig() - } - - // Unmarshalling happens inside, can't be nil - data, err := accessor.ByHash(hash) +func fixTxns(tx db.RoTx, result *types.RawBlockWithExtractedData) (*types.RawBlockWithExtractedData, error) { + // Need to decode transactions to get its hashes because external transaction hash + // calculated in a bit different way (not just Hash(bytes)). + transactions, err := serialization.DecodeContainer[*types.Transaction](result.InTransactions) if err != nil { return nil, err } - if assert.Enable { - var block types.Block - if err := block.UnmarshalNil(data.Block()); err != nil { - return nil, err - } - blockHash := block.Hash(api.shardId()) - check.PanicIfNotf(blockHash == hash, "block hash mismatch: %s != %s", blockHash, hash) - } - - result := &types.RawBlockWithExtractedData{ - Block: data.Block(), - } - if withTransactions { - result.InTransactions = data.InTransactions() - result.InTxCounts = data.InTxCounts() - result.OutTransactions = data.OutTransactions() - result.OutTxCounts = data.OutTxCounts() - result.Receipts = data.Receipts() - result.Errors = make(map[common.Hash]string) - result.ChildBlocks = data.ChildBlocks() - result.DbTimestamp = data.DbTimestamp() - result.Config = data.Config() - - // Need to decode transactions to get its hashes because external transaction hash - // calculated in a bit different way (not just Hash(bytes)). - transactions, err := serialization.DecodeContainer[*types.Transaction](result.InTransactions) - if err != nil { + for _, transaction := range transactions { + txnHash := transaction.Hash() + errMsg, err := db.ReadError(tx, txnHash) + if err != nil && !errors.Is(err, db.ErrKeyNotFound) { return nil, err } - for _, transaction := range transactions { - txnHash := transaction.Hash() - errMsg, err := db.ReadError(tx, txnHash) - if err != nil && !errors.Is(err, db.ErrKeyNotFound) { - return nil, err - } - if len(errMsg) > 0 { - result.Errors[txnHash] = errMsg + if len(errMsg) > 0 { + if result.Errors == nil { + result.Errors = make(map[common.Hash]string) } + result.Errors[txnHash] = errMsg } } + return result, nil } diff --git a/nil/services/rpc/rawapi/internal/local_call.go b/nil/services/rpc/rawapi/internal/local_call.go index 7eec120bd..d3b05c919 100644 --- a/nil/services/rpc/rawapi/internal/local_call.go +++ b/nil/services/rpc/rawapi/internal/local_call.go @@ -215,9 +215,9 @@ func (api *localShardApiRo) Call( hash = mainBlockHash } - block, err := db.ReadBlock(tx, shardId, hash) + block, err := api.accessor.Access(tx, shardId).GetBlockHeaderByHash(hash) if err != nil { - return nil, fmt.Errorf("failed to read block %s: %w", hash, err) + return nil, err } configAccessor, err := config.NewConfigAccessorFromBlockWithTx(tx, block, shardId) @@ -228,7 +228,7 @@ func (api *localShardApiRo) Call( es, err := execution.NewExecutionState(tx, shardId, execution.StateParams{ Block: block, ConfigAccessor: configAccessor, - StateAccessor: api.accessor, + BlockAccessor: api.accessor.BlockAccessor(), Mode: execution.ModeReadOnly, }) if err != nil { @@ -286,7 +286,7 @@ func (api *localShardApiRo) Call( esOld, err := execution.NewExecutionState(tx, shardId, execution.StateParams{ Block: block, ConfigAccessor: config.GetStubAccessor(), - StateAccessor: api.accessor, + BlockAccessor: api.accessor.BlockAccessor(), Mode: execution.ModeReadOnly, }) if err != nil { diff --git a/nil/services/rpc/rawapi/internal/local_receipt.go b/nil/services/rpc/rawapi/internal/local_receipt.go index 0eb6f636a..6b8ad2d8f 100644 --- a/nil/services/rpc/rawapi/internal/local_receipt.go +++ b/nil/services/rpc/rawapi/internal/local_receipt.go @@ -9,8 +9,6 @@ import ( "github.com/NilFoundation/nil/nil/common/logging" "github.com/NilFoundation/nil/nil/internal/db" "github.com/NilFoundation/nil/nil/internal/execution" - "github.com/NilFoundation/nil/nil/internal/mpt" - "github.com/NilFoundation/nil/nil/internal/serialization" "github.com/NilFoundation/nil/nil/internal/types" rawapitypes "github.com/NilFoundation/nil/nil/services/rpc/rawapi/types" ) @@ -25,103 +23,82 @@ func (api *localShardApiRo) GetInTransactionReceipt( } defer tx.Rollback() - block, indexes, err := api.getBlockAndInTransactionIndexByTransactionHash(tx, api.shardId(), hash) - if err != nil && !errors.Is(err, db.ErrKeyNotFound) { + txn, err := api.accessor.Access(tx, api.shardId()).GetInTxnByHash(hash) + if errors.Is(err, db.ErrKeyNotFound) { + receiptWithError, cachedReceipt := execution.FailureReceiptCache.Get(hash) + if !cachedReceipt { + // If the transaction is not found and there is no cached receipt, we have nothing to return. + return nil, nil + } + + receiptBytes, err := receiptWithError.Receipt.MarshalNil() + if err != nil { + return nil, err + } + return &rawapitypes.ReceiptInfo{ + ReceiptBytes: receiptBytes, + ErrorMessage: receiptWithError.Error.Error(), + Temporary: true, + }, nil + } + if err != nil { return nil, err } - var receipt *types.Receipt - var transaction *types.Transaction var gasPrice types.Value - includedInMain := false - if block != nil { - receipt, err = getBlockEntity[*types.Receipt]( - tx, api.shardId(), db.ReceiptTrieTable, block.ReceiptsRoot, indexes.TransactionIndex.Bytes()) - if err != nil && !errors.Is(err, db.ErrKeyNotFound) { - return nil, err - } - transaction, err = getBlockEntity[*types.Transaction]( - tx, - api.shardId(), - db.TransactionTrieTable, - block.InTransactionsRoot, - indexes.TransactionIndex.Bytes()) - if err != nil && !errors.Is(err, db.ErrKeyNotFound) { - return nil, err - } + if priorityFee, ok := execution.GetEffectivePriorityFee(txn.Block.BaseFee, txn.Transaction); ok { + gasPrice = txn.Block.BaseFee.Add(priorityFee) + } else if txn.Receipt.Status != types.ErrorBaseFeeTooHigh { + api.logger.Error(). + Stringer(logging.FieldTransactionHash, hash). + Msgf("Calculation of EffectivePriorityFee failed with wrong status: %s", txn.Receipt.Status) + } - if priorityFee, ok := execution.GetEffectivePriorityFee(block.BaseFee, transaction); ok { - gasPrice = block.BaseFee.Add(priorityFee) - } else if receipt.Status != types.ErrorBaseFeeTooHigh { - api.logger.Error(). - Stringer(logging.FieldTransactionHash, hash). - Msgf("Calculation of EffectivePriorityFee failed with wrong status: %s", receipt.Status) + // Check if the transaction is included in the main chain + rawMainBlock, err := api.nodeApi.GetFullBlockData(ctx, types.MainShardId, + rawapitypes.NamedBlockIdentifierAsBlockReference(rawapitypes.LatestBlock)) + if err == nil { + mainBlockData, err := rawMainBlock.DecodeBytes() + if err != nil { + return nil, err } - // Check if the transaction is included in the main chain - rawMainBlock, err := api.nodeApi.GetFullBlockData( - ctx, - types.MainShardId, - rawapitypes.NamedBlockIdentifierAsBlockReference(rawapitypes.LatestBlock)) - if err == nil { - mainBlockData, err := rawMainBlock.DecodeBytes() - if err != nil { - return nil, err + if api.shardId().IsMainShard() { + includedInMain = mainBlockData.Id >= txn.Block.Id + } else { + if len(rawMainBlock.ChildBlocks) < int(api.shardId()) { + return nil, fmt.Errorf( + "%w: main shard includes only %d blocks", + makeShardNotFoundError(methodNameChecked("GetInTransactionReceipt"), api.shardId()), + len(rawMainBlock.ChildBlocks)) } - - if api.shardId().IsMainShard() { - includedInMain = mainBlockData.Id >= block.Id - } else { - if len(rawMainBlock.ChildBlocks) < int(api.shardId()) { - return nil, fmt.Errorf( - "%w: main shard includes only %d blocks", - makeShardNotFoundError(methodNameChecked("GetInTransactionReceipt"), api.shardId()), - len(rawMainBlock.ChildBlocks)) - } - blockHash := rawMainBlock.ChildBlocks[api.shardId()-1] - if last, err := api.accessor.Access(tx, api.shardId()).GetBlock().ByHash(blockHash); err == nil { - includedInMain = last.Block().Id >= block.Id - } + blockHash := rawMainBlock.ChildBlocks[api.shardId()-1] + if last, err := api.accessor.Access(tx, api.shardId()).GetBlockHeaderByHash(blockHash); err == nil { + includedInMain = last.Id >= txn.Block.Id } } - } else { - gasPrice = types.DefaultGasPrice } - var errMsg string - var cachedReceipt bool - if receipt == nil { - var receiptWithError execution.ReceiptWithError - receiptWithError, cachedReceipt = execution.FailureReceiptCache.Get(hash) - if !cachedReceipt { - return nil, nil - } - - receipt = receiptWithError.Receipt - errMsg = receiptWithError.Error.Error() - } else { - errMsg, err = db.ReadError(tx, hash) - if err != nil && !errors.Is(err, db.ErrKeyNotFound) { - return nil, err - } + errMsg, err := db.ReadError(tx, hash) + if err != nil && !errors.Is(err, db.ErrKeyNotFound) { + return nil, err } var outReceipts []*rawapitypes.ReceiptInfo var outTransactions []common.Hash - if receipt.OutTxnNum != 0 { - outReceipts = make([]*rawapitypes.ReceiptInfo, 0, receipt.OutTxnNum) - for i := receipt.OutTxnIndex; i < receipt.OutTxnIndex+receipt.OutTxnNum; i++ { - res, err := api.accessor. - Access(tx, api.shardId()). - GetOutTransaction(). - ByIndex(types.TransactionIndex(i), block) + if txn.Receipt.OutTxnNum != 0 { + outReceipts = make([]*rawapitypes.ReceiptInfo, 0, txn.Receipt.OutTxnNum) + outTransactions = make([]common.Hash, 0, txn.Receipt.OutTxnNum) + for i := txn.Receipt.OutTxnIndex; i < txn.Receipt.OutTxnIndex+txn.Receipt.OutTxnNum; i++ { + res, err := api.accessor.Access(tx, api.shardId()). + GetOutTxnByIndex(types.TransactionIndex(i), txn.Block) if err != nil { return nil, err } - txnHash := res.Transaction().Hash() - r, err := api.nodeApi.GetInTransactionReceipt(ctx, res.Transaction().To.ShardId(), txnHash) + txnHash := res.Transaction.Hash() + r, err := api.nodeApi.GetInTransactionReceipt(ctx, res.Transaction.To.ShardId(), txnHash) if err != nil { return nil, err } @@ -130,78 +107,16 @@ func (api *localShardApiRo) GetInTransactionReceipt( } } - var receiptBytes []byte - if receipt != nil { - receiptBytes, err = receipt.MarshalNil() - if err != nil { - return nil, fmt.Errorf("failed to marshal receipt: %w", err) - } - } - - var flags types.TransactionFlags - if transaction != nil { - flags = transaction.Flags - } - - var blockId types.BlockNumber - var blockHash common.Hash - if block != nil { - blockId = block.Id - blockHash = block.Hash(api.shardId()) - } - return &rawapitypes.ReceiptInfo{ - ReceiptBytes: receiptBytes, - Flags: flags, - Index: indexes.TransactionIndex, - BlockHash: blockHash, - BlockId: blockId, + ReceiptBytes: txn.RawReceipt, + Flags: txn.Transaction.Flags, + Index: txn.Index, + BlockHash: txn.Block.Hash, + BlockId: txn.Block.Id, IncludedInMain: includedInMain, OutReceipts: outReceipts, OutTransactions: outTransactions, ErrorMessage: errMsg, GasPrice: gasPrice, - Temporary: cachedReceipt, }, nil } - -func (api *localShardApiRo) getBlockAndInTransactionIndexByTransactionHash( - tx db.RoTx, - shardId types.ShardId, - hash common.Hash, -) (*types.Block, db.BlockHashAndTransactionIndex, error) { - var index db.BlockHashAndTransactionIndex - value, err := tx.GetFromShard(shardId, db.BlockHashAndInTransactionIndexByTransactionHash, hash.Bytes()) - if err != nil { - return nil, index, err - } - if err := index.UnmarshalNil(value); err != nil { - return nil, index, err - } - - data, err := api.accessor.Access(tx, shardId).GetBlock().ByHash(index.BlockHash) - if err != nil { - return nil, db.BlockHashAndTransactionIndex{}, err - } - return data.Block(), index, nil -} - -func getBlockEntity[ - T interface { - ~*S - serialization.NilUnmarshaler - }, - S any, -]( - tx db.RoTx, - shardId types.ShardId, - tableName db.ShardedTableName, - rootHash common.Hash, - entityKey []byte, -) (*S, error) { - root := mpt.NewDbReader(tx, shardId, tableName) - if err := root.SetRootHash(rootHash); err != nil { - return nil, err - } - return mpt.GetEntity[T](root, entityKey) -} diff --git a/nil/services/rpc/rawapi/internal/local_system.go b/nil/services/rpc/rawapi/internal/local_system.go index 9c10bb310..9ce9aa780 100644 --- a/nil/services/rpc/rawapi/internal/local_system.go +++ b/nil/services/rpc/rawapi/internal/local_system.go @@ -45,13 +45,13 @@ func (api *localShardApiRo) GetShardIdList(ctx context.Context) ([]types.ShardId return nil, err } - block, err := api.accessor.Access(tx, types.MainShardId).GetBlock().ByHash(blockHash) + block, err := api.accessor.Access(tx, types.MainShardId).GetBlockHeaderByHash(blockHash) if err != nil { return nil, err } - treeShards := execution.NewDbShardBlocksTrieReader(tx, types.MainShardId, block.Block().Id) - if err := treeShards.SetRootHash(block.Block().ChildBlocksRootHash); err != nil { + treeShards := execution.NewDbShardBlocksTrieReader(tx, types.MainShardId, block.Id) + if err := treeShards.SetRootHash(block.ChildBlocksRootHash); err != nil { return nil, err } return treeShards.Keys() diff --git a/nil/services/rpc/rawapi/internal/local_transaction.go b/nil/services/rpc/rawapi/internal/local_transaction.go index a78e55ff0..7884b2dfd 100644 --- a/nil/services/rpc/rawapi/internal/local_transaction.go +++ b/nil/services/rpc/rawapi/internal/local_transaction.go @@ -6,97 +6,49 @@ import ( "github.com/NilFoundation/nil/nil/common" "github.com/NilFoundation/nil/nil/internal/db" - "github.com/NilFoundation/nil/nil/internal/mpt" + "github.com/NilFoundation/nil/nil/internal/execution" "github.com/NilFoundation/nil/nil/internal/types" rawapitypes "github.com/NilFoundation/nil/nil/services/rpc/rawapi/types" ) -func (api *localShardApiRo) getTransactionByHash(tx db.RoTx, hash common.Hash) (*rawapitypes.TransactionInfo, error) { - data, err := api.accessor.Access(tx, api.shardId()).GetInTransaction().WithReceipt().ByHash(hash) - if err != nil { - return nil, err - } - - txn := data.Transaction() - transactionBytes, err := txn.MarshalNil() - if err != nil { - return nil, fmt.Errorf("failed to marshal transaction: %w", err) - } - - receipt := data.Receipt() - receiptBytes, err := receipt.MarshalNil() - if err != nil { - return nil, fmt.Errorf("failed to marshal receipt: %w", err) - } - - block := data.Block() +func convertTxnInfo(scr *execution.Txn) *rawapitypes.TransactionInfo { return &rawapitypes.TransactionInfo{ - TransactionBytes: transactionBytes, - ReceiptBytes: receiptBytes, - Index: data.Index(), - BlockHash: block.Hash(api.shardId()), - BlockId: block.Id, - }, nil -} - -func getRawBlockEntity( - tx db.RoTx, shardId types.ShardId, tableName db.ShardedTableName, rootHash common.Hash, entityKey []byte, -) ([]byte, error) { - root := mpt.NewDbReader(tx, shardId, tableName) - if err := root.SetRootHash(rootHash); err != nil { - return nil, err + TransactionBytes: scr.RawTxn, + ReceiptBytes: scr.RawReceipt, + Index: scr.Index, + BlockHash: scr.Block.Hash, + BlockId: scr.Block.Id, } - entityBytes, err := root.Get(entityKey) - if err != nil { - return nil, err - } - return entityBytes, nil } -func (api *localShardApiRo) getInTransactionByBlockHashAndIndex( - tx db.RoTx, block *types.Block, txnIndex types.TransactionIndex, -) (*rawapitypes.TransactionInfo, error) { - rawTxn, err := getRawBlockEntity( - tx, api.shardId(), db.TransactionTrieTable, block.InTransactionsRoot, txnIndex.Bytes()) - if err != nil { - return nil, err - } - - rawReceipt, err := getRawBlockEntity(tx, api.shardId(), db.ReceiptTrieTable, block.ReceiptsRoot, txnIndex.Bytes()) +func (api *localShardApiRo) getTxnByHash(tx db.RoTx, hash common.Hash) (*rawapitypes.TransactionInfo, error) { + data, err := api.accessor.Access(tx, api.shardId()).GetInTxnByHash(hash) if err != nil { return nil, err } - return &rawapitypes.TransactionInfo{ - TransactionBytes: rawTxn, - ReceiptBytes: rawReceipt, - Index: txnIndex, - BlockHash: block.Hash(api.shardId()), - BlockId: block.Id, - }, nil + return convertTxnInfo(data), nil } -func (api *localShardApiRo) fetchBlockByRef(tx db.RoTx, blockRef rawapitypes.BlockReference) (*types.Block, error) { - hash, err := api.getBlockHashByReference(tx, blockRef) +func (api *localShardApiRo) getInTxnByBlockRefAndIndex( + tx db.RoTx, blockRef rawapitypes.BlockReference, index types.TransactionIndex, +) (*rawapitypes.TransactionInfo, error) { + blockHash, err := api.getBlockHashByRef(tx, blockRef) if err != nil { return nil, err } - data, err := api.accessor.Access(tx, api.shardId()).GetBlock().ByHash(hash) + block, err := api.accessor.Access(tx, api.shardId()).GetBlockHeaderByHash(blockHash) if err != nil { return nil, err } - return data.Block(), nil -} -func (api *localShardApiRo) getInTransactionByBlockRefAndIndex( - tx db.RoTx, blockRef rawapitypes.BlockReference, index types.TransactionIndex, -) (*rawapitypes.TransactionInfo, error) { - block, err := api.fetchBlockByRef(tx, blockRef) + data, err := api.accessor.Access(tx, api.shardId()). + GetInTxnByIndex(index, types.NewBlockWithRawHash(block, blockHash)) if err != nil { return nil, err } - return api.getInTransactionByBlockHashAndIndex(tx, block, index) + return convertTxnInfo(data), nil } func (api *localShardApiRo) GetInTransaction( @@ -110,8 +62,8 @@ func (api *localShardApiRo) GetInTransaction( defer tx.Rollback() if request.ByHash != nil { - return api.getTransactionByHash(tx, request.ByHash.Hash) + return api.getTxnByHash(tx, request.ByHash.Hash) } - return api.getInTransactionByBlockRefAndIndex( + return api.getInTxnByBlockRefAndIndex( tx, request.ByBlockRefAndIndex.BlockRef, request.ByBlockRefAndIndex.Index) } diff --git a/nil/services/synccommittee/prover/tracer/traces_collector.go b/nil/services/synccommittee/prover/tracer/traces_collector.go index bf8cbe23b..d80ddaaea 100644 --- a/nil/services/synccommittee/prover/tracer/traces_collector.go +++ b/nil/services/synccommittee/prover/tracer/traces_collector.go @@ -39,7 +39,7 @@ type remoteTracesCollectorImpl struct { logger logging.Logger mptTracer *mpttracer.MPTTracer rwTx db.RwTx - stateAccessor *execution.StateAccessor + blockAccessor *execution.BlockAccessor lastTracedBlock *types.BlockNumber } @@ -65,7 +65,7 @@ func NewRemoteTracesCollector( client: client, logger: logger, rwTx: rwTx, - stateAccessor: execution.NewStateAccessor(32, 0), + blockAccessor: execution.NewBlockAccessor(32), }, nil } @@ -239,7 +239,7 @@ func (tc *remoteTracesCollectorImpl) executeBlockAndCollectTraces( execution.StateParams{ Block: prevBlock.Block, ConfigAccessor: configAccessor, - StateAccessor: tc.stateAccessor, + BlockAccessor: tc.blockAccessor, ContractMptRepository: tc.mptTracer, }, ) @@ -255,7 +255,7 @@ func (tc *remoteTracesCollectorImpl) executeBlockAndCollectTraces( // Create block generator params blockGeneratorParams := execution.NewBlockGeneratorParams(shardId, uint32(len(gasPrices))) blockGeneratorParams.EvmTracingHooks = es.EvmTracingHooks - blockGeneratorParams.StateAccessor = tc.stateAccessor + blockGeneratorParams.BlockAccessor = tc.blockAccessor // Create block generator blockGenerator, err := execution.NewBlockGeneratorWithEs(