diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d68347c..e41f824b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added verification to ensure CARTESI_BLOCKCHAIN_ID matches the id returned from the Ethereum node + ## Changed - Changed CARTESI_BLOCKCHAIN_ID type from int to uint64 diff --git a/cmd/cartesi-rollups-node/chainid.go b/cmd/cartesi-rollups-node/chainid.go new file mode 100644 index 000000000..f7f7d7507 --- /dev/null +++ b/cmd/cartesi-rollups-node/chainid.go @@ -0,0 +1,46 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package main + +import ( + "context" + "fmt" + "time" + + "github.com/cartesi/rollups-node/internal/config" + "github.com/ethereum/go-ethereum/ethclient" +) + +const defaultTimeout = 3 * time.Second + +// Checks if the chain id from the configuration matches the chain id reported +// by the Ethereum node. If they don't, it returns an error. +func validateChainId(ctx context.Context, chainId uint64, ethereumNodeAddr string) error { + remoteChainId, err := getChainId(ctx, ethereumNodeAddr) + if err != nil { + config.ErrorLogger.Printf("Couldn't validate chainId: %v\n", err) + } else if chainId != remoteChainId { + return fmt.Errorf( + "chainId mismatch. Expected %v but Ethereum node returned %v", + chainId, + remoteChainId, + ) + } + return nil +} + +func getChainId(ctx context.Context, ethereumNodeAddr string) (uint64, error) { + ctx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + + client, err := ethclient.Dial(ethereumNodeAddr) + if err != nil { + return 0, fmt.Errorf("Failed to create RPC client: %v", err) + } + chainId, err := client.ChainID(ctx) + if err != nil { + return 0, fmt.Errorf("Failed to get chain id: %v", err) + } + return chainId.Uint64(), nil +} diff --git a/cmd/cartesi-rollups-node/chainid_test.go b/cmd/cartesi-rollups-node/chainid_test.go new file mode 100644 index 000000000..f8e5dede3 --- /dev/null +++ b/cmd/cartesi-rollups-node/chainid_test.go @@ -0,0 +1,48 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package main + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/suite" +) + +type ValidateChainIdSuite struct { + suite.Suite +} + +func TestValidateChainId(t *testing.T) { + suite.Run(t, new(ValidateChainIdSuite)) +} + +func (s *ValidateChainIdSuite) TestItFailsIfChainIdsDoNotMatch() { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintln(w, `{"jsonrpc":"2.0","id":67,"result":"0x7a69"}`) + })) + defer ts.Close() + localChainId := uint64(11111) + + err := validateChainId(context.Background(), localChainId, ts.URL) + + s.NotNil(err) +} + +func (s *ValidateChainIdSuite) TestItReturnsNilIfChainIdsMatch() { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintln(w, `{"jsonrpc":"2.0","id":67,"result":"0x7a69"}`) + })) + defer ts.Close() + localChainId := uint64(31337) + + err := validateChainId(context.Background(), localChainId, ts.URL) + + s.Nil(err) +} diff --git a/cmd/cartesi-rollups-node/main.go b/cmd/cartesi-rollups-node/main.go index b9b7cab68..c8ef42e36 100644 --- a/cmd/cartesi-rollups-node/main.go +++ b/cmd/cartesi-rollups-node/main.go @@ -20,6 +20,14 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() + if err := validateChainId( + ctx, + config.GetCartesiBlockchainId(), + config.GetCartesiBlockchainHttpEndpoint(), + ); err != nil { + config.ErrorLogger.Fatal(err) + } + sunodoValidatorEnabled := config.GetCartesiExperimentalSunodoValidatorEnabled() if !sunodoValidatorEnabled { // add Redis first