Skip to content

Commit 9f0ef4a

Browse files
authored
Merge pull request #160 from vulcanize/mainnet_tests
test harnest for arbitrary mainnet blocks and receipts
2 parents 1dd37be + 904163d commit 9f0ef4a

File tree

8 files changed

+470
-181
lines changed

8 files changed

+470
-181
lines changed
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// VulcanizeDB
2+
// Copyright © 2021 Vulcanize
3+
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package mainnet_tests
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"os"
24+
"testing"
25+
26+
"github.com/ipfs/go-cid"
27+
"github.com/jmoiron/sqlx"
28+
"github.com/multiformats/go-multihash"
29+
"github.com/stretchr/testify/require"
30+
31+
"github.com/ethereum/go-ethereum/core/types"
32+
"github.com/ethereum/go-ethereum/params"
33+
"github.com/ethereum/go-ethereum/rlp"
34+
"github.com/ethereum/go-ethereum/statediff/indexer/database/file"
35+
"github.com/ethereum/go-ethereum/statediff/indexer/database/sql/postgres"
36+
"github.com/ethereum/go-ethereum/statediff/indexer/interfaces"
37+
"github.com/ethereum/go-ethereum/statediff/indexer/ipld"
38+
"github.com/ethereum/go-ethereum/statediff/indexer/mocks"
39+
"github.com/ethereum/go-ethereum/statediff/indexer/test_helpers"
40+
)
41+
42+
var (
43+
testBlock *types.Block
44+
testReceipts types.Receipts
45+
testHeaderCID cid.Cid
46+
sqlxdb *sqlx.DB
47+
err error
48+
chainConf = params.MainnetChainConfig
49+
)
50+
51+
func init() {
52+
if os.Getenv("MODE") != "statediff" {
53+
fmt.Println("Skipping statediff test")
54+
os.Exit(0)
55+
}
56+
}
57+
58+
func setup(t *testing.T) {
59+
testBlock, testReceipts, err = TestBlocksAndReceiptsFromEnv()
60+
require.NoError(t, err)
61+
headerRLP, err := rlp.EncodeToBytes(testBlock.Header())
62+
require.NoError(t, err)
63+
64+
testHeaderCID, _ = ipld.RawdataToCid(ipld.MEthHeader, headerRLP, multihash.KECCAK_256)
65+
if _, err := os.Stat(file.TestConfig.FilePath); !errors.Is(err, os.ErrNotExist) {
66+
err := os.Remove(file.TestConfig.FilePath)
67+
require.NoError(t, err)
68+
}
69+
ind, err := file.NewStateDiffIndexer(context.Background(), chainConf, file.TestConfig)
70+
require.NoError(t, err)
71+
var tx interfaces.Batch
72+
tx, err = ind.PushBlock(
73+
testBlock,
74+
testReceipts,
75+
testBlock.Difficulty())
76+
require.NoError(t, err)
77+
78+
defer func() {
79+
if err := tx.Submit(err); err != nil {
80+
t.Fatal(err)
81+
}
82+
if err := ind.Close(); err != nil {
83+
t.Fatal(err)
84+
}
85+
}()
86+
for _, node := range mocks.StateDiffs {
87+
err = ind.PushStateNode(tx, node, testBlock.Hash().String())
88+
require.NoError(t, err)
89+
}
90+
91+
test_helpers.ExpectEqual(t, tx.(*file.BatchTx).BlockNumber, testBlock.Number().Uint64())
92+
93+
connStr := postgres.DefaultConfig.DbConnectionString()
94+
95+
sqlxdb, err = sqlx.Connect("postgres", connStr)
96+
if err != nil {
97+
t.Fatalf("failed to connect to db with connection string: %s err: %v", connStr, err)
98+
}
99+
}
100+
101+
func dumpData(t *testing.T) {
102+
sqlFileBytes, err := os.ReadFile(file.TestConfig.FilePath)
103+
require.NoError(t, err)
104+
105+
_, err = sqlxdb.Exec(string(sqlFileBytes))
106+
require.NoError(t, err)
107+
}
108+
109+
func tearDown(t *testing.T) {
110+
file.TearDownDB(t, sqlxdb)
111+
err := os.Remove(file.TestConfig.FilePath)
112+
require.NoError(t, err)
113+
err = sqlxdb.Close()
114+
require.NoError(t, err)
115+
}
116+
117+
func TestPushBlockAndState(t *testing.T) {
118+
t.Run("Test PushBlock and PushStateNode", func(t *testing.T) {
119+
setup(t)
120+
dumpData(t)
121+
tearDown(t)
122+
})
123+
}
Binary file not shown.

