diff --git a/apps/evm/cmd/rollback.go b/apps/evm/cmd/rollback.go index 8fefb4f0e..13009b1be 100644 --- a/apps/evm/cmd/rollback.go +++ b/apps/evm/cmd/rollback.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" ds "github.com/ipfs/go-datastore" + "github.com/rs/zerolog" "github.com/spf13/cobra" goheaderstore "github.com/celestiaorg/go-header/store" @@ -166,5 +167,5 @@ func createRollbackEngineClient(cmd *cobra.Command, db ds.Batching) (*evm.Engine return nil, fmt.Errorf("JWT secret file '%s' is empty", jwtSecretFile) } - return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, common.Hash{}, common.Address{}, db, false) + return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, common.Hash{}, common.Address{}, db, false, zerolog.Nop()) } diff --git a/apps/evm/cmd/run.go b/apps/evm/cmd/run.go index e4b130199..b52398f6f 100644 --- a/apps/evm/cmd/run.go +++ b/apps/evm/cmd/run.go @@ -3,12 +3,14 @@ package cmd import ( "bytes" "context" + "encoding/json" "fmt" "os" "path/filepath" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ipfs/go-datastore" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -56,9 +58,28 @@ var RunCmd = &cobra.Command{ } tracingEnabled := nodeConfig.Instrumentation.IsTracingEnabled() - executor, err := createExecutionClient(cmd, datastore, tracingEnabled) - if err != nil { - return err + + var executor execution.Executor + useGeth, _ := cmd.Flags().GetBool(evm.FlagEVMInProcessGeth) + if useGeth { + executor, err = createGethExecutionClient( + cmd, + datastore, + logger.With().Str("module", "geth_client").Logger(), + ) + if err != nil { + return err + } + } else { + executor, err = createRethExecutionClient( + cmd, + datastore, + tracingEnabled, + logger.With().Str("module", "engine_client").Logger(), + ) + if err != nil { + return err + } } blobClient, err := blobrpc.NewClient(context.Background(), nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") @@ -67,12 +88,6 @@ var RunCmd = &cobra.Command{ } daClient := block.NewDAClient(blobClient, nodeConfig, logger) - - // Attach logger to the EVM engine client if available - if ec, ok := executor.(*evm.EngineClient); ok { - ec.SetLogger(logger.With().Str("module", "engine_client").Logger()) - } - headerNamespace := da.NamespaceFromString(nodeConfig.DA.GetNamespace()) dataNamespace := da.NamespaceFromString(nodeConfig.DA.GetDataNamespace()) @@ -198,7 +213,13 @@ func createSequencer( return sequencer, nil } -func createExecutionClient(cmd *cobra.Command, db datastore.Batching, tracingEnabled bool) (execution.Executor, error) { +func createRethExecutionClient(cmd *cobra.Command, db datastore.Batching, tracingEnabled bool, logger zerolog.Logger) (execution.Executor, error) { + feeRecipientStr, err := cmd.Flags().GetString(evm.FlagEvmFeeRecipient) + if err != nil { + return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmFeeRecipient, err) + } + feeRecipient := common.HexToAddress(feeRecipientStr) + // Read execution client parameters from flags ethURL, err := cmd.Flags().GetString(evm.FlagEvmEthURL) if err != nil { @@ -234,16 +255,37 @@ func createExecutionClient(cmd *cobra.Command, db datastore.Batching, tracingEna if err != nil { return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmGenesisHash, err) } + + // Convert string parameters to Ethereum types + genesisHash := common.HexToHash(genesisHashStr) + + return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, genesisHash, feeRecipient, db, tracingEnabled, logger) +} + +func createGethExecutionClient(cmd *cobra.Command, db datastore.Batching, logger zerolog.Logger) (execution.Executor, error) { feeRecipientStr, err := cmd.Flags().GetString(evm.FlagEvmFeeRecipient) if err != nil { return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmFeeRecipient, err) } - - // Convert string parameters to Ethereum types - genesisHash := common.HexToHash(genesisHashStr) feeRecipient := common.HexToAddress(feeRecipientStr) - return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, genesisHash, feeRecipient, db, tracingEnabled) + genesisPath, _ := cmd.Flags().GetString(evm.FlagEVMGenesisPath) + if len(genesisPath) == 0 { + return nil, fmt.Errorf("genesis path must be provided when using in-process Geth") + } + + genesisBz, err := os.ReadFile(genesisPath) + if err != nil { + return nil, fmt.Errorf("failed to read genesis: %w", err) + } + + var genesis core.Genesis + if err := json.Unmarshal(genesisBz, &genesis); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis: %w", err) + } + + rpcAddress, _ := cmd.Flags().GetString(evm.FlagEVMRPCAddress) + return evm.NewEngineExecutionClientWithGeth(&genesis, feeRecipient, db, rpcAddress, logger) } // addFlags adds flags related to the EVM execution client @@ -254,4 +296,12 @@ func addFlags(cmd *cobra.Command) { cmd.Flags().String(evm.FlagEvmGenesisHash, "", "Hash of the genesis block") cmd.Flags().String(evm.FlagEvmFeeRecipient, "", "Address that will receive transaction fees") cmd.Flags().String(flagForceInclusionServer, "", "Address for force inclusion API server (e.g. 127.0.0.1:8547). If set, enables the server for direct DA submission") + cmd.Flags().Bool(evm.FlagEVMInProcessGeth, false, "Use in-process Geth for EVM execution instead of external execution client") + cmd.Flags().String(evm.FlagEVMGenesisPath, "", "EVM genesis path for Geth") + cmd.Flags().String(evm.FlagEVMRPCAddress, "", "Address for in-process Geth JSON-RPC server (e.g., 127.0.0.1:8545)") + + cmd.MarkFlagsMutuallyExclusive(evm.FlagEVMInProcessGeth, evm.FlagEvmEthURL) + cmd.MarkFlagsMutuallyExclusive(evm.FlagEVMInProcessGeth, evm.FlagEvmEngineURL) + cmd.MarkFlagsMutuallyExclusive(evm.FlagEVMInProcessGeth, evm.FlagEvmJWTSecretFile) + cmd.MarkFlagsMutuallyExclusive(evm.FlagEVMInProcessGeth, evm.FlagEvmGenesisHash) } diff --git a/apps/evm/go.mod b/apps/evm/go.mod index b1b1b73fb..600c19d9e 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -26,6 +26,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect @@ -47,6 +48,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect @@ -72,6 +74,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -165,6 +168,7 @@ require ( github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect diff --git a/apps/evm/go.sum b/apps/evm/go.sum index 1be27a9c5..c46d76e0d 100644 --- a/apps/evm/go.sum +++ b/apps/evm/go.sum @@ -257,6 +257,8 @@ github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhk github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= @@ -407,6 +409,9 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= @@ -441,6 +446,7 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -548,6 +554,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -613,6 +620,7 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= @@ -779,8 +787,22 @@ github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOo github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -924,6 +946,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -1127,6 +1150,7 @@ golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1146,6 +1170,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1159,6 +1184,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1254,6 +1280,7 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1262,8 +1289,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1286,6 +1316,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1444,6 +1475,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1646,10 +1678,15 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/execution/evm/engine_geth.go b/execution/evm/engine_geth.go new file mode 100644 index 000000000..d70a4d012 --- /dev/null +++ b/execution/evm/engine_geth.go @@ -0,0 +1,724 @@ +package evm + +import ( + "context" + "errors" + "fmt" + "math/big" + "net" + "net/http" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" + ds "github.com/ipfs/go-datastore" + "github.com/rs/zerolog" +) + +var ( + _ EngineRPCClient = (*gethEngineClient)(nil) + _ EthRPCClient = (*gethEthClient)(nil) +) + +// GethBackend is the in-process geth execution engine. +type GethBackend struct { + db ethdb.Database + chainConfig *params.ChainConfig + blockchain *core.BlockChain + txPool *txpool.TxPool + + mu sync.Mutex + pendingPayload *pendingPayload + + rpcServer *rpc.Server + httpServer *http.Server + rpcListener net.Listener + logger zerolog.Logger +} + +// pendingPayload represents a single in-flight payload build. +type pendingPayload struct { + id engine.PayloadID + parentHash common.Hash + timestamp uint64 + prevRandao common.Hash + feeRecipient common.Address + withdrawals []*types.Withdrawal + transactions [][]byte + gasLimit uint64 + built *engine.ExecutableData +} + +type gethEngineClient struct { + backend *GethBackend + logger zerolog.Logger +} + +type gethEthClient struct { + backend *GethBackend + logger zerolog.Logger +} + +// NewEngineExecutionClientWithGeth creates an EngineClient using in-process geth. +func NewEngineExecutionClientWithGeth( + genesis *core.Genesis, + feeRecipient common.Address, + db ds.Batching, + rpcAddress string, + logger zerolog.Logger, +) (*EngineClient, error) { + if db == nil { + return nil, errors.New("db is required") + } + if genesis == nil || genesis.Config == nil { + return nil, errors.New("genesis configuration is required") + } + + backend, err := newGethBackend(genesis, db, logger) + if err != nil { + return nil, fmt.Errorf("failed to create geth backend: %w", err) + } + + if rpcAddress != "" { + if err := backend.StartRPCServer(rpcAddress); err != nil { + backend.Close() + return nil, fmt.Errorf("failed to start RPC server: %w", err) + } + } + + genesisBlock := backend.blockchain.Genesis() + genesisHash := genesisBlock.Hash() + + logger.Info(). + Str("genesis_hash", genesisHash.Hex()). + Str("chain_id", genesis.Config.ChainID.String()). + Msg("created in-process geth execution client") + + return &EngineClient{ + engineClient: &gethEngineClient{backend: backend, logger: logger.With().Str("component", "geth-engine").Logger()}, + ethClient: &gethEthClient{backend: backend, logger: logger.With().Str("component", "geth-eth").Logger()}, + genesisHash: genesisHash, + feeRecipient: feeRecipient, + store: NewEVMStore(db), + currentHeadBlockHash: genesisHash, + currentSafeBlockHash: genesisHash, + currentFinalizedBlockHash: genesisHash, + blockHashCache: make(map[uint64]common.Hash), + logger: logger, + }, nil +} + +func newGethBackend(genesis *core.Genesis, db ds.Batching, logger zerolog.Logger) (*GethBackend, error) { + ethdb := rawdb.NewDatabase(&wrapper{db}) + trieDB := triedb.NewDatabase(ethdb, nil) + + // Auto-populate blob config for Cancun/Prague if needed + if genesis.Config != nil && genesis.Config.BlobScheduleConfig == nil { + if genesis.Config.CancunTime != nil || genesis.Config.PragueTime != nil { + genesis.Config.BlobScheduleConfig = ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + } + } + } + + chainConfig, genesisHash, _, err := core.SetupGenesisBlockWithOverride(ethdb, trieDB, genesis, nil) + if err != nil { + return nil, fmt.Errorf("failed to setup genesis: %w", err) + } + + logger.Info().Str("genesis_hash", genesisHash.Hex()).Msg("initialized genesis") + + bcConfig := core.DefaultConfig().WithStateScheme(rawdb.HashScheme) + blockchain, err := core.NewBlockChain(ethdb, genesis, newSovereignBeacon(), bcConfig) + if err != nil { + return nil, fmt.Errorf("failed to create blockchain: %w", err) + } + + if head := blockchain.CurrentBlock(); head != nil { + logger.Info().Uint64("height", head.Number.Uint64()).Msg("resuming from chain state") + } + + backend := &GethBackend{ + db: ethdb, + chainConfig: chainConfig, + blockchain: blockchain, + logger: logger, + } + + txPoolConfig := legacypool.DefaultConfig + txPoolConfig.NoLocals = true + legacyPool := legacypool.New(txPoolConfig, blockchain) + txPool, err := txpool.New(0, blockchain, []txpool.SubPool{legacyPool}) + if err != nil { + return nil, fmt.Errorf("failed to create tx pool: %w", err) + } + backend.txPool = txPool + + return backend, nil +} + +// Close shuts down the backend. +func (b *GethBackend) Close() error { + b.logger.Info().Msg("shutting down geth backend") + + if b.httpServer != nil { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = b.httpServer.Shutdown(ctx) + } + if b.rpcServer != nil { + b.rpcServer.Stop() + } + if b.txPool != nil { + b.txPool.Close() + } + if b.blockchain != nil { + b.blockchain.Stop() + } + if b.db != nil { + return b.db.Close() + } + return nil +} + +// ForkchoiceUpdated handles forkchoice updates and optionally starts payload building. +func (g *gethEngineClient) ForkchoiceUpdated(ctx context.Context, fcState engine.ForkchoiceStateV1, attrs map[string]any) (*engine.ForkChoiceResponse, error) { + g.backend.mu.Lock() + defer g.backend.mu.Unlock() + + headBlock := g.backend.blockchain.GetBlockByHash(fcState.HeadBlockHash) + if headBlock == nil { + return &engine.ForkChoiceResponse{ + PayloadStatus: engine.PayloadStatusV1{Status: engine.SYNCING}, + }, nil + } + + if _, err := g.backend.blockchain.SetCanonical(headBlock); err != nil { + return nil, fmt.Errorf("failed to set canonical head: %w", err) + } + + response := &engine.ForkChoiceResponse{ + PayloadStatus: engine.PayloadStatusV1{ + Status: engine.VALID, + LatestValidHash: &fcState.HeadBlockHash, + }, + } + + if attrs != nil { + payload, err := g.parsePayloadAttributes(fcState.HeadBlockHash, attrs) + if err != nil { + return nil, fmt.Errorf("invalid payload attributes: %w", err) + } + g.backend.pendingPayload = payload + response.PayloadID = &payload.id + + g.logger.Info(). + Str("payload_id", payload.id.String()). + Str("parent", fcState.HeadBlockHash.Hex()). + Uint64("timestamp", payload.timestamp). + Int("txs", len(payload.transactions)). + Msg("started payload build") + } + + return response, nil +} + +func (g *gethEngineClient) parsePayloadAttributes(parentHash common.Hash, attrs map[string]any) (*pendingPayload, error) { + p := &pendingPayload{ + parentHash: parentHash, + withdrawals: []*types.Withdrawal{}, + } + + // Generate simple sequential payload ID + p.id = engine.PayloadID{byte(time.Now().UnixNano() & 0xFF)} + + // Timestamp (required) + if ts, ok := attrs["timestamp"]; ok { + switch v := ts.(type) { + case int64: + p.timestamp = uint64(v) + case uint64: + p.timestamp = v + case float64: + p.timestamp = uint64(v) + default: + return nil, fmt.Errorf("invalid timestamp type: %T", ts) + } + } else { + return nil, errors.New("timestamp required") + } + + // PrevRandao + if pr, ok := attrs["prevRandao"]; ok { + switch v := pr.(type) { + case common.Hash: + p.prevRandao = v + case string: + p.prevRandao = common.HexToHash(v) + case []byte: + p.prevRandao = common.BytesToHash(v) + } + } + + // Fee recipient (required) + if fr, ok := attrs["suggestedFeeRecipient"]; ok { + switch v := fr.(type) { + case common.Address: + p.feeRecipient = v + case string: + p.feeRecipient = common.HexToAddress(v) + case []byte: + p.feeRecipient = common.BytesToAddress(v) + } + } else { + return nil, errors.New("suggestedFeeRecipient required") + } + + // Transactions + if txs, ok := attrs["transactions"]; ok { + switch v := txs.(type) { + case []string: + for _, txHex := range v { + if txBytes := common.FromHex(txHex); len(txBytes) > 0 { + p.transactions = append(p.transactions, txBytes) + } + } + case [][]byte: + p.transactions = v + } + } + + // Gas limit + if gl, ok := attrs["gasLimit"]; ok { + switch v := gl.(type) { + case uint64: + p.gasLimit = v + case int64: + p.gasLimit = uint64(v) + case float64: + p.gasLimit = uint64(v) + case *uint64: + if v != nil { + p.gasLimit = *v + } + } + } + + // Withdrawals + if w, ok := attrs["withdrawals"]; ok { + if withdrawals, ok := w.([]*types.Withdrawal); ok { + p.withdrawals = withdrawals + } + } + + return p, nil +} + +// GetPayload builds and returns the pending payload. +func (g *gethEngineClient) GetPayload(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { + g.backend.mu.Lock() + defer g.backend.mu.Unlock() + + p := g.backend.pendingPayload + if p == nil || p.id != payloadID { + return nil, fmt.Errorf("unknown payload ID: %s", payloadID.String()) + } + + if p.built == nil { + built, err := g.buildPayload(ctx, p) + if err != nil { + return nil, fmt.Errorf("failed to build payload: %w", err) + } + p.built = built + + g.logger.Info(). + Uint64("number", built.Number). + Str("hash", built.BlockHash.Hex()). + Int("txs", len(built.Transactions)). + Uint64("gas_used", built.GasUsed). + Msg("built payload") + } + + // Clear pending after retrieval + g.backend.pendingPayload = nil + + return &engine.ExecutionPayloadEnvelope{ + ExecutionPayload: p.built, + BlockValue: big.NewInt(0), + BlobsBundle: &engine.BlobsBundle{}, + }, nil +} + +func (g *gethEngineClient) buildPayload(ctx context.Context, p *pendingPayload) (*engine.ExecutableData, error) { + parent := g.backend.blockchain.GetBlockByHash(p.parentHash) + if parent == nil { + return nil, fmt.Errorf("parent not found: %s", p.parentHash.Hex()) + } + + if p.timestamp < parent.Time() { + return nil, fmt.Errorf("timestamp %d < parent %d", p.timestamp, parent.Time()) + } + + number := new(big.Int).Add(parent.Number(), big.NewInt(1)) + gasLimit := p.gasLimit + if gasLimit == 0 { + gasLimit = parent.GasLimit() + } + + var baseFee *big.Int + if g.backend.chainConfig.IsLondon(number) { + baseFee = calcBaseFee(g.backend.chainConfig, parent.Header()) + } + + header := &types.Header{ + ParentHash: p.parentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: p.feeRecipient, + Number: number, + GasLimit: gasLimit, + Time: p.timestamp, + MixDigest: p.prevRandao, + Difficulty: big.NewInt(0), + BaseFee: baseFee, + WithdrawalsHash: &types.EmptyWithdrawalsHash, + BlobGasUsed: new(uint64), + ExcessBlobGas: new(uint64), + ParentBeaconRoot: &common.Hash{}, + RequestsHash: &types.EmptyRequestsHash, + } + + stateDB, err := g.backend.blockchain.StateAt(parent.Root()) + if err != nil { + return nil, fmt.Errorf("failed to get state: %w", err) + } + + var ( + txs types.Transactions + receipts []*types.Receipt + gasUsed uint64 + ) + + blockContext := core.NewEVMBlockContext(header, g.backend.blockchain, nil) + gp := new(core.GasPool).AddGas(gasLimit) + + for _, txBytes := range p.transactions { + if len(txBytes) == 0 { + continue + } + + var tx types.Transaction + if err := tx.UnmarshalBinary(txBytes); err != nil { + continue + } + + stateDB.SetTxContext(tx.Hash(), len(txs)) + receipt, err := applyTransaction(g.backend.chainConfig, blockContext, gp, stateDB, header, &tx, &gasUsed) + if err != nil { + g.logger.Debug().Str("tx", tx.Hash().Hex()).Err(err).Msg("tx failed") + continue + } + + txs = append(txs, &tx) + receipts = append(receipts, receipt) + } + + // Process withdrawals + for _, w := range p.withdrawals { + amount := new(big.Int).SetUint64(w.Amount) + amount.Mul(amount, big.NewInt(params.GWei)) + stateDB.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal) + } + + header.GasUsed = gasUsed + header.Root = stateDB.IntermediateRoot(g.backend.chainConfig.IsEIP158(number)) + header.TxHash = types.DeriveSha(txs, trie.NewListHasher()) + header.ReceiptHash = types.DeriveSha(types.Receipts(receipts), trie.NewListHasher()) + header.Bloom = createBloom(receipts) + + if len(p.withdrawals) > 0 { + wh := types.DeriveSha(types.Withdrawals(p.withdrawals), trie.NewListHasher()) + header.WithdrawalsHash = &wh + } + + block := types.NewBlock(header, &types.Body{ + Transactions: txs, + Withdrawals: p.withdrawals, + }, receipts, trie.NewListHasher()) + + txData := make([][]byte, len(txs)) + for i, tx := range txs { + txData[i], _ = tx.MarshalBinary() + } + + return &engine.ExecutableData{ + ParentHash: header.ParentHash, + FeeRecipient: header.Coinbase, + StateRoot: header.Root, + ReceiptsRoot: header.ReceiptHash, + LogsBloom: header.Bloom[:], + Random: header.MixDigest, + Number: header.Number.Uint64(), + GasLimit: header.GasLimit, + GasUsed: header.GasUsed, + Timestamp: header.Time, + ExtraData: header.Extra, + BaseFeePerGas: header.BaseFee, + BlockHash: block.Hash(), + Transactions: txData, + Withdrawals: p.withdrawals, + BlobGasUsed: header.BlobGasUsed, + ExcessBlobGas: header.ExcessBlobGas, + }, nil +} + +// NewPayload validates and inserts a new block. +func (g *gethEngineClient) NewPayload(ctx context.Context, payload *engine.ExecutableData, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) { + g.backend.mu.Lock() + defer g.backend.mu.Unlock() + + if payload == nil { + return nil, errors.New("payload required") + } + + parent := g.backend.blockchain.GetBlockByHash(payload.ParentHash) + if parent == nil { + return &engine.PayloadStatusV1{Status: engine.SYNCING}, nil + } + + if payload.Number != parent.NumberU64()+1 { + parentHash := parent.Hash() + return &engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &parentHash}, nil + } + + if payload.Timestamp < parent.Time() { + parentHash := parent.Hash() + return &engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &parentHash}, nil + } + + var txs types.Transactions + for _, txData := range payload.Transactions { + var tx types.Transaction + if err := tx.UnmarshalBinary(txData); err != nil { + parentHash := parent.Hash() + return &engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &parentHash}, nil + } + txs = append(txs, &tx) + } + + gasLimit := payload.GasLimit + if gasLimit == 0 { + gasLimit = parent.GasLimit() + } + + // Build the header from payload data + // NOTE: We must set TxHash and ReceiptHash from the payload, not recompute them, + // because types.NewBlock would overwrite them based on the passed transactions/receipts. + withdrawalsHash := types.EmptyWithdrawalsHash + if len(payload.Withdrawals) > 0 { + withdrawalsHash = types.DeriveSha(types.Withdrawals(payload.Withdrawals), trie.NewListHasher()) + } + + header := &types.Header{ + ParentHash: payload.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: payload.FeeRecipient, + Root: payload.StateRoot, + TxHash: types.DeriveSha(txs, trie.NewListHasher()), + ReceiptHash: payload.ReceiptsRoot, + Bloom: types.BytesToBloom(payload.LogsBloom), + Difficulty: big.NewInt(0), + Number: big.NewInt(int64(payload.Number)), + GasLimit: gasLimit, + GasUsed: payload.GasUsed, + Time: payload.Timestamp, + Extra: payload.ExtraData, + MixDigest: payload.Random, + BaseFee: payload.BaseFeePerGas, + WithdrawalsHash: &withdrawalsHash, + BlobGasUsed: payload.BlobGasUsed, + ExcessBlobGas: payload.ExcessBlobGas, + ParentBeaconRoot: &common.Hash{}, + RequestsHash: &types.EmptyRequestsHash, + } + + // Compute block hash directly from header - don't use types.NewBlock which overwrites hashes + block := types.NewBlockWithHeader(header).WithBody(types.Body{ + Transactions: txs, + Withdrawals: payload.Withdrawals, + }) + + if block.Hash() != payload.BlockHash { + g.logger.Warn(). + Str("expected", payload.BlockHash.Hex()). + Str("got", block.Hash().Hex()). + Msg("block hash mismatch") + parentHash := parent.Hash() + return &engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &parentHash}, nil + } + + if _, err := g.backend.blockchain.InsertBlockWithoutSetHead(block, false); err != nil { + g.logger.Warn().Err(err).Str("hash", block.Hash().Hex()).Msg("block insertion failed") + parentHash := parent.Hash() + return &engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &parentHash}, nil + } + + blockHash := block.Hash() + g.logger.Info(). + Uint64("number", block.NumberU64()). + Str("hash", blockHash.Hex()). + Int("txs", len(txs)). + Msg("payload validated") + + return &engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &blockHash}, nil +} + +// HeaderByNumber returns a header by number. +func (g *gethEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + if number == nil { + header := g.backend.blockchain.CurrentBlock() + if header == nil { + return nil, errors.New("no current block") + } + return header, nil + } + block := g.backend.blockchain.GetBlockByNumber(number.Uint64()) + if block == nil { + return nil, fmt.Errorf("block %d not found", number.Uint64()) + } + return block.Header(), nil +} + +// GetTxs returns pending transactions. +func (g *gethEthClient) GetTxs(ctx context.Context) ([]string, error) { + pending := g.backend.txPool.Pending(txpool.PendingFilter{}) + var result []string + for _, txs := range pending { + for _, lazyTx := range txs { + if tx := lazyTx.Tx; tx != nil { + if data, err := tx.MarshalBinary(); err == nil { + result = append(result, "0x"+common.Bytes2Hex(data)) + } + } + } + } + return result, nil +} + +// calcBaseFee calculates EIP-1559 base fee. +func calcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { + next := new(big.Int).Add(parent.Number, big.NewInt(1)) + if !config.IsLondon(next) { + return nil + } + if !config.IsLondon(parent.Number) || parent.BaseFee == nil { + return big.NewInt(params.InitialBaseFee) + } + + target := parent.GasLimit / 2 + if target == 0 { + return new(big.Int).Set(parent.BaseFee) + } + + baseFee := new(big.Int).Set(parent.BaseFee) + if parent.GasUsed == target { + return baseFee + } + + if parent.GasUsed > target { + delta := new(big.Int).SetUint64(parent.GasUsed - target) + delta.Mul(delta, parent.BaseFee) + delta.Div(delta, new(big.Int).SetUint64(target)) + delta.Div(delta, big.NewInt(8)) + if delta.Sign() == 0 { + delta = big.NewInt(1) + } + return baseFee.Add(baseFee, delta) + } + + delta := new(big.Int).SetUint64(target - parent.GasUsed) + delta.Mul(delta, parent.BaseFee) + delta.Div(delta, new(big.Int).SetUint64(target)) + delta.Div(delta, big.NewInt(8)) + baseFee.Sub(baseFee, delta) + if baseFee.Sign() < 0 { + return big.NewInt(0) + } + return baseFee +} + +// applyTransaction executes a transaction and returns the receipt. +func applyTransaction( + config *params.ChainConfig, + blockContext vm.BlockContext, + gp *core.GasPool, + stateDB *state.StateDB, + header *types.Header, + tx *types.Transaction, + usedGas *uint64, +) (*types.Receipt, error) { + msg, err := core.TransactionToMessage(tx, types.LatestSigner(config), header.BaseFee) + if err != nil { + return nil, err + } + + evm := vm.NewEVM(blockContext, stateDB, config, vm.Config{}) + evm.SetTxContext(core.NewEVMTxContext(msg)) + + result, err := core.ApplyMessage(evm, msg, gp) + if err != nil { + return nil, err + } + + *usedGas += result.UsedGas + + receipt := &types.Receipt{ + Type: tx.Type(), + CumulativeGasUsed: *usedGas, + TxHash: tx.Hash(), + GasUsed: result.UsedGas, + Logs: stateDB.GetLogs(tx.Hash(), header.Number.Uint64(), common.Hash{}, header.Number.Uint64()), + BlockNumber: header.Number, + } + + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + + receipt.Bloom = types.CreateBloom(receipt) + + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(msg.From, tx.Nonce()) + } + + return receipt, nil +} + +func createBloom(receipts []*types.Receipt) types.Bloom { + var bloom types.Bloom + for _, r := range receipts { + b := types.CreateBloom(r) + for i := range bloom { + bloom[i] |= b[i] + } + } + return bloom +} diff --git a/execution/evm/engine_geth_consensus.go b/execution/evm/engine_geth_consensus.go new file mode 100644 index 000000000..844583bcb --- /dev/null +++ b/execution/evm/engine_geth_consensus.go @@ -0,0 +1,133 @@ +package evm + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +const ( + // maxExtraDataSize is the maximum allowed size for block extra data (32 bytes). + maxExtraDataSize = 32 + + // gasLimitBoundDivisor is the bound divisor for gas limit changes between blocks. + // Gas limit can only change by 1/1024 per block. + gasLimitBoundDivisor = 1024 + + // minGasLimit is the minimum gas limit allowed for blocks. + minGasLimit = 5000 +) + +// sovereignBeacon wraps the standard beacon consensus engine but allows +// equal timestamps (timestamp >= parent.timestamp) instead of requiring +// strictly increasing timestamps (timestamp > parent.timestamp). +// This enables subsecond block times for sovereign rollups while keeping +// all other beacon consensus rules intact. +type sovereignBeacon struct { + consensus.Engine +} + +// newSovereignBeacon creates a beacon consensus engine that allows equal timestamps. +func newSovereignBeacon() *sovereignBeacon { + return &sovereignBeacon{ + Engine: beacon.New(nil), + } +} + +// VerifyHeader checks whether a header conforms to the consensus rules. +// This override allows equal timestamps for subsecond block times. +func (sb *sovereignBeacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { + // Get parent header + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + + // Check timestamp - allow equal (>=) instead of strictly greater (>) + if header.Time < parent.Time { + return errors.New("invalid timestamp: must be >= parent timestamp") + } + + // Verify difficulty is zero (PoS requirement) + if header.Difficulty.Cmp(common.Big0) != 0 { + return errors.New("invalid difficulty: must be zero for PoS") + } + + // Verify nonce is zero (PoS requirement) + if header.Nonce != (types.BlockNonce{}) { + return errors.New("invalid nonce: must be zero for PoS") + } + + // Verify uncle hash is empty (PoS requirement) + if header.UncleHash != types.EmptyUncleHash { + return errors.New("invalid uncle hash: must be empty for PoS") + } + + // Verify extra data size limit + if len(header.Extra) > maxExtraDataSize { + return fmt.Errorf("invalid extra data size: have %d, max %d", len(header.Extra), maxExtraDataSize) + } + + // Verify gas limit bounds + if header.GasLimit > params.MaxGasLimit { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) + } + if header.GasLimit < minGasLimit { + return fmt.Errorf("invalid gasLimit: have %v, min %v", header.GasLimit, minGasLimit) + } + + // Verify gas limit change is within bounds (can only change by 1/1024 per block) + diff := int64(header.GasLimit) - int64(parent.GasLimit) + if diff < 0 { + diff = -diff + } + limit := parent.GasLimit / gasLimitBoundDivisor + if uint64(diff) >= limit { + return fmt.Errorf("invalid gas limit: have %d, want %d ± %d", header.GasLimit, parent.GasLimit, limit-1) + } + + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + + // Verify the header's EIP-1559 attributes. + if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { + return err + } + + // Verify EIP-4844 blob gas fields if Cancun is active + config := chain.Config() + if config.IsCancun(header.Number, header.Time) { + if err := eip4844.VerifyEIP4844Header(config, parent, header); err != nil { + return err + } + } + + return nil +} + +// VerifyHeaders verifies a batch of headers concurrently. +func (sb *sovereignBeacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for _, header := range headers { + select { + case <-abort: + return + case results <- sb.VerifyHeader(chain, header): + } + } + }() + + return abort, results +} diff --git a/execution/evm/engine_geth_db.go b/execution/evm/engine_geth_db.go new file mode 100644 index 000000000..bc54ac6a4 --- /dev/null +++ b/execution/evm/engine_geth_db.go @@ -0,0 +1,309 @@ +package evm + +import ( + "bytes" + "context" + "errors" + "sort" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/query" + "github.com/syndtr/goleveldb/leveldb" +) + +var _ ethdb.KeyValueStore = (*wrapper)(nil) + +type wrapper struct { + ds datastore.Batching +} + +// NewEVMDB creates an ethdb.KeyValueStore backed by a go-datastore. +func NewEVMDB(ds datastore.Batching) ethdb.KeyValueStore { + return &wrapper{ds} +} + +func toKey(key []byte) datastore.Key { + return datastore.NewKey(string(key)) +} + +func fromKey(key string) []byte { + if strings.HasPrefix(key, "/") { + return []byte(key[1:]) + } + return []byte(key) +} + +func (w *wrapper) Has(key []byte) (bool, error) { + return w.ds.Has(context.Background(), toKey(key)) +} + +func (w *wrapper) Get(key []byte) ([]byte, error) { + val, err := w.ds.Get(context.Background(), toKey(key)) + if errors.Is(err, datastore.ErrNotFound) { + return nil, leveldb.ErrNotFound + } + return val, err +} + +func (w *wrapper) Put(key []byte, value []byte) error { + return w.ds.Put(context.Background(), toKey(key), value) +} + +func (w *wrapper) Delete(key []byte) error { + return w.ds.Delete(context.Background(), toKey(key)) +} + +func (w *wrapper) DeleteRange(start, end []byte) error { + ctx := context.Background() + results, err := w.ds.Query(ctx, query.Query{KeysOnly: true}) + if err != nil { + return err + } + defer results.Close() + + for result := range results.Next() { + if result.Error != nil { + return result.Error + } + keyBytes := fromKey(result.Key) + if bytes.Compare(keyBytes, start) >= 0 && bytes.Compare(keyBytes, end) < 0 { + if err := w.ds.Delete(ctx, datastore.NewKey(result.Key)); err != nil { + return err + } + } + } + return nil +} + +func (w *wrapper) NewBatch() ethdb.Batch { + return &batch{ds: w.ds} +} + +func (w *wrapper) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ds: w.ds, ops: make([]batchOp, 0, size)} +} + +func (w *wrapper) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + return newIterator(w.ds, prefix, start) +} + +func (w *wrapper) Stat() (string, error) { + return "go-datastore wrapper", nil +} + +func (w *wrapper) SyncKeyValue() error { + return w.ds.Sync(context.Background(), datastore.NewKey("/")) +} + +func (w *wrapper) Compact(start []byte, limit []byte) error { + return nil // no-op +} + +func (w *wrapper) Close() error { + return w.ds.Close() +} + +// --- Batch --- + +type batchOp struct { + key []byte + value []byte + delete bool +} + +type batch struct { + ds datastore.Batching + ops []batchOp + size int + mu sync.Mutex +} + +func (b *batch) Put(key, value []byte) error { + b.mu.Lock() + defer b.mu.Unlock() + b.ops = append(b.ops, batchOp{key: append([]byte{}, key...), value: append([]byte{}, value...)}) + b.size += len(key) + len(value) + return nil +} + +func (b *batch) Delete(key []byte) error { + b.mu.Lock() + defer b.mu.Unlock() + b.ops = append(b.ops, batchOp{key: append([]byte{}, key...), delete: true}) + b.size += len(key) + return nil +} + +func (b *batch) DeleteRange(start, end []byte) error { + ctx := context.Background() + results, err := b.ds.Query(ctx, query.Query{KeysOnly: true}) + if err != nil { + return err + } + defer results.Close() + + b.mu.Lock() + defer b.mu.Unlock() + + for result := range results.Next() { + if result.Error != nil { + return result.Error + } + keyBytes := fromKey(result.Key) + if bytes.Compare(keyBytes, start) >= 0 && bytes.Compare(keyBytes, end) < 0 { + b.ops = append(b.ops, batchOp{key: append([]byte{}, keyBytes...), delete: true}) + b.size += len(keyBytes) + } + } + return nil +} + +func (b *batch) ValueSize() int { + b.mu.Lock() + defer b.mu.Unlock() + return b.size +} + +func (b *batch) Write() error { + b.mu.Lock() + defer b.mu.Unlock() + + dsBatch, err := b.ds.Batch(context.Background()) + if err != nil { + return err + } + + ctx := context.Background() + for _, op := range b.ops { + if op.delete { + if err := dsBatch.Delete(ctx, toKey(op.key)); err != nil { + return err + } + } else { + if err := dsBatch.Put(ctx, toKey(op.key), op.value); err != nil { + return err + } + } + } + return dsBatch.Commit(ctx) +} + +func (b *batch) Reset() { + b.mu.Lock() + defer b.mu.Unlock() + b.ops = b.ops[:0] + b.size = 0 +} + +func (b *batch) Replay(w ethdb.KeyValueWriter) error { + b.mu.Lock() + defer b.mu.Unlock() + + for _, op := range b.ops { + if op.delete { + if err := w.Delete(op.key); err != nil { + return err + } + } else { + if err := w.Put(op.key, op.value); err != nil { + return err + } + } + } + return nil +} + +// --- Iterator --- + +type iterator struct { + entries []query.Entry + index int + err error + closed bool + mu sync.Mutex +} + +func newIterator(ds datastore.Batching, prefix, start []byte) *iterator { + q := query.Query{} + if len(prefix) > 0 { + q.Prefix = "/" + string(prefix) + } + + results, err := ds.Query(context.Background(), q) + if err != nil { + return &iterator{err: err, index: -1} + } + + var entries []query.Entry + for result := range results.Next() { + if result.Error != nil { + results.Close() + return &iterator{err: result.Error, index: -1} + } + + keyBytes := fromKey(result.Key) + if len(prefix) > 0 && !bytes.HasPrefix(keyBytes, prefix) { + continue + } + if len(start) > 0 && bytes.Compare(keyBytes, start) < 0 { + continue + } + entries = append(entries, query.Entry{Key: result.Key, Value: result.Value}) + } + results.Close() + + // Sort for deterministic ordering + sort.Slice(entries, func(i, j int) bool { + return bytes.Compare(fromKey(entries[i].Key), fromKey(entries[j].Key)) < 0 + }) + + return &iterator{entries: entries, index: -1} +} + +func (it *iterator) Next() bool { + it.mu.Lock() + defer it.mu.Unlock() + + if it.closed || it.err != nil { + return false + } + it.index++ + return it.index < len(it.entries) +} + +func (it *iterator) Error() error { + it.mu.Lock() + defer it.mu.Unlock() + return it.err +} + +func (it *iterator) Key() []byte { + it.mu.Lock() + defer it.mu.Unlock() + + if it.closed || it.index < 0 || it.index >= len(it.entries) { + return nil + } + return fromKey(it.entries[it.index].Key) +} + +func (it *iterator) Value() []byte { + it.mu.Lock() + defer it.mu.Unlock() + + if it.closed || it.index < 0 || it.index >= len(it.entries) { + return nil + } + return it.entries[it.index].Value +} + +func (it *iterator) Release() { + it.mu.Lock() + defer it.mu.Unlock() + + it.closed = true + it.entries = nil +} diff --git a/execution/evm/engine_geth_rpc.go b/execution/evm/engine_geth_rpc.go new file mode 100644 index 000000000..dfa6d1846 --- /dev/null +++ b/execution/evm/engine_geth_rpc.go @@ -0,0 +1,710 @@ +package evm + +import ( + "errors" + "fmt" + "math/big" + "net" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/rs/zerolog" +) + +// EthRPCService implements essential eth_ JSON-RPC methods for block explorers. +type EthRPCService struct { + backend *GethBackend + logger zerolog.Logger +} + +// NetRPCService implements the net_ JSON-RPC namespace. +type NetRPCService struct { + backend *GethBackend +} + +// Web3RPCService implements the web3_ JSON-RPC namespace. +type Web3RPCService struct{} + +// TransactionArgs represents transaction call arguments. +type TransactionArgs struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` + MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` + Nonce *hexutil.Uint64 `json:"nonce"` +} + +// StartRPCServer starts a minimal JSON-RPC server for block explorer compatibility. +func (b *GethBackend) StartRPCServer(address string) error { + if address == "" { + return nil + } + + b.rpcServer = rpc.NewServer() + + if err := b.rpcServer.RegisterName("eth", &EthRPCService{backend: b, logger: b.logger.With().Str("rpc", "eth").Logger()}); err != nil { + return fmt.Errorf("failed to register eth: %w", err) + } + if err := b.rpcServer.RegisterName("net", &NetRPCService{backend: b}); err != nil { + return fmt.Errorf("failed to register net: %w", err) + } + if err := b.rpcServer.RegisterName("web3", &Web3RPCService{}); err != nil { + return fmt.Errorf("failed to register web3: %w", err) + } + + listener, err := net.Listen("tcp", address) + if err != nil { + return fmt.Errorf("failed to listen on %s: %w", address, err) + } + b.rpcListener = listener + + b.httpServer = &http.Server{ + Handler: &rpcHandler{rpcServer: b.rpcServer}, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + go func() { + b.logger.Info().Str("address", address).Msg("starting JSON-RPC server") + if err := b.httpServer.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { + b.logger.Error().Err(err).Msg("JSON-RPC server error") + } + }() + + return nil +} + +type rpcHandler struct { + rpcServer *rpc.Server +} + +func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + w.Header().Set("Content-Type", "application/json") + + if r.Method == "OPTIONS" { + return + } + if r.Method == "GET" { + _, _ = w.Write([]byte(`{"jsonrpc":"2.0","result":"ev-node RPC","id":null}`)) + return + } + if r.Method == "POST" { + h.rpcServer.ServeHTTP(w, r) + return + } + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) +} + +// --- Web3 namespace --- + +func (s *Web3RPCService) ClientVersion() string { + return "ev-node/1.0.0" +} + +func (s *Web3RPCService) Sha3(input hexutil.Bytes) hexutil.Bytes { + return crypto.Keccak256(input) +} + +// --- Net namespace --- + +func (s *NetRPCService) Version() string { + return s.backend.chainConfig.ChainID.String() +} + +func (s *NetRPCService) Listening() bool { + return true +} + +func (s *NetRPCService) PeerCount() hexutil.Uint { + return 0 +} + +// --- Eth namespace --- + +func (s *EthRPCService) ChainId() *hexutil.Big { + return (*hexutil.Big)(s.backend.chainConfig.ChainID) +} + +func (s *EthRPCService) BlockNumber() hexutil.Uint64 { + if header := s.backend.blockchain.CurrentBlock(); header != nil { + return hexutil.Uint64(header.Number.Uint64()) + } + return 0 +} + +func (s *EthRPCService) GasPrice() *hexutil.Big { + header := s.backend.blockchain.CurrentBlock() + if header == nil || header.BaseFee == nil { + return (*hexutil.Big)(big.NewInt(params.InitialBaseFee)) + } + return (*hexutil.Big)(new(big.Int).Add(header.BaseFee, big.NewInt(1e9))) +} + +func (s *EthRPCService) MaxPriorityFeePerGas() *hexutil.Big { + return (*hexutil.Big)(big.NewInt(1e9)) +} + +func (s *EthRPCService) Syncing() (interface{}, error) { + return false, nil +} + +func (s *EthRPCService) Accounts() []common.Address { + return []common.Address{} +} + +func (s *EthRPCService) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*hexutil.Big, error) { + stateDB, err := s.stateAtBlock(blockNr) + if err != nil { + return nil, err + } + return (*hexutil.Big)(stateDB.GetBalance(address).ToBig()), nil +} + +func (s *EthRPCService) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { + stateDB, err := s.stateAtBlock(blockNr) + if err != nil { + return 0, err + } + return hexutil.Uint64(stateDB.GetNonce(address)), nil +} + +func (s *EthRPCService) GetCode(address common.Address, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { + stateDB, err := s.stateAtBlock(blockNr) + if err != nil { + return nil, err + } + return stateDB.GetCode(address), nil +} + +func (s *EthRPCService) GetStorageAt(address common.Address, position hexutil.Big, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { + stateDB, err := s.stateAtBlock(blockNr) + if err != nil { + return nil, err + } + value := stateDB.GetState(address, common.BigToHash((*big.Int)(&position))) + return value[:], nil +} + +func (s *EthRPCService) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { + var block *types.Block + if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber { + if header := s.backend.blockchain.CurrentBlock(); header != nil { + block = s.backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + } + } else { + block = s.backend.blockchain.GetBlockByNumber(uint64(blockNr)) + } + if block == nil { + return nil, nil + } + return s.formatBlock(block, fullTx), nil +} + +func (s *EthRPCService) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { + block := s.backend.blockchain.GetBlockByHash(hash) + if block == nil { + return nil, nil + } + return s.formatBlock(block, fullTx), nil +} + +func (s *EthRPCService) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { + block := s.backend.blockchain.GetBlockByHash(hash) + if block == nil { + return nil + } + n := hexutil.Uint(len(block.Transactions())) + return &n +} + +func (s *EthRPCService) GetBlockTransactionCountByNumber(blockNr rpc.BlockNumber) *hexutil.Uint { + var block *types.Block + if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber { + if header := s.backend.blockchain.CurrentBlock(); header != nil { + block = s.backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + } + } else { + block = s.backend.blockchain.GetBlockByNumber(uint64(blockNr)) + } + if block == nil { + return nil + } + n := hexutil.Uint(len(block.Transactions())) + return &n +} + +func (s *EthRPCService) GetTransactionByHash(hash common.Hash) (map[string]interface{}, error) { + currentBlock := s.backend.blockchain.CurrentBlock() + if currentBlock == nil { + return nil, nil + } + + for i := currentBlock.Number.Uint64(); i > 0 && i > currentBlock.Number.Uint64()-1000; i-- { + block := s.backend.blockchain.GetBlockByNumber(i) + if block == nil { + continue + } + for idx, tx := range block.Transactions() { + if tx.Hash() == hash { + return s.formatTransaction(tx, block.Hash(), block.NumberU64(), uint64(idx)), nil + } + } + } + return nil, nil +} + +func (s *EthRPCService) GetTransactionByBlockHashAndIndex(hash common.Hash, index hexutil.Uint) map[string]interface{} { + block := s.backend.blockchain.GetBlockByHash(hash) + if block == nil { + return nil + } + txs := block.Transactions() + if int(index) >= len(txs) { + return nil + } + return s.formatTransaction(txs[index], block.Hash(), block.NumberU64(), uint64(index)) +} + +func (s *EthRPCService) GetTransactionByBlockNumberAndIndex(blockNr rpc.BlockNumber, index hexutil.Uint) map[string]interface{} { + var block *types.Block + if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber { + if header := s.backend.blockchain.CurrentBlock(); header != nil { + block = s.backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) + } + } else { + block = s.backend.blockchain.GetBlockByNumber(uint64(blockNr)) + } + if block == nil { + return nil + } + txs := block.Transactions() + if int(index) >= len(txs) { + return nil + } + return s.formatTransaction(txs[index], block.Hash(), block.NumberU64(), uint64(index)) +} + +func (s *EthRPCService) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { + currentBlock := s.backend.blockchain.CurrentBlock() + if currentBlock == nil { + return nil, nil + } + + for i := currentBlock.Number.Uint64(); i > 0 && i > currentBlock.Number.Uint64()-1000; i-- { + block := s.backend.blockchain.GetBlockByNumber(i) + if block == nil { + continue + } + for idx, tx := range block.Transactions() { + if tx.Hash() == hash { + receipts := s.backend.blockchain.GetReceiptsByHash(block.Hash()) + if idx >= len(receipts) { + return nil, nil + } + receipt := receipts[idx] + signer := types.LatestSignerForChainID(s.backend.chainConfig.ChainID) + from, _ := types.Sender(signer, tx) + + result := map[string]interface{}{ + "transactionHash": hash, + "transactionIndex": hexutil.Uint64(idx), + "blockHash": block.Hash(), + "blockNumber": (*hexutil.Big)(block.Number()), + "from": from, + "to": tx.To(), + "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed), + "gasUsed": hexutil.Uint64(receipt.GasUsed), + "contractAddress": nil, + "logs": receipt.Logs, + "logsBloom": receipt.Bloom, + "status": hexutil.Uint(receipt.Status), + "effectiveGasPrice": (*hexutil.Big)(tx.GasPrice()), + "type": hexutil.Uint(tx.Type()), + } + if receipt.ContractAddress != (common.Address{}) { + result["contractAddress"] = receipt.ContractAddress + } + if result["logs"] == nil { + result["logs"] = []*types.Log{} + } + return result, nil + } + } + } + return nil, nil +} + +func (s *EthRPCService) GetLogs(args map[string]interface{}) ([]*types.Log, error) { + currentBlock := s.backend.blockchain.CurrentBlock() + if currentBlock == nil { + return []*types.Log{}, nil + } + + fromBlock := currentBlock.Number.Uint64() + toBlock := currentBlock.Number.Uint64() + + if fb, ok := args["fromBlock"]; ok { + if fbStr, ok := fb.(string); ok && fbStr != "latest" && fbStr != "pending" { + if n, err := hexutil.DecodeUint64(fbStr); err == nil { + fromBlock = n + } + } + } + if tb, ok := args["toBlock"]; ok { + if tbStr, ok := tb.(string); ok && tbStr != "latest" && tbStr != "pending" { + if n, err := hexutil.DecodeUint64(tbStr); err == nil { + toBlock = n + } + } + } + + var addresses []common.Address + if addr, ok := args["address"]; ok { + switch v := addr.(type) { + case string: + addresses = append(addresses, common.HexToAddress(v)) + case []interface{}: + for _, a := range v { + if s, ok := a.(string); ok { + addresses = append(addresses, common.HexToAddress(s)) + } + } + } + } + + var logs []*types.Log + for i := fromBlock; i <= toBlock && i <= fromBlock+1000; i++ { + block := s.backend.blockchain.GetBlockByNumber(i) + if block == nil { + continue + } + receipts := s.backend.blockchain.GetReceiptsByHash(block.Hash()) + for _, receipt := range receipts { + for _, log := range receipt.Logs { + if len(addresses) == 0 || containsAddress(addresses, log.Address) { + logs = append(logs, log) + } + } + } + } + return logs, nil +} + +func (s *EthRPCService) Call(args TransactionArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { + stateDB, err := s.stateAtBlock(blockNr) + if err != nil { + return nil, err + } + + header := s.backend.blockchain.CurrentBlock() + if header == nil { + return nil, errors.New("no current block") + } + + msg := args.toMessage(header.BaseFee) + blockContext := core.NewEVMBlockContext(header, s.backend.blockchain, nil) + evm := vm.NewEVM(blockContext, stateDB, s.backend.chainConfig, vm.Config{}) + evm.SetTxContext(core.NewEVMTxContext(msg)) + + gp := new(core.GasPool).AddGas(header.GasLimit) + result, err := core.ApplyMessage(evm, msg, gp) + if err != nil { + return nil, err + } + if result.Err != nil { + return nil, result.Err + } + return result.Return(), nil +} + +func (s *EthRPCService) EstimateGas(args TransactionArgs, blockNr *rpc.BlockNumber) (hexutil.Uint64, error) { + bn := rpc.LatestBlockNumber + if blockNr != nil { + bn = *blockNr + } + + stateDB, err := s.stateAtBlock(bn) + if err != nil { + return 0, err + } + + header := s.backend.blockchain.CurrentBlock() + if header == nil { + return 0, errors.New("no current block") + } + + hi := header.GasLimit + if args.Gas != nil && uint64(*args.Gas) < hi { + hi = uint64(*args.Gas) + } + lo := uint64(21000) + + for lo+1 < hi { + mid := (lo + hi) / 2 + args.Gas = (*hexutil.Uint64)(&mid) + + msg := args.toMessage(header.BaseFee) + stateCopy := stateDB.Copy() + blockContext := core.NewEVMBlockContext(header, s.backend.blockchain, nil) + evm := vm.NewEVM(blockContext, stateCopy, s.backend.chainConfig, vm.Config{}) + evm.SetTxContext(core.NewEVMTxContext(msg)) + + gp := new(core.GasPool).AddGas(mid) + result, err := core.ApplyMessage(evm, msg, gp) + if err != nil || result.Failed() { + lo = mid + } else { + hi = mid + } + } + + return hexutil.Uint64(hi), nil +} + +func (s *EthRPCService) SendRawTransaction(encodedTx hexutil.Bytes) (common.Hash, error) { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(encodedTx); err != nil { + return common.Hash{}, err + } + + errs := s.backend.txPool.Add([]*types.Transaction{tx}, true) + if len(errs) > 0 && errs[0] != nil { + return common.Hash{}, errs[0] + } + + s.logger.Info().Str("tx_hash", tx.Hash().Hex()).Msg("received transaction") + return tx.Hash(), nil +} + +func (s *EthRPCService) FeeHistory(blockCount hexutil.Uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (map[string]interface{}, error) { + if blockCount == 0 || blockCount > 1024 { + blockCount = 1024 + } + + var endBlock uint64 + if lastBlock == rpc.LatestBlockNumber || lastBlock == rpc.PendingBlockNumber { + endBlock = s.backend.blockchain.CurrentBlock().Number.Uint64() + } else { + endBlock = uint64(lastBlock) + } + + startBlock := endBlock - uint64(blockCount) + 1 + if startBlock > endBlock { + startBlock = 0 + } + + baseFees := make([]*hexutil.Big, 0) + gasUsedRatios := make([]float64, 0) + + for i := startBlock; i <= endBlock; i++ { + block := s.backend.blockchain.GetBlockByNumber(i) + if block == nil { + continue + } + header := block.Header() + + if header.BaseFee != nil { + baseFees = append(baseFees, (*hexutil.Big)(header.BaseFee)) + } else { + baseFees = append(baseFees, (*hexutil.Big)(big.NewInt(0))) + } + + if header.GasLimit > 0 { + gasUsedRatios = append(gasUsedRatios, float64(header.GasUsed)/float64(header.GasLimit)) + } else { + gasUsedRatios = append(gasUsedRatios, 0) + } + } + + if len(baseFees) > 0 { + baseFees = append(baseFees, baseFees[len(baseFees)-1]) + } + + return map[string]interface{}{ + "oldestBlock": (*hexutil.Big)(new(big.Int).SetUint64(startBlock)), + "baseFeePerGas": baseFees, + "gasUsedRatio": gasUsedRatios, + }, nil +} + +// GetUncleCountByBlockHash returns 0 (no uncles in PoS). +func (s *EthRPCService) GetUncleCountByBlockHash(hash common.Hash) *hexutil.Uint { + n := hexutil.Uint(0) + return &n +} + +// GetUncleCountByBlockNumber returns 0 (no uncles in PoS). +func (s *EthRPCService) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber) *hexutil.Uint { + n := hexutil.Uint(0) + return &n +} + +// --- Helpers --- + +func (s *EthRPCService) stateAtBlock(blockNr rpc.BlockNumber) (*state.StateDB, error) { + var header *types.Header + if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber { + header = s.backend.blockchain.CurrentBlock() + } else { + block := s.backend.blockchain.GetBlockByNumber(uint64(blockNr)) + if block == nil { + return nil, fmt.Errorf("block %d not found", blockNr) + } + header = block.Header() + } + if header == nil { + return nil, errors.New("no current block") + } + return s.backend.blockchain.StateAt(header.Root) +} + +func (s *EthRPCService) formatBlock(block *types.Block, fullTx bool) map[string]interface{} { + header := block.Header() + result := map[string]interface{}{ + "number": (*hexutil.Big)(header.Number), + "hash": block.Hash(), + "parentHash": header.ParentHash, + "nonce": header.Nonce, + "sha3Uncles": header.UncleHash, + "logsBloom": header.Bloom, + "transactionsRoot": header.TxHash, + "stateRoot": header.Root, + "receiptsRoot": header.ReceiptHash, + "miner": header.Coinbase, + "difficulty": (*hexutil.Big)(header.Difficulty), + "extraData": hexutil.Bytes(header.Extra), + "size": hexutil.Uint64(block.Size()), + "gasLimit": hexutil.Uint64(header.GasLimit), + "gasUsed": hexutil.Uint64(header.GasUsed), + "timestamp": hexutil.Uint64(header.Time), + "mixHash": header.MixDigest, + "totalDifficulty": (*hexutil.Big)(big.NewInt(0)), + "uncles": []common.Hash{}, + } + + if header.BaseFee != nil { + result["baseFeePerGas"] = (*hexutil.Big)(header.BaseFee) + } + + txs := block.Transactions() + if fullTx { + txList := make([]map[string]interface{}, len(txs)) + for i, tx := range txs { + txList[i] = s.formatTransaction(tx, block.Hash(), header.Number.Uint64(), uint64(i)) + } + result["transactions"] = txList + } else { + txHashes := make([]common.Hash, len(txs)) + for i, tx := range txs { + txHashes[i] = tx.Hash() + } + result["transactions"] = txHashes + } + + return result +} + +func (s *EthRPCService) formatTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) map[string]interface{} { + signer := types.LatestSignerForChainID(s.backend.chainConfig.ChainID) + from, _ := types.Sender(signer, tx) + + result := map[string]interface{}{ + "hash": tx.Hash(), + "nonce": hexutil.Uint64(tx.Nonce()), + "blockHash": blockHash, + "blockNumber": (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)), + "transactionIndex": hexutil.Uint64(index), + "from": from, + "to": tx.To(), + "value": (*hexutil.Big)(tx.Value()), + "gas": hexutil.Uint64(tx.Gas()), + "gasPrice": (*hexutil.Big)(tx.GasPrice()), + "input": hexutil.Bytes(tx.Data()), + } + + if tx.Type() != types.LegacyTxType { + result["maxFeePerGas"] = (*hexutil.Big)(tx.GasFeeCap()) + result["maxPriorityFeePerGas"] = (*hexutil.Big)(tx.GasTipCap()) + result["type"] = hexutil.Uint64(tx.Type()) + } + + v, r, ss := tx.RawSignatureValues() + result["v"] = (*hexutil.Big)(v) + result["r"] = (*hexutil.Big)(r) + result["s"] = (*hexutil.Big)(ss) + + return result +} + +func (args *TransactionArgs) toMessage(baseFee *big.Int) *core.Message { + var from common.Address + if args.From != nil { + from = *args.From + } + + var gas uint64 = 50000000 + if args.Gas != nil { + gas = uint64(*args.Gas) + } + + var value *big.Int + if args.Value != nil { + value = (*big.Int)(args.Value) + } else { + value = big.NewInt(0) + } + + var data []byte + if args.Data != nil { + data = *args.Data + } else if args.Input != nil { + data = *args.Input + } + + var gasPrice *big.Int + if args.GasPrice != nil { + gasPrice = (*big.Int)(args.GasPrice) + } else if baseFee != nil { + gasPrice = new(big.Int).Add(baseFee, big.NewInt(1e9)) + } else { + gasPrice = big.NewInt(params.InitialBaseFee) + } + + return &core.Message{ + From: from, + To: args.To, + Value: value, + GasLimit: gas, + GasPrice: gasPrice, + GasFeeCap: gasPrice, + GasTipCap: big.NewInt(0), + Data: data, + SkipNonceChecks: true, + } +} + +func containsAddress(addrs []common.Address, addr common.Address) bool { + for _, a := range addrs { + if a == addr { + return true + } + } + return false +} diff --git a/execution/evm/engine_geth_test.go b/execution/evm/engine_geth_test.go new file mode 100644 index 000000000..a6ceb453d --- /dev/null +++ b/execution/evm/engine_geth_test.go @@ -0,0 +1,712 @@ +package evm + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + ds "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testGenesis() *core.Genesis { + testKey, _ := crypto.GenerateKey() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + + return &core.Genesis{ + Config: params.AllDevChainProtocolChanges, + Difficulty: big.NewInt(0), + GasLimit: 30_000_000, + Alloc: types.GenesisAlloc{ + testAddr: {Balance: new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18))}, + }, + Timestamp: uint64(time.Now().Unix()), + } +} + +func testDatastore() ds.Batching { + return sync.MutexWrap(ds.NewMapDatastore()) +} + +func TestNewEngineExecutionClientWithGeth(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + require.NotNil(t, client) + assert.NotEqual(t, common.Hash{}, client.genesisHash) + assert.Equal(t, feeRecipient, client.feeRecipient) +} + +func TestNewEngineExecutionClientWithGeth_NilDB(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + _, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, nil, "", logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "db is required") +} + +func TestNewEngineExecutionClientWithGeth_NilGenesis(t *testing.T) { + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + _, err := NewEngineExecutionClientWithGeth(nil, feeRecipient, db, "", logger) + require.Error(t, err) + assert.Contains(t, err.Error(), "genesis configuration is required") +} + +func TestGethEngineClient_InitChain(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + + ctx := context.Background() + genesisTime := time.Now() + + stateRoot, err := client.InitChain(ctx, genesisTime, 1, "1337") + require.NoError(t, err) + assert.NotEmpty(t, stateRoot) +} + +func TestGethEngineClient_GetLatestHeight(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + + ctx := context.Background() + height, err := client.GetLatestHeight(ctx) + require.NoError(t, err) + assert.Equal(t, uint64(0), height) +} + +func TestGethEthClient_HeaderByNumber(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + + ctx := context.Background() + + header, err := client.ethClient.HeaderByNumber(ctx, big.NewInt(0)) + require.NoError(t, err) + assert.NotNil(t, header) + assert.Equal(t, uint64(0), header.Number.Uint64()) + + latestHeader, err := client.ethClient.HeaderByNumber(ctx, nil) + require.NoError(t, err) + assert.NotNil(t, latestHeader) +} + +func TestGethEthClient_GetTxs_EmptyPool(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + + ctx := context.Background() + txs, err := client.GetTxs(ctx) + require.NoError(t, err) + assert.Empty(t, txs) +} + +func TestGethEngineClient_ExecuteTxs_EmptyBlock(t *testing.T) { + genesis := testGenesis() + db := testDatastore() + logger := zerolog.Nop() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + client, err := NewEngineExecutionClientWithGeth(genesis, feeRecipient, db, "", logger) + require.NoError(t, err) + + ctx := context.Background() + genesisTime := time.Now() + + stateRoot, err := client.InitChain(ctx, genesisTime, 1, "1337") + require.NoError(t, err) + + newStateRoot, err := client.ExecuteTxs(ctx, [][]byte{}, 1, genesisTime.Add(time.Second*12), stateRoot) + require.NoError(t, err) + assert.NotEmpty(t, newStateRoot) +} + +func TestGethEngineClient_ForkchoiceUpdated(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, nil) + require.NoError(t, err) + assert.Equal(t, engine.VALID, resp.PayloadStatus.Status) + assert.Nil(t, resp.PayloadID) +} + +func TestGethEngineClient_ForkchoiceUpdated_WithPayloadAttributes(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + attrs := map[string]any{ + "timestamp": time.Now().Unix() + 12, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{}, + "gasLimit": uint64(30_000_000), + "withdrawals": []*types.Withdrawal{}, + } + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, attrs) + require.NoError(t, err) + assert.Equal(t, engine.VALID, resp.PayloadStatus.Status) + assert.NotNil(t, resp.PayloadID) +} + +func TestGethEngineClient_GetPayload(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + attrs := map[string]any{ + "timestamp": time.Now().Unix() + 12, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{}, + "gasLimit": uint64(30_000_000), + "withdrawals": []*types.Withdrawal{}, + } + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, attrs) + require.NoError(t, err) + require.NotNil(t, resp.PayloadID) + + envelope, err := engineClient.GetPayload(ctx, *resp.PayloadID) + require.NoError(t, err) + assert.NotNil(t, envelope) + assert.NotNil(t, envelope.ExecutionPayload) + assert.Equal(t, uint64(1), envelope.ExecutionPayload.Number) + assert.Equal(t, feeRecipient, envelope.ExecutionPayload.FeeRecipient) +} + +func TestGethEngineClient_NewPayload(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + attrs := map[string]any{ + "timestamp": time.Now().Unix() + 12, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{}, + "gasLimit": uint64(30_000_000), + "withdrawals": []*types.Withdrawal{}, + } + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, attrs) + require.NoError(t, err) + require.NotNil(t, resp.PayloadID) + + envelope, err := engineClient.GetPayload(ctx, *resp.PayloadID) + require.NoError(t, err) + + status, err := engineClient.NewPayload(ctx, envelope.ExecutionPayload, nil, "", nil) + require.NoError(t, err) + assert.Equal(t, engine.VALID, status.Status) +} + +func TestGethEngineClient_ForkchoiceUpdated_UnknownHead(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + + unknownHash := common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234") + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: unknownHash, + SafeBlockHash: unknownHash, + FinalizedBlockHash: unknownHash, + }, nil) + require.NoError(t, err) + assert.Equal(t, engine.SYNCING, resp.PayloadStatus.Status) +} + +func TestGethBackend_Close(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + + err = backend.Close() + require.NoError(t, err) +} + +func TestCalcBaseFee(t *testing.T) { + config := params.AllDevChainProtocolChanges + + // At exactly 50% full, base fee stays the same + parent := &types.Header{ + Number: big.NewInt(100), + GasLimit: 30_000_000, + GasUsed: 15_000_000, + BaseFee: big.NewInt(1_000_000_000), + } + baseFee := calcBaseFee(config, parent) + require.NotNil(t, baseFee) + assert.Equal(t, parent.BaseFee, baseFee) +} + +func TestCalcBaseFee_OverTarget(t *testing.T) { + config := params.AllDevChainProtocolChanges + + parent := &types.Header{ + Number: big.NewInt(100), + GasLimit: 30_000_000, + GasUsed: 20_000_000, // >50% full + BaseFee: big.NewInt(1_000_000_000), + } + baseFee := calcBaseFee(config, parent) + require.NotNil(t, baseFee) + assert.Greater(t, baseFee.Int64(), parent.BaseFee.Int64()) +} + +func TestCalcBaseFee_UnderTarget(t *testing.T) { + config := params.AllDevChainProtocolChanges + + parent := &types.Header{ + Number: big.NewInt(100), + GasLimit: 30_000_000, + GasUsed: 5_000_000, // <50% full + BaseFee: big.NewInt(1_000_000_000), + } + baseFee := calcBaseFee(config, parent) + require.NotNil(t, baseFee) + assert.Less(t, baseFee.Int64(), parent.BaseFee.Int64()) +} + +func TestParsePayloadAttributes(t *testing.T) { + logger := zerolog.Nop() + engineClient := &gethEngineClient{logger: logger} + + parentHash := common.HexToHash("0xabcd") + feeRecipient := common.HexToAddress("0x1234") + timestamp := int64(1234567890) + + attrs := map[string]any{ + "timestamp": timestamp, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{"0xaabbcc", "0xddeeff"}, + "gasLimit": uint64(30_000_000), + } + + payload, err := engineClient.parsePayloadAttributes(parentHash, attrs) + require.NoError(t, err) + assert.Equal(t, parentHash, payload.parentHash) + assert.Equal(t, uint64(timestamp), payload.timestamp) + assert.Equal(t, feeRecipient, payload.feeRecipient) + assert.Equal(t, uint64(30_000_000), payload.gasLimit) + assert.Len(t, payload.transactions, 2) +} + +func TestCreateBloom(t *testing.T) { + bloom := createBloom([]*types.Receipt{}) + assert.Equal(t, types.Bloom{}, bloom) + + receipt := &types.Receipt{Status: types.ReceiptStatusSuccessful, Logs: []*types.Log{}} + bloom = createBloom([]*types.Receipt{receipt}) + assert.NotNil(t, bloom) +} + +func TestGethEngineClient_GetPayloadRemovesFromPending(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + attrs := map[string]any{ + "timestamp": time.Now().Unix() + 12, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{}, + "gasLimit": uint64(30_000_000), + "withdrawals": []*types.Withdrawal{}, + } + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, attrs) + require.NoError(t, err) + require.NotNil(t, resp.PayloadID) + + assert.NotNil(t, backend.pendingPayload) + + envelope, err := engineClient.GetPayload(ctx, *resp.PayloadID) + require.NoError(t, err) + assert.NotNil(t, envelope) + + assert.Nil(t, backend.pendingPayload) + + _, err = engineClient.GetPayload(ctx, *resp.PayloadID) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown payload ID") +} + +func TestGethEngineClient_WithdrawalProcessing(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + engineClient := &gethEngineClient{backend: backend, logger: logger} + ctx := context.Background() + genesisBlock := backend.blockchain.Genesis() + feeRecipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + + withdrawalAddr1 := common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + withdrawalAddr2 := common.HexToAddress("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") + + stateDB, err := backend.blockchain.StateAt(genesisBlock.Root()) + require.NoError(t, err) + assert.True(t, stateDB.GetBalance(withdrawalAddr1).IsZero()) + assert.True(t, stateDB.GetBalance(withdrawalAddr2).IsZero()) + + withdrawals := []*types.Withdrawal{ + {Index: 0, Validator: 1, Address: withdrawalAddr1, Amount: 1000000000}, // 1 ETH in Gwei + {Index: 1, Validator: 2, Address: withdrawalAddr2, Amount: 500000000}, // 0.5 ETH in Gwei + } + + attrs := map[string]any{ + "timestamp": time.Now().Unix() + 12, + "prevRandao": common.Hash{1, 2, 3}, + "suggestedFeeRecipient": feeRecipient, + "transactions": []string{}, + "gasLimit": uint64(30_000_000), + "withdrawals": withdrawals, + } + + resp, err := engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: genesisBlock.Hash(), + SafeBlockHash: genesisBlock.Hash(), + FinalizedBlockHash: genesisBlock.Hash(), + }, attrs) + require.NoError(t, err) + require.NotNil(t, resp.PayloadID) + + envelope, err := engineClient.GetPayload(ctx, *resp.PayloadID) + require.NoError(t, err) + assert.Len(t, envelope.ExecutionPayload.Withdrawals, 2) + + status, err := engineClient.NewPayload(ctx, envelope.ExecutionPayload, nil, "", nil) + require.NoError(t, err) + assert.Equal(t, engine.VALID, status.Status) + + _, err = engineClient.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{ + HeadBlockHash: envelope.ExecutionPayload.BlockHash, + SafeBlockHash: envelope.ExecutionPayload.BlockHash, + FinalizedBlockHash: envelope.ExecutionPayload.BlockHash, + }, nil) + require.NoError(t, err) + + newBlock := backend.blockchain.GetBlockByHash(envelope.ExecutionPayload.BlockHash) + require.NotNil(t, newBlock) + + newStateDB, err := backend.blockchain.StateAt(newBlock.Root()) + require.NoError(t, err) + + expectedBalance1 := new(big.Int).Mul(big.NewInt(1000000000), big.NewInt(1e9)) + expectedBalance2 := new(big.Int).Mul(big.NewInt(500000000), big.NewInt(1e9)) + + assert.Equal(t, expectedBalance1.String(), newStateDB.GetBalance(withdrawalAddr1).ToBig().String()) + assert.Equal(t, expectedBalance2.String(), newStateDB.GetBalance(withdrawalAddr2).ToBig().String()) +} + +func TestGethEngineClient_ContractCreationAddress(t *testing.T) { + sender := common.HexToAddress("0x1234567890123456789012345678901234567890") + nonce := uint64(0) + expectedAddr := crypto.CreateAddress(sender, nonce) + + expectedAddr2 := crypto.CreateAddress(sender, nonce) + assert.Equal(t, expectedAddr, expectedAddr2) + + differentAddr := crypto.CreateAddress(sender, nonce+1) + assert.NotEqual(t, expectedAddr, differentAddr) + + differentSender := common.HexToAddress("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + differentAddr2 := crypto.CreateAddress(differentSender, nonce) + assert.NotEqual(t, expectedAddr, differentAddr2) +} + +func TestWeb3Sha3(t *testing.T) { + service := &Web3RPCService{} + + input := hexutil.Bytes("hello") + result := service.Sha3(input) + + expected := common.FromHex("0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8") + assert.Equal(t, expected, []byte(result)) +} + +func TestEthRPCService_Accounts(t *testing.T) { + service := &EthRPCService{} + assert.Empty(t, service.Accounts()) +} + +func TestEthRPCService_Syncing(t *testing.T) { + service := &EthRPCService{} + result, err := service.Syncing() + require.NoError(t, err) + assert.Equal(t, false, result) +} + +func TestEthRPCService_FeeHistory(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + result, err := service.FeeHistory(10, rpc.LatestBlockNumber, []float64{25, 50, 75}) + require.NoError(t, err) + assert.NotNil(t, result) + assert.NotNil(t, result["oldestBlock"]) +} + +func TestEthRPCService_UnclesMethods(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + count := service.GetUncleCountByBlockHash(common.Hash{}) + assert.NotNil(t, count) + assert.Equal(t, hexutil.Uint(0), *count) + + count = service.GetUncleCountByBlockNumber(rpc.LatestBlockNumber) + assert.NotNil(t, count) + assert.Equal(t, hexutil.Uint(0), *count) +} + +func TestEthRPCService_BlockTransactionCount(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + genesisHash := backend.blockchain.Genesis().Hash() + count := service.GetBlockTransactionCountByHash(genesisHash) + assert.NotNil(t, count) + assert.Equal(t, hexutil.Uint(0), *count) + + count = service.GetBlockTransactionCountByNumber(0) + assert.NotNil(t, count) + assert.Equal(t, hexutil.Uint(0), *count) +} + +func TestEthRPCService_GetBlockByNumber(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + block, err := service.GetBlockByNumber(0, false) + require.NoError(t, err) + assert.NotNil(t, block) + assert.Equal(t, (*hexutil.Big)(big.NewInt(0)), block["number"]) + + block, err = service.GetBlockByNumber(rpc.LatestBlockNumber, true) + require.NoError(t, err) + assert.NotNil(t, block) +} + +func TestEthRPCService_GetBlockByHash(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + genesisHash := backend.blockchain.Genesis().Hash() + block, err := service.GetBlockByHash(genesisHash, false) + require.NoError(t, err) + assert.NotNil(t, block) + assert.Equal(t, genesisHash, block["hash"]) +} + +func TestEthRPCService_GasPrice(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + gasPrice := service.GasPrice() + assert.NotNil(t, gasPrice) + assert.Greater(t, (*big.Int)(gasPrice).Int64(), int64(0)) +} + +func TestEthRPCService_ChainId(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + chainId := service.ChainId() + assert.NotNil(t, chainId) +} + +func TestEthRPCService_BlockNumber(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &EthRPCService{backend: backend, logger: logger} + + blockNumber := service.BlockNumber() + assert.Equal(t, hexutil.Uint64(0), blockNumber) +} + +func TestNetRPCService(t *testing.T) { + genesis := testGenesis() + logger := zerolog.Nop() + + backend, err := newGethBackend(genesis, ds.NewMapDatastore(), logger) + require.NoError(t, err) + defer backend.Close() + + service := &NetRPCService{backend: backend} + + assert.NotEmpty(t, service.Version()) + assert.True(t, service.Listening()) + assert.Equal(t, hexutil.Uint(0), service.PeerCount()) +} + +func TestWeb3RPCService(t *testing.T) { + service := &Web3RPCService{} + + assert.NotEmpty(t, service.ClientVersion()) + + hash := service.Sha3([]byte("test")) + assert.Len(t, hash, 32) +} diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 1a61fdc20..7717df66b 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -198,6 +198,7 @@ func NewEngineExecutionClient( feeRecipient common.Address, db ds.Batching, tracingEnabled bool, + logger zerolog.Logger, ) (*EngineClient, error) { if db == nil { return nil, errors.New("db is required for EVM execution client") @@ -261,15 +262,10 @@ func NewEngineExecutionClient( currentSafeBlockHash: genesisHash, currentFinalizedBlockHash: genesisHash, blockHashCache: make(map[uint64]common.Hash), - logger: zerolog.Nop(), + logger: logger, }, nil } -// SetLogger allows callers to attach a structured logger. -func (c *EngineClient) SetLogger(l zerolog.Logger) { - c.logger = l -} - // InitChain initializes the blockchain with the given genesis parameters func (c *EngineClient) InitChain(ctx context.Context, genesisTime time.Time, initialHeight uint64, chainID string) ([]byte, error) { if initialHeight != 1 { diff --git a/execution/evm/flags.go b/execution/evm/flags.go index 3c1bb7125..9aa7c9572 100644 --- a/execution/evm/flags.go +++ b/execution/evm/flags.go @@ -6,4 +6,8 @@ const ( FlagEvmJWTSecretFile = "evm.jwt-secret-file" FlagEvmGenesisHash = "evm.genesis-hash" FlagEvmFeeRecipient = "evm.fee-recipient" + + FlagEVMInProcessGeth = "evm.geth" + FlagEVMGenesisPath = "evm.geth.genesis-path" + FlagEVMRPCAddress = "evm.geth.rpc-address" ) diff --git a/execution/evm/go.mod b/execution/evm/go.mod index ffa9a879d..931ecc0f9 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -7,9 +7,11 @@ require ( github.com/evstack/ev-node v1.0.0-beta.10 github.com/evstack/ev-node/core v1.0.0-beta.5 github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/holiman/uint256 v1.3.2 github.com/ipfs/go-datastore v0.9.0 github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 @@ -28,6 +30,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/celestiaorg/go-square/v3 v3.0.2 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -40,6 +43,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -53,7 +57,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/holiman/uint256 v1.3.2 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.6.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -85,7 +89,6 @@ require ( github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect - github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 98ecfcfac..76d2073df 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -8,6 +8,8 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -275,17 +277,20 @@ github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJh github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= @@ -542,6 +547,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/execution/evm/test/execution_test.go b/execution/evm/test/execution_test.go index aa6f2db17..867b99b77 100644 --- a/execution/evm/test/execution_test.go +++ b/execution/evm/test/execution_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/evstack/ev-node/execution/evm" @@ -75,6 +76,7 @@ func TestEngineExecution(t *testing.T) { common.Address{}, store, false, + zerolog.Nop(), ) require.NoError(tt, err) @@ -172,6 +174,7 @@ func TestEngineExecution(t *testing.T) { common.Address{}, store, false, + zerolog.Nop(), ) require.NoError(tt, err) diff --git a/execution/evm/test/go.mod b/execution/evm/test/go.mod index a1f5f4def..a7bb560a8 100644 --- a/execution/evm/test/go.mod +++ b/execution/evm/test/go.mod @@ -8,6 +8,7 @@ require ( github.com/evstack/ev-node/execution/evm v0.0.0-00010101000000-000000000000 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/ipfs/go-datastore v0.9.0 + github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 ) @@ -29,6 +30,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/avast/retry-go/v4 v4.6.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect @@ -71,6 +73,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/evstack/ev-node v1.0.0-beta.10 // indirect github.com/evstack/ev-node/core v1.0.0-beta.5 // indirect @@ -104,6 +107,7 @@ require ( github.com/hashicorp/go-metrics v0.5.3 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -147,7 +151,6 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect diff --git a/execution/evm/test/go.sum b/execution/evm/test/go.sum index 316f45ea3..14359e6eb 100644 --- a/execution/evm/test/go.sum +++ b/execution/evm/test/go.sum @@ -49,6 +49,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= diff --git a/test/e2e/go.mod b/test/e2e/go.mod index 4415a3ae5..5ee521881 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -45,6 +45,7 @@ require ( github.com/DataDog/zstd v1.5.7 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/avast/retry-go/v4 v4.6.1 // indirect github.com/bcp-innovations/hyperlane-cosmos v1.0.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -100,6 +101,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.8.0 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/evstack/ev-node/core v1.0.0-beta.5 // indirect github.com/fatih/color v1.18.0 // indirect @@ -147,6 +149,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/skiplist v1.2.1 // indirect github.com/huin/goupnp v1.3.0 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index fbd726735..6048db1ff 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -86,6 +86,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=