From a05606e0a685db958bb1a3185e3426e733dd6724 Mon Sep 17 00:00:00 2001 From: brion Date: Wed, 16 Nov 2022 11:21:16 +0800 Subject: [PATCH 01/12] support eth_multiCall and rpc cache --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 15 ++++- eth/api_backend.go | 9 +++ eth/backend.go | 8 ++- eth/ethconfig/config.go | 4 ++ go.mod | 7 +- go.sum | 47 +++++--------- internal/ethapi/api.go | 129 +++++++++++++++++++++++++++++++++++-- internal/ethapi/backend.go | 2 + les/api_backend.go | 6 ++ 10 files changed, 185 insertions(+), 43 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f18743260c..d364d51914 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -182,6 +182,7 @@ var ( utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCapFlag, + utils.RPCCacheFlag, utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalTxFeeCapFlag, utils.AllowUnprotectedTxs, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2aa0309d12..ae6b559b0f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -65,8 +65,8 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/txtrace" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/txtrace" pcsclite "github.com/gballet/go-libpcsclite" gopsutil "github.com/shirou/gopsutil/mem" "github.com/urfave/cli/v2" @@ -607,6 +607,12 @@ var ( Value: ethconfig.Defaults.RPCGasCap, Category: flags.APICategory, } + RPCCacheFlag = &cli.Uint64Flag{ + Name: "rpc.cache", + Usage: "Sets rpc cache that can be used in eth_call/eth_multiCall (0=infinite)", + Value: ethconfig.Defaults.RPCCache, + Category: flags.APICategory, + } RPCGlobalEVMTimeoutFlag = &cli.DurationFlag{ Name: "rpc.evmtimeout", Usage: "Sets a timeout used for eth_call (0=infinite)", @@ -988,12 +994,12 @@ var ( TxTraceEnabledFlag = &cli.BoolFlag{ Name: "txtrace", Usage: "Enable transaction trace while evm processing, default result is openEthereum style", - Category: flags.MiscCategory, + Category: flags.MiscCategory, } TxTraceStoreFlag = &flags.DirectoryFlag{ Name: "txtrace.store", Usage: "Data directory for store transaction trace result (default = inside datadir)", - Category: flags.MiscCategory, + Category: flags.MiscCategory, } ) @@ -1886,6 +1892,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(RPCGlobalTxFeeCapFlag.Name) { cfg.RPCTxFeeCap = ctx.Float64(RPCGlobalTxFeeCapFlag.Name) } + if ctx.IsSet(RPCCacheFlag.Name) { + cfg.RPCCache = ctx.Uint64(RPCCacheFlag.Name) + } if ctx.IsSet(NoDiscoverFlag.Name) { cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{} } else if ctx.IsSet(DNSDiscoveryFlag.Name) { diff --git a/eth/api_backend.go b/eth/api_backend.go index 00ecacc31d..1945d57793 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -22,6 +22,7 @@ import ( "math/big" "time" + "github.com/dgraph-io/ristretto" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -46,6 +47,7 @@ type EthAPIBackend struct { allowUnprotectedTxs bool eth *Ethereum gpo *gasprice.Oracle + callCache *ristretto.Cache } // ChainConfig returns the active chain configuration. @@ -336,6 +338,13 @@ func (b *EthAPIBackend) RPCTxFeeCap() float64 { return b.eth.config.RPCTxFeeCap } +func (b *EthAPIBackend) SetCallCache(key string, value interface{}, weight int64) { + b.callCache.Set(key, value, weight) +} +func (b *EthAPIBackend) GetCallCache(key string) (interface{}, bool) { + return b.callCache.Get(key) +} + func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections diff --git a/eth/backend.go b/eth/backend.go index 22be2ef961..1a0df86513 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -26,6 +26,7 @@ import ( "sync" "sync/atomic" + "github.com/dgraph-io/ristretto" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -257,7 +258,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + callCache, _ := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, // number of keys to track frequency of (10M). + MaxCost: int64(config.RPCCache), // maximum cost of cache (1GB). + BufferItems: 64, // number of keys per Get buffer. + }) + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, callCache} if eth.APIBackend.allowUnprotectedTxs { log.Info("Unprotected transactions allowed") } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 61232a4fcf..3059d91d09 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -93,6 +93,7 @@ var Defaults = Config{ }, TxPool: core.DefaultTxPoolConfig, RPCGasCap: 50000000, + RPCCache: 1000000, // 1 Million call results RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether @@ -203,6 +204,9 @@ type Config struct { // RPCGasCap is the global gas cap for eth-call variants. RPCGasCap uint64 + // RPCCache is the cache setting for eth_call/eth_multiCall + RPCCache uint64 + // RPCEVMTimeout is the global timeout for eth-call. RPCEVMTimeout time.Duration diff --git a/go.mod b/go.mod index 513fc32406..e1a4666396 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 + github.com/dgraph-io/ristretto v0.1.1 github.com/docker/docker v1.6.2 github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf github.com/edsrzf/mmap-go v1.0.0 @@ -60,7 +61,7 @@ require ( golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a + golang.org/x/sys v0.0.0-20221010170243-090e33056c14 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba @@ -82,12 +83,12 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect - github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect diff --git a/go.sum b/go.sum index e3121aae7c..2e4c8733f9 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,12 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -116,11 +120,14 @@ github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsvi github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.19/go.mod h1:IJBNMtzKcNHPtllYihy6BL2IgK1u+32JriaTbdt4v+w= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0= @@ -129,9 +136,8 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlK github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc= github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= @@ -158,7 +164,6 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -166,6 +171,7 @@ github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoB github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -181,6 +187,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -206,7 +213,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -233,7 +239,6 @@ github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= @@ -322,29 +327,19 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -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.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 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/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= @@ -410,14 +405,8 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 h1:m+8fKfQwCAy1QjzINvKe/pYtLjo2dl59x2w9YSEJxuY= github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= -github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= @@ -511,11 +500,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -562,7 +549,6 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -571,10 +557,9 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -620,7 +605,6 @@ golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 h1:0c3L82FDQ5rt1bjTBlchS8t6RQ6299/+5bWMnRLh+uI= golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= @@ -687,6 +671,7 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 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/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2d5831891a..bd602a5a90 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -18,10 +18,13 @@ package ethapi import ( "context" + "crypto/sha256" "errors" "fmt" "math/big" + "strconv" "strings" + "sync" "time" "github.com/davecgh/go-spew/spew" @@ -41,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -53,6 +57,13 @@ type EthereumAPI struct { b Backend } +var ( + ethCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethcall/cache/hit", nil) + ethCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethcall/cache/count", nil) + ethMultiCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/hit", nil) + ethMultiCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/count", nil) +) + // NewEthereumAPI creates a new Ethereum protocol API. func NewEthereumAPI(b Backend) *EthereumAPI { return &EthereumAPI{b} @@ -86,6 +97,13 @@ type feeHistoryResult struct { GasUsedRatio []float64 `json:"gasUsedRatio"` } +type multicallResult struct { + Err string `json:"err"` + FromCache bool `json:"fromCache"` + Result hexutil.Bytes `json:"result"` + GasUsed uint64 `json:"gasUsed"` +} + // FeeHistory returns the fee market history. func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, int(blockCount), lastBlock, rewardPercentiles) @@ -737,10 +755,10 @@ func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) m } // GetBlockByNumber returns the requested canonical block. -// * When blockNr is -1 the chain head is returned. -// * When blockNr is -2 the pending chain head is returned. -// * When fullTx is true all transactions in the block are returned, otherwise -// only the transaction hash is returned. +// - When blockNr is -1 the chain head is returned. +// - When blockNr is -2 the pending chain head is returned. +// - When fullTx is true all transactions in the block are returned, otherwise +// only the transaction hash is returned. func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { @@ -1012,6 +1030,86 @@ func (e *revertError) ErrorData() interface{} { return e.reason } +func ethCallCacheKey(blockNum int64, to *common.Address, input []byte) string { + h := sha256.New() + h.Write(input) + bs := h.Sum(nil) + + key := strconv.FormatInt(blockNum, 10) + key += strings.ToLower(string(to.Bytes())) + key += string(bs) + return key +} + +func (s *BlockChainAPI) ethCallCacheBlockNr(blockNrOrHash rpc.BlockNumberOrHash) int64 { + var blockNr int64 + if n, ok := blockNrOrHash.Number(); ok { + blockNr = n.Int64() + if n == rpc.LatestBlockNumber || n == rpc.PendingBlockNumber { + blockNr = s.b.CurrentBlock().Header().Number.Int64() + } + } + return blockNr +} + +func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) ([]*multicallResult, error) { + ret := make([]*multicallResult, len(args)) + + var wg sync.WaitGroup + for i, arg := range args { + wg.Add(1) + go func(i int, arg TransactionArgs) { + defer wg.Done() + + var result *core.ExecutionResult + var err error + + // try load result from cache + blockNr := s.ethCallCacheBlockNr(blockNrOrHash) + cacheKey := ethCallCacheKey(blockNr, arg.To, arg.data()) + + ethMultiCallCacheCount.Mark(1) + if r, ok := s.b.GetCallCache(cacheKey); ok { + ethMultiCallCacheHit.Mark(1) + if res, ok := r.(*core.ExecutionResult); ok { + ret[i] = &multicallResult{ + Result: res.Return(), + FromCache: true, + GasUsed: res.UsedGas, + } + return + } + } + + defer func() { + // cache result on success + if err == nil && result.Err == nil { + s.b.SetCallCache(cacheKey, result, int64(len(result.ReturnData))) + } + }() + + var errstr string + result, err = DoCall(ctx, s.b, arg, blockNrOrHash, overrides, 5*time.Second, s.b.RPCGasCap()) + if err != nil { + errstr = err.Error() + } else if len(result.Revert()) > 0 { + errstr = newRevertError(result).Error() + } else if result.Err != nil { + errstr = result.Err.Error() + } + + ret[i] = &multicallResult{ + Result: result.Return(), + Err: errstr, + GasUsed: result.UsedGas, + } + }(i, arg) + } + wg.Wait() + + return ret, nil +} + // Call executes the given transaction on the state for the given block number. // // Additionally, the caller can specify a batch of contract for fields overriding. @@ -1019,7 +1117,28 @@ func (e *revertError) ErrorData() interface{} { // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { - result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) + var result *core.ExecutionResult + var err error + + // try load result from cache + blockNr := s.ethCallCacheBlockNr(blockNrOrHash) + cacheKey := ethCallCacheKey(blockNr, args.To, args.data()) + ethCallCacheCount.Mark(1) + if r, ok := s.b.GetCallCache(cacheKey); ok { + ethCallCacheHit.Mark(1) + if res, ok := r.(*core.ExecutionResult); ok { + return res.Return(), res.Err + } + } + + defer func() { + // cache result on success + if err == nil && result.Err == nil { + s.b.SetCallCache(cacheKey, result, int64(len(result.ReturnData))) + } + }() + + result, err = DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) if err != nil { return nil, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 5b4ceb6310..00a3707144 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -52,6 +52,8 @@ type Backend interface { RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs UnprotectedAllowed() bool // allows only for EIP155 transactions. + SetCallCache(key string, value interface{}, weight int64) + GetCallCache(key string) (interface{}, bool) // Blockchain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 5b4213134b..a60a0ff89a 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -133,6 +133,12 @@ func (b *LesApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) return nil, nil } +func (b *LesApiBackend) SetCallCache(key string, value interface{}, weight int64) { +} +func (b *LesApiBackend) GetCallCache(key string) (interface{}, bool) { + return nil, false +} + func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { header, err := b.HeaderByNumber(ctx, number) if err != nil { From 516f98e356eef01164567676460b9cb38b586c32 Mon Sep 17 00:00:00 2001 From: brion Date: Mon, 19 Dec 2022 11:40:00 +0800 Subject: [PATCH 02/12] adjust implementation --- internal/ethapi/api.go | 110 +------------ internal/ethapi/api_multicall.go | 260 +++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 109 deletions(-) create mode 100644 internal/ethapi/api_multicall.go diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bd602a5a90..3db335fc63 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -18,13 +18,10 @@ package ethapi import ( "context" - "crypto/sha256" "errors" "fmt" "math/big" - "strconv" "strings" - "sync" "time" "github.com/davecgh/go-spew/spew" @@ -44,7 +41,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -57,13 +53,6 @@ type EthereumAPI struct { b Backend } -var ( - ethCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethcall/cache/hit", nil) - ethCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethcall/cache/count", nil) - ethMultiCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/hit", nil) - ethMultiCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/count", nil) -) - // NewEthereumAPI creates a new Ethereum protocol API. func NewEthereumAPI(b Backend) *EthereumAPI { return &EthereumAPI{b} @@ -97,13 +86,6 @@ type feeHistoryResult struct { GasUsedRatio []float64 `json:"gasUsedRatio"` } -type multicallResult struct { - Err string `json:"err"` - FromCache bool `json:"fromCache"` - Result hexutil.Bytes `json:"result"` - GasUsed uint64 `json:"gasUsed"` -} - // FeeHistory returns the fee market history. func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, int(blockCount), lastBlock, rewardPercentiles) @@ -1030,17 +1012,6 @@ func (e *revertError) ErrorData() interface{} { return e.reason } -func ethCallCacheKey(blockNum int64, to *common.Address, input []byte) string { - h := sha256.New() - h.Write(input) - bs := h.Sum(nil) - - key := strconv.FormatInt(blockNum, 10) - key += strings.ToLower(string(to.Bytes())) - key += string(bs) - return key -} - func (s *BlockChainAPI) ethCallCacheBlockNr(blockNrOrHash rpc.BlockNumberOrHash) int64 { var blockNr int64 if n, ok := blockNrOrHash.Number(); ok { @@ -1052,64 +1023,6 @@ func (s *BlockChainAPI) ethCallCacheBlockNr(blockNrOrHash rpc.BlockNumberOrHash) return blockNr } -func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) ([]*multicallResult, error) { - ret := make([]*multicallResult, len(args)) - - var wg sync.WaitGroup - for i, arg := range args { - wg.Add(1) - go func(i int, arg TransactionArgs) { - defer wg.Done() - - var result *core.ExecutionResult - var err error - - // try load result from cache - blockNr := s.ethCallCacheBlockNr(blockNrOrHash) - cacheKey := ethCallCacheKey(blockNr, arg.To, arg.data()) - - ethMultiCallCacheCount.Mark(1) - if r, ok := s.b.GetCallCache(cacheKey); ok { - ethMultiCallCacheHit.Mark(1) - if res, ok := r.(*core.ExecutionResult); ok { - ret[i] = &multicallResult{ - Result: res.Return(), - FromCache: true, - GasUsed: res.UsedGas, - } - return - } - } - - defer func() { - // cache result on success - if err == nil && result.Err == nil { - s.b.SetCallCache(cacheKey, result, int64(len(result.ReturnData))) - } - }() - - var errstr string - result, err = DoCall(ctx, s.b, arg, blockNrOrHash, overrides, 5*time.Second, s.b.RPCGasCap()) - if err != nil { - errstr = err.Error() - } else if len(result.Revert()) > 0 { - errstr = newRevertError(result).Error() - } else if result.Err != nil { - errstr = result.Err.Error() - } - - ret[i] = &multicallResult{ - Result: result.Return(), - Err: errstr, - GasUsed: result.UsedGas, - } - }(i, arg) - } - wg.Wait() - - return ret, nil -} - // Call executes the given transaction on the state for the given block number. // // Additionally, the caller can specify a batch of contract for fields overriding. @@ -1117,28 +1030,7 @@ func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, b // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { - var result *core.ExecutionResult - var err error - - // try load result from cache - blockNr := s.ethCallCacheBlockNr(blockNrOrHash) - cacheKey := ethCallCacheKey(blockNr, args.To, args.data()) - ethCallCacheCount.Mark(1) - if r, ok := s.b.GetCallCache(cacheKey); ok { - ethCallCacheHit.Mark(1) - if res, ok := r.(*core.ExecutionResult); ok { - return res.Return(), res.Err - } - } - - defer func() { - // cache result on success - if err == nil && result.Err == nil { - s.b.SetCallCache(cacheKey, result, int64(len(result.ReturnData))) - } - }() - - result, err = DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) if err != nil { return nil, err } diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go new file mode 100644 index 0000000000..4bd82842f7 --- /dev/null +++ b/internal/ethapi/api_multicall.go @@ -0,0 +1,260 @@ +package ethapi + +import ( + "bytes" + "context" + "crypto/sha256" + "fmt" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "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/metrics" + "github.com/ethereum/go-ethereum/rpc" +) + +type multiCallResp struct { + Results []*callResult `json:"results"` + Stats *multiCallStats `json:"stats"` +} + +type callResult struct { + Code int `json:"code"` + Err string `json:"err"` + FromCache bool `json:"fromCache"` + Result hexutil.Bytes `json:"result"` + GasUsed int64 `json:"gasUsed"` + TimeCost float64 `json:"timeCost"` +} + +type multiCallStats struct { + BlockNum int64 `json:"blockNum"` + BlockHash common.Hash `json:"blockHash"` + BlockTime int64 `json:"blockTime"` + Success bool `json:"success"` + CacheEnabled bool `json:"cacheEnabled"` + // gasUsed, excluding calls from cache + GasUsed int64 `json:"gasUsed"` + OriginGasUsed int64 `json:"originGasUsed"` + CacheHitCount int64 `json:"cacheHitCount"` +} + +const ( + singleCallTimeout = 1 * time.Second + multiCallLimit = 50 + + errParam = -40001 + errConsensus = -40002 // error on consensus check + errLogic = -40003 // logic error + errEVM = -40004 // error on evm execution +) + +var ( + ethMultiCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/hit", nil) + ethMultiCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/count", nil) + + errCancelled = fmt.Errorf("execution aborted (timeout = %v)", singleCallTimeout) +) + +func ethCallCacheKey(b Backend, blockHash common.Hash, to *common.Address, input []byte) string { + var sb strings.Builder + + h := sha256.New() + h.Write(input) + bs := h.Sum(nil) + + sb.Grow(len(bs) + len(to.Bytes()) + len(blockHash)) + sb.Write(blockHash[:]) + sb.Write(bytes.ToLower(to.Bytes())) + sb.Write(bs) + + return sb.String() +} + +func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *types.Header, arg TransactionArgs, disableCache bool) (*callResult, error) { + var err error + var result = &callResult{} + + start := time.Now() + + if !disableCache { + // try load result from cache + cacheKey := ethCallCacheKey(b, header.Hash(), arg.To, arg.data()) + ethMultiCallCacheCount.Mark(1) + if r, ok := b.GetCallCache(cacheKey); ok { + ethMultiCallCacheHit.Mark(1) + if res, ok := r.(*callResult); ok { + res.FromCache = true + return res, nil + } + } + defer func() { + // `err` here specifics to non-evm error. Evm internal error won't prevent + // caching the result + if err == nil { + b.SetCallCache(cacheKey, result, int64(len(result.Result))) + } + }() + } + // make sure this will be called prior to the SetCallCache defer func on returning + defer func() { + result.TimeCost = time.Since(start).Seconds() + }() + + // Get a new instance of the EVM. + msg, err := arg.ToMessage(b.RPCGasCap(), header.BaseFee) + if err != nil { + result.Code = errParam + result.Err = err.Error() + return result, err + } + + evm, _, _ := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}) // never return error + + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + // Execute the message. + gp := new(core.GasPool).AddGas(math.MaxUint64) + evmRet, err := core.ApplyMessage(evm, msg, gp) + if err != nil { + result.Code = errConsensus + result.Err = err.Error() + return result, err + } + + // If the timer caused an abort, return an appropriate error message + if evm.Cancelled() { + err = errCancelled + result.Code = errLogic + result.Err = err.Error() + return result, err + } + + if evmRet.Err != nil { + e := evmRet.Err + if len(evmRet.Revert()) > 0 { + e = newRevertError(evmRet) + } + result.Code = errEVM + result.Err = e.Error() + return result, e + } + + result.Result = evmRet.Return() + result.GasUsed = int64(evmRet.UsedGas) + + return result, nil +} + +func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, pfastFail, puseParallel, pdisableCache *bool, overrides *StateOverride) (resp *multiCallResp, err error) { + + // maximum calls check + if len(args) > multiCallLimit { + return nil, fmt.Errorf("calls exceed limit, expected: <%v, actual: %v", multiCallLimit, len(args)) + } + + setb := func(p *bool, d bool) bool { + if p == nil { + return d + } + return *p + } + + fastFail := setb(pfastFail, true) + useParallel := setb(puseParallel, true) + disableCache := setb(pdisableCache, false) + + // check block & state + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + if err := overrides.Apply(state); err != nil { + return nil, err + } + blockTime := header.Time + + ret := make([]*callResult, len(args)) + stats := &multiCallStats{ + BlockNum: header.Number.Int64(), + BlockHash: header.Hash(), + BlockTime: int64(blockTime), + Success: true, + CacheEnabled: !disableCache, + } + + ctx, cancel := context.WithTimeout(ctx, singleCallTimeout) + defer cancel() + + if useParallel { + // run in parallel + var wg sync.WaitGroup + for i, arg := range args { + wg.Add(1) + go func(i int, arg TransactionArgs) { + defer wg.Done() + + // state is not reentrancy in concurrent scenarios, so use a copy + state, _, _ := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + r, _ := doOneCall(ctx, s.b, state, header, arg, disableCache) + ret[i] = r + if r.Err != "" { + stats.Success = false + if fastFail { + cancel() + } + return + } + + if r.FromCache { + stats.CacheHitCount++ + } else { + stats.GasUsed += r.GasUsed + } + stats.OriginGasUsed += r.GasUsed + }(i, arg) + } + wg.Wait() + + return &multiCallResp{Results: ret, Stats: stats}, nil + } + + // run in sequence + failedOnce := false + for i, arg := range args { + if failedOnce { + ret[i] = nil + continue + } + + r, _ := doOneCall(ctx, s.b, state, header, arg, disableCache) + ret[i] = r + if r.Err != "" { + stats.Success = false + if fastFail { + failedOnce = true + } + continue + } + + if r.FromCache { + stats.CacheHitCount++ + } else { + stats.GasUsed += r.GasUsed + } + stats.OriginGasUsed += r.GasUsed + } + + return &multiCallResp{Results: ret, Stats: stats}, nil +} From 6fc7bc6c3e394cc20b7ffaeddb8d356b7f446374 Mon Sep 17 00:00:00 2001 From: brion Date: Mon, 19 Dec 2022 23:30:37 +0800 Subject: [PATCH 03/12] support get native token balance from multicall --- internal/ethapi/api.go | 11 ---- internal/ethapi/api_multicall.go | 100 ++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3db335fc63..02c4bceba9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1012,17 +1012,6 @@ func (e *revertError) ErrorData() interface{} { return e.reason } -func (s *BlockChainAPI) ethCallCacheBlockNr(blockNrOrHash rpc.BlockNumberOrHash) int64 { - var blockNr int64 - if n, ok := blockNrOrHash.Number(); ok { - blockNr = n.Int64() - if n == rpc.LatestBlockNumber || n == rpc.PendingBlockNumber { - blockNr = s.b.CurrentBlock().Header().Number.Int64() - } - } - return blockNr -} - // Call executes the given transaction on the state for the given block number. // // Additionally, the caller can specify a batch of contract for fields overriding. diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 4bd82842f7..b7ab8c881d 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -63,6 +64,61 @@ var ( errCancelled = fmt.Errorf("execution aborted (timeout = %v)", singleCallTimeout) ) +const ( + nativeAddr = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +) + +var ( + // copied from: accounts/abi/abi_test.go + Uint8, _ = abi.NewType("uint8", "", nil) + Uint256, _ = abi.NewType("uint256", "", nil) + String, _ = abi.NewType("string", "", nil) + Address, _ = abi.NewType("address", "", nil) + + erc20ABI = abi.ABI{ + Methods: map[string]abi.Method{ + "name": funcName, + "symbol": funcSymbol, + "decimals": funcDecimals, + "totalSupply": funcTotalSupply, + "balanceOf": funcBalanceOf, + }, + } + + funcName = abi.NewMethod("name", "name", abi.Function, "", false, false, + []abi.Argument{}, + []abi.Argument{ + {Name: "", Type: String, Indexed: false}, + }, + ) + funcSymbol = abi.NewMethod("symbol", "symbol", abi.Function, "", false, false, + []abi.Argument{}, + []abi.Argument{ + {Name: "", Type: String, Indexed: false}, + }, + ) + funcDecimals = abi.NewMethod("decimals", "decimals", abi.Function, "", false, false, + []abi.Argument{}, + []abi.Argument{ + {Name: "", Type: Uint8, Indexed: false}, + }, + ) + funcTotalSupply = abi.NewMethod("totalSupply", "totalSupply", abi.Function, "", false, false, + []abi.Argument{}, + []abi.Argument{ + {Name: "", Type: Uint256, Indexed: false}, + }, + ) + funcBalanceOf = abi.NewMethod("balanceOf", "balanceOf", abi.Function, "", false, false, + []abi.Argument{ + {Name: "", Type: Address, Indexed: false}, + }, + []abi.Argument{ + {Name: "", Type: Uint256, Indexed: false}, + }, + ) +) + func ethCallCacheKey(b Backend, blockHash common.Hash, to *common.Address, input []byte) string { var sb strings.Builder @@ -78,6 +134,40 @@ func ethCallCacheKey(b Backend, blockHash common.Hash, to *common.Address, input return sb.String() } +func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) ([]byte, error) { + data := msg.Data() + method, err := erc20ABI.MethodById(data) + if err != nil { + return []byte("0x"), err + } + switch method.Name { + case "name", "symbol": + return method.Outputs.Pack("ETH") + case "decimals": + return method.Outputs.Pack(18) + case "totalSupply": + return method.Outputs.Pack(120 * 1_000_000 * 18) + } + + if method.Name == "balanceOf" { + inputs, err := method.Inputs.Unpack(data[4:]) + if err != nil || len(inputs) == 0 { + return []byte("0x"), fmt.Errorf("input error") + } + address, ok := inputs[0].(common.Address) + if !ok { + return []byte("0x"), fmt.Errorf("input address parse error") + } + balance, err := method.Outputs.Pack(state.GetBalance(address)) + if err != nil { + return []byte("0x"), err + } + return balance, state.Error() + } + + return []byte("0x"), nil +} + func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *types.Header, arg TransactionArgs, disableCache bool) (*callResult, error) { var err error var result = &callResult{} @@ -108,7 +198,6 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ result.TimeCost = time.Since(start).Seconds() }() - // Get a new instance of the EVM. msg, err := arg.ToMessage(b.RPCGasCap(), header.BaseFee) if err != nil { result.Code = errParam @@ -116,6 +205,15 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ return result, err } + // skip EVM if requests for native token + if strings.ToLower(msg.To().Hex()) == nativeAddr { + result.Result, err = handleNative(ctx, state, msg) + result.Code = errLogic + result.Err = err.Error() + return result, err + } + + // Get a new instance of the EVM. evm, _, _ := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}) // never return error // Wait for the context to be done and cancel the evm. Even if the From a04e57e42a917cc9bb67f951d4256f2da4775cd5 Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 00:33:34 +0800 Subject: [PATCH 04/12] fix type error --- internal/ethapi/api_multicall.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index b7ab8c881d..389e206079 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -142,11 +142,11 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) } switch method.Name { case "name", "symbol": - return method.Outputs.Pack("ETH") + return method.Outputs.PackValues([]interface{}{"ETH"}) case "decimals": - return method.Outputs.Pack(18) + return method.Outputs.PackValues([]interface{}{18}) case "totalSupply": - return method.Outputs.Pack(120 * 1_000_000 * 18) + return method.Outputs.PackValues([]interface{}{120 * 1_000_000 * 18}) } if method.Name == "balanceOf" { From 052ade3c8d5fc17391330f1dc3532c779c72c577 Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 00:40:27 +0800 Subject: [PATCH 05/12] update --- internal/ethapi/api_multicall.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 389e206079..97971f58bb 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -208,8 +208,10 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ // skip EVM if requests for native token if strings.ToLower(msg.To().Hex()) == nativeAddr { result.Result, err = handleNative(ctx, state, msg) - result.Code = errLogic - result.Err = err.Error() + if err != nil { + result.Code = errLogic + result.Err = err.Error() + } return result, err } From 98a30d9d43016a428bd5be94ccdedf4cb75e89b6 Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 00:54:32 +0800 Subject: [PATCH 06/12] update --- internal/ethapi/api_multicall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 97971f58bb..c07bf592a7 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -144,7 +144,7 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) case "name", "symbol": return method.Outputs.PackValues([]interface{}{"ETH"}) case "decimals": - return method.Outputs.PackValues([]interface{}{18}) + return method.Outputs.PackValues([]interface{}{uint8(18)}) case "totalSupply": return method.Outputs.PackValues([]interface{}{120 * 1_000_000 * 18}) } From c7e7267ece22f83febad2d89a4a501d049392ce8 Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 01:03:11 +0800 Subject: [PATCH 07/12] update --- internal/ethapi/api_multicall.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index c07bf592a7..0bbd38cfff 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha256" "fmt" + "math/big" "strings" "sync" "time" @@ -142,11 +143,11 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) } switch method.Name { case "name", "symbol": - return method.Outputs.PackValues([]interface{}{"ETH"}) + return method.Outputs.Pack("ETH") case "decimals": - return method.Outputs.PackValues([]interface{}{uint8(18)}) + return method.Outputs.Pack(uint8(18)) case "totalSupply": - return method.Outputs.PackValues([]interface{}{120 * 1_000_000 * 18}) + return method.Outputs.Pack(big.NewInt(120 * 1_000_000 * 18)) } if method.Name == "balanceOf" { From 21920416e8d5821ad9e37cec5735a4a8789eb69e Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 01:12:42 +0800 Subject: [PATCH 08/12] update --- internal/ethapi/api_multicall.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 0bbd38cfff..b7f6cfe09d 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -139,7 +139,7 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) data := msg.Data() method, err := erc20ABI.MethodById(data) if err != nil { - return []byte("0x"), err + return nil, err } switch method.Name { case "name", "symbol": @@ -153,20 +153,21 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) if method.Name == "balanceOf" { inputs, err := method.Inputs.Unpack(data[4:]) if err != nil || len(inputs) == 0 { - return []byte("0x"), fmt.Errorf("input error") + return nil, fmt.Errorf("input error") } address, ok := inputs[0].(common.Address) if !ok { - return []byte("0x"), fmt.Errorf("input address parse error") + return nil, fmt.Errorf("input address parse error") } balance, err := method.Outputs.Pack(state.GetBalance(address)) if err != nil { - return []byte("0x"), err + return nil, err } return balance, state.Error() } - return []byte("0x"), nil + // should never reach here + return nil, fmt.Errorf("unsupported method") } func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *types.Header, arg TransactionArgs, disableCache bool) (*callResult, error) { From 39a9e05c88201aeee289a90213ef6ab136329d04 Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 01:39:36 +0800 Subject: [PATCH 09/12] update --- internal/ethapi/api_multicall.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index b7f6cfe09d..31f8e6b156 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -308,8 +308,7 @@ func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, b defer wg.Done() // state is not reentrancy in concurrent scenarios, so use a copy - state, _, _ := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - r, _ := doOneCall(ctx, s.b, state, header, arg, disableCache) + r, _ := doOneCall(ctx, s.b, state.Copy(), header, arg, disableCache) ret[i] = r if r.Err != "" { stats.Success = false From 4eac03ec488d33e8ad09eae72c051485da7f768e Mon Sep 17 00:00:00 2001 From: brion Date: Tue, 20 Dec 2022 17:06:00 +0800 Subject: [PATCH 10/12] update --- internal/ethapi/api_multicall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 31f8e6b156..ca30f1dd5d 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -147,7 +147,7 @@ func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) case "decimals": return method.Outputs.Pack(uint8(18)) case "totalSupply": - return method.Outputs.Pack(big.NewInt(120 * 1_000_000 * 18)) + return method.Outputs.Pack(big.NewInt(1_000_000_000_000_000_000)) // 1 ETH } if method.Name == "balanceOf" { From 00829e5e356da3ceac4a77f6fd4a7491424a438b Mon Sep 17 00:00:00 2001 From: brion Date: Thu, 22 Dec 2022 21:41:21 +0800 Subject: [PATCH 11/12] update --- internal/ethapi/api_multicall.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index ca30f1dd5d..23e89d9a68 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -42,10 +42,6 @@ type multiCallStats struct { BlockTime int64 `json:"blockTime"` Success bool `json:"success"` CacheEnabled bool `json:"cacheEnabled"` - // gasUsed, excluding calls from cache - GasUsed int64 `json:"gasUsed"` - OriginGasUsed int64 `json:"originGasUsed"` - CacheHitCount int64 `json:"cacheHitCount"` } const ( @@ -317,13 +313,6 @@ func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, b } return } - - if r.FromCache { - stats.CacheHitCount++ - } else { - stats.GasUsed += r.GasUsed - } - stats.OriginGasUsed += r.GasUsed }(i, arg) } wg.Wait() @@ -348,13 +337,6 @@ func (s *BlockChainAPI) MultiCall(ctx context.Context, args []TransactionArgs, b } continue } - - if r.FromCache { - stats.CacheHitCount++ - } else { - stats.GasUsed += r.GasUsed - } - stats.OriginGasUsed += r.GasUsed } return &multiCallResp{Results: ret, Stats: stats}, nil From 8028f6499f566cfa1a6d1fff095bfba812055498 Mon Sep 17 00:00:00 2001 From: brion Date: Wed, 28 Dec 2022 20:14:52 +0800 Subject: [PATCH 12/12] refine err codes --- internal/ethapi/api_multicall.go | 76 ++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/internal/ethapi/api_multicall.go b/internal/ethapi/api_multicall.go index 23e89d9a68..6032d8c60e 100644 --- a/internal/ethapi/api_multicall.go +++ b/internal/ethapi/api_multicall.go @@ -48,17 +48,23 @@ const ( singleCallTimeout = 1 * time.Second multiCallLimit = 50 - errParam = -40001 - errConsensus = -40002 // error on consensus check - errLogic = -40003 // logic error - errEVM = -40004 // error on evm execution + // client param error + errCodeTxArgs = -40000 + errNativeMethodNotFound = -40001 + errNativeMethodInput = -40002 + errNativeMethodInputAddress = -40003 + + // evm processing error + errNativeMethodOutput = -40010 + errNativeMethodStateError = -40011 + errMessageExecuting = -40012 + errEVMCancelled = -40013 + errEVMReverted = -40014 ) var ( ethMultiCallCacheHit = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/hit", nil) ethMultiCallCacheCount = metrics.GetOrRegisterMeter("rpc/ethmulticall/cache/count", nil) - - errCancelled = fmt.Errorf("execution aborted (timeout = %v)", singleCallTimeout) ) const ( @@ -131,39 +137,51 @@ func ethCallCacheKey(b Backend, blockHash common.Hash, to *common.Address, input return sb.String() } -func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) ([]byte, error) { +func handleNative(ctx context.Context, state *state.StateDB, msg types.Message) ([]byte, int, error) { data := msg.Data() method, err := erc20ABI.MethodById(data) if err != nil { - return nil, err + return nil, errNativeMethodNotFound, err } switch method.Name { case "name", "symbol": - return method.Outputs.Pack("ETH") + res, err := method.Outputs.Pack("ETH") + if err != nil { + return nil, errNativeMethodOutput, err + } + return res, 0, nil case "decimals": - return method.Outputs.Pack(uint8(18)) + res, err := method.Outputs.Pack(uint8(18)) + if err != nil { + return nil, errNativeMethodOutput, err + } + return res, 0, nil case "totalSupply": - return method.Outputs.Pack(big.NewInt(1_000_000_000_000_000_000)) // 1 ETH - } - - if method.Name == "balanceOf" { + res, err := method.Outputs.Pack(big.NewInt(1_000_000_000_000_000_000)) // 1 ETH + if err != nil { + return nil, errNativeMethodOutput, err + } + return res, 0, nil + case "balanceOf": inputs, err := method.Inputs.Unpack(data[4:]) if err != nil || len(inputs) == 0 { - return nil, fmt.Errorf("input error") + return nil, errNativeMethodInput, err } address, ok := inputs[0].(common.Address) if !ok { - return nil, fmt.Errorf("input address parse error") + return nil, errNativeMethodInputAddress, fmt.Errorf("input address error") } balance, err := method.Outputs.Pack(state.GetBalance(address)) if err != nil { - return nil, err + return nil, errNativeMethodOutput, err } - return balance, state.Error() + if state.Error() != nil { + return nil, errNativeMethodStateError, state.Error() + } + return balance, 0, nil + default: + return nil, errNativeMethodNotFound, fmt.Errorf("method not found") } - - // should never reach here - return nil, fmt.Errorf("unsupported method") } func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *types.Header, arg TransactionArgs, disableCache bool) (*callResult, error) { @@ -198,18 +216,19 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ msg, err := arg.ToMessage(b.RPCGasCap(), header.BaseFee) if err != nil { - result.Code = errParam + result.Code = errCodeTxArgs result.Err = err.Error() return result, err } // skip EVM if requests for native token if strings.ToLower(msg.To().Hex()) == nativeAddr { - result.Result, err = handleNative(ctx, state, msg) + res, code, err := handleNative(ctx, state, msg) if err != nil { - result.Code = errLogic + result.Code = code result.Err = err.Error() } + result.Result = res return result, err } @@ -226,16 +245,15 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ gp := new(core.GasPool).AddGas(math.MaxUint64) evmRet, err := core.ApplyMessage(evm, msg, gp) if err != nil { - result.Code = errConsensus + result.Code = errMessageExecuting result.Err = err.Error() return result, err } // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { - err = errCancelled - result.Code = errLogic - result.Err = err.Error() + result.Code = errEVMCancelled + result.Err = fmt.Sprintf("execution cancelled, either fast failed or exceeding timeout(%v)", singleCallTimeout) return result, err } @@ -244,7 +262,7 @@ func doOneCall(ctx context.Context, b Backend, state *state.StateDB, header *typ if len(evmRet.Revert()) > 0 { e = newRevertError(evmRet) } - result.Code = errEVM + result.Code = errEVMReverted result.Err = e.Error() return result, e }