diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go
new file mode 100644
index 000000000000..04b30b2a2206
--- /dev/null
+++ b/core/rawdb/accessors_chain.go
@@ -0,0 +1,280 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rawdb
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/ethdb"
+ "github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/params"
+ "github.com/XinFinOrg/XDPoSChain/rlp"
+)
+
+// ReadHeaderNumber returns the header number assigned to a hash.
+func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
+ data, _ := db.Get(headerNumberKey(hash))
+ if len(data) != 8 {
+ return nil
+ }
+ number := binary.BigEndian.Uint64(data)
+ return &number
+}
+
+// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
+func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
+ // First try to look up the data in ancient database. Extra hash
+ // comparison is necessary since ancient database only maintains
+ // the canonical data.
+ data, _ := db.Ancient(freezerBodiesTable, number)
+ if len(data) > 0 {
+ h, _ := db.Ancient(freezerHashTable, number)
+ if common.BytesToHash(h) == hash {
+ return data
+ }
+ }
+ // Then try to look up the data in leveldb.
+ data, _ = db.Get(blockBodyKey(number, hash))
+ if len(data) > 0 {
+ return data
+ }
+ // In the background freezer is moving data from leveldb to flatten files.
+ // So during the first check for ancient db, the data is not yet in there,
+ // but when we reach into leveldb, the data was already moved. That would
+ // result in a not found error.
+ data, _ = db.Ancient(freezerBodiesTable, number)
+ if len(data) > 0 {
+ h, _ := db.Ancient(freezerHashTable, number)
+ if common.BytesToHash(h) == hash {
+ return data
+ }
+ }
+ return nil // Can't find the data anywhere.
+}
+
+// WriteBodyRLP stores an RLP encoded block body into the database.
+func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
+ if err := db.Put(blockBodyKey(number, hash), rlp); err != nil {
+ log.Crit("Failed to store block body", "err", err)
+ }
+}
+
+// ReadBody retrieves the block body corresponding to the hash.
+func ReadBody(db ethdb.Reader, hash common.Hash, number uint64) *types.Body {
+ data := ReadBodyRLP(db, hash, number)
+ if len(data) == 0 {
+ return nil
+ }
+ body := new(types.Body)
+ if err := rlp.Decode(bytes.NewReader(data), body); err != nil {
+ log.Error("Invalid block body RLP", "hash", hash, "err", err)
+ return nil
+ }
+ return body
+}
+
+// WriteBody stores a block body into the database.
+func WriteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64, body *types.Body) {
+ data, err := rlp.EncodeToBytes(body)
+ if err != nil {
+ log.Crit("Failed to RLP encode body", "err", err)
+ }
+ WriteBodyRLP(db, hash, number, data)
+}
+
+// ReadReceiptsRLP retrieves all the transaction receipts belonging to a block in RLP encoding.
+func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
+ // First try to look up the data in ancient database. Extra hash
+ // comparison is necessary since ancient database only maintains
+ // the canonical data.
+ data, _ := db.Ancient(freezerReceiptTable, number)
+ if len(data) > 0 {
+ h, _ := db.Ancient(freezerHashTable, number)
+ if common.BytesToHash(h) == hash {
+ return data
+ }
+ }
+ // Then try to look up the data in leveldb.
+ data, _ = db.Get(blockReceiptsKey(number, hash))
+ if len(data) > 0 {
+ return data
+ }
+ // In the background freezer is moving data from leveldb to flatten files.
+ // So during the first check for ancient db, the data is not yet in there,
+ // but when we reach into leveldb, the data was already moved. That would
+ // result in a not found error.
+ data, _ = db.Ancient(freezerReceiptTable, number)
+ if len(data) > 0 {
+ h, _ := db.Ancient(freezerHashTable, number)
+ if common.BytesToHash(h) == hash {
+ return data
+ }
+ }
+ return nil // Can't find the data anywhere.
+}
+
+// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
+// The receipt metadata fields are not guaranteed to be populated, so they
+// should not be used. Use ReadReceipts instead if the metadata is needed.
+func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts {
+ // Retrieve the flattened receipt slice
+ data := ReadReceiptsRLP(db, hash, number)
+ if len(data) == 0 {
+ return nil
+ }
+ // Convert the receipts from their storage form to their internal representation
+ storageReceipts := []*types.ReceiptForStorage{}
+ if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
+ log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
+ return nil
+ }
+ receipts := make(types.Receipts, len(storageReceipts))
+ for i, storageReceipt := range storageReceipts {
+ receipts[i] = (*types.Receipt)(storageReceipt)
+ }
+ return receipts
+}
+
+// ReadReceipts retrieves all the transaction receipts belonging to a block, including
+// its correspoinding metadata fields. If it is unable to populate these metadata
+// fields then nil is returned.
+//
+// The current implementation populates these metadata fields by reading the receipts'
+// corresponding block body, so if the block body is not found it will return nil even
+// if the receipt itself is stored.
+func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) types.Receipts {
+ // We're deriving many fields from the block body, retrieve beside the receipt
+ receipts := ReadRawReceipts(db, hash, number)
+ if receipts == nil {
+ return nil
+ }
+ body := ReadBody(db, hash, number)
+ if body == nil {
+ log.Error("Missing body but have receipt", "hash", hash, "number", number)
+ return nil
+ }
+ if err := receipts.DeriveFields(config, hash, number, body.Transactions); err != nil {
+ log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
+ return nil
+ }
+ return receipts
+}
+
+// WriteReceipts stores all the transaction receipts belonging to a block.
+func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts types.Receipts) {
+ // Convert the receipts into their storage form and serialize them
+ storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
+ for i, receipt := range receipts {
+ storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
+ }
+ bytes, err := rlp.EncodeToBytes(storageReceipts)
+ if err != nil {
+ log.Crit("Failed to encode block receipts", "err", err)
+ }
+ // Store the flattened receipt slice
+ if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil {
+ log.Crit("Failed to store block receipts", "err", err)
+ }
+}
+
+// storedReceiptRLP is the storage encoding of a receipt.
+// Re-definition in core/types/receipt.go.
+type storedReceiptRLP struct {
+ PostStateOrStatus []byte
+ CumulativeGasUsed uint64
+ Bloom types.Bloom
+ TxHash common.Hash
+ ContractAddress common.Address
+ Logs []*types.LogForStorage
+ GasUsed uint64
+}
+
+// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
+// the list of logs. When decoding a stored receipt into this object we
+// avoid creating the bloom filter.
+type receiptLogs struct {
+ Logs []*types.Log
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
+ var stored storedReceiptRLP
+ if err := s.Decode(&stored); err != nil {
+ return err
+ }
+ r.Logs = make([]*types.Log, len(stored.Logs))
+ for i, log := range stored.Logs {
+ r.Logs[i] = (*types.Log)(log)
+ }
+ return nil
+}
+
+// DeriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc.
+func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error {
+ logIndex := uint(0)
+ if len(txs) != len(receipts) {
+ return errors.New("transaction and receipt count mismatch")
+ }
+ for i := 0; i < len(receipts); i++ {
+ txHash := txs[i].Hash()
+ // The derived log fields can simply be set from the block and transaction
+ for j := 0; j < len(receipts[i].Logs); j++ {
+ receipts[i].Logs[j].BlockNumber = number
+ receipts[i].Logs[j].BlockHash = hash
+ receipts[i].Logs[j].TxHash = txHash
+ receipts[i].Logs[j].TxIndex = uint(i)
+ receipts[i].Logs[j].Index = logIndex
+ logIndex++
+ }
+ }
+ return nil
+}
+
+// ReadLogs retrieves the logs for all transactions in a block. The log fields
+// are populated with metadata. In case the receipts or the block body
+// are not found, a nil is returned.
+func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64) [][]*types.Log {
+ // Retrieve the flattened receipt slice
+ data := ReadReceiptsRLP(db, hash, number)
+ if len(data) == 0 {
+ return nil
+ }
+ receipts := []*receiptLogs{}
+ if err := rlp.DecodeBytes(data, &receipts); err != nil {
+ log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
+ return nil
+ }
+
+ body := ReadBody(db, hash, number)
+ if body == nil {
+ log.Error("Missing body but have receipt", "hash", hash, "number", number)
+ return nil
+ }
+ if err := deriveLogFields(receipts, hash, number, body.Transactions); err != nil {
+ log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
+ return nil
+ }
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs
+}
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
new file mode 100644
index 000000000000..1b6527dbd2e1
--- /dev/null
+++ b/core/rawdb/accessors_chain_test.go
@@ -0,0 +1,243 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rawdb
+
+import (
+ "bytes"
+ "encoding/hex"
+ "io/ioutil"
+ "math/big"
+ "testing"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/params"
+ "github.com/XinFinOrg/XDPoSChain/rlp"
+)
+
+type fullLogRLP struct {
+ Address common.Address
+ Topics []common.Hash
+ Data []byte
+ BlockNumber uint64
+ TxHash common.Hash
+ TxIndex uint
+ BlockHash common.Hash
+ Index uint
+}
+
+func newFullLogRLP(l *types.Log) *fullLogRLP {
+ return &fullLogRLP{
+ Address: l.Address,
+ Topics: l.Topics,
+ Data: l.Data,
+ BlockNumber: l.BlockNumber,
+ TxHash: l.TxHash,
+ TxIndex: l.TxIndex,
+ BlockHash: l.BlockHash,
+ Index: l.Index,
+ }
+}
+
+// Tests that logs associated with a single block can be retrieved.
+func TestReadLogs(t *testing.T) {
+ db := NewMemoryDatabase()
+
+ // Create a live block since we need metadata to reconstruct the receipt
+ tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
+ tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
+
+ body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
+
+ // Create the two receipts to manage afterwards
+ receipt1 := &types.Receipt{
+ Status: types.ReceiptStatusFailed,
+ CumulativeGasUsed: 1,
+ Logs: []*types.Log{
+ {Address: common.BytesToAddress([]byte{0x11})},
+ {Address: common.BytesToAddress([]byte{0x01, 0x11})},
+ },
+ TxHash: tx1.Hash(),
+ ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
+ GasUsed: 111111,
+ }
+ receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
+
+ receipt2 := &types.Receipt{
+ PostState: common.Hash{2}.Bytes(),
+ CumulativeGasUsed: 2,
+ Logs: []*types.Log{
+ {Address: common.BytesToAddress([]byte{0x22})},
+ {Address: common.BytesToAddress([]byte{0x02, 0x22})},
+ },
+ TxHash: tx2.Hash(),
+ ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
+ GasUsed: 222222,
+ }
+ receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2})
+ receipts := []*types.Receipt{receipt1, receipt2}
+
+ hash := common.BytesToHash([]byte{0x03, 0x14})
+ // Check that no receipt entries are in a pristine database
+ if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
+ t.Fatalf("non existent receipts returned: %v", rs)
+ }
+ // Insert the body that corresponds to the receipts
+ WriteBody(db, hash, 0, body)
+
+ // Insert the receipt slice into the database and check presence
+ WriteReceipts(db, hash, 0, receipts)
+
+ logs := ReadLogs(db, hash, 0)
+ if len(logs) == 0 {
+ t.Fatalf("no logs returned")
+ }
+ if have, want := len(logs), 2; have != want {
+ t.Fatalf("unexpected number of logs returned, have %d want %d", have, want)
+ }
+ if have, want := len(logs[0]), 2; have != want {
+ t.Fatalf("unexpected number of logs[0] returned, have %d want %d", have, want)
+ }
+ if have, want := len(logs[1]), 2; have != want {
+ t.Fatalf("unexpected number of logs[1] returned, have %d want %d", have, want)
+ }
+
+ // Fill in log fields so we can compare their rlp encoding
+ if err := types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, 0, body.Transactions); err != nil {
+ t.Fatal(err)
+ }
+ for i, pr := range receipts {
+ for j, pl := range pr.Logs {
+ rlpHave, err := rlp.EncodeToBytes(newFullLogRLP(logs[i][j]))
+ if err != nil {
+ t.Fatal(err)
+ }
+ rlpWant, err := rlp.EncodeToBytes(newFullLogRLP(pl))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(rlpHave, rlpWant) {
+ t.Fatalf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
+ }
+ }
+ }
+}
+
+func TestDeriveLogFields(t *testing.T) {
+ // Create a few transactions to have receipts for
+ to2 := common.HexToAddress("0x2")
+ to3 := common.HexToAddress("0x3")
+ txs := types.Transactions{
+ types.NewTx(&types.LegacyTx{
+ Nonce: 1,
+ Value: big.NewInt(1),
+ Gas: 1,
+ GasPrice: big.NewInt(1),
+ }),
+ types.NewTx(&types.LegacyTx{
+ To: &to2,
+ Nonce: 2,
+ Value: big.NewInt(2),
+ Gas: 2,
+ GasPrice: big.NewInt(2),
+ }),
+ types.NewTx(&types.AccessListTx{
+ To: &to3,
+ Nonce: 3,
+ Value: big.NewInt(3),
+ Gas: 3,
+ GasPrice: big.NewInt(3),
+ }),
+ }
+ // Create the corresponding receipts
+ receipts := []*receiptLogs{
+ {
+ Logs: []*types.Log{
+ {Address: common.BytesToAddress([]byte{0x11})},
+ {Address: common.BytesToAddress([]byte{0x01, 0x11})},
+ },
+ },
+ {
+ Logs: []*types.Log{
+ {Address: common.BytesToAddress([]byte{0x22})},
+ {Address: common.BytesToAddress([]byte{0x02, 0x22})},
+ },
+ },
+ {
+ Logs: []*types.Log{
+ {Address: common.BytesToAddress([]byte{0x33})},
+ {Address: common.BytesToAddress([]byte{0x03, 0x33})},
+ },
+ },
+ }
+
+ // Derive log metadata fields
+ number := big.NewInt(1)
+ hash := common.BytesToHash([]byte{0x03, 0x14})
+ if err := deriveLogFields(receipts, hash, number.Uint64(), txs); err != nil {
+ t.Fatal(err)
+ }
+
+ // Iterate over all the computed fields and check that they're correct
+ logIndex := uint(0)
+ for i := range receipts {
+ for j := range receipts[i].Logs {
+ if receipts[i].Logs[j].BlockNumber != number.Uint64() {
+ t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64())
+ }
+ if receipts[i].Logs[j].BlockHash != hash {
+ t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
+ }
+ if receipts[i].Logs[j].TxHash != txs[i].Hash() {
+ t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
+ }
+ if receipts[i].Logs[j].TxIndex != uint(i) {
+ t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
+ }
+ if receipts[i].Logs[j].Index != logIndex {
+ t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex)
+ }
+ logIndex++
+ }
+ }
+}
+
+func BenchmarkDecodeRLPLogs(b *testing.B) {
+ // Encoded receipts from block 0x14ee094309fbe8f70b65f45ebcc08fb33f126942d97464aad5eb91cfd1e2d269
+ buf, err := ioutil.ReadFile("testdata/stored_receipts.bin")
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.Run("ReceiptForStorage", func(b *testing.B) {
+ b.ReportAllocs()
+ var r []*types.ReceiptForStorage
+ for i := 0; i < b.N; i++ {
+ if err := rlp.DecodeBytes(buf, &r); err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+ b.Run("rlpLogs", func(b *testing.B) {
+ b.ReportAllocs()
+ var r []*receiptLogs
+ for i := 0; i < b.N; i++ {
+ if err := rlp.DecodeBytes(buf, &r); err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+}
diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go
new file mode 100644
index 000000000000..d9d567e57828
--- /dev/null
+++ b/core/rawdb/schema.go
@@ -0,0 +1,66 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package rawdb contains a collection of low level database accessors.
+package rawdb
+
+import (
+ "encoding/binary"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+)
+
+// The fields below define the low level database schema prefixing.
+var (
+ // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
+ headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian)
+ blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
+
+ blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
+)
+
+const (
+ // freezerHashTable indicates the name of the freezer canonical hash table.
+ freezerHashTable = "hashes"
+
+ // freezerBodiesTable indicates the name of the freezer block body table.
+ freezerBodiesTable = "bodies"
+
+ // freezerReceiptTable indicates the name of the freezer receipts table.
+ freezerReceiptTable = "receipts"
+)
+
+// encodeBlockNumber encodes a block number as big endian uint64
+func encodeBlockNumber(number uint64) []byte {
+ enc := make([]byte, 8)
+ binary.BigEndian.PutUint64(enc, number)
+ return enc
+}
+
+// headerNumberKey = headerNumberPrefix + hash
+func headerNumberKey(hash common.Hash) []byte {
+ return append(headerNumberPrefix, hash.Bytes()...)
+}
+
+// blockBodyKey = blockBodyPrefix + num (uint64 big endian) + hash
+func blockBodyKey(number uint64, hash common.Hash) []byte {
+ return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
+}
+
+// blockReceiptsKey = blockReceiptsPrefix + num (uint64 big endian) + hash
+func blockReceiptsKey(number uint64, hash common.Hash) []byte {
+ return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
+}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 9600ad1d794e..76507b521064 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -26,6 +26,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
+ "github.com/XinFinOrg/XDPoSChain/crypto"
+ "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
@@ -328,3 +330,47 @@ func (r Receipts) GetRlp(i int) []byte {
}
return bytes
}
+
+// DeriveFields fills the receipts with their computed fields based on consensus
+// data and contextual infos like containing block and transactions.
+func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error {
+ signer := MakeSigner(config, new(big.Int).SetUint64(number))
+
+ logIndex := uint(0)
+ if len(txs) != len(r) {
+ return errors.New("transaction and receipt count mismatch")
+ }
+ for i := 0; i < len(r); i++ {
+ // The transaction type and hash can be retrieved from the transaction itself
+ r[i].Type = txs[i].Type()
+ r[i].TxHash = txs[i].Hash()
+
+ // block location fields
+ r[i].BlockHash = hash
+ r[i].BlockNumber = new(big.Int).SetUint64(number)
+ r[i].TransactionIndex = uint(i)
+
+ // The contract address can be derived from the transaction itself
+ if txs[i].To() == nil {
+ // Deriving the signer is expensive, only do if it's actually needed
+ from, _ := Sender(signer, txs[i])
+ r[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce())
+ }
+ // The used gas can be calculated based on previous r
+ if i == 0 {
+ r[i].GasUsed = r[i].CumulativeGasUsed
+ } else {
+ r[i].GasUsed = r[i].CumulativeGasUsed - r[i-1].CumulativeGasUsed
+ }
+ // The derived log fields can simply be set from the block and transaction
+ for j := 0; j < len(r[i].Logs); j++ {
+ r[i].Logs[j].BlockNumber = number
+ r[i].Logs[j].BlockHash = hash
+ r[i].Logs[j].TxHash = r[i].TxHash
+ r[i].Logs[j].TxIndex = uint(i)
+ r[i].Logs[j].Index = logIndex
+ logIndex++
+ }
+ }
+ return nil
+}
diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go
index 82fec06c9667..1ca5a2864774 100644
--- a/core/types/receipt_test.go
+++ b/core/types/receipt_test.go
@@ -18,11 +18,14 @@ package types
import (
"bytes"
+ "math"
"math/big"
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/crypto"
+ "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
@@ -141,6 +144,169 @@ func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
return rlp.EncodeToBytes(stored)
}
+// Tests that receipt data can be correctly derived from the contextual infos
+func TestDeriveFields(t *testing.T) {
+ // Create a few transactions to have receipts for
+ to2 := common.HexToAddress("0x2")
+ to3 := common.HexToAddress("0x3")
+ txs := Transactions{
+ NewTx(&LegacyTx{
+ Nonce: 1,
+ Value: big.NewInt(1),
+ Gas: 1,
+ GasPrice: big.NewInt(1),
+ }),
+ NewTx(&LegacyTx{
+ To: &to2,
+ Nonce: 2,
+ Value: big.NewInt(2),
+ Gas: 2,
+ GasPrice: big.NewInt(2),
+ }),
+ NewTx(&AccessListTx{
+ To: &to3,
+ Nonce: 3,
+ Value: big.NewInt(3),
+ Gas: 3,
+ GasPrice: big.NewInt(3),
+ }),
+ }
+ // Create the corresponding receipts
+ receipts := Receipts{
+ &Receipt{
+ Status: ReceiptStatusFailed,
+ CumulativeGasUsed: 1,
+ Logs: []*Log{
+ {Address: common.BytesToAddress([]byte{0x11})},
+ {Address: common.BytesToAddress([]byte{0x01, 0x11})},
+ },
+ TxHash: txs[0].Hash(),
+ ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
+ GasUsed: 1,
+ },
+ &Receipt{
+ PostState: common.Hash{2}.Bytes(),
+ CumulativeGasUsed: 3,
+ Logs: []*Log{
+ {Address: common.BytesToAddress([]byte{0x22})},
+ {Address: common.BytesToAddress([]byte{0x02, 0x22})},
+ },
+ TxHash: txs[1].Hash(),
+ ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
+ GasUsed: 2,
+ },
+ &Receipt{
+ Type: AccessListTxType,
+ PostState: common.Hash{3}.Bytes(),
+ CumulativeGasUsed: 6,
+ Logs: []*Log{
+ {Address: common.BytesToAddress([]byte{0x33})},
+ {Address: common.BytesToAddress([]byte{0x03, 0x33})},
+ },
+ TxHash: txs[2].Hash(),
+ ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}),
+ GasUsed: 3,
+ },
+ }
+ // Clear all the computed fields and re-derive them
+ number := big.NewInt(1)
+ hash := common.BytesToHash([]byte{0x03, 0x14})
+
+ clearComputedFieldsOnReceipts(t, receipts)
+ if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil {
+ t.Fatalf("DeriveFields(...) = %v, want ", err)
+ }
+ // Iterate over all the computed fields and check that they're correct
+ signer := MakeSigner(params.TestChainConfig, number)
+
+ logIndex := uint(0)
+ for i := range receipts {
+ if receipts[i].Type != txs[i].Type() {
+ t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type())
+ }
+ if receipts[i].TxHash != txs[i].Hash() {
+ t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String())
+ }
+ if receipts[i].BlockHash != hash {
+ t.Errorf("receipts[%d].BlockHash = %s, want %s", i, receipts[i].BlockHash.String(), hash.String())
+ }
+ if receipts[i].BlockNumber.Cmp(number) != 0 {
+ t.Errorf("receipts[%c].BlockNumber = %s, want %s", i, receipts[i].BlockNumber.String(), number.String())
+ }
+ if receipts[i].TransactionIndex != uint(i) {
+ t.Errorf("receipts[%d].TransactionIndex = %d, want %d", i, receipts[i].TransactionIndex, i)
+ }
+ if receipts[i].GasUsed != txs[i].Gas() {
+ t.Errorf("receipts[%d].GasUsed = %d, want %d", i, receipts[i].GasUsed, txs[i].Gas())
+ }
+ if txs[i].To() != nil && receipts[i].ContractAddress != (common.Address{}) {
+ t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), (common.Address{}).String())
+ }
+ from, _ := Sender(signer, txs[i])
+ contractAddress := crypto.CreateAddress(from, txs[i].Nonce())
+ if txs[i].To() == nil && receipts[i].ContractAddress != contractAddress {
+ t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), contractAddress.String())
+ }
+ for j := range receipts[i].Logs {
+ if receipts[i].Logs[j].BlockNumber != number.Uint64() {
+ t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64())
+ }
+ if receipts[i].Logs[j].BlockHash != hash {
+ t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
+ }
+ if receipts[i].Logs[j].TxHash != txs[i].Hash() {
+ t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
+ }
+ if receipts[i].Logs[j].TxIndex != uint(i) {
+ t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
+ }
+ if receipts[i].Logs[j].Index != logIndex {
+ t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex)
+ }
+ logIndex++
+ }
+ }
+}
+
+func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) {
+ t.Helper()
+
+ for _, receipt := range receipts {
+ clearComputedFieldsOnReceipt(t, receipt)
+ }
+}
+
+func clearComputedFieldsOnReceipt(t *testing.T, receipt *Receipt) {
+ t.Helper()
+
+ receipt.TxHash = common.Hash{}
+ receipt.BlockHash = common.Hash{}
+ receipt.BlockNumber = big.NewInt(math.MaxUint32)
+ receipt.TransactionIndex = math.MaxUint32
+ receipt.ContractAddress = common.Address{}
+ receipt.GasUsed = 0
+
+ clearComputedFieldsOnLogs(t, receipt.Logs)
+}
+
+func clearComputedFieldsOnLogs(t *testing.T, logs []*Log) {
+ t.Helper()
+
+ for _, log := range logs {
+ clearComputedFieldsOnLog(t, log)
+ }
+}
+
+func clearComputedFieldsOnLog(t *testing.T, log *Log) {
+ t.Helper()
+
+ log.BlockNumber = math.MaxUint32
+ log.BlockHash = common.Hash{}
+ log.TxHash = common.Hash{}
+ log.TxIndex = math.MaxUint32
+ log.Index = math.MaxUint32
+}
+
// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt
// rlp decoder, which failed due to a shadowing error.
func TestTypedReceiptEncodingDecoding(t *testing.T) {
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 062b9f3db7f9..b37b5e6346a5 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -40,6 +40,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/contracts"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/bloombits"
+ "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
stateDatabase "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
@@ -230,13 +231,14 @@ func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash)
}
func (b *EthApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
- receipts := core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
- if receipts == nil {
- return nil, nil
- }
- logs := make([][]*types.Log, len(receipts))
- for i, receipt := range receipts {
- logs[i] = receipt.Logs
+ db := b.eth.ChainDb()
+ number := rawdb.ReadHeaderNumber(db, blockHash)
+ if number == nil {
+ return nil, errors.New("failed to get block number from hash")
+ }
+ logs := rawdb.ReadLogs(db, blockHash, *number)
+ if logs == nil {
+ return nil, errors.New("failed to get logs for block")
}
return logs, nil
}