Merge pull request #170 from vulcanize/v1.10.13-statediff-0.0.29

log processing bug fix
This commit is contained in:
Ian Norden 2021-12-17 17:52:54 -06:00 committed by GitHub
commit 9d0ec30de1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 436 additions and 46 deletions

View File

@ -299,7 +299,7 @@ type processArgs struct {
rctTrieNodes []*ipld.EthRctTrie rctTrieNodes []*ipld.EthRctTrie
txNodes []*ipld.EthTx txNodes []*ipld.EthTx
txTrieNodes []*ipld.EthTxTrie txTrieNodes []*ipld.EthTxTrie
logTrieNodes [][]*ipld.EthLogTrie logTrieNodes [][]node.Node
logLeafNodeCIDs [][]cid.Cid logLeafNodeCIDs [][]cid.Cid
rctLeafNodeCIDs []cid.Cid rctLeafNodeCIDs []cid.Cid
} }

View File

@ -49,6 +49,7 @@ var (
mockBlock *types.Block mockBlock *types.Block
headerCID, trx1CID, trx2CID, trx3CID, trx4CID, trx5CID cid.Cid headerCID, trx1CID, trx2CID, trx3CID, trx4CID, trx5CID cid.Cid
rct1CID, rct2CID, rct3CID, rct4CID, rct5CID cid.Cid rct1CID, rct2CID, rct3CID, rct4CID, rct5CID cid.Cid
rctLeaf1, rctLeaf2, rctLeaf3, rctLeaf4, rctLeaf5 []byte
state1CID, state2CID, storageCID cid.Cid state1CID, state2CID, storageCID cid.Cid
) )
@ -124,14 +125,49 @@ func init() {
trx3CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx3, multihash.KECCAK_256) trx3CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx3, multihash.KECCAK_256)
trx4CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx4, multihash.KECCAK_256) trx4CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx4, multihash.KECCAK_256)
trx5CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx5, multihash.KECCAK_256) trx5CID, _ = ipld.RawdataToCid(ipld.MEthTx, tx5, multihash.KECCAK_256)
rct1CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct1, multihash.KECCAK_256) /*
rct2CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct2, multihash.KECCAK_256) rct1Node, _ := ipld.NewReceipt(rcts[0])
rct3CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct3, multihash.KECCAK_256) rct2Node, _ := ipld.NewReceipt(rcts[1])
rct4CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct4, multihash.KECCAK_256) rct3Node, _ := ipld.NewReceipt(rcts[2])
rct5CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, rct5, multihash.KECCAK_256) rct4Node, _ := ipld.NewReceipt(rcts[3])
rct5Node, _ := ipld.NewReceipt(rcts[4])
*/
state1CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.ContractLeafNode, multihash.KECCAK_256) state1CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.ContractLeafNode, multihash.KECCAK_256)
state2CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.AccountLeafNode, multihash.KECCAK_256) state2CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, mocks.AccountLeafNode, multihash.KECCAK_256)
storageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, mocks.StorageLeafNode, multihash.KECCAK_256) storageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, mocks.StorageLeafNode, multihash.KECCAK_256)
receiptTrie := ipld.NewRctTrie()
receiptTrie.Add(0, rct1)
receiptTrie.Add(1, rct2)
receiptTrie.Add(2, rct3)
receiptTrie.Add(3, rct4)
receiptTrie.Add(4, rct5)
rctLeafNodes, keys, _ := receiptTrie.GetLeafNodes()
rctleafNodeCids := make([]cid.Cid, len(rctLeafNodes))
orderedRctLeafNodes := make([][]byte, len(rctLeafNodes))
for i, rln := range rctLeafNodes {
var idx uint
r := bytes.NewReader(keys[i].TrieKey)
rlp.Decode(r, &idx)
rctleafNodeCids[idx] = rln.Cid()
orderedRctLeafNodes[idx] = rln.RawData()
}
rct1CID = rctleafNodeCids[0]
rct2CID = rctleafNodeCids[1]
rct3CID = rctleafNodeCids[2]
rct4CID = rctleafNodeCids[3]
rct5CID = rctleafNodeCids[4]
rctLeaf1 = orderedRctLeafNodes[0]
rctLeaf2 = orderedRctLeafNodes[1]
rctLeaf3 = orderedRctLeafNodes[2]
rctLeaf4 = orderedRctLeafNodes[3]
rctLeaf5 = orderedRctLeafNodes[4]
} }
func setup(t *testing.T) { func setup(t *testing.T) {
@ -416,7 +452,7 @@ func TestPublishAndIndexer(t *testing.T) {
switch c { switch c {
case rct1CID.String(): case rct1CID.String():
shared.ExpectEqual(t, data, rct1) shared.ExpectEqual(t, data, rctLeaf1)
var postStatus uint64 var postStatus uint64
pgStr = `SELECT post_status FROM eth.receipt_cids WHERE leaf_cid = $1` pgStr = `SELECT post_status FROM eth.receipt_cids WHERE leaf_cid = $1`
err = db.Get(&postStatus, pgStr, c) err = db.Get(&postStatus, pgStr, c)
@ -425,7 +461,7 @@ func TestPublishAndIndexer(t *testing.T) {
} }
shared.ExpectEqual(t, postStatus, mocks.ExpectedPostStatus) shared.ExpectEqual(t, postStatus, mocks.ExpectedPostStatus)
case rct2CID.String(): case rct2CID.String():
shared.ExpectEqual(t, data, rct2) shared.ExpectEqual(t, data, rctLeaf2)
var postState string var postState string
pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1` pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1`
err = db.Get(&postState, pgStr, c) err = db.Get(&postState, pgStr, c)
@ -434,7 +470,7 @@ func TestPublishAndIndexer(t *testing.T) {
} }
shared.ExpectEqual(t, postState, mocks.ExpectedPostState1) shared.ExpectEqual(t, postState, mocks.ExpectedPostState1)
case rct3CID.String(): case rct3CID.String():
shared.ExpectEqual(t, data, rct3) shared.ExpectEqual(t, data, rctLeaf3)
var postState string var postState string
pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1` pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1`
err = db.Get(&postState, pgStr, c) err = db.Get(&postState, pgStr, c)
@ -443,7 +479,7 @@ func TestPublishAndIndexer(t *testing.T) {
} }
shared.ExpectEqual(t, postState, mocks.ExpectedPostState2) shared.ExpectEqual(t, postState, mocks.ExpectedPostState2)
case rct4CID.String(): case rct4CID.String():
shared.ExpectEqual(t, data, rct4) shared.ExpectEqual(t, data, rctLeaf4)
var postState string var postState string
pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1` pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1`
err = db.Get(&postState, pgStr, c) err = db.Get(&postState, pgStr, c)
@ -452,7 +488,7 @@ func TestPublishAndIndexer(t *testing.T) {
} }
shared.ExpectEqual(t, postState, mocks.ExpectedPostState3) shared.ExpectEqual(t, postState, mocks.ExpectedPostState3)
case rct5CID.String(): case rct5CID.String():
shared.ExpectEqual(t, data, rct5) shared.ExpectEqual(t, data, rctLeaf5)
var postState string var postState string
pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1` pgStr = `SELECT post_state FROM eth.receipt_cids WHERE leaf_cid = $1`
err = db.Get(&postState, pgStr, c) err = db.Get(&postState, pgStr, c)

View File

@ -3,6 +3,8 @@ package ipld
import ( import (
"fmt" "fmt"
node "github.com/ipfs/go-ipld-format"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
@ -91,13 +93,13 @@ func newLogTrie() *logTrie {
// getNodes invokes the localTrie, which computes the root hash of the // getNodes invokes the localTrie, which computes the root hash of the
// log trie and returns its database keys, to return a slice // log trie and returns its database keys, to return a slice
// of EthLogTrie nodes. // of EthLogTrie nodes.
func (rt *logTrie) getNodes() ([]*EthLogTrie, error) { func (rt *logTrie) getNodes() ([]node.Node, error) {
keys, err := rt.getKeys() keys, err := rt.getKeys()
if err != nil { if err != nil {
return nil, err return nil, err
} }
out := make([]*EthLogTrie, 0, len(keys)) out := make([]node.Node, 0, len(keys))
for _, k := range keys { for _, k := range keys {
n, err := rt.getNodeFromDB(k) n, err := rt.getNodeFromDB(k)
if err != nil { if err != nil {
@ -114,14 +116,8 @@ func (rt *logTrie) getNodeFromDB(key []byte) (*EthLogTrie, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c, err := RawdataToCid(MEthLogTrie, rawdata, multihash.KECCAK_256)
if err != nil {
return nil, err
}
tn := &TrieNode{ tn := &TrieNode{
cid: c, cid: keccak256ToCid(MEthLogTrie, key),
rawdata: rawdata, rawdata: rawdata,
} }
return &EthLogTrie{TrieNode: tn}, nil return &EthLogTrie{TrieNode: tn}, nil

View File

@ -23,6 +23,8 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
node "github.com/ipfs/go-ipld-format"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -123,7 +125,7 @@ func FromBlockJSON(r io.Reader) (*EthHeader, []*EthTx, []*EthTxTrie, error) {
// FromBlockAndReceipts takes a block and processes it // FromBlockAndReceipts takes a block and processes it
// to return it a set of IPLD nodes for further processing. // to return it a set of IPLD nodes for further processing.
func FromBlockAndReceipts(block *types.Block, receipts []*types.Receipt) (*EthHeader, []*EthHeader, []*EthTx, []*EthTxTrie, []*EthReceipt, []*EthRctTrie, [][]*EthLogTrie, [][]cid.Cid, []cid.Cid, error) { func FromBlockAndReceipts(block *types.Block, receipts []*types.Receipt) (*EthHeader, []*EthHeader, []*EthTx, []*EthTxTrie, []*EthReceipt, []*EthRctTrie, [][]node.Node, [][]cid.Cid, []cid.Cid, error) {
// Process the header // Process the header
headerNode, err := NewEthHeader(block.Header()) headerNode, err := NewEthHeader(block.Header())
if err != nil { if err != nil {
@ -148,10 +150,10 @@ func FromBlockAndReceipts(block *types.Block, receipts []*types.Receipt) (*EthHe
} }
// Process the receipts and logs // Process the receipts and logs
rctNodes, tctTrieNodes, logTrieNodes, logLeafNodeCIDs, rctLeafNodeCIDs, err := processReceiptsAndLogs(receipts, rctNodes, tctTrieNodes, logTrieAndLogNodes, logLeafNodeCIDs, rctLeafNodeCIDs, err := processReceiptsAndLogs(receipts,
block.Header().ReceiptHash[:]) block.Header().ReceiptHash[:])
return headerNode, uncleNodes, txNodes, txTrieNodes, rctNodes, tctTrieNodes, logTrieNodes, logLeafNodeCIDs, rctLeafNodeCIDs, err return headerNode, uncleNodes, txNodes, txTrieNodes, rctNodes, tctTrieNodes, logTrieAndLogNodes, logLeafNodeCIDs, rctLeafNodeCIDs, err
} }
// processTransactions will take the found transactions in a parsed block body // processTransactions will take the found transactions in a parsed block body
@ -180,11 +182,11 @@ func processTransactions(txs []*types.Transaction, expectedTxRoot []byte) ([]*Et
// processReceiptsAndLogs will take in receipts // processReceiptsAndLogs will take in receipts
// to return IPLD node slices for eth-rct, eth-rct-trie, eth-log, eth-log-trie, eth-log-trie-CID, eth-rct-trie-CID // to return IPLD node slices for eth-rct, eth-rct-trie, eth-log, eth-log-trie, eth-log-trie-CID, eth-rct-trie-CID
func processReceiptsAndLogs(rcts []*types.Receipt, expectedRctRoot []byte) ([]*EthReceipt, []*EthRctTrie, [][]*EthLogTrie, [][]cid.Cid, []cid.Cid, error) { func processReceiptsAndLogs(rcts []*types.Receipt, expectedRctRoot []byte) ([]*EthReceipt, []*EthRctTrie, [][]node.Node, [][]cid.Cid, []cid.Cid, error) {
// Pre allocating memory. // Pre allocating memory.
ethRctNodes := make([]*EthReceipt, 0, len(rcts)) ethRctNodes := make([]*EthReceipt, 0, len(rcts))
ethLogleafNodeCids := make([][]cid.Cid, 0, len(rcts)) ethLogleafNodeCids := make([][]cid.Cid, 0, len(rcts))
ethLogTrieNodes := make([][]*EthLogTrie, 0, len(rcts)) ethLogTrieAndLogNodes := make([][]node.Node, 0, len(rcts))
receiptTrie := NewRctTrie() receiptTrie := NewRctTrie()
@ -195,7 +197,7 @@ func processReceiptsAndLogs(rcts []*types.Receipt, expectedRctRoot []byte) ([]*E
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err
} }
rct.LogRoot = logTrieHash rct.LogRoot = logTrieHash
ethLogTrieNodes = append(ethLogTrieNodes, logTrieNodes) ethLogTrieAndLogNodes = append(ethLogTrieAndLogNodes, logTrieNodes)
ethLogleafNodeCids = append(ethLogleafNodeCids, leafNodeCids) ethLogleafNodeCids = append(ethLogleafNodeCids, leafNodeCids)
ethRct, err := NewReceipt(rct) ethRct, err := NewReceipt(rct)
@ -235,22 +237,38 @@ func processReceiptsAndLogs(rcts []*types.Receipt, expectedRctRoot []byte) ([]*E
ethRctleafNodeCids[idx] = rln.Cid() ethRctleafNodeCids[idx] = rln.Cid()
} }
return ethRctNodes, rctTrieNodes, ethLogTrieNodes, ethLogleafNodeCids, ethRctleafNodeCids, err return ethRctNodes, rctTrieNodes, ethLogTrieAndLogNodes, ethLogleafNodeCids, ethRctleafNodeCids, err
} }
func processLogs(logs []*types.Log) ([]*EthLogTrie, []cid.Cid, common.Hash, error) { const keccak256Length = 32
func processLogs(logs []*types.Log) ([]node.Node, []cid.Cid, common.Hash, error) {
logTr := newLogTrie() logTr := newLogTrie()
shortLog := make(map[uint64]*EthLog, len(logs))
for idx, log := range logs { for idx, log := range logs {
ethLog, err := NewLog(log) logRaw, err := rlp.EncodeToBytes(log)
if err != nil { if err != nil {
return nil, nil, common.Hash{}, err return nil, nil, common.Hash{}, err
} }
if err = logTr.Add(idx, ethLog.RawData()); err != nil { // if len(logRaw) <= keccak256Length it is possible this value's "leaf node"
// will be stored in its parent branch but only if len(partialPathOfTheNode) + len(logRaw) <= keccak256Length
// But we can't tell what the partial path will be until the trie is Commit()-ed
// So wait until we collect all the leaf nodes, and if we are missing any at the indexes we note in shortLogCIDs
// we know that these "leaf nodes" were internalized into their parent branch node and we move forward with
// using the cid.Cid we cached in shortLogCIDs
if len(logRaw) <= keccak256Length {
logNode, err := NewLog(log)
if err != nil {
return nil, nil, common.Hash{}, err
}
shortLog[uint64(idx)] = logNode
}
if err = logTr.Add(idx, logRaw); err != nil {
return nil, nil, common.Hash{}, err return nil, nil, common.Hash{}, err
} }
} }
logTrieNodes, err := logTr.getNodes() logTrieAndLogNodes, err := logTr.getNodes()
if err != nil { if err != nil {
return nil, nil, common.Hash{}, err return nil, nil, common.Hash{}, err
} }
@ -259,8 +277,7 @@ func processLogs(logs []*types.Log) ([]*EthLogTrie, []cid.Cid, common.Hash, erro
if err != nil { if err != nil {
return nil, nil, common.Hash{}, err return nil, nil, common.Hash{}, err
} }
leafNodeCids := make([]cid.Cid, len(logs))
leafNodeCids := make([]cid.Cid, len(leafNodes))
for i, ln := range leafNodes { for i, ln := range leafNodes {
var idx uint var idx uint
@ -271,6 +288,15 @@ func processLogs(logs []*types.Log) ([]*EthLogTrie, []cid.Cid, common.Hash, erro
} }
leafNodeCids[idx] = ln.Cid() leafNodeCids[idx] = ln.Cid()
} }
// this is where we check which logs <= keccak256Length were actually internalized into parent branch node
// and replace those that were with the cid.Cid for the raw log IPLD
for i, l := range shortLog {
if !leafNodeCids[i].Defined() {
leafNodeCids[i] = l.Cid()
// if the leaf node was internalized, we append an IPLD for log itself to the list of IPLDs we need to publish
logTrieAndLogNodes = append(logTrieAndLogNodes, l)
}
}
return logTrieNodes, leafNodeCids, common.BytesToHash(logTr.rootHash()), err return logTrieAndLogNodes, leafNodeCids, common.BytesToHash(logTr.rootHash()), err
} }

View File

@ -166,14 +166,8 @@ func (rt *rctTrie) getNodeFromDB(key []byte) (*EthRctTrie, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
cid, err := RawdataToCid(MEthTxReceiptTrie, rawdata, multihash.KECCAK_256)
if err != nil {
return nil, err
}
tn := &TrieNode{ tn := &TrieNode{
cid: cid, cid: keccak256ToCid(MEthTxReceiptTrie, key),
rawdata: rawdata, rawdata: rawdata,
} }

View File

@ -135,12 +135,8 @@ func (tt *txTrie) getNodes() ([]*EthTxTrie, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c, err := RawdataToCid(MEthTxTrie, rawdata, multihash.KECCAK_256)
if err != nil {
return nil, err
}
tn := &TrieNode{ tn := &TrieNode{
cid: c, cid: keccak256ToCid(MEthTxTrie, k),
rawdata: rawdata, rawdata: rawdata,
} }
out = append(out, &EthTxTrie{TrieNode: tn}) out = append(out, &EthTxTrie{TrieNode: tn})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,107 @@
// VulcanizeDB
// Copyright © 2021 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program 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 Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package mainnet_tests
import (
"fmt"
"math/big"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/statediff/indexer"
"github.com/ethereum/go-ethereum/statediff/indexer/mocks"
"github.com/ethereum/go-ethereum/statediff/indexer/postgres"
"github.com/ethereum/go-ethereum/statediff/indexer/shared"
)
var (
err error
db *postgres.DB
chainConf = params.MainnetChainConfig
)
func init() {
if os.Getenv("MODE") != "statediff" {
fmt.Println("Skipping statediff test")
os.Exit(0)
}
}
func TestPushBlockAndState(t *testing.T) {
conf := DefaultTestConfig
rawURL := os.Getenv(TEST_RAW_URL)
if rawURL == "" {
fmt.Printf("Warning: no raw url configured for statediffing mainnet tests, will look for local file and"+
"then try default endpoint (%s)\r\n", DefaultTestConfig.RawURL)
} else {
conf.RawURL = rawURL
}
for _, blockNumber := range problemBlocks {
conf.BlockNumber = big.NewInt(blockNumber)
tb, trs, err := TestBlockAndReceipts(conf)
require.NoError(t, err)
testPushBlockAndState(t, tb, trs)
}
testBlock, testReceipts, err := TestBlockAndReceiptsFromEnv(conf)
require.NoError(t, err)
testPushBlockAndState(t, testBlock, testReceipts)
}
func testPushBlockAndState(t *testing.T, block *types.Block, receipts types.Receipts) {
t.Run("Test PushBlock and PushStateNode", func(t *testing.T) {
setup(t, block, receipts)
tearDown(t)
})
}
func setup(t *testing.T, testBlock *types.Block, testReceipts types.Receipts) {
db, err = shared.SetupDB()
if err != nil {
t.Fatal(err)
}
ind, err := indexer.NewStateDiffIndexer(chainConf, db)
require.NoError(t, err)
var tx *indexer.BlockTx
tx, err = ind.PushBlock(
testBlock,
testReceipts,
testBlock.Difficulty())
require.NoError(t, err)
defer func() {
if err := tx.Close(err); err != nil {
t.Fatal(err)
}
}()
for _, node := range mocks.StateDiffs {
err = ind.PushStateNode(tx, node)
require.NoError(t, err)
}
shared.ExpectEqual(t, tx.BlockNumber, testBlock.Number().Uint64())
}
func tearDown(t *testing.T) {
indexer.TearDownDB(t, db)
err = db.Close()
require.NoError(t, err)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,235 @@
// VulcanizeDB
// Copyright © 2021 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program 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 Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package mainnet_tests
import (
"context"
"errors"
"fmt"
"math/big"
"os"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
)
const (
defaultBlockFilePath = "./block"
defaultReceiptsFilePath = "./receipts"
)
const (
TEST_RAW_URL = "TEST_RAW_URL"
TEST_BLOCK_NUMBER = "TEST_BLOCK_NUMBER"
)
var problemBlocks = []int64{
12600011,
12619985,
12625121,
12655432,
12579670,
12914664,
}
// TestConfig holds configuration params for mainnet tests
type TestConfig struct {
RawURL string
BlockNumber *big.Int
LocalCache bool
}
// DefaultTestConfig is the default TestConfig
var DefaultTestConfig = TestConfig{
RawURL: "http://127.0.0.1:8545",
BlockNumber: big.NewInt(12914664),
LocalCache: true,
}
// TestBlockAndReceiptsFromEnv retrieves the block and receipts using env variables to override default config block number
func TestBlockAndReceiptsFromEnv(conf TestConfig) (*types.Block, types.Receipts, error) {
blockNumberStr := os.Getenv(TEST_BLOCK_NUMBER)
blockNumber, ok := new(big.Int).SetString(blockNumberStr, 10)
if !ok {
fmt.Printf("Warning: no blockNumber configured for statediffing mainnet tests, using default (%d)\r\n",
DefaultTestConfig.BlockNumber)
} else {
conf.BlockNumber = blockNumber
}
return TestBlockAndReceipts(conf)
}
// TestBlockAndReceipts retrieves the block and receipts for the provided test config
// It first tries to load files from the local system before setting up and using an ethclient.Client to pull the data
func TestBlockAndReceipts(conf TestConfig) (*types.Block, types.Receipts, error) {
var cli *ethclient.Client
var err error
var block *types.Block
var receipts types.Receipts
blockFilePath := fmt.Sprintf("%s_%s.rlp", defaultBlockFilePath, conf.BlockNumber.String())
if _, err = os.Stat(blockFilePath); !errors.Is(err, os.ErrNotExist) {
fmt.Printf("local file (%s) found for block %s\n", blockFilePath, conf.BlockNumber.String())
block, err = LoadBlockRLP(blockFilePath)
if err != nil {
fmt.Printf("loading local file (%s) failed (%s), dialing remote client at %s\n", blockFilePath, err.Error(), conf.RawURL)
cli, err = ethclient.Dial(conf.RawURL)
if err != nil {
return nil, nil, err
}
block, err = FetchBlock(cli, conf.BlockNumber)
if err != nil {
return nil, nil, err
}
if conf.LocalCache {
if err := WriteBlockRLP(blockFilePath, block); err != nil {
return nil, nil, err
}
}
}
} else {
fmt.Printf("no local file found for block %s, dialing remote client at %s\n", conf.BlockNumber.String(), conf.RawURL)
cli, err = ethclient.Dial(conf.RawURL)
if err != nil {
return nil, nil, err
}
block, err = FetchBlock(cli, conf.BlockNumber)
if err != nil {
return nil, nil, err
}
if conf.LocalCache {
if err := WriteBlockRLP(blockFilePath, block); err != nil {
return nil, nil, err
}
}
}
receiptsFilePath := fmt.Sprintf("%s_%s.rlp", defaultReceiptsFilePath, conf.BlockNumber.String())
if _, err = os.Stat(receiptsFilePath); !errors.Is(err, os.ErrNotExist) {
fmt.Printf("local file (%s) found for block %s receipts\n", receiptsFilePath, conf.BlockNumber.String())
receipts, err = LoadReceiptsEncoding(receiptsFilePath, len(block.Transactions()))
if err != nil {
fmt.Printf("loading local file (%s) failed (%s), dialing remote client at %s\n", receiptsFilePath, err.Error(), conf.RawURL)
if cli == nil {
cli, err = ethclient.Dial(conf.RawURL)
if err != nil {
return nil, nil, err
}
}
receipts, err = FetchReceipts(cli, block)
if err != nil {
return nil, nil, err
}
if conf.LocalCache {
if err := WriteReceiptsEncoding(receiptsFilePath, block.Number(), receipts); err != nil {
return nil, nil, err
}
}
}
} else {
fmt.Printf("no local file found for block %s receipts, dialing remote client at %s\n", conf.BlockNumber.String(), conf.RawURL)
if cli == nil {
cli, err = ethclient.Dial(conf.RawURL)
if err != nil {
return nil, nil, err
}
}
receipts, err = FetchReceipts(cli, block)
if err != nil {
return nil, nil, err
}
if conf.LocalCache {
if err := WriteReceiptsEncoding(receiptsFilePath, block.Number(), receipts); err != nil {
return nil, nil, err
}
}
}
return block, receipts, nil
}
// FetchBlock fetches the block at the provided height using the ethclient.Client
func FetchBlock(cli *ethclient.Client, blockNumber *big.Int) (*types.Block, error) {
return cli.BlockByNumber(context.Background(), blockNumber)
}
// FetchReceipts fetches the receipts for the provided block using the ethclient.Client
func FetchReceipts(cli *ethclient.Client, block *types.Block) (types.Receipts, error) {
receipts := make(types.Receipts, len(block.Transactions()))
for i, tx := range block.Transactions() {
rct, err := cli.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
return nil, err
}
receipts[i] = rct
}
return receipts, nil
}
// WriteBlockRLP writes out the RLP encoding of the block to the provided filePath
func WriteBlockRLP(filePath string, block *types.Block) error {
if filePath == "" {
filePath = fmt.Sprintf("%s_%s.rlp", defaultBlockFilePath, block.Number().String())
}
if _, err := os.Stat(filePath); !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot create file, file (%s) already exists", filePath)
}
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("unable to create file (%s), err: %v", filePath, err)
}
fmt.Printf("writing block rlp to file at %s\r\n", filePath)
if err := block.EncodeRLP(file); err != nil {
return err
}
return file.Close()
}
// LoadBlockRLP loads block from the rlp at filePath
func LoadBlockRLP(filePath string) (*types.Block, error) {
blockBytes, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
block := new(types.Block)
return block, rlp.DecodeBytes(blockBytes, block)
}
// LoadReceiptsEncoding loads receipts from the encoding at filePath
func LoadReceiptsEncoding(filePath string, cap int) (types.Receipts, error) {
rctsBytes, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
receipts := new(types.Receipts)
return *receipts, rlp.DecodeBytes(rctsBytes, receipts)
}
// WriteReceiptsEncoding writes out the consensus encoding of the receipts to the provided io.WriteCloser
func WriteReceiptsEncoding(filePath string, blockNumber *big.Int, receipts types.Receipts) error {
if filePath == "" {
filePath = fmt.Sprintf("%s_%s.rlp", defaultReceiptsFilePath, blockNumber.String())
}
if _, err := os.Stat(filePath); !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot create file, file (%s) already exists", filePath)
}
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("unable to create file (%s), err: %v", filePath, err)
}
defer file.Close()
fmt.Printf("writing receipts rlp to file at %s\r\n", filePath)
return rlp.Encode(file, receipts)
}