diff --git a/backend/cmd/store/main.go b/backend/cmd/store/main.go index 279723a7e..421d6f5ca 100644 --- a/backend/cmd/store/main.go +++ b/backend/cmd/store/main.go @@ -2,8 +2,10 @@ package main import ( "errors" + "fmt" "net/http" "os" + "strconv" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" "github.com/gobitfly/beaconchain/pkg/commons/log" @@ -15,6 +17,10 @@ func main() { project := args[0] instance := args[1] table := args[2] + port, err := strconv.Atoi(args[3]) + if err != nil { + panic(err) + } bt, err := database.NewBigTable(project, instance, nil) if err != nil { @@ -22,8 +28,8 @@ func main() { } remote := database.NewRemote(database.Wrap(bt, table)) go func() { - log.Info("starting remote raw store on port 8087") - if err := http.ListenAndServe("0.0.0.0:8087", remote.Routes()); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Infof("starting remote raw store on port %d", port) + if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), remote.Routes()); err != nil && !errors.Is(err, http.ErrServerClosed) { panic(err) } }() diff --git a/backend/go.mod b/backend/go.mod index bfe37db04..03f1150bf 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -34,6 +34,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.4 github.com/gomodule/redigo v1.9.2 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/csrf v1.7.2 github.com/gorilla/handlers v1.5.2 diff --git a/backend/internal/e2e/data_test.go b/backend/internal/e2e/data_test.go index 8acb6c715..d8eb48e6f 100644 --- a/backend/internal/e2e/data_test.go +++ b/backend/internal/e2e/data_test.go @@ -12,20 +12,15 @@ import ( "github.com/gobitfly/beaconchain/internal/th" "github.com/gobitfly/beaconchain/pkg/commons/db2/data" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/db2test" "github.com/gobitfly/beaconchain/pkg/commons/indexer" "github.com/gobitfly/beaconchain/pkg/commons/rpc" "github.com/gobitfly/beaconchain/pkg/commons/types" ) func TestStoreWithBackend(t *testing.T) { - clientBT, adminBT := databasetest.NewBigTable(t) - bigtable, err := database.NewBigTableWithClient(context.Background(), clientBT, adminBT, data.Schema) - if err != nil { - t.Fatal(err) - } + store := db2test.NewDataStore(t) - store := data.NewStore(database.Wrap(bigtable, data.Table)) backend := th.NewBackend(t) _, usdt := backend.DeployToken(t, "usdt", "usdt", backend.BankAccount.From) @@ -49,6 +44,7 @@ func TestStoreWithBackend(t *testing.T) { t.Fatal(i, j, err) } backend.Commit() + lastBlock, err := backend.Client().BlockNumber(context.Background()) if err != nil { t.Fatal(err) @@ -57,7 +53,7 @@ func TestStoreWithBackend(t *testing.T) { if err != nil { t.Fatal(err) } - if err := indexer.IndexBlocksWithTransformers(fmt.Sprintf("%d", backend.ChainID), []*types.Eth1Block{block}); err != nil { + if err := indexer.IndexBlocks(fmt.Sprintf("%d", backend.ChainID), []*types.Eth1Block{block}); err != nil { t.Fatal(err) } } @@ -65,19 +61,7 @@ func TestStoreWithBackend(t *testing.T) { t.Run("get interactions", func(t *testing.T) { efficiencies := make(map[string]int64) - interactions, _, err := store.Get(addresses, nil, 50, data.WithDatabaseStats(func(msg string, args ...any) { - var efficiency int64 - var rowRange string - for i := 0; i < len(args); i = i + 2 { - if args[i].(string) == database.KeyStatEfficiency { - efficiency = args[i+1].(int64) - } - if args[i].(string) == database.KeyStatRange { - rowRange = args[i+1].(string) - } - } - efficiencies[rowRange] = efficiency - })) + interactions, _, err := store.Get(addresses, nil, 50, data.WithDatabaseStats(getEfficiencies(efficiencies))) if err != nil { t.Fatal(err) } @@ -94,3 +78,19 @@ func TestStoreWithBackend(t *testing.T) { } }) } + +func getEfficiencies(efficiencies map[string]int64) func(msg string, args ...any) { + return func(msg string, args ...any) { + var efficiency int64 + var rowRange string + for i := 0; i < len(args); i = i + 2 { + if args[i].(string) == database.KeyStatEfficiency { + efficiency = args[i+1].(int64) + } + if args[i].(string) == database.KeyStatRange { + rowRange = args[i+1].(string) + } + } + efficiencies[rowRange] = efficiency + } +} diff --git a/backend/pkg/commons/db2/data/data_test.go b/backend/pkg/commons/db2/data/data_test.go index 376c34be1..b9e22442e 100644 --- a/backend/pkg/commons/db2/data/data_test.go +++ b/backend/pkg/commons/db2/data/data_test.go @@ -11,7 +11,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" "github.com/gobitfly/beaconchain/pkg/commons/types" ) diff --git a/backend/pkg/commons/db2/data/keys.go b/backend/pkg/commons/db2/data/keys.go index f86722e77..058bd47ff 100644 --- a/backend/pkg/commons/db2/data/keys.go +++ b/backend/pkg/commons/db2/data/keys.go @@ -117,7 +117,7 @@ func transactionKeys(chainID string, transaction *types.Eth1TransactionIndexed, // key are sorted side (+optional other address), chainID, type, asset func transferKeys(chainID string, transaction *types.Eth1ERC20Indexed, index int, logIndex int) (string, []string) { - main := "ERC20::" + main := "ERC20:::" baseKeys := []string{ "all:
", "all:chainID:
:", diff --git a/backend/pkg/commons/db2/database/bigtable_test.go b/backend/pkg/commons/db2/database/bigtable_test.go index e2b811f56..8e559b1ba 100644 --- a/backend/pkg/commons/db2/database/bigtable_test.go +++ b/backend/pkg/commons/db2/database/bigtable_test.go @@ -16,7 +16,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" ) func TestNewBigTable(t *testing.T) { diff --git a/backend/pkg/commons/db2/databasetest/bigtable.go b/backend/pkg/commons/db2/database/databasetest/bigtable.go similarity index 100% rename from backend/pkg/commons/db2/databasetest/bigtable.go rename to backend/pkg/commons/db2/database/databasetest/bigtable.go diff --git a/backend/pkg/commons/db2/database/remote_test.go b/backend/pkg/commons/db2/database/remote_test.go index ee5bb4e37..4a3a830ff 100644 --- a/backend/pkg/commons/db2/database/remote_test.go +++ b/backend/pkg/commons/db2/database/remote_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" ) func TestRemote(t *testing.T) { diff --git a/backend/pkg/commons/db2/rawtest/raw.go b/backend/pkg/commons/db2/db2test/db2test.go similarity index 63% rename from backend/pkg/commons/db2/rawtest/raw.go rename to backend/pkg/commons/db2/db2test/db2test.go index ab633e69e..0e66fb30e 100644 --- a/backend/pkg/commons/db2/rawtest/raw.go +++ b/backend/pkg/commons/db2/db2test/db2test.go @@ -1,4 +1,4 @@ -package rawtest +package db2test import ( "context" @@ -8,40 +8,42 @@ import ( "strings" "testing" - "github.com/gobitfly/beaconchain/internal/th" + "github.com/gobitfly/beaconchain/pkg/commons/db2/data" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" "github.com/gobitfly/beaconchain/pkg/commons/db2/raw" ) -func NewRandSeededStore(t *testing.T) (raw.Store, *th.BlockchainBackend) { - t.Helper() +func NewDataStore(t *testing.T) data.Store { client, admin := databasetest.NewBigTable(t) - bt, err := database.NewBigTableWithClient(context.Background(), client, admin, raw.Schema) + db, err := database.NewBigTableWithClient(context.Background(), client, admin, data.Schema) if err != nil { t.Fatal(err) } + return data.NewStore(database.Wrap(db, data.Table)) +} - db := raw.NewStore(database.Wrap(bt, raw.BlocksRawTable)) - - backend := th.NewBackend(t) - for i := 0; i < 10; i++ { - temp := th.CreateEOA(t) - backend.FundOneEther(t, temp.From) - } - lastBlock, err := backend.Client().BlockNumber(context.Background()) +func NewRawStore(t *testing.T) raw.Store { + client, admin := databasetest.NewBigTable(t) + db, err := database.NewBigTableWithClient(context.Background(), client, admin, raw.Schema) if err != nil { t.Fatal(err) } - var blocks []raw.FullBlockData - for i := uint64(0); i <= lastBlock; i++ { - blocks = append(blocks, makeRawBlock(t, backend.Endpoint, uint64(backend.ChainID), i)) + return raw.NewStore(database.Wrap(db, raw.Table)) +} + +func NewStores(t *testing.T) (raw.Store, data.Store) { + return NewRawStore(t), NewDataStore(t) +} + +func AddBlockToRawStore(t *testing.T, store raw.Store, endpoint string, chainID uint64, blocks []uint64) { + var fullBlocks []raw.FullBlockData + for _, block := range blocks { + fullBlocks = append(fullBlocks, makeRawBlock(t, endpoint, chainID, block)) } - if err := db.AddBlocks(blocks); err != nil { + if err := store.AddBlocks(fullBlocks); err != nil { t.Fatal(err) } - - return db, backend } func makeRawBlock(t *testing.T, endpoint string, chainID uint64, block uint64) raw.FullBlockData { diff --git a/backend/pkg/commons/db2/parser_test.go b/backend/pkg/commons/db2/parser_test.go deleted file mode 100644 index 7b842a24d..000000000 --- a/backend/pkg/commons/db2/parser_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package db2 - -import ( - "context" - "math/big" - "os" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/rpc" - - "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/raw" - "github.com/gobitfly/beaconchain/pkg/commons/db2/rawtest" -) - -func TestRawWithBackend(t *testing.T) { - raw, backend := rawtest.NewRandSeededStore(t) - blocks, err := raw.ReadBlocksByNumber(uint64(backend.ChainID), 0, 10) - if err != nil { - t.Fatal(err) - } - for _, b := range blocks { - expectedBlock, err := backend.Client().BlockByNumber(context.Background(), big.NewInt(b.BlockNumber)) - if err != nil { - t.Fatal(err) - } - expectedReceipts, err := backend.Client().BlockReceipts(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.BlockNumber))) - if err != nil { - t.Fatal(err) - } - block, receipts, _, err := EthParse(b) - if err != nil { - t.Fatal(err) - } - if got, want := block.Number().String(), expectedBlock.Number().String(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := block.Hash().String(), expectedBlock.Hash().String(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := block.TxHash().String(), expectedBlock.TxHash().String(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := block.UncleHash().String(), expectedBlock.UncleHash().String(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := block.ReceiptHash().String(), expectedBlock.ReceiptHash().String(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if len(expectedReceipts) != 0 { - if got, want := receipts, expectedReceipts; !reflect.DeepEqual(got, want) { - t.Errorf("got %v, want %v", got, want) - } - } - } -} - -func TestRawRemoteRealCondition(t *testing.T) { - remote := os.Getenv("REMOTE_URL") - if remote == "" { - t.Skip("skipping test, set REMOTE_URL") - } - - client := database.NewRemoteClient(remote) - db := raw.NewStore(client) - block, err := db.ReadBlockByNumber(1, 6008149) - if err != nil { - panic(err) - } - - ethBlock, receipts, traces, err := EthParse(block) - if err != nil { - t.Errorf("failed to parse block: %v", err) - } - for i, transaction := range ethBlock.Transactions() { - if got, want := receipts[i].TxHash, transaction.Hash(); got != want { - t.Errorf("got %v, want %v", got, want) - } - if got, want := traces[i].TxHash, transaction.Hash().Hex(); got != want { - t.Errorf("got %v, want %v", got, want) - } - } -} diff --git a/backend/pkg/commons/db2/raw/client_test.go b/backend/pkg/commons/db2/raw/client_test.go index ab82479d9..0b2b2660d 100644 --- a/backend/pkg/commons/db2/raw/client_test.go +++ b/backend/pkg/commons/db2/raw/client_test.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" ) const ( @@ -48,7 +48,7 @@ func TestBigTableClientRealCondition(t *testing.T) { t.Fatal(err) } - rawStore := NewStore(database.Wrap(bt, BlocksRawTable)) + rawStore := NewStore(database.Wrap(bt, Table)) rpcClient, err := rpc.DialOptions(context.Background(), "http://foo.bar", rpc.WithHTTPClient(&http.Client{ Transport: NewBigTableEthRaw(rawStore, chainID), })) @@ -130,7 +130,7 @@ func BenchmarkRawBigTable(b *testing.B) { b.Fatal(err) } - rawStore := WithCache(NewStore(database.Wrap(bt, BlocksRawTable))) + rawStore := WithCache(NewStore(database.Wrap(bt, Table))) rpcClient, err := rpc.DialOptions(context.Background(), "http://foo.bar", rpc.WithHTTPClient(&http.Client{ Transport: NewBigTableEthRaw(rawStore, chainID), })) @@ -173,7 +173,7 @@ func TestBigTableClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rawStore := NewStore(database.Wrap(bt, BlocksRawTable)) + rawStore := NewStore(database.Wrap(bt, Table)) if err := rawStore.AddBlocks([]FullBlockData{tt.block}); err != nil { t.Fatal(err) } @@ -237,7 +237,7 @@ func TestBigTableClientWithFallback(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rawStore := NewStore(database.Wrap(bt, BlocksRawTable)) + rawStore := NewStore(database.Wrap(bt, Table)) rpcClient, err := rpc.DialOptions(context.Background(), node, rpc.WithHTTPClient(&http.Client{ Transport: NewWithFallback(NewBigTableEthRaw(rawStore, tt.block.ChainID), http.DefaultTransport), diff --git a/backend/pkg/commons/db2/parser.go b/backend/pkg/commons/db2/raw/parser.go similarity index 88% rename from backend/pkg/commons/db2/parser.go rename to backend/pkg/commons/db2/raw/parser.go index a040357fa..7a1c635f1 100644 --- a/backend/pkg/commons/db2/parser.go +++ b/backend/pkg/commons/db2/raw/parser.go @@ -1,4 +1,4 @@ -package db2 +package raw import ( "encoding/json" @@ -10,31 +10,16 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/gobitfly/beaconchain/pkg/commons/db2/jsonrpc" - "github.com/gobitfly/beaconchain/pkg/commons/db2/raw" + "github.com/gobitfly/beaconchain/pkg/commons/types/geth" ) -type GethTrace struct { - TxHash string - Result *GethTraceCall +type FullGethBlock struct { + block *types.Block + receipts []*types.Receipt + traces []*geth.Trace } -type GethTraceCall struct { - TransactionPosition int - Time string - GasUsed string - From common.Address - To common.Address - Value string - Gas string - Input string - Output string - Error string - RevertReason string // todo have a look at this, it could improve revert message - Type string - Calls []*GethTraceCall -} - -var EthParse = func(rawBlock *raw.FullBlockData) (*types.Block, []*types.Receipt, []*GethTrace, error) { +var GethParse = func(rawBlock *FullBlockData) (*types.Block, []*types.Receipt, []*geth.Trace, error) { var blockResp, receiptsResp, tracesResp jsonrpc.Message _ = json.Unmarshal(rawBlock.Receipts, &receiptsResp) _ = json.Unmarshal(rawBlock.Block, &blockResp) @@ -49,7 +34,7 @@ var EthParse = func(rawBlock *raw.FullBlockData) (*types.Block, []*types.Receipt } var receipts []*types.Receipt - var traces []*GethTrace + var traces []*geth.Trace if len(block.Transactions()) != 0 { if err := json.Unmarshal(receiptsResp.Result, &receipts); err != nil { return nil, nil, nil, err @@ -67,6 +52,14 @@ var EthParse = func(rawBlock *raw.FullBlockData) (*types.Block, []*types.Receipt // ie: old block traces don't include the hash traces[i].TxHash = receipts[i].TxHash.Hex() } + // manually insert the transaction position + for i := 0; i < len(traces); i++ { + calls := []*geth.TraceCall{traces[i].Result} + for len(calls) != 0 { + calls[0].TransactionPosition = i + calls = append(calls[1:], calls[0].Calls...) + } + } } return block, receipts, traces, nil diff --git a/backend/pkg/commons/db2/raw/parser_test.go b/backend/pkg/commons/db2/raw/parser_test.go new file mode 100644 index 000000000..1a21074c9 --- /dev/null +++ b/backend/pkg/commons/db2/raw/parser_test.go @@ -0,0 +1,155 @@ +package raw + +import ( + "context" + "fmt" + "io" + "math/big" + "net/http" + "os" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/rpc" + + "github.com/gobitfly/beaconchain/internal/th" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" +) + +func TestRawWithBackend(t *testing.T) { + raw, backend := newRandSeededStore(t) + blocks, err := raw.ReadBlocksByNumber(uint64(backend.ChainID), 0, 10) + if err != nil { + t.Fatal(err) + } + for _, b := range blocks { + expectedBlock, err := backend.Client().BlockByNumber(context.Background(), big.NewInt(b.BlockNumber)) + if err != nil { + t.Fatal(err) + } + expectedReceipts, err := backend.Client().BlockReceipts(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.BlockNumber))) + if err != nil { + t.Fatal(err) + } + block, receipts, _, err := GethParse(b) + if err != nil { + t.Fatal(err) + } + if got, want := block.Number().String(), expectedBlock.Number().String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if got, want := block.Hash().String(), expectedBlock.Hash().String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if got, want := block.TxHash().String(), expectedBlock.TxHash().String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if got, want := block.UncleHash().String(), expectedBlock.UncleHash().String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if got, want := block.ReceiptHash().String(), expectedBlock.ReceiptHash().String(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if len(expectedReceipts) != 0 { + if got, want := receipts, expectedReceipts; !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } + } + } +} + +func TestRawRemoteRealCondition(t *testing.T) { + remote := os.Getenv("REMOTE_URL") + if remote == "" { + t.Skip("skipping test, set REMOTE_URL") + } + + client := database.NewRemoteClient(remote) + db := NewStore(client) + block, err := db.ReadBlockByNumber(1, 6008149) + if err != nil { + panic(err) + } + + ethBlock, receipts, traces, err := GethParse(block) + if err != nil { + t.Errorf("failed to parse block: %v", err) + } + for i, transaction := range ethBlock.Transactions() { + if got, want := receipts[i].TxHash, transaction.Hash(); got != want { + t.Errorf("got %v, want %v", got, want) + } + if got, want := traces[i].TxHash, transaction.Hash().Hex(); got != want { + t.Errorf("got %v, want %v", got, want) + } + } +} + +func newRandSeededStore(t *testing.T) (Store, *th.BlockchainBackend) { + t.Helper() + client, admin := databasetest.NewBigTable(t) + bt, err := database.NewBigTableWithClient(context.Background(), client, admin, Schema) + if err != nil { + t.Fatal(err) + } + + db := NewStore(database.Wrap(bt, Table)) + + backend := th.NewBackend(t) + for i := 0; i < 10; i++ { + temp := th.CreateEOA(t) + backend.FundOneEther(t, temp.From) + } + lastBlock, err := backend.Client().BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + var blocks []FullBlockData + for i := uint64(0); i <= lastBlock; i++ { + blocks = append(blocks, makeRawBlock(t, backend.Endpoint, uint64(backend.ChainID), i)) + } + if err := db.AddBlocks(blocks); err != nil { + t.Fatal(err) + } + + return db, backend +} + +func makeRawBlock(t *testing.T, endpoint string, chainID uint64, block uint64) FullBlockData { + getReceipts := `{"jsonrpc":"2.0","method":"eth_getBlockReceipts","params":["0x%x"],"id":%d}` + getBlock := `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x%x", true],"id":%d}` + getTraces := `{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["0x%x", {"tracer": "callTracer"}],"id":%d}` + id := 1 + + return FullBlockData{ + ChainID: chainID, + BlockNumber: int64(block), + BlockHash: nil, + BlockUnclesCount: 0, + BlockTxs: nil, + Block: httpCall(t, endpoint, fmt.Sprintf(getBlock, block, id)), + Receipts: httpCall(t, endpoint, fmt.Sprintf(getReceipts, block, id)), + Traces: httpCall(t, endpoint, fmt.Sprintf(getTraces, block, id)), + Uncles: nil, + } +} + +func httpCall(t *testing.T, endpoint string, body string) []byte { + req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(body)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + return b +} diff --git a/backend/pkg/commons/db2/raw/raw_test.go b/backend/pkg/commons/db2/raw/raw_test.go index 44216f090..5964ac72d 100644 --- a/backend/pkg/commons/db2/raw/raw_test.go +++ b/backend/pkg/commons/db2/raw/raw_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" ) func TestRaw(t *testing.T) { @@ -19,7 +19,7 @@ func TestRaw(t *testing.T) { } store := Store{ - db: database.Wrap(s, BlocksRawTable), + db: database.Wrap(s, Table), compressor: noOpCompressor{}, } diff --git a/backend/pkg/commons/db2/raw/tables.go b/backend/pkg/commons/db2/raw/tables.go index 7cb44872f..bf37198d4 100644 --- a/backend/pkg/commons/db2/raw/tables.go +++ b/backend/pkg/commons/db2/raw/tables.go @@ -1,6 +1,6 @@ package raw -const BlocksRawTable = "blocks-Schema" +const Table = "blocks-raw" const BT_COLUMNFAMILY_BLOCK = "b" const BT_COLUMN_BLOCK = "b" @@ -14,7 +14,7 @@ const BT_COLUMN_UNCLES = "u" const MAX_EL_BLOCK_NUMBER = int64(1_000_000_000_000 - 1) var Schema = map[string][]string{ - BlocksRawTable: { + Table: { BT_COLUMNFAMILY_BLOCK, BT_COLUMNFAMILY_RECEIPTS, BT_COLUMNFAMILY_TRACES, diff --git a/backend/pkg/commons/indexer/indexer.go b/backend/pkg/commons/indexer/indexer.go index e5ef6234d..ce7b83750 100644 --- a/backend/pkg/commons/indexer/indexer.go +++ b/backend/pkg/commons/indexer/indexer.go @@ -26,10 +26,11 @@ func New(store data.Store, transformers ...TransformFunc) *Indexer { } } -func (indexer *Indexer) IndexBlocksWithTransformers(chainID string, blocks []*types.Eth1Block) error { - updates := make(map[string][]database.Item) - updatesMetadata := make(map[string][]database.Item) +func (indexer *Indexer) IndexBlocks(chainID string, blocks []*types.Eth1Block) error { for _, block := range blocks { + updates := make(map[string][]database.Item) + updatesMetadata := make(map[string][]database.Item) + for _, transform := range indexer.transformers { update, updateMetadata, err := transform(chainID, block) if err != nil { @@ -46,12 +47,12 @@ func (indexer *Indexer) IndexBlocksWithTransformers(chainID string, blocks []*ty maps.Copy(updatesMetadata, metadata.BlockKeysMutation(chainID, block.Number, block.Hash, metaKeys)) } } - } - if len(updates) > 0 { - err := indexer.store.AddItems(updates) - if err != nil { - return fmt.Errorf("error writing blocks [%v-%v] to bigtable data table: %w", blocks[0].Number, blocks[len(blocks)-1].Number, err) + if len(updates) > 0 { + err := indexer.store.AddItems(updates) + if err != nil { + return fmt.Errorf("error writing blocks [%v-%v] to bigtable data table: %w", blocks[0].Number, blocks[len(blocks)-1].Number, err) + } } } diff --git a/backend/pkg/commons/indexer/indexer_test.go b/backend/pkg/commons/indexer/indexer_test.go index b72e3b068..a1258370f 100644 --- a/backend/pkg/commons/indexer/indexer_test.go +++ b/backend/pkg/commons/indexer/indexer_test.go @@ -9,7 +9,7 @@ import ( "github.com/gobitfly/beaconchain/internal/th" "github.com/gobitfly/beaconchain/pkg/commons/db2/data" "github.com/gobitfly/beaconchain/pkg/commons/db2/database" - "github.com/gobitfly/beaconchain/pkg/commons/db2/databasetest" + "github.com/gobitfly/beaconchain/pkg/commons/db2/database/databasetest" "github.com/gobitfly/beaconchain/pkg/commons/rpc" "github.com/gobitfly/beaconchain/pkg/commons/types" ) @@ -51,7 +51,7 @@ func TestIndexer(t *testing.T) { if err != nil { t.Fatal(err) } - if err := indexer.IndexBlocksWithTransformers(fmt.Sprintf("%d", backend.ChainID), []*types.Eth1Block{block}); err != nil { + if err := indexer.IndexBlocks(fmt.Sprintf("%d", backend.ChainID), []*types.Eth1Block{block}); err != nil { t.Fatal(err) } } @@ -64,8 +64,4 @@ func TestIndexer(t *testing.T) { if len(rows) == 0 { t.Errorf("no rows found") } - - for _, row := range rows { - t.Log(row.Key) - } } diff --git a/backend/pkg/commons/rpc/client.go b/backend/pkg/commons/rpc/client.go index adb62de71..3592cb1ab 100644 --- a/backend/pkg/commons/rpc/client.go +++ b/backend/pkg/commons/rpc/client.go @@ -1,3 +1,523 @@ package rpc -var CurrentClient *LighthouseClient +import ( + "context" + "fmt" + "math/big" + "strings" + "sync" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + geth_types "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/gobitfly/beaconchain/pkg/commons/db2/raw" + "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/metrics" + "github.com/gobitfly/beaconchain/pkg/commons/types" + "github.com/gobitfly/beaconchain/pkg/commons/types/geth" +) + +type Eth1InternalTransactionWithPosition struct { + types.Eth1InternalTransaction + txPosition int +} + +type NodeClient struct { + chainID *big.Int + rpcClient *rpc.Client + ethClient *ethclient.Client +} + +type ClientV2 interface { + GetBlocks(start, end int64, traceMode string) ([]*types.Eth1Block, error) + GetBlock(number int64, traceMode string) (*types.Eth1Block, error) +} + +func NewNodeClient(endpoint string) (*NodeClient, error) { + rpcClient, err := rpc.DialOptions(context.Background(), endpoint) + if err != nil { + return nil, fmt.Errorf("error dialing rpc node: %w", err) + } + + ethClient := ethclient.NewClient(rpcClient) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + chainID, err := ethClient.ChainID(ctx) + if err != nil { + return nil, fmt.Errorf("error getting chainid of rpcclient: %w", err) + } + + return &NodeClient{ + chainID: chainID, + rpcClient: rpcClient, + ethClient: ethClient, + }, nil +} + +func (client *NodeClient) GetBlocks(start, end int64, traceMode string) ([]*types.Eth1Block, error) { + blocks := make([]*types.Eth1Block, end-start+1) + for i := start; i <= end; i++ { + block, err := client.GetBlock(i, traceMode) + if err != nil { + return nil, err + } + blocks[i-start] = block + } + return blocks, nil +} + +func (client *NodeClient) GetBlock(number int64, traceMode string) (*types.Eth1Block, error) { + start := time.Now() + timings := &types.GetBlockTimings{} + mu := sync.Mutex{} + + defer func() { + metrics.TaskDuration.WithLabelValues("rpc_el_get_block").Observe(time.Since(start).Seconds()) + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + var traces []*Eth1InternalTransactionWithPosition + var block *geth_types.Block + var receipts []*geth_types.Receipt + g := new(errgroup.Group) + g.Go(func() error { + b, err := client.ethClient.BlockByNumber(ctx, big.NewInt(number)) + if err != nil { + return err + } + mu.Lock() + timings.Headers = time.Since(start) + mu.Unlock() + block = b + return nil + }) + g.Go(func() error { + if err := client.rpcClient.CallContext(ctx, &receipts, "eth_getBlockReceipts", fmt.Sprintf("0x%x", number)); err != nil { + return fmt.Errorf("error retrieving receipts for block %v: %w", number, err) + } + mu.Lock() + timings.Receipts = time.Since(start) + mu.Unlock() + return nil + }) + g.Go(func() error { + t, err := client.getTrace(traceMode, big.NewInt(number)) + if err != nil { + return fmt.Errorf("error retrieving traces for block %v: %w", number, err) + } + traces = t + mu.Lock() + timings.Traces = time.Since(start) + mu.Unlock() + return nil + }) + if err := g.Wait(); err != nil { + return nil, err + } + + return parseBlock(block, receipts, traces, client.ethClient.TransactionSender), nil +} + +func (client *NodeClient) getTrace(traceMode string, blockNumber *big.Int) ([]*Eth1InternalTransactionWithPosition, error) { + if blockNumber.Uint64() == 0 { // genesis block is not traceable + return nil, nil + } + switch traceMode { + case "parity": + return client.getTraceParity(blockNumber) + case "parity/geth": + traces, err := client.getTraceParity(blockNumber) + if err == nil { + return traces, nil + } + log.Error(err, fmt.Sprintf("error tracing block via parity style traces (%v)", blockNumber), 0) + // fallback to geth traces + fallthrough + case "geth": + return client.getTraceGeth(blockNumber) + } + return nil, fmt.Errorf("unknown trace mode '%s'", traceMode) +} + +func (client *NodeClient) getTraceParity(blockNumber *big.Int) ([]*Eth1InternalTransactionWithPosition, error) { + traces, err := client.traceParity(blockNumber.Uint64()) + if err != nil { + return nil, fmt.Errorf("error tracing block via parity style traces (%v): %w", blockNumber, err) + } + + var indexedTraces []*Eth1InternalTransactionWithPosition + for _, trace := range traces { + if trace.Type == "reward" { + continue + } + if trace.TransactionHash == "" { + continue + } + + from, to, value, traceType := trace.ConvertFields() + indexedTraces = append(indexedTraces, &Eth1InternalTransactionWithPosition{ + Eth1InternalTransaction: types.Eth1InternalTransaction{ + Type: traceType, + From: from, + To: to, + Value: value, + ErrorMsg: trace.Error, + Path: fmt.Sprint(trace.TraceAddress), + }, + txPosition: trace.TransactionPosition, + }) + } + return indexedTraces, nil +} + +func (client *NodeClient) getTraceGeth(blockNumber *big.Int) ([]*Eth1InternalTransactionWithPosition, error) { + traces, err := client.traceGeth(blockNumber) + if err != nil { + return nil, fmt.Errorf("error tracing block via geth style traces (%v): %w", blockNumber, err) + } + + var indexedTraces []*Eth1InternalTransactionWithPosition + var txPosition int + paths := make(map[*geth.TraceCall]string) + for i, trace := range traces { + switch trace.Type { + case "CREATE2": + trace.Type = "CREATE" + case "CREATE", "SELFDESTRUCT", "SUICIDE", "CALL", "DELEGATECALL", "STATICCALL", "CALLCODE": + case "": + logrus.WithFields(logrus.Fields{"type": trace.Type, "block.Number": blockNumber}).Errorf("geth style trace without type") + spew.Dump(trace) + continue + default: + spew.Dump(trace) + logrus.Fatalf("unknown trace type %v in tx %v:%v", trace.Type, blockNumber.String(), trace.TransactionPosition) + } + if txPosition != trace.TransactionPosition { + txPosition = trace.TransactionPosition + paths = make(map[*geth.TraceCall]string) + } + for index, call := range trace.Calls { + paths[call] = fmt.Sprintf("%s %d", paths[trace], index) + } + log.Tracef("appending trace %v to tx %d:%x from %v to %v value %v", i, blockNumber, trace.TransactionPosition, trace.From, trace.To, trace.Value) + indexedTraces = append(indexedTraces, &Eth1InternalTransactionWithPosition{ + Eth1InternalTransaction: types.Eth1InternalTransaction{ + Type: strings.ToLower(trace.Type), + From: trace.From.Bytes(), + To: trace.To.Bytes(), + Value: common.FromHex(trace.Value), + ErrorMsg: trace.Error, + Path: fmt.Sprintf("[%s]", strings.TrimPrefix(paths[trace], " ")), + }, + txPosition: trace.TransactionPosition, + }) + } + return indexedTraces, nil +} + +func (client *NodeClient) traceGeth(blockNumber *big.Int) ([]*geth.TraceCall, error) { + var res []*geth.Trace + + err := client.rpcClient.Call(&res, "debug_traceBlockByNumber", hexutil.EncodeBig(blockNumber), gethTracerArg) + if err != nil { + return nil, err + } + + data := make([]*geth.TraceCall, 0, 20) + for i, r := range res { + r.Result.TransactionPosition = i + extractCalls(r.Result, &data) + } + + return data, nil +} + +func (client *NodeClient) traceParity(blockNumber uint64) ([]*ParityTraceResult, error) { + var res []*ParityTraceResult + + err := client.rpcClient.Call(&res, "trace_block", blockNumber) + if err != nil { + return nil, err + } + + return res, nil +} + +type RawStoreClient struct { + chainID *big.Int + store raw.StoreReader +} + +func NewRawStoreClient(chainID *big.Int, store raw.StoreReader) *RawStoreClient { + return &RawStoreClient{ + chainID: chainID, + store: store, + } +} + +func (client *RawStoreClient) GetBlocks(start, end int64, traceMode string) ([]*types.Eth1Block, error) { + rawBlocks, err := client.store.ReadBlocksByNumber(client.chainID.Uint64(), start, end) + if err != nil { + return nil, err + } + var blocks []*types.Eth1Block + for _, rawBlock := range rawBlocks { + block, receipts, traces, err := raw.GethParse(rawBlock) + if err != nil { + return nil, err + } + parsedBlock := parseBlock(block, receipts, parseTraceGeth(traces), func(_ context.Context, tx *geth_types.Transaction, block common.Hash, _ uint) (common.Address, error) { + // Try to load the address from the cache. + return geth_types.Sender(&raw.SenderFromDBSigner{Blockhash: block}, tx) + }) + blocks = append(blocks, parsedBlock) + } + return blocks, nil +} + +func (client *RawStoreClient) GetBlock(number int64, traceMode string) (*types.Eth1Block, error) { + rawBlock, err := client.store.ReadBlockByNumber(client.chainID.Uint64(), number) + if err != nil { + return nil, err + } + block, receipts, traces, err := raw.GethParse(rawBlock) + if err != nil { + return nil, err + } + return parseBlock(block, receipts, parseTraceGeth(traces), func(_ context.Context, tx *geth_types.Transaction, block common.Hash, _ uint) (common.Address, error) { + // Try to load the address from the cache. + return geth_types.Sender(&raw.SenderFromDBSigner{Blockhash: block}, tx) + }), nil +} + +type MultiClient struct { + node *NodeClient + rawStore *RawStoreClient +} + +func NewMultiClient(endpoint string, store raw.StoreReader) (*MultiClient, error) { + nodeClient, err := NewNodeClient(endpoint) + if err != nil { + return nil, err + } + return &MultiClient{ + node: nodeClient, + rawStore: NewRawStoreClient(nodeClient.chainID, store), + }, nil +} + +func (m MultiClient) GetBlocks(start, end int64, traceMode string) ([]*types.Eth1Block, error) { + return m.rawStore.GetBlocks(start, end, traceMode) +} + +func (m MultiClient) GetBlock(number int64, traceMode string) (*types.Eth1Block, error) { + return m.rawStore.GetBlock(number, traceMode) +} + +type TransactionSenderFunc func(ctx context.Context, tx *geth_types.Transaction, block common.Hash, index uint) (common.Address, error) + +func parseBlock(block *geth_types.Block, receipts []*geth_types.Receipt, traces []*Eth1InternalTransactionWithPosition, txSender TransactionSenderFunc) *types.Eth1Block { + withdrawals := make([]*types.Eth1Withdrawal, len(block.Withdrawals())) + for i, withdrawal := range block.Withdrawals() { + withdrawals[i] = &types.Eth1Withdrawal{ + Index: withdrawal.Index, + ValidatorIndex: withdrawal.Validator, + Address: withdrawal.Address.Bytes(), + Amount: new(big.Int).SetUint64(withdrawal.Amount).Bytes(), + } + } + + transactions := make([]*types.Eth1Transaction, len(block.Transactions())) + traceIndex := 0 + for txPosition, receipt := range receipts { + logs := make([]*types.Eth1Log, len(receipt.Logs)) + for i, log := range receipt.Logs { + topics := make([][]byte, len(log.Topics)) + for j, topic := range log.Topics { + topics[j] = topic.Bytes() + } + logs[i] = &types.Eth1Log{ + Address: log.Address.Bytes(), + Data: log.Data, + Removed: log.Removed, + Topics: topics, + } + } + + var internals []*types.Eth1InternalTransaction + for ; traceIndex < len(traces) && traces[traceIndex].txPosition == txPosition; traceIndex++ { + internals = append(internals, &traces[traceIndex].Eth1InternalTransaction) + } + + tx := block.Transactions()[txPosition] + transactions[txPosition] = &types.Eth1Transaction{ + Type: uint32(tx.Type()), + Nonce: tx.Nonce(), + GasPrice: tx.GasPrice().Bytes(), + MaxPriorityFeePerGas: tx.GasTipCap().Bytes(), + MaxFeePerGas: tx.GasFeeCap().Bytes(), + Gas: tx.Gas(), + Value: tx.Value().Bytes(), + Data: tx.Data(), + To: func() []byte { + if tx.To() != nil { + return tx.To().Bytes() + } + return nil + }(), + From: func() []byte { + // this won't make a request in most cases as the sender is already present in the cache + // context https://github.com/ethereum/go-ethereum/blob/v1.14.11/ethclient/ethclient.go#L268 + sender, err := txSender(context.Background(), tx, block.Hash(), uint(txPosition)) + if err != nil { + sender = common.HexToAddress("abababababababababababababababababababab") + logrus.Errorf("could not retrieve tx sender %v: %v", tx.Hash(), err) + } + return sender.Bytes() + }(), + ChainId: tx.ChainId().Bytes(), + AccessList: []*types.AccessList{}, + Hash: tx.Hash().Bytes(), + ContractAddress: receipt.ContractAddress[:], + CommulativeGasUsed: receipt.CumulativeGasUsed, + GasUsed: receipt.GasUsed, + LogsBloom: receipt.Bloom[:], + Status: receipt.Status, + Logs: logs, + Itx: internals, + MaxFeePerBlobGas: func() []byte { + if tx.BlobGasFeeCap() != nil { + return tx.BlobGasFeeCap().Bytes() + } + return nil + }(), + BlobVersionedHashes: func() (b [][]byte) { + for _, h := range tx.BlobHashes() { + b = append(b, h.Bytes()) + } + return b + }(), + BlobGasPrice: func() []byte { + if receipt.BlobGasPrice != nil { + return receipt.BlobGasPrice.Bytes() + } + return nil + }(), + BlobGasUsed: receipt.BlobGasUsed, + } + } + + uncles := make([]*types.Eth1Block, len(block.Uncles())) + for i, uncle := range block.Uncles() { + uncles[i] = &types.Eth1Block{ + Hash: uncle.Hash().Bytes(), + ParentHash: uncle.ParentHash.Bytes(), + UncleHash: uncle.UncleHash.Bytes(), + Coinbase: uncle.Coinbase.Bytes(), + Root: uncle.Root.Bytes(), + TxHash: uncle.TxHash.Bytes(), + ReceiptHash: uncle.ReceiptHash.Bytes(), + Difficulty: uncle.Difficulty.Bytes(), + Number: uncle.Number.Uint64(), + GasLimit: uncle.GasLimit, + GasUsed: uncle.GasUsed, + Time: timestamppb.New(time.Unix(int64(uncle.Time), 0)), + Extra: uncle.Extra, + MixDigest: uncle.MixDigest.Bytes(), + Bloom: uncle.Bloom.Bytes(), + } + } + + return &types.Eth1Block{ + Hash: block.Hash().Bytes(), + ParentHash: block.ParentHash().Bytes(), + UncleHash: block.UncleHash().Bytes(), + Coinbase: block.Coinbase().Bytes(), + Root: block.Root().Bytes(), + TxHash: block.TxHash().Bytes(), + ReceiptHash: block.ReceiptHash().Bytes(), + Difficulty: block.Difficulty().Bytes(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + Time: timestamppb.New(time.Unix(int64(block.Time()), 0)), + Extra: block.Extra(), + MixDigest: block.MixDigest().Bytes(), + Bloom: block.Bloom().Bytes(), + BaseFee: func() []byte { + if block.BaseFee() != nil { + return block.BaseFee().Bytes() + } + return nil + }(), + Uncles: uncles, + Transactions: transactions, + Withdrawals: withdrawals, + BlobGasUsed: func() uint64 { + blobGasUsed := block.BlobGasUsed() + if blobGasUsed != nil { + return *blobGasUsed + } + return 0 + }(), + ExcessBlobGas: func() uint64 { + excessBlobGas := block.ExcessBlobGas() + if excessBlobGas != nil { + return *excessBlobGas + } + return 0 + }(), + } +} + +func parseTraceGeth(traces []*geth.Trace) []*Eth1InternalTransactionWithPosition { + var indexedTraces []*Eth1InternalTransactionWithPosition + var txPosition int + paths := make(map[*geth.TraceCall]string) + for i, trace := range traces { + switch trace.Result.Type { + case "CREATE2": + trace.Result.Type = "CREATE" + case "CREATE", "SELFDESTRUCT", "SUICIDE", "CALL", "DELEGATECALL", "STATICCALL", "CALLCODE": + case "": + logrus.WithFields(logrus.Fields{"type": trace.Result.Type, "tx.hash": trace.TxHash}).Errorf("geth style trace without type") + spew.Dump(trace) + continue + default: + spew.Dump(trace) + logrus.Fatalf("unknown trace type %v in tx %v", trace.Result.Type, trace.TxHash) + } + if txPosition != trace.Result.TransactionPosition { + txPosition = trace.Result.TransactionPosition + paths = make(map[*geth.TraceCall]string) + } + for index, call := range trace.Result.Calls { + paths[call] = fmt.Sprintf("%s %d", paths[trace.Result], index) + } + log.Tracef("appending trace %v to tx %s from %v to %v value %v", i, trace.TxHash, trace.Result.From, trace.Result.To, trace.Result.Value) + indexedTraces = append(indexedTraces, &Eth1InternalTransactionWithPosition{ + Eth1InternalTransaction: types.Eth1InternalTransaction{ + Type: strings.ToLower(trace.Result.Type), + From: trace.Result.From.Bytes(), + To: trace.Result.To.Bytes(), + Value: common.FromHex(trace.Result.Value), + ErrorMsg: trace.Result.Error, + Path: fmt.Sprintf("[%s]", strings.TrimPrefix(paths[trace.Result], " ")), + }, + txPosition: trace.Result.TransactionPosition, + }) + } + return indexedTraces +} diff --git a/backend/pkg/commons/rpc/client_test.go b/backend/pkg/commons/rpc/client_test.go new file mode 100644 index 000000000..f41db58d2 --- /dev/null +++ b/backend/pkg/commons/rpc/client_test.go @@ -0,0 +1,56 @@ +package rpc + +import ( + "context" + "math/big" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/gobitfly/beaconchain/internal/th" + "github.com/gobitfly/beaconchain/pkg/commons/db2/db2test" +) + +func TestCmpNodeAndRawStoreClient(t *testing.T) { + backend := th.NewBackend(t) + + nodeClient, err := NewNodeClient(backend.Endpoint) + if err != nil { + t.Fatal(err) + } + + rawStore := db2test.NewRawStore(t) + rawStoreClient := RawStoreClient{ + chainID: big.NewInt(int64(backend.ChainID)), + store: rawStore, + } + + for i := 0; i < 1; i++ { + receiver := th.CreateEOA(t).From + tx := backend.MakeTx(t, backend.BankAccount, &receiver, big.NewInt(1), nil) + if err := backend.Client().SendTransaction(context.Background(), tx); err != nil { + t.Fatal(err) + } + } + backend.Commit() + lastBlock, err := backend.Client().BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + + db2test.AddBlockToRawStore(t, rawStore, backend.Endpoint, uint64(backend.ChainID), []uint64{lastBlock}) + rawStoreBlock, err := rawStoreClient.GetBlock(int64(lastBlock), "geth") + if err != nil { + t.Fatal(err) + } + + nodeBlock, err := nodeClient.GetBlock(int64(lastBlock), "geth") + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(nodeBlock, rawStoreBlock, protocmp.Transform()); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } +} diff --git a/backend/pkg/commons/rpc/erigon.go b/backend/pkg/commons/rpc/erigon.go index 7313d0d21..b133f72ba 100644 --- a/backend/pkg/commons/rpc/erigon.go +++ b/backend/pkg/commons/rpc/erigon.go @@ -10,6 +10,7 @@ import ( "github.com/gobitfly/beaconchain/pkg/commons/contracts/oneinchoracle" "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/types/geth" "github.com/gobitfly/beaconchain/pkg/commons/erc20" @@ -49,12 +50,7 @@ func NewErigonClient(endpoint string) (*ErigonClient, error) { return nil, fmt.Errorf("error dialing rpc node: %w", err) } client.rpcClient = rpcClient - - ethClient, err := ethclient.Dial(client.endpoint) - if err != nil { - return nil, fmt.Errorf("error dialing rpc node: %w", err) - } - client.ethClient = ethClient + client.ethClient = ethclient.NewClient(rpcClient) client.multiChecker, err = NewBalance(common.HexToAddress("0xb1F8e55c7f64D203C1400B9D8555d050F94aDF39"), client.ethClient) if err != nil { @@ -426,7 +422,7 @@ var gethTracerArg = map[string]string{ "tracer": "callTracer", } -func extractCalls(r *GethTraceCallResult, d *[]*GethTraceCallResult) { +func extractCalls(r *geth.TraceCall, d *[]*geth.TraceCall) { if r == nil { return } @@ -441,15 +437,15 @@ func extractCalls(r *GethTraceCallResult, d *[]*GethTraceCallResult) { } } -func (client *ErigonClient) TraceGeth(blockHash common.Hash) ([]*GethTraceCallResult, error) { - var res []*GethTraceCallResultWrapper +func (client *ErigonClient) TraceGeth(blockHash common.Hash) ([]*geth.TraceCall, error) { + var res []*geth.Trace err := client.rpcClient.Call(&res, "debug_traceBlockByHash", blockHash, gethTracerArg) if err != nil { return nil, err } - data := make([]*GethTraceCallResult, 0, 20) + data := make([]*geth.TraceCall, 0, 20) for i, r := range res { r.Result.TransactionPosition = i extractCalls(r.Result, &data) diff --git a/backend/pkg/commons/types/geth/types.go b/backend/pkg/commons/types/geth/types.go new file mode 100644 index 000000000..e61878991 --- /dev/null +++ b/backend/pkg/commons/types/geth/types.go @@ -0,0 +1,26 @@ +package geth + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type Trace struct { + TxHash string + Result *TraceCall +} + +type TraceCall struct { + TransactionPosition int // todo use something else, that field is not provided by geth, it's manually inserted + Time string + GasUsed string + From common.Address + To common.Address + Value string + Gas string + Input string + Output string + Error string + RevertReason string // todo have a look at this, it could improve revert message + Type string + Calls []*TraceCall +}