From 85bf354f1c19f6cfd08b8f667d069b1db29db4a4 Mon Sep 17 00:00:00 2001 From: Victor Yves Crispim Date: Thu, 7 Mar 2024 16:17:28 -0300 Subject: [PATCH] feat: validate chain id --- CHANGELOG.md | 10 ++++ cmd/cartesi-rollups-node/chainid.go | 73 ++++++++++++++++++++++++ cmd/cartesi-rollups-node/chainid_test.go | 47 +++++++++++++++ cmd/cartesi-rollups-node/main.go | 7 +++ 4 files changed, 137 insertions(+) create mode 100644 cmd/cartesi-rollups-node/chainid.go create mode 100644 cmd/cartesi-rollups-node/chainid_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index beeaacfac..a1d114322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 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 uint + ## [1.3.0] 2024-02-09 ### Added diff --git a/cmd/cartesi-rollups-node/chainid.go b/cmd/cartesi-rollups-node/chainid.go new file mode 100644 index 000000000..b44cec049 --- /dev/null +++ b/cmd/cartesi-rollups-node/chainid.go @@ -0,0 +1,73 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/cartesi/rollups-node/internal/config" +) + +// 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(chainId uint, ethereumNodeAddr string) error { + remoteChainId, err := getChainId(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(ethereumNodeAddr string) (uint, error) { + // create and send the request + requestBody := strings.NewReader(`{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":67}`) + resp, err := http.Post(ethereumNodeAddr, "application/json", requestBody) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + // parse the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, err + } + var chainIdResponse *GetChainIdResponse + err = json.Unmarshal(body, &chainIdResponse) + if err != nil { + return 0, fmt.Errorf("Failed to parse response: %v", err) + } + + // handle the response + if chainIdResponse.Error != nil { + return 0, fmt.Errorf("Failed to get chainId: %v", chainIdResponse.Error.Message) + } + chainId, err := strconv.ParseUint(chainIdResponse.Result[2:], 16, 64) + if err != nil { + return 0, fmt.Errorf("Failed to parse chainId: %v", err) + } + return uint(chainId), nil +} + +type GetChainIdResponse struct { + // The chainId encoded as a hex string prefixed with "0x" + Result string `json:"result"` + Error *GetChainIdError `json:"error"` +} + +type GetChainIdError struct { + Code int `json:"code"` + Message string `json:"message"` +} diff --git a/cmd/cartesi-rollups-node/chainid_test.go b/cmd/cartesi-rollups-node/chainid_test.go new file mode 100644 index 000000000..fbbd3c50e --- /dev/null +++ b/cmd/cartesi-rollups-node/chainid_test.go @@ -0,0 +1,47 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package main + +import ( + "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 := uint(11111) + + err := validateChainId(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 := uint(31337) + + err := validateChainId(localChainId, ts.URL) + + s.Nil(err) +} diff --git a/cmd/cartesi-rollups-node/main.go b/cmd/cartesi-rollups-node/main.go index b9b7cab68..26b2d714b 100644 --- a/cmd/cartesi-rollups-node/main.go +++ b/cmd/cartesi-rollups-node/main.go @@ -20,6 +20,13 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() + if err := validateChainId( + config.GetCartesiBlockchainId(), + config.GetCartesiBlockchainHttpEndpoint(), + ); err != nil { + config.ErrorLogger.Fatal(err) + } + sunodoValidatorEnabled := config.GetCartesiExperimentalSunodoValidatorEnabled() if !sunodoValidatorEnabled { // add Redis first