Diff for: statediff/indexer/database/file/mainnet_tests/statediffing_test_file.sql

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// VulcanizeDB
2+
// Copyright © 2021 Vulcanize
3+
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package mainnet_tests
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"math/big"
24+
"os"
25+
26+
"github.com/ethereum/go-ethereum/core/types"
27+
"github.com/ethereum/go-ethereum/ethclient"
28+
"github.com/ethereum/go-ethereum/rlp"
29+
)
30+
31+
const (
32+
defaultBlockFilePath = "./block"
33+
defaultReceiptsFilePath = "./receipts"
34+
)
35+
36+
const (
37+
TEST_RAW_URL = "TEST_RAW_URL"
38+
TEST_BLOCK_NUMBER = "TEST_BLOCK_NUMBER"
39+
TEST_LOCAL_CACHE = "TEST_LOCAL_CACHE"
40+
)
41+
42+
// TestConfig holds configuration params for mainnet tests
43+
type TestConfig struct {
44+
RawURL string
45+
BlockNumber *big.Int
46+
LocalCache bool
47+
}
48+
49+
// DefaultTestConfig is the default TestConfig
50+
var DefaultTestConfig = TestConfig{
51+
RawURL: "http://127.0.0.1:8545",
52+
BlockNumber: big.NewInt(12914664),
53+
LocalCache: true,
54+
}
55+
56+
// TestBlocksAndReceiptsFromEnv retrieves the block and receipts using env variables to override default config
57+
func TestBlocksAndReceiptsFromEnv() (*types.Block, types.Receipts, error) {
58+
conf := DefaultTestConfig
59+
rawURL := os.Getenv(TEST_RAW_URL)
60+
if rawURL == "" {
61+
fmt.Printf("Warning: no raw url configured for statediffing mainnet tests, will look for local file and"+
62+
"then try default endpoint (%s)\r\n", DefaultTestConfig.RawURL)
63+
} else {
64+
conf.RawURL = rawURL
65+
}
66+
blockNumberStr := os.Getenv(TEST_BLOCK_NUMBER)
67+
blockNumber, ok := new(big.Int).SetString(blockNumberStr, 10)
68+
if !ok {
69+
fmt.Printf("Warning: no blockNumber configured for statediffing mainnet tests, using default (%d)\r\n",
70+
DefaultTestConfig.BlockNumber)
71+
} else {
72+
conf.BlockNumber = blockNumber
73+
}
74+
return TestBlocksAndReceipts(conf)
75+
}
76+
77+
// TestBlocksAndReceipts retrieves the block and receipts for the provided test config
78+
// It first tries to load files from the local system before setting up and using an ethclient.Client to pull the data
79+
func TestBlocksAndReceipts(conf TestConfig) (*types.Block, types.Receipts, error) {
80+
var cli *ethclient.Client
81+
var err error
82+
var block *types.Block
83+
var receipts types.Receipts
84+
blockFilePath := fmt.Sprintf("%s_%s.rlp", defaultBlockFilePath, conf.BlockNumber.String())
85+
if _, err = os.Stat(blockFilePath); !errors.Is(err, os.ErrNotExist) {
86+
fmt.Printf("local file (%s) found for block %s\n", blockFilePath, conf.BlockNumber.String())
87+
block, err = LoadBlockRLP(blockFilePath)
88+
if err != nil {
89+
fmt.Printf("loading local file (%s) failed (%s), dialing remote client at %s\n", blockFilePath, err.Error(), conf.RawURL)
90+
cli, err = ethclient.Dial(conf.RawURL)
91+
if err != nil {
92+
return nil, nil, err
93+
}
94+
block, err = FetchBlock(cli, conf.BlockNumber)
95+
if err != nil {
96+
return nil, nil, err
97+
}
98+
if conf.LocalCache {
99+
if err := WriteBlockRLP(blockFilePath, block); err != nil {
100+
return nil, nil, err
101+
}
102+
}
103+
}
104+
} else {
105+
fmt.Printf("no local file found for block %s, dialing remote client at %s\n", conf.BlockNumber.String(), conf.RawURL)
106+
cli, err = ethclient.Dial(conf.RawURL)
107+
if err != nil {
108+
return nil, nil, err
109+
}
110+
block, err = FetchBlock(cli, conf.BlockNumber)
111+
if err != nil {
112+
return nil, nil, err
113+
}
114+
if conf.LocalCache {
115+
if err := WriteBlockRLP(blockFilePath, block); err != nil {
116+
return nil, nil, err
117+
}
118+
}
119+
}
120+
receiptsFilePath := fmt.Sprintf("%s_%s.rlp", defaultReceiptsFilePath, conf.BlockNumber.String())
121+
if _, err = os.Stat(receiptsFilePath); !errors.Is(err, os.ErrNotExist) {
122+
fmt.Printf("local file (%s) found for block %s receipts\n", receiptsFilePath, conf.BlockNumber.String())
123+
receipts, err = LoadReceiptsEncoding(receiptsFilePath, len(block.Transactions()))
124+
if err != nil {
125+
fmt.Printf("loading local file (%s) failed (%s), dialing remote client at %s\n", receiptsFilePath, err.Error(), conf.RawURL)
126+
if cli == nil {
127+
cli, err = ethclient.Dial(conf.RawURL)
128+
if err != nil {
129+
return nil, nil, err
130+
}
131+
}
132+
receipts, err = FetchReceipts(cli, block)
133+
if err != nil {
134+
return nil, nil, err
135+
}
136+
if conf.LocalCache {
137+
if err := WriteReceiptsEncoding(receiptsFilePath, block.Number(), receipts); err != nil {
138+
return nil, nil, err
139+
}
140+
}
141+
}
142+
} else {
143+
fmt.Printf("no local file found for block %s receipts, dialing remote client at %s\n", conf.BlockNumber.String(), conf.RawURL)
144+
if cli == nil {
145+
cli, err = ethclient.Dial(conf.RawURL)
146+
if err != nil {
147+
return nil, nil, err
148+
}
149+
}
150+
receipts, err = FetchReceipts(cli, block)
151+
if err != nil {
152+
return nil, nil, err
153+
}
154+
if conf.LocalCache {
155+
if err := WriteReceiptsEncoding(receiptsFilePath, block.Number(), receipts); err != nil {
156+
return nil, nil, err
157+
}
158+
}
159+
}
160+
return block, receipts, nil
161+
}
162+
163+
// FetchBlock fetches the block at the provided height using the ethclient.Client
164+
func FetchBlock(cli *ethclient.Client, blockNumber *big.Int) (*types.Block, error) {
165+
return cli.BlockByNumber(context.Background(), blockNumber)
166+
}
167+
168+
// FetchReceipts fetches the receipts for the provided block using the ethclient.Client
169+
func FetchReceipts(cli *ethclient.Client, block *types.Block) (types.Receipts, error) {
170+
receipts := make(types.Receipts, len(block.Transactions()))
171+
for i, tx := range block.Transactions() {
172+
rct, err := cli.TransactionReceipt(context.Background(), tx.Hash())
173+
if err != nil {
174+
return nil, err
175+
}
176+
receipts[i] = rct
177+
}
178+
return receipts, nil
179+
}
180+
181+
// WriteBlockRLP writes out the RLP encoding of the block to the provided filePath
182+
func WriteBlockRLP(filePath string, block *types.Block) error {
183+
if filePath == "" {
184+
filePath = fmt.Sprintf("%s_%s.rlp", defaultBlockFilePath, block.Number().String())
185+
}
186+
if _, err := os.Stat(filePath); !errors.Is(err, os.ErrNotExist) {
187+
return fmt.Errorf("cannot create file, file (%s) already exists", filePath)
188+
}
189+
file, err := os.Create(filePath)
190+
if err != nil {
191+
return fmt.Errorf("unable to create file (%s), err: %v", filePath, err)
192+
}
193+
fmt.Printf("writing block rlp to file at %s\r\n", filePath)
194+
if err := block.EncodeRLP(file); err != nil {
195+
return err
196+
}
197+
return file.Close()
198+
}
199+
200+
// LoadBlockRLP loads block from the rlp at filePath
201+
func LoadBlockRLP(filePath string) (*types.Block, error) {
202+
blockBytes, err := os.ReadFile(filePath)
203+
if err != nil {
204+
return nil, err
205+
}
206+
block := new(types.Block)
207+
return block, rlp.DecodeBytes(blockBytes, block)
208+
}
209+
210+
// LoadReceiptsEncoding loads receipts from the encoding at filePath
211+
func LoadReceiptsEncoding(filePath string, cap int) (types.Receipts, error) {
212+
rctsBytes, err := os.ReadFile(filePath)
213+
if err != nil {
214+
return nil, err
215+
}
216+
receipts := new(types.Receipts)
217+
return *receipts, rlp.DecodeBytes(rctsBytes, receipts)
218+
}
219+
220+
// WriteReceiptsEncoding writes out the consensus encoding of the receipts to the provided io.WriteCloser
221+
func WriteReceiptsEncoding(filePath string, blockNumber *big.Int, receipts types.Receipts) error {
222+
if filePath == "" {
223+
filePath = fmt.Sprintf("%s_%s.rlp", defaultReceiptsFilePath, blockNumber.String())
224+
}
225+
if _, err := os.Stat(filePath); !errors.Is(err, os.ErrNotExist) {
226+
return fmt.Errorf("cannot create file, file (%s) already exists", filePath)
227+
}
228+
file, err := os.Create(filePath)
229+
if err != nil {
230+
return fmt.Errorf("unable to create file (%s), err: %v", filePath, err)
231+
}
232+
defer file.Close()
233+
fmt.Printf("writing receipts rlp to file at %s\r\n", filePath)
234+
return rlp.Encode(file, receipts)
235+
}

0 commit comments

Comments
 (0)