diff --git a/.travis.yml b/.travis.yml index 1c56f5d5d..94d2dda73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,10 +24,12 @@ jobs: script: - go run build/ci.go lint - # This builder does the Docker Hub build and upload for amd64 + # These builders create the Docker sub-images for multi-arch push and each + # will attempt to push the multi-arch image if they are the last builder - stage: build if: type = push os: linux + arch: amd64 dist: bionic go: 1.16.x env: @@ -36,8 +38,27 @@ jobs: - docker git: submodules: false # avoid cloning ethereum/tests + before_install: + - export DOCKER_CLI_EXPERIMENTAL=enabled script: - - go run build/ci.go docker -upload karalabe/geth-docker-test + - go run build/ci.go docker -image -manifest amd64,arm64 -upload karalabe/geth-docker-test + + - stage: build + if: type = push + os: linux + arch: arm64 + dist: bionic + go: 1.16.x + env: + - docker + services: + - docker + git: + submodules: false # avoid cloning ethereum/tests + before_install: + - export DOCKER_CLI_EXPERIMENTAL=enabled + script: + - go run build/ci.go docker -image -manifest amd64,arm64 -upload karalabe/geth-docker-test # This builder does the Ubuntu PPA upload - stage: build diff --git a/Dockerfile b/Dockerfile index 6e0dea11b..e76c5765b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,15 @@ +# Support setting various labels on the final image +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + # Build Geth in a stock Go builder container FROM golang:1.16-alpine as builder -RUN apk add --no-cache make gcc musl-dev linux-headers git +RUN apk add --no-cache gcc musl-dev linux-headers git ADD . /go-ethereum -RUN cd /go-ethereum && make geth +RUN cd /go-ethereum && go run build/ci.go install ./cmd/geth # Pull Geth into a second stage deploy alpine container FROM alpine:latest @@ -14,3 +19,10 @@ COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ EXPOSE 8545 8546 30303 30303/udp ENTRYPOINT ["geth"] + +# Add some metadata labels to help programatic image consumption +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM" diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 483afad8c..71f63b7a4 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -1,10 +1,15 @@ +# Support setting various labels on the final image +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + # Build Geth in a stock Go builder container FROM golang:1.16-alpine as builder -RUN apk add --no-cache make gcc musl-dev linux-headers git +RUN apk add --no-cache gcc musl-dev linux-headers git ADD . /go-ethereum -RUN cd /go-ethereum && make all +RUN cd /go-ethereum && go run build/ci.go install # Pull all binaries into a second stage deploy alpine container FROM alpine:latest @@ -13,3 +18,10 @@ RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ EXPOSE 8545 8546 30303 30303/udp + +# Add some metadata labels to help programatic image consumption +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM" diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 71648059c..274f6e4d9 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -229,13 +229,13 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } - head, err := c.transactor.HeaderByNumber(opts.Context, nil) + head, err := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil) if err != nil { return nil, err } if head.BaseFee != nil && opts.GasPrice == nil { if opts.GasTipCap == nil { - tip, err := c.transactor.SuggestGasTipCap(opts.Context) + tip, err := c.transactor.SuggestGasTipCap(ensureContext(opts.Context)) if err != nil { return nil, err } @@ -256,13 +256,10 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") } if opts.GasPrice == nil { - price, err := c.transactor.SuggestGasTipCap(opts.Context) + price, err := c.transactor.SuggestGasPrice(ensureContext(opts.Context)) if err != nil { return nil, err } - if head.BaseFee != nil { - price.Add(price, head.BaseFee) - } opts.GasPrice = price } } @@ -443,7 +440,7 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin // user specified it as such. func ensureContext(ctx context.Context) context.Context { if ctx == nil { - return context.TODO() + return context.Background() } return ctx } diff --git a/build/ci.go b/build/ci.go index 12e2b8b57..63dd60632 100644 --- a/build/ci.go +++ b/build/ci.go @@ -54,6 +54,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" "time" @@ -183,7 +184,7 @@ func main() { case "archive": doArchive(os.Args[2:]) case "docker": - doDockerImage(os.Args[2:]) + doDocker(os.Args[2:]) case "debsrc": doDebianSource(os.Args[2:]) case "nsis": @@ -455,9 +456,11 @@ func maybeSkipArchive(env build.Environment) { } // Builds the docker images and optionally uploads them to Docker Hub. -func doDockerImage(cmdline []string) { +func doDocker(cmdline []string) { var ( - upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) + image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) + manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`) + upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) ) flag.CommandLine.Parse(cmdline) @@ -465,6 +468,15 @@ func doDockerImage(cmdline []string) { env := build.Env() maybeSkipArchive(env) + // Retrieve the upload credentials and authenticate + user := getenvBase64("DOCKER_HUB_USERNAME") + pass := getenvBase64("DOCKER_HUB_PASSWORD") + + if len(user) > 0 && len(pass) > 0 { + auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin") + auther.Stdin = bytes.NewReader(pass) + build.MustRun(auther) + } // Retrieve the version infos to build and push to the following paths: // - ethereum/client-go:latest - Pushes to the master branch, Geth only // - ethereum/client-go:stable - Version tag publish on GitHub, Geth only @@ -482,25 +494,130 @@ func doDockerImage(cmdline []string) { case strings.HasPrefix(env.Tag, "v1."): tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), params.Version} } - // Build the docker images via CLI (don't pull in the `moby` dep to call 3 commands) - build.MustRunCommand("docker", "build", "--tag", fmt.Sprintf("%s:TAG", *upload), ".") - build.MustRunCommand("docker", "build", "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".") + // If architecture specific image builds are requested, build and push them + if *image { + build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:TAG", *upload), ".") + build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".") - // Retrieve the upload credentials and authenticate - user := getenvBase64("DOCKER_HUB_USERNAME") - pass := getenvBase64("DOCKER_HUB_PASSWORD") + // Tag and upload the images to Docker Hub + for _, tag := range tags { + gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) + toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) - if len(user) > 0 && len(pass) > 0 { - auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin") - auther.Stdin = bytes.NewReader(pass) - build.MustRun(auther) + // If the image already exists (non version tag), check the build + // number to prevent overwriting a newer commit if concurrent builds + // are running. This is still a tiny bit racey if two published are + // done at the same time, but that's extremely unlikely even on the + // master branch. + for _, img := range []string{gethImage, toolImage} { + if exec.Command("docker", "pull", img).Run() != nil { + continue // Generally the only failure is a missing image, which is good + } + buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() + if err != nil { + log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) + } + buildnum = bytes.TrimSpace(buildnum) + + if len(buildnum) > 0 && len(env.Buildnum) > 0 { + oldnum, err := strconv.Atoi(string(buildnum)) + if err != nil { + log.Fatalf("Failed to parse old image build number: %v", err) + } + newnum, err := strconv.Atoi(env.Buildnum) + if err != nil { + log.Fatalf("Failed to parse current build number: %v", err) + } + if oldnum > newnum { + log.Fatalf("Current build number %d not newer than existing %d", newnum, oldnum) + } else { + log.Printf("Updating %s from build %d to %d", img, oldnum, newnum) + } + } + } + build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), gethImage) + build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), toolImage) + build.MustRunCommand("docker", "push", gethImage) + build.MustRunCommand("docker", "push", toolImage) + } } - // Tag and upload the images to Docker Hub - for _, tag := range tags { - build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), fmt.Sprintf("%s:%s", *upload, tag)) - build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), fmt.Sprintf("%s:alltools-%s", *upload, tag)) - build.MustRunCommand("docker", "push", fmt.Sprintf("%s:%s", *upload, tag)) - build.MustRunCommand("docker", "push", fmt.Sprintf("%s:alltools-%s", *upload, tag)) + // If multi-arch image manifest push is requested, assemble it + if len(*manifest) != 0 { + // Since different architectures are pushed by different builders, wait + // until all required images are updated. + var mismatch bool + for i := 0; i < 2; i++ { // 2 attempts, second is race check + mismatch = false // hope there's no mismatch now + + for _, tag := range tags { + for _, arch := range strings.Split(*manifest, ",") { + gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, arch) + toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, arch) + + for _, img := range []string{gethImage, toolImage} { + if out, err := exec.Command("docker", "pull", img).CombinedOutput(); err != nil { + log.Printf("Required image %s unavailable: %v\nOutput: %s", img, err, out) + mismatch = true + break + } + buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() + if err != nil { + log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) + } + buildnum = bytes.TrimSpace(buildnum) + + if string(buildnum) != env.Buildnum { + log.Printf("Build number mismatch on %s: want %s, have %s", img, env.Buildnum, buildnum) + mismatch = true + break + } + } + if mismatch { + break + } + } + if mismatch { + break + } + } + if mismatch { + // Build numbers mismatching, retry in a short time to + // avoid concurrent failes in both publisher images. If + // however the retry failed too, it means the concurrent + // builder is still crunching, let that do the publish. + if i == 0 { + time.Sleep(30 * time.Second) + } + continue + } + break + } + if mismatch { + log.Println("Relinquishing publish to other builder") + return + } + // Assemble and push the Geth manifest image + for _, tag := range tags { + gethImage := fmt.Sprintf("%s:%s", *upload, tag) + + var gethSubImages []string + for _, arch := range strings.Split(*manifest, ",") { + gethSubImages = append(gethSubImages, gethImage+"-"+arch) + } + build.MustRunCommand("docker", append([]string{"manifest", "create", gethImage}, gethSubImages...)...) + build.MustRunCommand("docker", "manifest", "push", gethImage) + } + // Assemble and push the alltools manifest image + for _, tag := range tags { + toolImage := fmt.Sprintf("%s:alltools-%s", *upload, tag) + + var toolSubImages []string + for _, arch := range strings.Split(*manifest, ",") { + toolSubImages = append(toolSubImages, toolImage+"-"+arch) + } + build.MustRunCommand("docker", append([]string{"manifest", "create", toolImage}, toolSubImages...)...) + build.MustRunCommand("docker", "manifest", "push", toolImage) + } } } diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go index a9a213f33..6f7365483 100644 --- a/cmd/devp2p/internal/ethtest/helpers.go +++ b/cmd/devp2p/internal/ethtest/helpers.go @@ -24,6 +24,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" @@ -649,58 +650,68 @@ func (s *Suite) hashAnnounce(isEth66 bool) error { return fmt.Errorf("peering failed: %v", err) } // create NewBlockHashes announcement - nextBlock := s.fullChain.blocks[s.chain.Len()] - newBlockHash := &NewBlockHashes{ - {Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()}, + type anno struct { + Hash common.Hash // Hash of one particular block being announced + Number uint64 // Number of one particular block being announced } - + nextBlock := s.fullChain.blocks[s.chain.Len()] + announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()} + newBlockHash := &NewBlockHashes{announcement} if err := sendConn.Write(newBlockHash); err != nil { return fmt.Errorf("failed to write to connection: %v", err) } + // Announcement sent, now wait for a header request + var ( + id uint64 + msg Message + blockHeaderReq GetBlockHeaders + ) if isEth66 { - // expect GetBlockHeaders request, and respond - id, msg := sendConn.Read66() + id, msg = sendConn.Read66() switch msg := msg.(type) { case GetBlockHeaders: - blockHeaderReq := msg - if blockHeaderReq.Amount != 1 { - return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) - } - if blockHeaderReq.Origin.Hash != nextBlock.Hash() { - return fmt.Errorf("unexpected block header requested: %v", pretty.Sdump(blockHeaderReq)) - } - resp := ð.BlockHeadersPacket66{ - RequestId: id, - BlockHeadersPacket: eth.BlockHeadersPacket{ - nextBlock.Header(), - }, - } - if err := sendConn.Write66(resp, BlockHeaders{}.Code()); err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } + blockHeaderReq = msg default: return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) } + if blockHeaderReq.Amount != 1 { + return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) + } + if blockHeaderReq.Origin.Hash != announcement.Hash { + return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v", + pretty.Sdump(announcement), + pretty.Sdump(blockHeaderReq)) + } + if err := sendConn.Write66(ð.BlockHeadersPacket66{ + RequestId: id, + BlockHeadersPacket: eth.BlockHeadersPacket{ + nextBlock.Header(), + }, + }, BlockHeaders{}.Code()); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } } else { - // expect GetBlockHeaders request, and respond - switch msg := sendConn.Read().(type) { + msg = sendConn.Read() + switch msg := msg.(type) { case *GetBlockHeaders: - blockHeaderReq := *msg - if blockHeaderReq.Amount != 1 { - return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) - } - if blockHeaderReq.Origin.Hash != nextBlock.Hash() { - return fmt.Errorf("unexpected block header requested: %v", pretty.Sdump(blockHeaderReq)) - } - if err := sendConn.Write(&BlockHeaders{nextBlock.Header()}); err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } + blockHeaderReq = *msg default: return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) } + if blockHeaderReq.Amount != 1 { + return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) + } + if blockHeaderReq.Origin.Hash != announcement.Hash { + return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v", + pretty.Sdump(announcement), + pretty.Sdump(blockHeaderReq)) + } + if err := sendConn.Write(&BlockHeaders{nextBlock.Header()}); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } } // wait for block announcement - msg := recvConn.readAndServe(s.chain, timeout) + msg = recvConn.readAndServe(s.chain, timeout) switch msg := msg.(type) { case *NewBlockHashes: hashes := *msg diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go index 140b96bfa..1b5e5304e 100644 --- a/cmd/devp2p/internal/v4test/discv4tests.go +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -21,7 +21,6 @@ import ( "crypto/rand" "fmt" "net" - "reflect" "time" "github.com/ethereum/go-ethereum/crypto" @@ -89,16 +88,18 @@ func BasicPing(t *utesting.T) { // checkPong verifies that reply is a valid PONG matching the given ping hash. func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { - if reply == nil || reply.Kind() != v4wire.PongPacket { - return fmt.Errorf("expected PONG reply, got %v", reply) + if reply == nil { + return fmt.Errorf("expected PONG reply, got nil") + } + if reply.Kind() != v4wire.PongPacket { + return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply) } pong := reply.(*v4wire.Pong) if !bytes.Equal(pong.ReplyTok, pingHash) { return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) } - wantEndpoint := te.localEndpoint(te.l1) - if !reflect.DeepEqual(pong.To, wantEndpoint) { - return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, wantEndpoint) + if want := te.localEndpoint(te.l1); !want.IP.Equal(pong.To.IP) || want.UDP != pong.To.UDP { + return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, want) } if v4wire.Expired(pong.Expiration) { return fmt.Errorf("PONG is expired (%v)", pong.Expiration) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index ccc90618b..1ab2f001e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -152,7 +152,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } vmConfig.Tracer = tracer vmConfig.Debug = (tracer != nil) - statedb.Prepare(tx.Hash(), blockHash, txIndex) + statedb.Prepare(tx.Hash(), txIndex) txContext := core.NewEVMTxContext(msg) snapshot := statedb.Snapshot() evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) @@ -197,7 +197,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } // Set the receipt logs and create the bloom filter. - receipt.Logs = statedb.GetLogs(tx.Hash()) + receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) // These three are non-consensus fields: //receipt.BlockHash diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go index 695fdba1e..c7f079c02 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -16,11 +16,11 @@ var _ = (*stEnvMarshaling)(nil) // MarshalJSON marshals as JSON. func (s stEnv) MarshalJSON() ([]byte, error) { type stEnv struct { - Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` @@ -40,11 +40,11 @@ func (s stEnv) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (s *stEnv) UnmarshalJSON(input []byte) error { type stEnv struct { - Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 8a3e4e0ea..b9c0d17f3 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -129,11 +129,6 @@ var ( Name: "noreturndata", Usage: "disable return data output", } - EVMInterpreterFlag = cli.StringFlag{ - Name: "vm.evm", - Usage: "External EVM configuration (default = built-in interpreter)", - Value: "", - } ) var stateTransitionCommand = cli.Command{ @@ -185,7 +180,6 @@ func init() { DisableStackFlag, DisableStorageFlag, DisableReturnDataFlag, - EVMInterpreterFlag, } app.Commands = []cli.Command{ compileCommand, diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 2d890ef1a..e409d2692 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -211,9 +211,8 @@ func runCmd(ctx *cli.Context) error { Coinbase: genesisConfig.Coinbase, BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), EVMConfig: vm.Config{ - Tracer: tracer, - Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), - EVMInterpreter: ctx.GlobalString(EVMInterpreterFlag.Name), + Tracer: tracer, + Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), }, } diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c4ebf6488..117be96ae 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -63,7 +64,12 @@ var tomlSettings = toml.Config{ return field }, MissingField: func(rt reflect.Type, field string) error { - link := "" + id := fmt.Sprintf("%s.%s", rt.String(), field) + if deprecated(id) { + log.Warn("Config field is deprecated and won't have an effect", "name", id) + return nil + } + var link string if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" { link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name()) } @@ -228,3 +234,14 @@ func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name) } } + +func deprecated(field string) bool { + switch field { + case "ethconfig.Config.EVMInterpreter": + return true + case "ethconfig.Config.EWASMInterpreter": + return true + default: + return false + } +} diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 053ce96aa..151c12c68 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -137,14 +137,18 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { name: name, geth: runGeth(t, args...), } - // wait before we can attach to it. TODO: probe for it properly - time.Sleep(1 * time.Second) - var err error ipcpath := ipcEndpoint(ipcName, g.geth.Datadir) - if g.rpc, err = rpc.Dial(ipcpath); err != nil { - t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) + // We can't know exactly how long geth will take to start, so we try 10 + // times over a 5 second period. + var err error + for i := 0; i < 10; i++ { + time.Sleep(500 * time.Millisecond) + if g.rpc, err = rpc.Dial(ipcpath); err == nil { + return g + } } - return g + t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) + return nil } func initGeth(t *testing.T) string { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index bef31b517..581fc0352 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -150,8 +150,6 @@ var ( utils.GpoPercentileFlag, utils.GpoMaxGasPriceFlag, utils.GpoIgnoreGasPriceFlag, - utils.EWASMInterpreterFlag, - utils.EVMInterpreterFlag, utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 44444bdd5..dea0b7c08 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -203,8 +203,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "VIRTUAL MACHINE", Flags: []cli.Flag{ utils.VMEnableDebugFlag, - utils.EVMInterpreterFlag, - utils.EWASMInterpreterFlag, }, }, { diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index b238af031..35cfada66 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -482,7 +482,7 @@ ADD puppeth.png /dashboard/puppeth.png EXPOSE 80 -CMD ["node", "/server.js"] +CMD ["node", "./server.js"] ` // dashboardComposefile is the docker-compose.yml file required to deploy and diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cc5af5625..7ed5907db 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -760,16 +760,6 @@ var ( Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", Value: metrics.DefaultConfig.InfluxDBTags, } - EWASMInterpreterFlag = cli.StringFlag{ - Name: "vm.ewasm", - Usage: "External ewasm configuration (default = built-in interpreter)", - Value: "", - } - EVMInterpreterFlag = cli.StringFlag{ - Name: "vm.evm", - Usage: "External EVM configuration (default = built-in interpreter)", - Value: "", - } CatalystFlag = cli.BoolFlag{ Name: "catalyst", @@ -1302,8 +1292,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { // If we are running the light client, apply another group // settings for gas oracle. if light { - cfg.Blocks = ethconfig.LightClientGPO.Blocks - cfg.Percentile = ethconfig.LightClientGPO.Percentile + *cfg = ethconfig.LightClientGPO } if ctx.GlobalIsSet(GpoBlocksFlag.Name) { cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name) @@ -1587,13 +1576,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name) } - if ctx.GlobalIsSet(EWASMInterpreterFlag.Name) { - cfg.EWASMInterpreter = ctx.GlobalString(EWASMInterpreterFlag.Name) - } - - if ctx.GlobalIsSet(EVMInterpreterFlag.Name) { - cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name) - } if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name) } diff --git a/consensus/clique/api.go b/consensus/clique/api.go index 8e9a1e7de..6129b5cc5 100644 --- a/consensus/clique/api.go +++ b/consensus/clique/api.go @@ -17,11 +17,14 @@ package clique import ( + "encoding/json" "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -175,3 +178,51 @@ func (api *API) Status() (*status, error) { NumBlocks: numBlocks, }, nil } + +type blockNumberOrHashOrRLP struct { + *rpc.BlockNumberOrHash + RLP hexutil.Bytes `json:"rlp,omitempty"` +} + +func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error { + bnOrHash := new(rpc.BlockNumberOrHash) + // Try to unmarshal bNrOrHash + if err := bnOrHash.UnmarshalJSON(data); err == nil { + sb.BlockNumberOrHash = bnOrHash + return nil + } + // Try to unmarshal RLP + var input string + if err := json.Unmarshal(data, &input); err != nil { + return err + } + sb.RLP = hexutil.MustDecode(input) + return nil +} + +// GetSigner returns the signer for a specific clique block. +// Can be called with either a blocknumber, blockhash or an rlp encoded blob. +// The RLP encoded blob can either be a block or a header. +func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) { + if len(rlpOrBlockNr.RLP) == 0 { + blockNrOrHash := rlpOrBlockNr.BlockNumberOrHash + var header *types.Header + if blockNrOrHash == nil { + header = api.chain.CurrentHeader() + } else if hash, ok := blockNrOrHash.Hash(); ok { + header = api.chain.GetHeaderByHash(hash) + } else if number, ok := blockNrOrHash.Number(); ok { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + return api.clique.Author(header) + } + block := new(types.Block) + if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, block); err == nil { + return api.clique.Author(block.Header()) + } + header := new(types.Header) + if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, header); err != nil { + return common.Address{}, err + } + return api.clique.Author(header) +} diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index b693e8051..449095e72 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -710,7 +710,7 @@ func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API { func SealHash(header *types.Header) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() encodeSigHeader(hasher, header) - hasher.Sum(hash[:0]) + hasher.(crypto.KeccakState).Read(hash[:]) return hash } diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 4b8ff807f..1bd32acd3 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -112,3 +112,16 @@ func TestReimportMirroredState(t *testing.T) { t.Fatalf("chain head mismatch: have %d, want %d", head, 3) } } + +func TestSealHash(t *testing.T) { + have := SealHash(&types.Header{ + Difficulty: new(big.Int), + Number: new(big.Int), + Extra: make([]byte, 32+65), + BaseFee: new(big.Int), + }) + want := common.HexToHash("0xbd3d1fa43fbc4c5bfcc91b179ec92e2861df3654de60468beb908ff805359e8f") + if have != want { + t.Errorf("have %x, want %x", have, want) + } +} diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go index 333411db5..fd400b688 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559_test.go @@ -44,7 +44,6 @@ func copyConfig(original *params.ChainConfig) *params.ChainConfig { MuirGlacierBlock: original.MuirGlacierBlock, BerlinBlock: original.BerlinBlock, LondonBlock: original.LondonBlock, - EWASMBlock: original.EWASMBlock, CatalystBlock: original.CatalystBlock, Ethash: original.Ethash, Clique: original.Clique, diff --git a/core/chain_makers.go b/core/chain_makers.go index dc207f202..929a2aa3a 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -102,7 +102,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } - b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) + b.statedb.Prepare(tx.Hash(), len(b.txs)) receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) if err != nil { panic(err) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 5829e863e..916bffadc 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -61,8 +61,10 @@ func TestCreation(t *testing.T) { {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block - {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block - {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block + {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block + {12964999, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block + {12965000, ID{Hash: checksumToBytes(0xb715077d), Next: 0}}, // First London block + {20000000, ID{Hash: checksumToBytes(0xb715077d), Next: 0}}, // Future London block }, }, // Ropsten test cases @@ -203,11 +205,11 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet London, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). - {88888888, ID{Hash: checksumToBytes(0x0eb440f6), Next: 88888888}, ErrLocalIncompatibleOrStale}, + {88888888, ID{Hash: checksumToBytes(0xb715077d), Next: 88888888}, ErrLocalIncompatibleOrStale}, // Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing // fork) at block 7279999, before Petersburg. Local is incompatible. diff --git a/core/genesis.go b/core/genesis.go index 19890406e..dc50ff475 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -415,7 +415,10 @@ func DefaultCalaverasGenesisBlock() *Genesis { func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis { // Override the default period to the user requested one config := *params.AllCliqueProtocolChanges - config.Clique.Period = period + config.Clique = ¶ms.CliqueConfig{ + Period: period, + Epoch: config.Clique.Epoch, + } // Assemble and return the genesis with the precompiles and faucet pre-funded return &Genesis{ diff --git a/core/headerchain.go b/core/headerchain.go index 1dbf95878..07307c710 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -165,6 +165,7 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit ) batch := hc.chainDb.NewBatch() + parentKnown := true // Set to true to force hc.HasHeader check the first iteration for i, header := range headers { var hash common.Hash // The headers have already been validated at this point, so we already @@ -178,8 +179,10 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit number := header.Number.Uint64() newTD.Add(newTD, header.Difficulty) + // If the parent was not present, store it // If the header is already known, skip it, otherwise store - if !hc.HasHeader(hash, number) { + alreadyKnown := parentKnown && hc.HasHeader(hash, number) + if !alreadyKnown { // Irrelevant of the canonical status, write the TD and header to the database. rawdb.WriteTd(batch, hash, number, newTD) hc.tdCache.Add(hash, new(big.Int).Set(newTD)) @@ -192,6 +195,7 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit firstInserted = i } } + parentKnown = alreadyKnown lastHeader, lastHash, lastNumber = header, hash, number } @@ -570,7 +574,7 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d if parent == nil { parent = hc.genesisHeader } - parentHash = hdr.ParentHash + parentHash = parent.Hash() // Notably, since geth has the possibility for setting the head to a low // height which is even lower than ancient head. diff --git a/core/state/state_object.go b/core/state/state_object.go index f93f47d5f..38621ffb6 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -450,9 +450,6 @@ func (s *stateObject) setBalance(amount *big.Int) { s.data.Balance = amount } -// Return the gas back to the origin. Used by the Virtual machine or Closures -func (s *stateObject) ReturnGas(gas *big.Int) {} - func (s *stateObject) deepCopy(db *StateDB) *stateObject { stateObject := newObject(db, s.address, s.data) if s.trie != nil { diff --git a/core/state/statedb.go b/core/state/statedb.go index b6811b5a5..ad4d9ff93 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -89,10 +89,10 @@ type StateDB struct { // The refund counter, also used by state transitioning. refund uint64 - thash, bhash common.Hash - txIndex int - logs map[common.Hash][]*types.Log - logSize uint + thash common.Hash + txIndex int + logs map[common.Hash][]*types.Log + logSize uint preimages map[common.Hash][]byte @@ -186,15 +186,18 @@ func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) log.TxHash = s.thash - log.BlockHash = s.bhash log.TxIndex = uint(s.txIndex) log.Index = s.logSize s.logs[s.thash] = append(s.logs[s.thash], log) s.logSize++ } -func (s *StateDB) GetLogs(hash common.Hash) []*types.Log { - return s.logs[hash] +func (s *StateDB) GetLogs(hash common.Hash, blockHash common.Hash) []*types.Log { + logs := s.logs[hash] + for _, l := range logs { + l.BlockHash = blockHash + } + return logs } func (s *StateDB) Logs() []*types.Log { @@ -272,11 +275,6 @@ func (s *StateDB) TxIndex() int { return s.txIndex } -// BlockHash returns the current block hash set by Prepare. -func (s *StateDB) BlockHash() common.Hash { - return s.bhash -} - func (s *StateDB) GetCode(addr common.Address) []byte { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -333,17 +331,6 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, return proof, err } -// GetStorageProofByHash returns the Merkle proof for given storage slot. -func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][]byte, error) { - var proof proofList - trie := s.StorageTrie(a) - if trie == nil { - return proof, errors.New("storage trie for requested address does not exist") - } - err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) - return proof, err -} - // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) @@ -597,7 +584,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) } } newobj = newObject(s, addr, Account{}) - newobj.setNonce(0) // sets the object to dirty if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { @@ -894,9 +880,8 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Prepare sets the current transaction hash and index and block hash which is // used when the EVM emits new state logs. -func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { +func (s *StateDB) Prepare(thash common.Hash, ti int) { s.thash = thash - s.bhash = bhash s.txIndex = ti s.accessList = newAccessList() } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index f81455312..e9576d4dc 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -463,9 +463,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d", state.GetRefund(), checkstate.GetRefund()) } - if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) { + if !reflect.DeepEqual(state.GetLogs(common.Hash{}, common.Hash{}), checkstate.GetLogs(common.Hash{}, common.Hash{})) { return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", - state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) + state.GetLogs(common.Hash{}, common.Hash{}), checkstate.GetLogs(common.Hash{}, common.Hash{})) } return nil } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index ac5e95c5c..25c3730e3 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -312,12 +312,11 @@ func (sf *subfetcher) loop() { default: // No termination request yet, prefetch the next entry - taskid := string(task) - if _, ok := sf.seen[taskid]; ok { + if _, ok := sf.seen[string(task)]; ok { sf.dups++ } else { sf.trie.TryGet(task) - sf.seen[taskid] = struct{}{} + sf.seen[string(task)] = struct{}{} } } } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index ecdfa67f0..10a172294 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -67,7 +67,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c if err != nil { return // Also invalid block, bail out } - statedb.Prepare(tx.Hash(), block.Hash(), i) + statedb.Prepare(tx.Hash(), i) if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { return // Ugh, something went horribly wrong, bail out } diff --git a/core/state_processor.go b/core/state_processor.go index 6d91b2db2..09f80380d 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -18,6 +18,7 @@ package core import ( "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -57,11 +58,13 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( - receipts types.Receipts - usedGas = new(uint64) - header = block.Header() - allLogs []*types.Log - gp = new(GasPool).AddGas(block.GasLimit()) + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) ) // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { @@ -77,9 +80,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg pluginBlockProcessingError(tx, block, err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - statedb.Prepare(tx.Hash(), block.Hash(), i) + statedb.Prepare(tx.Hash(), i) pluginPreProcessTransaction(tx, block, i) - receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) + receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { pluginBlockProcessingError(tx, block, err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) @@ -94,7 +97,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *usedGas, nil } -func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { +func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) @@ -107,10 +110,10 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // Update the state with pending changes. var root []byte - if config.IsByzantium(header.Number) { + if config.IsByzantium(blockNumber) { statedb.Finalise(true) } else { - root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() + root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() } *usedGas += result.UsedGas @@ -131,10 +134,10 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon } // Set the receipt logs and create the bloom filter. - receipt.Logs = statedb.GetLogs(tx.Hash()) + receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - receipt.BlockHash = statedb.BlockHash() - receipt.BlockNumber = header.Number + receipt.BlockHash = blockHash + receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) return receipt, err } @@ -151,5 +154,5 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) - return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv) + return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } diff --git a/core/tx_journal.go b/core/tx_journal.go index 41b5156d4..d282126a0 100644 --- a/core/tx_journal.go +++ b/core/tx_journal.go @@ -138,7 +138,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro journal.writer = nil } // Generate a new journal with the contents of the current pool - replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } @@ -158,7 +158,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro if err = os.Rename(journal.path+".new", journal.path); err != nil { return err } - sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0755) + sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } diff --git a/core/tx_pool.go b/core/tx_pool.go index a583c3d53..c5b604748 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -494,6 +494,23 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common return pending, queued } +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + var pending types.Transactions + if list, ok := pool.pending[addr]; ok { + pending = list.Flatten() + } + var queued types.Transactions + if list, ok := pool.queue[addr]; ok { + queued = list.Flatten() + } + return pending, queued +} + // Pending retrieves all currently processable transactions, grouped by origin // account and sorted by nonce. The returned transaction set is a copy and can be // freely modified by calling code. diff --git a/core/types/block.go b/core/types/block.go index d189c86c2..360f1eb47 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -129,6 +129,11 @@ func (h *Header) SanityCheck() error { if eLen := len(h.Extra); eLen > 100*1024 { return fmt.Errorf("too large block extradata: size %d", eLen) } + if h.BaseFee != nil { + if bfLen := h.BaseFee.BitLen(); bfLen > 256 { + return fmt.Errorf("too large base fee: bitlen %d", bfLen) + } + } return nil } diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go index 90e1c14d9..3ffa9c2fe 100644 --- a/core/types/gen_log_json.go +++ b/core/types/gen_log_json.go @@ -18,12 +18,12 @@ func (l Log) MarshalJSON() ([]byte, error) { Address common.Address `json:"address" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"` Data hexutil.Bytes `json:"data" gencodec:"required"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` - TxHash common.Hash `json:"transactionHash" gencodec:"required"` - TxIndex hexutil.Uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index hexutil.Uint `json:"logIndex"` - Removed bool `json:"removed"` + BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash common.Hash `json:"blockHash" rlp:"-"` + Index hexutil.Uint `json:"logIndex" rlp:"-"` + Removed bool `json:"removed" rlp:"-"` } var enc Log enc.Address = l.Address @@ -44,12 +44,12 @@ func (l *Log) UnmarshalJSON(input []byte) error { Address *common.Address `json:"address" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"` Data *hexutil.Bytes `json:"data" gencodec:"required"` - BlockNumber *hexutil.Uint64 `json:"blockNumber"` - TxHash *common.Hash `json:"transactionHash" gencodec:"required"` - TxIndex *hexutil.Uint `json:"transactionIndex"` - BlockHash *common.Hash `json:"blockHash"` - Index *hexutil.Uint `json:"logIndex"` - Removed *bool `json:"removed"` + BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash *common.Hash `json:"blockHash" rlp:"-"` + Index *hexutil.Uint `json:"logIndex" rlp:"-"` + Removed *bool `json:"removed" rlp:"-"` } var dec Log if err := json.Unmarshal(input, &dec); err != nil { diff --git a/core/types/log.go b/core/types/log.go index 87865bdb2..067708db3 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -17,11 +17,8 @@ package types import ( - "io" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" ) //go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go @@ -40,19 +37,19 @@ type Log struct { // Derived fields. These fields are filled in by the node // but not secured by consensus. // block in which the transaction was included - BlockNumber uint64 `json:"blockNumber"` + BlockNumber uint64 `json:"blockNumber" rlp:"-"` // hash of the transaction - TxHash common.Hash `json:"transactionHash" gencodec:"required"` + TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` // index of the transaction in the block - TxIndex uint `json:"transactionIndex"` + TxIndex uint `json:"transactionIndex" rlp:"-"` // hash of the block in which the transaction was included - BlockHash common.Hash `json:"blockHash"` + BlockHash common.Hash `json:"blockHash" rlp:"-"` // index of the log in the block - Index uint `json:"logIndex"` + Index uint `json:"logIndex" rlp:"-"` // The Removed field is true if this log was reverted due to a chain reorganisation. // You must pay attention to this field if you receive logs through a filter query. - Removed bool `json:"removed"` + Removed bool `json:"removed" rlp:"-"` } type logMarshaling struct { @@ -61,52 +58,3 @@ type logMarshaling struct { TxIndex hexutil.Uint Index hexutil.Uint } - -type rlpLog struct { - Address common.Address - Topics []common.Hash - Data []byte -} - -// rlpStorageLog is the storage encoding of a log. -type rlpStorageLog rlpLog - -// EncodeRLP implements rlp.Encoder. -func (l *Log) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}) -} - -// DecodeRLP implements rlp.Decoder. -func (l *Log) DecodeRLP(s *rlp.Stream) error { - var dec rlpLog - err := s.Decode(&dec) - if err == nil { - l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data - } - return err -} - -// LogForStorage is a wrapper around a Log that flattens and parses the entire content of -// a log including non-consensus fields. -type LogForStorage Log - -// EncodeRLP implements rlp.Encoder. -func (l *LogForStorage) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, rlpStorageLog{ - Address: l.Address, - Topics: l.Topics, - Data: l.Data, - }) -} - -// DecodeRLP implements rlp.Decoder. -// -// Note some redundant fields(e.g. block number, tx hash etc) will be assembled later. -func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error { - var dec rlpStorageLog - if err := s.Decode(&dec); err != nil { - return err - } - *l = LogForStorage{Address: dec.Address, Topics: dec.Topics, Data: dec.Data} - return nil -} diff --git a/core/types/receipt.go b/core/types/receipt.go index b949bd2bd..d9029ca2e 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -94,7 +94,7 @@ type receiptRLP struct { type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 - Logs []*LogForStorage + Logs []*Log } // NewReceipt creates a barebone transaction receipt, copying the init fields. @@ -217,10 +217,7 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { enc := &storedReceiptRLP{ PostStateOrStatus: (*Receipt)(r).statusEncoding(), CumulativeGasUsed: r.CumulativeGasUsed, - Logs: make([]*LogForStorage, len(r.Logs)), - } - for i, log := range r.Logs { - enc.Logs[i] = (*LogForStorage)(log) + Logs: r.Logs, } return rlp.Encode(w, enc) } @@ -235,10 +232,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { return err } r.CumulativeGasUsed = stored.CumulativeGasUsed - r.Logs = make([]*Log, len(stored.Logs)) - for i, log := range stored.Logs { - r.Logs[i] = (*Log)(log) - } + r.Logs = stored.Logs r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) return nil } diff --git a/core/types/transaction.go b/core/types/transaction.go index 920318a16..a556f4b57 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -67,7 +67,7 @@ func NewTx(inner TxData) *Transaction { // TxData is the underlying data of a transaction. // -// This is implemented by LegacyTx and AccessListTx. +// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx. type TxData interface { txType() byte // returns the type ID copy() TxData // creates a deep copy and initializes all fields diff --git a/core/vm/evm.go b/core/vm/evm.go index c7e25eb30..896476673 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,7 +17,6 @@ package vm import ( - "errors" "math/big" "sync/atomic" "time" @@ -58,24 +57,6 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { return p, ok } -// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. -func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { - for _, interpreter := range evm.interpreters { - if interpreter.CanRun(contract.Code) { - if evm.interpreter != interpreter { - // Ensure that the interpreter pointer is set back - // to its current value upon return. - defer func(i Interpreter) { - evm.interpreter = i - }(evm.interpreter) - evm.interpreter = interpreter - } - return interpreter.Run(contract, input, readOnly) - } - } - return nil, errors.New("no compatible interpreter") -} - // BlockContext provides the EVM with auxiliary information. Once provided // it shouldn't be modified. type BlockContext struct { @@ -131,8 +112,7 @@ type EVM struct { Config Config // global (to this context) ethereum virtual machine // used throughout the execution of the tx. - interpreters []Interpreter - interpreter Interpreter + interpreter *EVMInterpreter // abort is used to abort the EVM calling operations // NOTE: must be set atomically abort int32 @@ -146,36 +126,14 @@ type EVM struct { // only ever be used *once*. func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM { evm := &EVM{ - Context: blockCtx, - TxContext: txCtx, - StateDB: statedb, - Config: config, - chainConfig: chainConfig, - chainRules: chainConfig.Rules(blockCtx.BlockNumber), - interpreters: make([]Interpreter, 0, 1), + Context: blockCtx, + TxContext: txCtx, + StateDB: statedb, + Config: config, + chainConfig: chainConfig, + chainRules: chainConfig.Rules(blockCtx.BlockNumber), } - - if chainConfig.IsEWASM(blockCtx.BlockNumber) { - // to be implemented by EVM-C and Wagon PRs. - // if vmConfig.EWASMInterpreter != "" { - // extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":") - // path := extIntOpts[0] - // options := []string{} - // if len(extIntOpts) > 1 { - // options = extIntOpts[1..] - // } - // evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options)) - // } else { - // evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig)) - // } - panic("No supported ewasm interpreter yet.") - } - - // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here - // as we always want to have the built-in EVM as the failover option. - evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, config)) - evm.interpreter = evm.interpreters[0] - + evm.interpreter = NewEVMInterpreter(evm, config) return evm } @@ -198,7 +156,7 @@ func (evm *EVM) Cancelled() bool { } // Interpreter returns the current interpreter -func (evm *EVM) Interpreter() Interpreter { +func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter } @@ -256,7 +214,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) - ret, err = run(evm, contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } } @@ -308,7 +266,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) - ret, err = run(evm, contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } if err != nil { @@ -343,7 +301,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) - ret, err = run(evm, contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } if err != nil { @@ -394,7 +352,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. - ret, err = run(evm, contract, input, true) + ret, err = evm.interpreter.Run(contract, input, true) gas = contract.Gas } if err != nil { @@ -462,7 +420,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } start := time.Now() - ret, err := run(evm, contract, nil, false) + ret, err := evm.interpreter.Run(contract, nil, false) // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index ac04afe8b..365f3b791 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -4,11 +4,11 @@ package vm import ( "encoding/json" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" ) var _ = (*structLogMarshaling)(nil) @@ -22,8 +22,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { GasCost math.HexOrDecimal64 `json:"gasCost"` Memory hexutil.Bytes `json:"memory"` MemorySize int `json:"memSize"` - Stack []*math.HexOrDecimal256 `json:"stack"` - ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + Stack []uint256.Int `json:"stack"` ReturnData hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` @@ -39,12 +38,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.GasCost = math.HexOrDecimal64(s.GasCost) enc.Memory = s.Memory enc.MemorySize = s.MemorySize - if s.Stack != nil { - enc.Stack = make([]*math.HexOrDecimal256, len(s.Stack)) - for k, v := range s.Stack { - enc.Stack[k] = (*math.HexOrDecimal256)(v) - } - } + enc.Stack = s.Stack enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth @@ -64,7 +58,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { GasCost *math.HexOrDecimal64 `json:"gasCost"` Memory *hexutil.Bytes `json:"memory"` MemorySize *int `json:"memSize"` - Stack []*math.HexOrDecimal256 `json:"stack"` + Stack []uint256.Int `json:"stack"` ReturnData *hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` @@ -94,10 +88,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { s.MemorySize = *dec.MemorySize } if dec.Stack != nil { - s.Stack = make([]*big.Int, len(dec.Stack)) - for k, v := range dec.Stack { - s.Stack[k] = (*big.Int)(v) - } + s.Stack = dec.Stack } if dec.ReturnData != nil { s.ReturnData = *dec.ReturnData diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 575de1376..560d26a0b 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -96,7 +96,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) - evmInterpreter = env.interpreter.(*EVMInterpreter) + evmInterpreter = env.interpreter ) for i, test := range tests { @@ -234,7 +234,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) - interpreter = env.interpreter.(*EVMInterpreter) + interpreter = env.interpreter ) result := make([]TwoOperandTestcase, len(args)) for i, param := range args { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 0efa78c97..9cf0c4e2c 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -35,34 +35,9 @@ type Config struct { JumpTable [256]*operation // EVM instruction table, automatically populated if unset - EWASMInterpreter string // External EWASM interpreter options - EVMInterpreter string // External EVM interpreter options - ExtraEips []int // Additional EIPS that are to be enabled } -// Interpreter is used to run Ethereum based contracts and will utilise the -// passed environment to query external sources for state information. -// The Interpreter will run the byte code VM based on the passed -// configuration. -type Interpreter interface { - // Run loops and evaluates the contract's code with the given input data and returns - // the return byte-slice and an error if one occurred. - Run(contract *Contract, input []byte, static bool) ([]byte, error) - // CanRun tells if the contract, passed as an argument, can be - // run by the current interpreter. This is meant so that the - // caller can do something like: - // - // ```golang - // for _, interpreter := range interpreters { - // if interpreter.CanRun(contract.code) { - // interpreter.Run(contract.code, input) - // } - // } - // ``` - CanRun([]byte) bool -} - // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { @@ -303,9 +278,3 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } return nil, nil } - -// CanRun tells if the contract, passed as an argument, can be -// run by the current interpreter. -func (in *EVMInterpreter) CanRun(code []byte) bool { - return true -} diff --git a/core/vm/logger.go b/core/vm/logger.go index 9ccaafc77..900a5e585 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) // Storage represents a contract's storage. @@ -66,7 +67,7 @@ type StructLog struct { GasCost uint64 `json:"gasCost"` Memory []byte `json:"memory"` MemorySize int `json:"memSize"` - Stack []*big.Int `json:"stack"` + Stack []uint256.Int `json:"stack"` ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` @@ -76,7 +77,6 @@ type StructLog struct { // overrides for gencodec type structLogMarshaling struct { - Stack []*math.HexOrDecimal256 Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes @@ -135,6 +135,14 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { return logger } +// Reset clears the data held by the logger. +func (l *StructLogger) Reset() { + l.storage = make(map[common.Address]Storage) + l.output = make([]byte, 0) + l.logs = l.logs[:0] + l.err = nil +} + // CaptureStart implements the Tracer interface to initialize the tracing operation. func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } @@ -157,16 +165,16 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui copy(mem, memory.Data()) } // Copy a snapshot of the current stack state to a new buffer - var stck []*big.Int + var stck []uint256.Int if !l.cfg.DisableStack { - stck = make([]*big.Int, len(stack.Data())) + stck = make([]uint256.Int, len(stack.Data())) for i, item := range stack.Data() { - stck[i] = new(big.Int).Set(item.ToBig()) + stck[i] = item } } // Copy a snapshot of the current storage to a new container var storage Storage - if !l.cfg.DisableStorage { + if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) { // initialise new changed values storage container for this contract // if not present. if l.storage[contract.Address()] == nil { @@ -179,16 +187,16 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui value = env.StateDB.GetState(contract.Address(), address) ) l.storage[contract.Address()][address] = value - } - // capture SSTORE opcodes and record the written entry in the local storage. - if op == SSTORE && stack.len() >= 2 { + storage = l.storage[contract.Address()].Copy() + } else if op == SSTORE && stack.len() >= 2 { + // capture SSTORE opcodes and record the written entry in the local storage. var ( value = common.Hash(stack.data[stack.len()-2].Bytes32()) address = common.Hash(stack.data[stack.len()-1].Bytes32()) ) l.storage[contract.Address()][address] = value + storage = l.storage[contract.Address()].Copy() } - storage = l.storage[contract.Address()].Copy() } var rdata []byte if !l.cfg.DisableReturnData { @@ -238,7 +246,7 @@ func WriteTrace(writer io.Writer, logs []StructLog) { if len(log.Stack) > 0 { fmt.Fprintln(writer, "Stack:") for i := len(log.Stack) - 1; i >= 0; i-- { - fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) + fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex()) } } if len(log.Memory) > 0 { @@ -314,7 +322,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 // format stack var a []string for _, elem := range stack.data { - a = append(a, fmt.Sprintf("%v", elem.String())) + a = append(a, elem.Hex()) } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 93878b980..5210f479f 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -57,7 +57,6 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint Gas: gas, GasCost: cost, MemorySize: memory.Len(), - Storage: nil, Depth: depth, RefundCounter: env.StateDB.GetRefund(), Err: err, @@ -66,12 +65,7 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Memory = memory.Data() } if !l.cfg.DisableStack { - //TODO(@holiman) improve this - logstack := make([]*big.Int, len(stack.Data())) - for i, item := range stack.Data() { - logstack[i] = item.ToBig() - } - log.Stack = logstack + log.Stack = stack.data } if !l.cfg.DisableReturnData { log.ReturnData = rData diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 0bbfd4469..730d8374b 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -30,7 +30,6 @@ type dummyContractRef struct { calledForEach bool } -func (dummyContractRef) ReturnGas(*big.Int) {} func (dummyContractRef) Address() common.Address { return common.Address{} } func (dummyContractRef) Value() *big.Int { return new(big.Int) } func (dummyContractRef) SetCode(common.Hash, []byte) {} diff --git a/crypto/secp256k1/panic_cb.go b/crypto/secp256k1/panic_cb.go index 262846fd8..5da2bea37 100644 --- a/crypto/secp256k1/panic_cb.go +++ b/crypto/secp256k1/panic_cb.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. -// +build !gofuzz cgo +// +build !gofuzz +// +build cgo package secp256k1 diff --git a/crypto/secp256k1/scalar_mult_cgo.go b/crypto/secp256k1/scalar_mult_cgo.go index 34998ad1a..f28a1c782 100644 --- a/crypto/secp256k1/scalar_mult_cgo.go +++ b/crypto/secp256k1/scalar_mult_cgo.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. -// +build !gofuzz cgo +// +build !gofuzz +// +build cgo package secp256k1 diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 9e942ac6f..a1bcf7796 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. -// +build !gofuzz cgo +// +build !gofuzz +// +build cgo // Package secp256k1 wraps the bitcoin secp256k1 C library. package secp256k1 diff --git a/crypto/signature_cgo.go b/crypto/signature_cgo.go index 1fe84509e..843360298 100644 --- a/crypto/signature_cgo.go +++ b/crypto/signature_cgo.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build !nacl,!js,cgo +// +build !nacl,!js,cgo,!gofuzz package crypto diff --git a/crypto/signature_nocgo.go b/crypto/signature_nocgo.go index 067d32e13..77c8a1db0 100644 --- a/crypto/signature_nocgo.go +++ b/crypto/signature_nocgo.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build nacl js !cgo +// +build nacl js !cgo gofuzz package crypto diff --git a/eth/api.go b/eth/api.go index 6a22c9e41..8b96d1f31 100644 --- a/eth/api.go +++ b/eth/api.go @@ -129,6 +129,12 @@ func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { return true } +// SetGasLimit sets the gaslimit to target towards during mining. +func (api *PrivateMinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool { + api.e.Miner().SetGasCeil(uint64(gasLimit)) + return true +} + // SetEtherbase sets the etherbase of the miner func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool { api.e.SetEtherbase(etherbase) diff --git a/eth/api_backend.go b/eth/api_backend.go index e6810f2a9..f22462c7c 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -133,6 +133,10 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r return nil, errors.New("invalid arguments; neither block nor hash specified") } +func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return b.eth.miner.PendingBlockAndReceipts() +} + func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { // Pending state is only known by the miner if number == rpc.PendingBlockNumber { @@ -263,6 +267,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, return b.eth.TxPool().Content() } +func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + return b.eth.TxPool().ContentFrom(addr) +} + func (b *EthAPIBackend) TxPool() *core.TxPool { return b.eth.TxPool() } @@ -279,6 +287,10 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) return b.gpo.SuggestTipCap(ctx) } +func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { + return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) +} + func (b *EthAPIBackend) ChainDb() ethdb.Database { return b.eth.ChainDb() } diff --git a/eth/backend.go b/eth/backend.go index c2480b378..78cbe7a9e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -175,8 +175,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, - EWASMInterpreter: config.EWASMInterpreter, - EVMInterpreter: config.EVMInterpreter, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index c074ac5af..2622c4a14 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -178,7 +178,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD from, _ := types.Sender(signer, tx) // Execute the transaction - env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount) + env.state.Prepare(tx.Hash(), env.tcount) err = env.commitTransaction(tx, coinbase) switch err { case core.ErrGasLimitReached: diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index ac7edc2c6..04ec12cfa 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -40,10 +40,10 @@ const ( ) var ( - blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download - blockCacheInitialItems = 2048 // Initial number of blocks to start fetching, before we know the sizes of the blocks - blockCacheMemory = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching - blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones + blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download + blockCacheInitialItems = 2048 // Initial number of blocks to start fetching, before we know the sizes of the blocks + blockCacheMemory = 256 * 1024 * 1024 // Maximum amount of memory to use for block caching + blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones ) var ( @@ -783,8 +783,9 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLists [][]*types.Header) (int, error) { q.lock.Lock() defer q.lock.Unlock() + trieHasher := trie.NewStackTrie(nil) validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Transactions(txLists[index]), trie.NewStackTrie(nil)) != header.TxHash { + if types.DeriveSha(types.Transactions(txLists[index]), trieHasher) != header.TxHash { return errInvalidBody } if types.CalcUncleHash(uncleLists[index]) != header.UncleHash { @@ -808,8 +809,9 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, error) { q.lock.Lock() defer q.lock.Unlock() + trieHasher := trie.NewStackTrie(nil) validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Receipts(receiptList[index]), trie.NewStackTrie(nil)) != header.ReceiptHash { + if types.DeriveSha(types.Receipts(receiptList[index]), trieHasher) != header.ReceiptHash { return errInvalidReceipt } return nil diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 349c8da6c..0913b69d7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -41,18 +41,22 @@ import ( // FullNodeGPO contains default gasprice oracle settings for full node. var FullNodeGPO = gasprice.Config{ - Blocks: 20, - Percentile: 60, - MaxPrice: gasprice.DefaultMaxPrice, - IgnorePrice: gasprice.DefaultIgnorePrice, + Blocks: 20, + Percentile: 60, + MaxHeaderHistory: 0, + MaxBlockHistory: 0, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, } // LightClientGPO contains default gasprice oracle settings for light client. var LightClientGPO = gasprice.Config{ - Blocks: 2, - Percentile: 60, - MaxPrice: gasprice.DefaultMaxPrice, - IgnorePrice: gasprice.DefaultIgnorePrice, + Blocks: 2, + Percentile: 60, + MaxHeaderHistory: 300, + MaxBlockHistory: 5, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, } // Defaults contains default settings for use on the Ethereum main net. @@ -182,12 +186,6 @@ type Config struct { // Miscellaneous options DocRoot string `toml:"-"` - // Type of the EWASM interpreter ("" for default) - EWASMInterpreter string - - // Type of the EVM interpreter ("" for default) - EVMInterpreter string - // RPCGasCap is the global gas cap for eth-call variants. RPCGasCap uint64 diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index ca93b2ad0..2310dd449 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -3,6 +3,7 @@ package ethconfig import ( + "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -53,12 +54,11 @@ func (c Config) MarshalTOML() (interface{}, error) { GPO gasprice.Config EnablePreimageRecording bool DocRoot string `toml:"-"` - EWASMInterpreter string - EVMInterpreter string - RPCGasCap uint64 `toml:",omitempty"` - RPCTxFeeCap float64 `toml:",omitempty"` + RPCGasCap uint64 + RPCTxFeeCap float64 Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + OverrideLondon *big.Int `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -97,12 +97,11 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.GPO = c.GPO enc.EnablePreimageRecording = c.EnablePreimageRecording enc.DocRoot = c.DocRoot - enc.EWASMInterpreter = c.EWASMInterpreter - enc.EVMInterpreter = c.EVMInterpreter enc.RPCGasCap = c.RPCGasCap enc.RPCTxFeeCap = c.RPCTxFeeCap enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle + enc.OverrideLondon = c.OverrideLondon return &enc, nil } @@ -145,12 +144,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { GPO *gasprice.Config EnablePreimageRecording *bool DocRoot *string `toml:"-"` - EWASMInterpreter *string - EVMInterpreter *string - RPCGasCap *uint64 `toml:",omitempty"` - RPCTxFeeCap *float64 `toml:",omitempty"` + RPCGasCap *uint64 + RPCTxFeeCap *float64 Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + OverrideLondon *big.Int `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -264,12 +262,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.DocRoot != nil { c.DocRoot = *dec.DocRoot } - if dec.EWASMInterpreter != nil { - c.EWASMInterpreter = *dec.EWASMInterpreter - } - if dec.EVMInterpreter != nil { - c.EVMInterpreter = *dec.EVMInterpreter - } if dec.RPCGasCap != nil { c.RPCGasCap = *dec.RPCGasCap } @@ -282,5 +274,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.CheckpointOracle != nil { c.CheckpointOracle = dec.CheckpointOracle } + if dec.OverrideLondon != nil { + c.OverrideLondon = dec.OverrideLondon + } return nil } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 3177a877e..45983c97c 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -833,15 +833,17 @@ func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { // internal state. func (f *BlockFetcher) forgetHash(hash common.Hash) { // Remove all pending announces and decrement DOS counters - for _, announce := range f.announced[hash] { - f.announces[announce.origin]-- - if f.announces[announce.origin] <= 0 { - delete(f.announces, announce.origin) + if announceMap, ok := f.announced[hash]; ok { + for _, announce := range announceMap { + f.announces[announce.origin]-- + if f.announces[announce.origin] <= 0 { + delete(f.announces, announce.origin) + } + } + delete(f.announced, hash) + if f.announceChangeHook != nil { + f.announceChangeHook(hash, false) } - } - delete(f.announced, hash) - if f.announceChangeHook != nil { - f.announceChangeHook(hash, false) } // Remove any pending fetches and decrement the DOS counters if announce := f.fetching[hash]; announce != nil { diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 54b5b13fb..b6d1125b5 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -698,6 +698,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0) imported := make(chan interface{}) + announced := make(chan interface{}) tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { if light { if header == nil { @@ -712,9 +713,23 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { } } // Announce a block with a bad number, check for immediate drop + tester.fetcher.announceChangeHook = func(hash common.Hash, b bool) { + announced <- nil + } tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) + verifyAnnounce := func() { + for i := 0; i < 2; i++ { + select { + case <-announced: + continue + case <-time.After(1 * time.Second): + t.Fatal("announce timeout") + return + } + } + } + verifyAnnounce() verifyImportEvent(t, imported, false) - tester.lock.RLock() dropped := tester.drops["bad"] tester.lock.RUnlock() @@ -722,11 +737,11 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { if !dropped { t.Fatalf("peer with invalid numbered announcement not dropped") } - goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) // Make sure a good announcement passes without a drop tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher) + verifyAnnounce() verifyImportEvent(t, imported, true) tester.lock.RLock() diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go new file mode 100644 index 000000000..a14dd594b --- /dev/null +++ b/eth/gasprice/feehistory.go @@ -0,0 +1,300 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package gasprice + +import ( + "context" + "errors" + "fmt" + "math/big" + "sort" + "sync/atomic" + + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errInvalidPercentile = errors.New("invalid reward percentile") + errRequestBeyondHead = errors.New("request beyond head block") +) + +const ( + // maxFeeHistory is the maximum number of blocks that can be retrieved for a + // fee history request. + maxFeeHistory = 1024 + + // maxBlockFetchers is the max number of goroutines to spin up to pull blocks + // for the fee history calculation (mostly relevant for LES). + maxBlockFetchers = 4 +) + +// blockFees represents a single block for processing +type blockFees struct { + // set by the caller + blockNumber rpc.BlockNumber + header *types.Header + block *types.Block // only set if reward percentiles are requested + receipts types.Receipts + // filled by processBlock + reward []*big.Int + baseFee, nextBaseFee *big.Int + gasUsedRatio float64 + err error +} + +// txGasAndReward is sorted in ascending order based on reward +type ( + txGasAndReward struct { + gasUsed uint64 + reward *big.Int + } + sortGasAndReward []txGasAndReward +) + +func (s sortGasAndReward) Len() int { return len(s) } +func (s sortGasAndReward) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortGasAndReward) Less(i, j int) bool { + return s[i].reward.Cmp(s[j].reward) < 0 +} + +// processBlock takes a blockFees structure with the blockNumber, the header and optionally +// the block field filled in, retrieves the block from the backend if not present yet and +// fills in the rest of the fields. +func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { + chainconfig := oracle.backend.ChainConfig() + if bf.baseFee = bf.header.BaseFee; bf.baseFee == nil { + bf.baseFee = new(big.Int) + } + if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { + bf.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header) + } else { + bf.nextBaseFee = new(big.Int) + } + bf.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) + if len(percentiles) == 0 { + // rewards were not requested, return null + return + } + if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { + log.Error("Block or receipts are missing while reward percentiles are requested") + return + } + + bf.reward = make([]*big.Int, len(percentiles)) + if len(bf.block.Transactions()) == 0 { + // return an all zero row if there are no transactions to gather data from + for i := range bf.reward { + bf.reward[i] = new(big.Int) + } + return + } + + sorter := make(sortGasAndReward, len(bf.block.Transactions())) + for i, tx := range bf.block.Transactions() { + reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) + sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} + } + sort.Sort(sorter) + + var txIndex int + sumGasUsed := sorter[0].gasUsed + + for i, p := range percentiles { + thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100) + for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 { + txIndex++ + sumGasUsed += sorter[txIndex].gasUsed + } + bf.reward[i] = sorter[txIndex].reward + } +} + +// resolveBlockRange resolves the specified block range to absolute block numbers while also +// enforcing backend specific limitations. The pending block and corresponding receipts are +// also returned if requested and available. +// Note: an error is only returned if retrieving the head header has failed. If there are no +// retrievable blocks in the specified range then zero block count is returned with no error. +func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks, maxHistory int) (*types.Block, []*types.Receipt, rpc.BlockNumber, int, error) { + var ( + headBlock rpc.BlockNumber + pendingBlock *types.Block + pendingReceipts types.Receipts + ) + // query either pending block or head header and set headBlock + if lastBlock == rpc.PendingBlockNumber { + if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { + lastBlock = rpc.BlockNumber(pendingBlock.NumberU64()) + headBlock = lastBlock - 1 + } else { + // pending block not supported by backend, process until latest block + lastBlock = rpc.LatestBlockNumber + blocks-- + if blocks == 0 { + return nil, nil, 0, 0, nil + } + } + } + if pendingBlock == nil { + // if pending block is not fetched then we retrieve the head header to get the head block number + if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil { + headBlock = rpc.BlockNumber(latestHeader.Number.Uint64()) + } else { + return nil, nil, 0, 0, err + } + } + if lastBlock == rpc.LatestBlockNumber { + lastBlock = headBlock + } else if pendingBlock == nil && lastBlock > headBlock { + return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock) + } + if maxHistory != 0 { + // limit retrieval to the given number of latest blocks + if tooOldCount := int64(headBlock) - int64(maxHistory) - int64(lastBlock) + int64(blocks); tooOldCount > 0 { + // tooOldCount is the number of requested blocks that are too old to be served + if int64(blocks) > tooOldCount { + blocks -= int(tooOldCount) + } else { + return nil, nil, 0, 0, nil + } + } + } + // ensure not trying to retrieve before genesis + if rpc.BlockNumber(blocks) > lastBlock+1 { + blocks = int(lastBlock + 1) + } + return pendingBlock, pendingReceipts, lastBlock, blocks, nil +} + +// FeeHistory returns data relevant for fee estimation based on the specified range of blocks. +// The range can be specified either with absolute block numbers or ending with the latest +// or pending block. Backends may or may not support gathering data from the pending block +// or blocks older than a certain age (specified in maxHistory). The first block of the +// actually processed range is returned to avoid ambiguity when parts of the requested range +// are not available or when the head has changed during processing this request. +// Three arrays are returned based on the processed blocks: +// - reward: the requested percentiles of effective priority fees per gas of transactions in each +// block, sorted in ascending order and weighted by gas used. +// - baseFee: base fee per gas in the given block +// - gasUsedRatio: gasUsed/gasLimit in the given block +// Note: baseFee includes the next block after the newest of the returned range, because this +// value can be derived from the newest block. +func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error) { + if blocks < 1 { + return 0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks + } + if blocks > maxFeeHistory { + log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) + blocks = maxFeeHistory + } + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return 0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) + } + if i > 0 && p < rewardPercentiles[i-1] { + return 0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) + } + } + // Only process blocks if reward percentiles were requested + maxHistory := oracle.maxHeaderHistory + if len(rewardPercentiles) != 0 { + maxHistory = oracle.maxBlockHistory + } + var ( + pendingBlock *types.Block + pendingReceipts []*types.Receipt + err error + ) + pendingBlock, pendingReceipts, lastBlock, blocks, err = oracle.resolveBlockRange(ctx, lastBlock, blocks, maxHistory) + if err != nil || blocks == 0 { + return 0, nil, nil, nil, err + } + oldestBlock := lastBlock + 1 - rpc.BlockNumber(blocks) + + var ( + next = int64(oldestBlock) + results = make(chan *blockFees, blocks) + ) + for i := 0; i < maxBlockFetchers && i < blocks; i++ { + go func() { + for { + // Retrieve the next block number to fetch with this goroutine + blockNumber := rpc.BlockNumber(atomic.AddInt64(&next, 1) - 1) + if blockNumber > lastBlock { + return + } + + fees := &blockFees{blockNumber: blockNumber} + if pendingBlock != nil && blockNumber >= rpc.BlockNumber(pendingBlock.NumberU64()) { + fees.block, fees.receipts = pendingBlock, pendingReceipts + } else { + if len(rewardPercentiles) != 0 { + fees.block, fees.err = oracle.backend.BlockByNumber(ctx, blockNumber) + if fees.block != nil && fees.err == nil { + fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) + } + } else { + fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, blockNumber) + } + } + if fees.block != nil { + fees.header = fees.block.Header() + } + if fees.header != nil { + oracle.processBlock(fees, rewardPercentiles) + } + // send to results even if empty to guarantee that blocks items are sent in total + results <- fees + } + }() + } + var ( + reward = make([][]*big.Int, blocks) + baseFee = make([]*big.Int, blocks+1) + gasUsedRatio = make([]float64, blocks) + firstMissing = blocks + ) + for ; blocks > 0; blocks-- { + fees := <-results + if fees.err != nil { + return 0, nil, nil, nil, fees.err + } + i := int(fees.blockNumber - oldestBlock) + if fees.header != nil { + reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.reward, fees.baseFee, fees.nextBaseFee, fees.gasUsedRatio + } else { + // getting no block and no error means we are requesting into the future (might happen because of a reorg) + if i < firstMissing { + firstMissing = i + } + } + } + if firstMissing == 0 { + return 0, nil, nil, nil, nil + } + if len(rewardPercentiles) != 0 { + reward = reward[:firstMissing] + } else { + reward = nil + } + baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] + return oldestBlock, reward, baseFee, gasUsedRatio, nil +} diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go new file mode 100644 index 000000000..57cfb260c --- /dev/null +++ b/eth/gasprice/feehistory_test.go @@ -0,0 +1,89 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package gasprice + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/rpc" +) + +func TestFeeHistory(t *testing.T) { + var cases = []struct { + pending bool + maxHeader, maxBlock int + count int + last rpc.BlockNumber + percent []float64 + expFirst rpc.BlockNumber + expCount int + expErr error + }{ + {false, 0, 0, 10, 30, nil, 21, 10, nil}, + {false, 0, 0, 10, 30, []float64{0, 10}, 21, 10, nil}, + {false, 0, 0, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, + {false, 0, 0, 1000000000, 30, nil, 0, 31, nil}, + {false, 0, 0, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, + {false, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {true, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil}, + {false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil}, + {false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil}, + {false, 0, 0, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, + {false, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, + {true, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 2, nil}, + {true, 0, 0, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil}, + } + for i, c := range cases { + config := Config{ + MaxHeaderHistory: c.maxHeader, + MaxBlockHistory: c.maxBlock, + } + backend := newTestBackend(t, big.NewInt(16), c.pending) + oracle := NewOracle(backend, config) + + first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) + + expReward := c.expCount + if len(c.percent) == 0 { + expReward = 0 + } + expBaseFee := c.expCount + if expBaseFee != 0 { + expBaseFee++ + } + + if first != c.expFirst { + t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) + } + if len(reward) != expReward { + t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward)) + } + if len(baseFee) != expBaseFee { + t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee)) + } + if len(ratio) != c.expCount { + t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio)) + } + if err != c.expErr && !errors.Is(err, c.expErr) { + t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err) + } + } +} diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 9a800877c..407eeaa28 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -37,17 +37,21 @@ var ( ) type Config struct { - Blocks int - Percentile int - Default *big.Int `toml:",omitempty"` - MaxPrice *big.Int `toml:",omitempty"` - IgnorePrice *big.Int `toml:",omitempty"` + Blocks int + Percentile int + MaxHeaderHistory int + MaxBlockHistory int + Default *big.Int `toml:",omitempty"` + MaxPrice *big.Int `toml:",omitempty"` + IgnorePrice *big.Int `toml:",omitempty"` } // OracleBackend includes all necessary background APIs for oracle. type OracleBackend interface { HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) + PendingBlockAndReceipts() (*types.Block, types.Receipts) ChainConfig() *params.ChainConfig } @@ -62,8 +66,8 @@ type Oracle struct { cacheLock sync.RWMutex fetchLock sync.Mutex - checkBlocks int - percentile int + checkBlocks, percentile int + maxHeaderHistory, maxBlockHistory int } // NewOracle returns a new gasprice oracle which can recommend suitable @@ -96,12 +100,14 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) } return &Oracle{ - backend: backend, - lastPrice: params.Default, - maxPrice: maxPrice, - ignorePrice: ignorePrice, - checkBlocks: blocks, - percentile: percent, + backend: backend, + lastPrice: params.Default, + maxPrice: maxPrice, + ignorePrice: ignorePrice, + checkBlocks: blocks, + percentile: percent, + maxHeaderHistory: params.MaxHeaderHistory, + maxBlockHistory: params.MaxBlockHistory, } } @@ -111,36 +117,36 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be // necessary to add the basefee to the returned number to fall back to the legacy // behavior. -func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { - head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) +func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { + head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) headHash := head.Hash() // If the latest gasprice is still available, return it. - gpo.cacheLock.RLock() - lastHead, lastPrice := gpo.lastHead, gpo.lastPrice - gpo.cacheLock.RUnlock() + oracle.cacheLock.RLock() + lastHead, lastPrice := oracle.lastHead, oracle.lastPrice + oracle.cacheLock.RUnlock() if headHash == lastHead { return new(big.Int).Set(lastPrice), nil } - gpo.fetchLock.Lock() - defer gpo.fetchLock.Unlock() + oracle.fetchLock.Lock() + defer oracle.fetchLock.Unlock() // Try checking the cache again, maybe the last fetch fetched what we need - gpo.cacheLock.RLock() - lastHead, lastPrice = gpo.lastHead, gpo.lastPrice - gpo.cacheLock.RUnlock() + oracle.cacheLock.RLock() + lastHead, lastPrice = oracle.lastHead, oracle.lastPrice + oracle.cacheLock.RUnlock() if headHash == lastHead { return new(big.Int).Set(lastPrice), nil } var ( sent, exp int number = head.Number.Uint64() - result = make(chan results, gpo.checkBlocks) + result = make(chan results, oracle.checkBlocks) quit = make(chan struct{}) results []*big.Int ) - for sent < gpo.checkBlocks && number > 0 { - go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) + for sent < oracle.checkBlocks && number > 0 { + go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) sent++ exp++ number-- @@ -155,15 +161,15 @@ func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // Nothing returned. There are two special cases here: // - The block is empty // - All the transactions included are sent by the miner itself. - // In these cases, use the latest calculated price for samping. + // In these cases, use the latest calculated price for sampling. if len(res.values) == 0 { res.values = []*big.Int{lastPrice} } // Besides, in order to collect enough data for sampling, if nothing // meaningful returned, try to query more blocks. But the maximum // is 2*checkBlocks. - if len(res.values) == 1 && len(results)+1+exp < gpo.checkBlocks*2 && number > 0 { - go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) + if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { + go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) sent++ exp++ number-- @@ -173,15 +179,15 @@ func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { price := lastPrice if len(results) > 0 { sort.Sort(bigIntArray(results)) - price = results[(len(results)-1)*gpo.percentile/100] + price = results[(len(results)-1)*oracle.percentile/100] } - if price.Cmp(gpo.maxPrice) > 0 { - price = new(big.Int).Set(gpo.maxPrice) + if price.Cmp(oracle.maxPrice) > 0 { + price = new(big.Int).Set(oracle.maxPrice) } - gpo.cacheLock.Lock() - gpo.lastHead = headHash - gpo.lastPrice = price - gpo.cacheLock.Unlock() + oracle.cacheLock.Lock() + oracle.lastHead = headHash + oracle.lastPrice = price + oracle.cacheLock.Unlock() return new(big.Int).Set(price), nil } @@ -219,8 +225,8 @@ func (s *txSorter) Less(i, j int) bool { // and sends it to the result channel. If the block is empty or all transactions // are sent by the miner itself(it doesn't make any sense to include this kind of // transaction prices for sampling), nil gasprice is returned. -func (gpo *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { - block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) +func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { + block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) if block == nil { select { case result <- results{nil, err}: diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index f86449c5a..dea8fea95 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -33,29 +33,64 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +const testHead = 32 + type testBackend struct { - chain *core.BlockChain + chain *core.BlockChain + pending bool // pending block available } func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number > testHead { + return nil, nil + } if number == rpc.LatestBlockNumber { - return b.chain.CurrentBlock().Header(), nil + number = testHead + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = testHead + 1 + } else { + return nil, nil + } } return b.chain.GetHeaderByNumber(uint64(number)), nil } func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number > testHead { + return nil, nil + } if number == rpc.LatestBlockNumber { - return b.chain.CurrentBlock(), nil + number = testHead + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = testHead + 1 + } else { + return nil, nil + } } return b.chain.GetBlockByNumber(uint64(number)), nil } +func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return b.chain.GetReceiptsByHash(hash), nil +} + +func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + if b.pending { + block := b.chain.GetBlockByNumber(testHead + 1) + return block, b.chain.GetReceiptsByHash(block.Hash()) + } + return nil, nil +} + func (b *testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } -func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend { +func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend { var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key.PublicKey) @@ -76,7 +111,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend { genesis, _ := gspec.Commit(db) // Generate testing blocks - blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, 32, func(i int, b *core.BlockGen) { + blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) var tx *types.Transaction @@ -116,7 +151,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend { t.Fatalf("Failed to create local chain, %v", err) } chain.InsertChain(blocks) - return &testBackend{chain: chain} + return &testBackend{chain: chain, pending: pending} } func (b *testBackend) CurrentHeader() *types.Header { @@ -144,7 +179,7 @@ func TestSuggestTipCap(t *testing.T) { {big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future } for _, c := range cases { - backend := newTestBackend(t, c.fork) + backend := newTestBackend(t, c.fork, false) oracle := NewOracle(backend, config) // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 8d5373972..eb178311f 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -170,7 +170,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) - statedb.Prepare(tx.Hash(), block.Hash(), idx) + statedb.Prepare(tx.Hash(), idx) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 39591ae23..d8d290454 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -179,13 +179,6 @@ type StdTraceConfig struct { TxHash common.Hash } -// txTraceContext is the contextual infos about a transaction before it gets run. -type txTraceContext struct { - index int // Index of the transaction within the block - hash common.Hash // Hash of the transaction - block common.Hash // Hash of the block containing the transaction -} - // txTraceResult is the result of a single transaction trace. type txTraceResult struct { Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer @@ -273,10 +266,10 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer, task.block.BaseFee()) - txctx := &txTraceContext{ - index: i, - hash: tx.Hash(), - block: task.block.Hash(), + txctx := &Context{ + BlockHash: task.block.Hash(), + TxIndex: i, + TxHash: tx.Hash(), } res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { @@ -525,10 +518,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer, block.BaseFee()) - txctx := &txTraceContext{ - index: task.index, - hash: txs[task.index].Hash(), - block: blockHash, + txctx := &Context{ + BlockHash: blockHash, + TxIndex: task.index, + TxHash: txs[task.index].Hash(), } res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { @@ -547,7 +540,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer, block.BaseFee()) - statedb.Prepare(tx.Hash(), block.Hash(), i) + statedb.Prepare(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err @@ -661,7 +654,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) - statedb.Prepare(tx.Hash(), block.Hash(), i) + statedb.Prepare(tx.Hash(), i) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -719,10 +712,10 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * if err != nil { return nil, err } - txctx := &txTraceContext{ - index: int(index), - hash: hash, - block: blockHash, + txctx := &Context{ + BlockHash: blockHash, + TxIndex: int(index), + TxHash: hash, } return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } @@ -778,13 +771,13 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc Reexec: config.Reexec, } } - return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig) + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTraceContext, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -805,7 +798,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac tracer = tr(statedb) } else { // Constuct the JavaScript tracer to execute with - if tracer, err = New(*config.Tracer, txContext); err != nil { + if tracer, err = New(*config.Tracer, txctx); err != nil { return nil, err } // Handle timeouts and RPC cancellations @@ -829,7 +822,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) // Call Prepare to clear out the statedb access list - statedb.Prepare(txctx.hash, txctx.block, txctx.index) + statedb.Prepare(txctx.TxHash, txctx.TxIndex) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 218903dd9..2c52761f5 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -310,12 +310,22 @@ type Tracer struct { interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption + + activePrecompiles []common.Address // Updated on CaptureStart based on given rules +} + +// Context contains some contextual infos for a transaction execution that is not +// available from within the EVM object. +type Context struct { + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) } // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func New(code string, txCtx vm.TxContext) (*Tracer, error) { +func New(code string, ctx *Context) (*Tracer, error) { // Resolve any tracers by name and assemble the tracer object if tracer, ok := tracer(code); ok { code = tracer @@ -334,8 +344,14 @@ func New(code string, txCtx vm.TxContext) (*Tracer, error) { depthValue: new(uint), refundValue: new(uint), } - tracer.ctx["gasPrice"] = txCtx.GasPrice + if ctx.BlockHash != (common.Hash{}) { + tracer.ctx["blockHash"] = ctx.BlockHash + if ctx.TxHash != (common.Hash{}) { + tracer.ctx["txIndex"] = ctx.TxIndex + tracer.ctx["txHash"] = ctx.TxHash + } + } // Set up builtins for this environment tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { ctx.PushString(hexutil.Encode(popSlice(ctx))) @@ -400,8 +416,14 @@ func New(code string, txCtx vm.TxContext) (*Tracer, error) { return 1 }) tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int { - _, ok := vm.PrecompiledContractsIstanbul[common.BytesToAddress(popSlice(ctx))] - ctx.PushBoolean(ok) + addr := common.BytesToAddress(popSlice(ctx)) + for _, p := range tracer.activePrecompiles { + if p == addr { + ctx.PushBoolean(true) + return 1 + } + } + ctx.PushBoolean(false) return 1 }) tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int { @@ -550,11 +572,16 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr jst.ctx["to"] = to jst.ctx["input"] = input jst.ctx["gas"] = gas + jst.ctx["gasPrice"] = env.TxContext.GasPrice jst.ctx["value"] = value // Initialize the context jst.ctx["block"] = env.Context.BlockNumber.Uint64() jst.dbWrapper.db = env.StateDB + // Update list of precompiles based on current block + rules := env.ChainConfig().Rules(env.Context.BlockNumber) + jst.activePrecompiles = vm.ActivePrecompiles(rules) + // Compute intrinsic gas isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) @@ -646,6 +673,13 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) { case *big.Int: pushBigInt(val, jst.vm) + case int: + jst.vm.PushInt(val) + + case common.Hash: + ptr := jst.vm.PushFixedBuffer(32) + copy(makeSlice(ptr, 32), val[:]) + default: panic(fmt.Sprintf("unsupported type: %T", val)) } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 033824474..d2d3e57c4 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -39,7 +39,6 @@ func (account) SetBalance(*big.Int) {} func (account) SetNonce(uint64) {} func (account) Balance() *big.Int { return nil } func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(*big.Int) {} func (account) SetCode(common.Hash, []byte) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} @@ -59,8 +58,8 @@ func testCtx() *vmContext { return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } -func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { - env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) +func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { + env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) var ( startGas uint64 = 10000 value = big.NewInt(0) @@ -80,12 +79,14 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { func TestTracer(t *testing.T) { execTracer := func(code string) ([]byte, string) { t.Helper() - ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} - tracer, err := New(code, ctx.txCtx) + tracer, err := New(code, new(Context)) if err != nil { t.Fatal(err) } - ret, err := runTrace(tracer, ctx) + ret, err := runTrace(tracer, &vmContext{ + blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, + txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}, + }, params.TestChainConfig) if err != nil { return nil, err.Error() // Stringify to allow comparison without nil checks } @@ -132,25 +133,21 @@ func TestHalt(t *testing.T) { t.Skip("duktape doesn't support abortion") timeout := errors.New("stahp") - vmctx := testCtx() - tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx) + tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", new(Context)) if err != nil { t.Fatal(err) } - go func() { time.Sleep(1 * time.Second) tracer.Stop(timeout) }() - - if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" { + if _, err = runTrace(tracer, testCtx(), params.TestChainConfig); err.Error() != "stahp in server-side tracer function 'step'" { t.Errorf("Expected timeout error, got %v", err) } } func TestHaltBetweenSteps(t *testing.T) { - vmctx := testCtx() - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx) + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context)) if err != nil { t.Fatal(err) } @@ -158,7 +155,6 @@ func TestHaltBetweenSteps(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } - tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) @@ -182,12 +178,14 @@ func TestNoStepExec(t *testing.T) { } execTracer := func(code string) []byte { t.Helper() - ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} - tracer, err := New(code, ctx.txCtx) + tracer, err := New(code, new(Context)) if err != nil { t.Fatal(err) } - ret, err := runEmptyTrace(tracer, ctx) + ret, err := runEmptyTrace(tracer, &vmContext{ + blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, + txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}, + }) if err != nil { t.Fatal(err) } @@ -207,3 +205,34 @@ func TestNoStepExec(t *testing.T) { } } } + +func TestIsPrecompile(t *testing.T) { + chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), CatalystBlock: nil, Ethash: new(params.EthashConfig), Clique: nil} + chaincfg.ByzantiumBlock = big.NewInt(100) + chaincfg.IstanbulBlock = big.NewInt(200) + chaincfg.BerlinBlock = big.NewInt(300) + txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} + tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context)) + if err != nil { + t.Fatal(err) + } + + blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)} + res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) + if err != nil { + t.Error(err) + } + if string(res) != "false" { + t.Errorf("Tracer should not consider blake2f as precompile in byzantium") + } + + tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context)) + blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)} + res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) + if err != nil { + t.Error(err) + } + if string(res) != "true" { + t.Errorf("Tracer should consider blake2f as precompile in istanbul") + } +} diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 8b01edd7b..8fbbf154b 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer", txContext) + tracer, err := New("prestateTracer", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -248,7 +248,7 @@ func TestCallTracer(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer", txContext) + tracer, err := New("callTracer", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -300,3 +300,81 @@ func jsonEqual(x, y interface{}) bool { } return reflect.DeepEqual(xTrace, yTrace) } + +func BenchmarkTransactionTrace(b *testing.B) { + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + from := crypto.PubkeyToAddress(key.PublicKey) + gas := uint64(1000000) // 1M gas + to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") + signer := types.LatestSignerForChainID(big.NewInt(1337)) + tx, err := types.SignNewTx(key, signer, + &types.LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: gas, + To: &to, + }) + if err != nil { + b.Fatal(err) + } + txContext := vm.TxContext{ + Origin: from, + GasPrice: tx.GasPrice(), + } + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(uint64(5)), + Time: new(big.Int).SetUint64(uint64(5)), + Difficulty: big.NewInt(0xffffffff), + GasLimit: gas, + } + alloc := core.GenesisAlloc{} + // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns + // the address + loop := []byte{ + byte(vm.JUMPDEST), // [ count ] + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{ + Nonce: 1, + Code: loop, + Balance: big.NewInt(1), + } + alloc[from] = core.GenesisAccount{ + Nonce: 1, + Code: []byte{}, + Balance: big.NewInt(500000000000000), + } + _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) + // Create the tracer, the EVM environment and run it + tracer := vm.NewStructLogger(&vm.LogConfig{ + Debug: false, + //DisableStorage: true, + //DisableMemory: true, + //DisableReturnData: true, + }) + evm := vm.NewEVM(context, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer}) + msg, err := tx.AsMessage(signer, nil) + if err != nil { + b.Fatalf("failed to prepare transaction for tracing: %v", err) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + snap := statedb.Snapshot() + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + _, err = st.TransitionDb() + if err != nil { + b.Fatal(err) + } + statedb.RevertToSnapshot(snap) + if have, want := len(tracer.StructLogs()), 244752; have != want { + b.Fatalf("trace wrong, want %d steps, have %d", want, have) + } + tracer.Reset() + } +} diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 80240dcb9..9f6832313 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -284,17 +284,6 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (* return r, err } -func toBlockNumArg(number *big.Int) string { - if number == nil { - return "latest" - } - pending := big.NewInt(-1) - if number.Cmp(pending) == 0 { - return "pending" - } - return hexutil.EncodeBig(number) -} - type rpcProgress struct { StartingBlock hexutil.Uint64 CurrentBlock hexutil.Uint64 @@ -462,8 +451,6 @@ func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { return uint(num), err } -// TODO: SubscribePendingTransactions (needs server side) - // Contract Calling // CallContract executes a message call transaction, which is directly executed in the VM @@ -537,6 +524,17 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) } +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + pending := big.NewInt(-1) + if number.Cmp(pending) == 0 { + return "pending" + } + return hexutil.EncodeBig(number) +} + func toCallArg(msg ethereum.CallMsg) interface{} { arg := map[string]interface{}{ "from": msg.From, diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go new file mode 100644 index 000000000..538e23727 --- /dev/null +++ b/ethclient/gethclient/gethclient.go @@ -0,0 +1,235 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package gethclient provides an RPC client for geth-specific APIs. +package gethclient + +import ( + "context" + "math/big" + "runtime" + "runtime/debug" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// Client is a wrapper around rpc.Client that implements geth-specific functionality. +// +// If you want to use the standardized Ethereum RPC functionality, use ethclient.Client instead. +type Client struct { + c *rpc.Client +} + +// New creates a client that uses the given RPC client. +func New(c *rpc.Client) *Client { + return &Client{c} +} + +// CreateAccessList tries to create an access list for a specific transaction based on the +// current pending state of the blockchain. +func (ec *Client) CreateAccessList(ctx context.Context, msg ethereum.CallMsg) (*types.AccessList, uint64, string, error) { + type accessListResult struct { + Accesslist *types.AccessList `json:"accessList"` + Error string `json:"error,omitempty"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + } + var result accessListResult + if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil { + return nil, 0, "", err + } + return result.Accesslist, uint64(result.GasUsed), result.Error, nil +} + +// AccountResult is the result of a GetProof operation. +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *big.Int `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} + +// StorageResult provides a proof for a key-value pair. +type StorageResult struct { + Key string `json:"key"` + Value *big.Int `json:"value"` + Proof []string `json:"proof"` +} + +// GetProof returns the account and storage values of the specified account including the Merkle-proof. +// The block number can be nil, in which case the value is taken from the latest known block. +func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) { + + type storageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` + } + + type accountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []storageResult `json:"storageProof"` + } + + var res accountResult + err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber)) + // Turn hexutils back to normal datatypes + storageResults := make([]StorageResult, 0, len(res.StorageProof)) + for _, st := range res.StorageProof { + storageResults = append(storageResults, StorageResult{ + Key: st.Key, + Value: st.Value.ToInt(), + Proof: st.Proof, + }) + } + result := AccountResult{ + Address: res.Address, + AccountProof: res.AccountProof, + Balance: res.Balance.ToInt(), + Nonce: uint64(res.Nonce), + CodeHash: res.CodeHash, + StorageHash: res.StorageHash, + } + return &result, err +} + +// OverrideAccount specifies the state of an account to be overridden. +type OverrideAccount struct { + Nonce uint64 `json:"nonce"` + Code []byte `json:"code"` + Balance *big.Int `json:"balance"` + State map[common.Hash]common.Hash `json:"state"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff"` +} + +// CallContract executes a message call transaction, which is directly executed in the VM +// of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +// +// overrides specifies a map of contract states that should be overwritten before executing +// the message call. +// Please use ethclient.CallContract instead if you don't need the override functionality. +func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext( + ctx, &hex, "eth_call", toCallArg(msg), + toBlockNumArg(blockNumber), toOverrideMap(overrides), + ) + return hex, err +} + +// GCStats retrieves the current garbage collection stats from a geth node. +func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { + var result debug.GCStats + err := ec.c.CallContext(ctx, &result, "debug_gcStats") + return &result, err +} + +// MemStats retrieves the current memory stats from a geth node. +func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) { + var result runtime.MemStats + err := ec.c.CallContext(ctx, &result, "debug_memStats") + return &result, err +} + +// SetHead sets the current head of the local chain by block number. +// Note, this is a destructive action and may severely damage your chain. +// Use with extreme caution. +func (ec *Client) SetHead(ctx context.Context, number *big.Int) error { + return ec.c.CallContext(ctx, nil, "debug_setHead", toBlockNumArg(number)) +} + +// GetNodeInfo retrieves the node info of a geth node. +func (ec *Client) GetNodeInfo(ctx context.Context) (*p2p.NodeInfo, error) { + var result p2p.NodeInfo + err := ec.c.CallContext(ctx, &result, "admin_nodeInfo") + return &result, err +} + +// SubscribePendingTransactions subscribes to new pending transactions. +func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) { + return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions") +} + +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + pending := big.NewInt(-1) + if number.Cmp(pending) == 0 { + return "pending" + } + return hexutil.EncodeBig(number) +} + +func toCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["data"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + if msg.GasPrice != nil { + arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) + } + return arg +} + +func toOverrideMap(overrides *map[common.Address]OverrideAccount) interface{} { + if overrides == nil { + return nil + } + type overrideAccount struct { + Nonce hexutil.Uint64 `json:"nonce"` + Code hexutil.Bytes `json:"code"` + Balance *hexutil.Big `json:"balance"` + State map[common.Hash]common.Hash `json:"state"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff"` + } + result := make(map[common.Address]overrideAccount) + for addr, override := range *overrides { + result[addr] = overrideAccount{ + Nonce: hexutil.Uint64(override.Nonce), + Code: override.Code, + Balance: (*hexutil.Big)(override.Balance), + State: override.State, + StateDiff: override.StateDiff, + } + } + return &result +} diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go new file mode 100644 index 000000000..3c408c225 --- /dev/null +++ b/ethclient/gethclient/gethclient_test.go @@ -0,0 +1,305 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package gethclient + +import ( + "bytes" + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + testBalance = big.NewInt(2e15) +) + +func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { + // Generate test chain. + genesis, blocks := generateTestChain() + // Create node + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("can't create new node: %v", err) + } + // Create Ethereum Service + config := ðconfig.Config{Genesis: genesis} + config.Ethash.PowMode = ethash.ModeFake + ethservice, err := eth.New(n, config) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + // Import the test chain. + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + return n, blocks +} + +func generateTestChain() (*core.Genesis, []*types.Block) { + db := rawdb.NewMemoryDatabase() + config := params.AllEthashProtocolChanges + genesis := &core.Genesis{ + Config: config, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + } + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + } + gblock := genesis.ToBlock(db) + engine := ethash.NewFaker() + blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate) + blocks = append([]*types.Block{gblock}, blocks...) + return genesis, blocks +} + +func TestGethClient(t *testing.T) { + backend, _ := newTestBackend(t) + client, err := backend.Attach() + if err != nil { + t.Fatal(err) + } + defer backend.Close() + defer client.Close() + + tests := map[string]struct { + test func(t *testing.T) + }{ + "TestAccessList": { + func(t *testing.T) { testAccessList(t, client) }, + }, + "TestGetProof": { + func(t *testing.T) { testGetProof(t, client) }, + }, + "TestGCStats": { + func(t *testing.T) { testGCStats(t, client) }, + }, + "TestMemStats": { + func(t *testing.T) { testMemStats(t, client) }, + }, + "TestGetNodeInfo": { + func(t *testing.T) { testGetNodeInfo(t, client) }, + }, + "TestSetHead": { + func(t *testing.T) { testSetHead(t, client) }, + }, + "TestSubscribePendingTxs": { + func(t *testing.T) { testSubscribePendingTransactions(t, client) }, + }, + "TestCallContract": { + func(t *testing.T) { testCallContract(t, client) }, + }, + } + t.Parallel() + for name, tt := range tests { + t.Run(name, tt.test) + } +} + +func testAccessList(t *testing.T, client *rpc.Client) { + ec := New(client) + // Test transfer + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1), + Value: big.NewInt(1), + } + al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if vmErr != "" { + t.Fatalf("unexpected vm error: %v", vmErr) + } + if gas != 21000 { + t.Fatalf("unexpected gas used: %v", gas) + } + if len(*al) != 0 { + t.Fatalf("unexpected length of accesslist: %v", len(*al)) + } + // Test reverting transaction + msg = ethereum.CallMsg{ + From: testAddr, + To: nil, + Gas: 100000, + GasPrice: big.NewInt(1000000000), + Value: big.NewInt(1), + Data: common.FromHex("0x608060806080608155fd"), + } + al, gas, vmErr, err = ec.CreateAccessList(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if vmErr == "" { + t.Fatalf("wanted vmErr, got none") + } + if gas == 21000 { + t.Fatalf("unexpected gas used: %v", gas) + } + if len(*al) != 1 || al.StorageKeys() != 1 { + t.Fatalf("unexpected length of accesslist: %v", len(*al)) + } + // address changes between calls, so we can't test for it. + if (*al)[0].Address == common.HexToAddress("0x0") { + t.Fatalf("unexpected address: %v", (*al)[0].Address) + } + if (*al)[0].StorageKeys[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000081") { + t.Fatalf("unexpected storage key: %v", (*al)[0].StorageKeys[0]) + } +} + +func testGetProof(t *testing.T, client *rpc.Client) { + ec := New(client) + ethcl := ethclient.NewClient(client) + result, err := ec.GetProof(context.Background(), testAddr, []string{}, nil) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(result.Address[:], testAddr[:]) { + t.Fatalf("unexpected address, want: %v got: %v", testAddr, result.Address) + } + // test nonce + nonce, _ := ethcl.NonceAt(context.Background(), result.Address, nil) + if result.Nonce != nonce { + t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce) + } + // test balance + balance, _ := ethcl.BalanceAt(context.Background(), result.Address, nil) + if result.Balance.Cmp(balance) != 0 { + t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance) + } +} + +func testGCStats(t *testing.T, client *rpc.Client) { + ec := New(client) + _, err := ec.GCStats(context.Background()) + if err != nil { + t.Fatal(err) + } +} + +func testMemStats(t *testing.T, client *rpc.Client) { + ec := New(client) + stats, err := ec.MemStats(context.Background()) + if err != nil { + t.Fatal(err) + } + if stats.Alloc == 0 { + t.Fatal("Invalid mem stats retrieved") + } +} + +func testGetNodeInfo(t *testing.T, client *rpc.Client) { + ec := New(client) + info, err := ec.GetNodeInfo(context.Background()) + if err != nil { + t.Fatal(err) + } + + if info.Name == "" { + t.Fatal("Invalid node info retrieved") + } +} + +func testSetHead(t *testing.T, client *rpc.Client) { + ec := New(client) + err := ec.SetHead(context.Background(), big.NewInt(0)) + if err != nil { + t.Fatal(err) + } +} + +func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) { + ec := New(client) + ethcl := ethclient.NewClient(client) + // Subscribe to Transactions + ch := make(chan common.Hash) + ec.SubscribePendingTransactions(context.Background(), ch) + // Send a transaction + chainID, err := ethcl.ChainID(context.Background()) + if err != nil { + t.Fatal(err) + } + // Create transaction + tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + signer := types.LatestSignerForChainID(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + t.Fatal(err) + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + t.Fatal(err) + } + // Send transaction + err = ethcl.SendTransaction(context.Background(), signedTx) + if err != nil { + t.Fatal(err) + } + // Check that the transaction was send over the channel + hash := <-ch + if hash != signedTx.Hash() { + t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash()) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := New(client) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1000000000), + Value: big.NewInt(1), + } + // CallContract without override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // CallContract with override + override := OverrideAccount{ + Nonce: 1, + } + mapAcc := make(map[common.Address]OverrideAccount) + mapAcc[testAddr] = override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 42d88f6db..148359110 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -77,7 +77,7 @@ type fullNodeBackend interface { Miner() *miner.Miner BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) CurrentBlock() *types.Block - SuggestPrice(ctx context.Context) (*big.Int, error) + SuggestGasTipCap(ctx context.Context) (*big.Int, error) } // Service implements an Ethereum netstats reporting daemon that pushes local @@ -780,8 +780,11 @@ func (s *Service) reportStats(conn *connWrapper) error { sync := fullBackend.Downloader().Progress() syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock - price, _ := fullBackend.SuggestPrice(context.Background()) + price, _ := fullBackend.SuggestGasTipCap(context.Background()) gasprice = int(price.Uint64()) + if basefee := fullBackend.CurrentHeader().BaseFee; basefee != nil { + gasprice += int(basefee.Uint64()) + } } else { sync := s.backend.Downloader().Progress() syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock diff --git a/go.mod b/go.mod index d35ac1c57..046578016 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.2.0 - github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 + github.com/huin/goupnp v1.0.1-0.20210626160114-33cdcbb30dda github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e diff --git a/go.sum b/go.sum index 889d47e20..8fde7ce0f 100644 --- a/go.sum +++ b/go.sum @@ -213,8 +213,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 h1:bcAj8KroPf552TScjFPIakjH2/tdIrIH8F+cc4v4SRo= -github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= +github.com/huin/goupnp v1.0.1-0.20210626160114-33cdcbb30dda h1:Vofqyy/Ysqit++X33unU0Gr08b6P35hKm3juytDrBVI= +github.com/huin/goupnp v1.0.1-0.20210626160114-33cdcbb30dda/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= 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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -422,7 +422,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/graphql/graphql.go b/graphql/graphql.go index 0c11a3a0e..d35994234 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -29,7 +29,6 @@ import ( "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/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" @@ -94,7 +93,11 @@ func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) { if err != nil { return hexutil.Big{}, err } - return hexutil.Big(*state.GetBalance(a.address)), nil + balance := state.GetBalance(a.address) + if balance == nil { + return hexutil.Big{}, fmt.Errorf("failed to load balance %x", a.address) + } + return hexutil.Big(*balance), nil } func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) { @@ -179,8 +182,9 @@ type Transaction struct { // resolve returns the internal transaction object, fetching it if needed. func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) { if t.tx == nil { - tx, blockHash, _, index := rawdb.ReadTransaction(t.backend.ChainDb(), t.hash) - if tx != nil { + // Try to return an already finalized transaction + tx, blockHash, _, index, err := t.backend.GetTransaction(ctx, t.hash) + if err == nil && tx != nil { t.tx = tx blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) t.block = &Block{ @@ -188,9 +192,10 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) { numberOrHash: &blockNrOrHash, } t.index = index - } else { - t.tx = t.backend.GetPoolTransaction(t.hash) + return t.tx, nil } + // No finalized transaction, try to retrieve it from the pool + t.tx = t.backend.GetPoolTransaction(t.hash) } return t.tx, nil } @@ -286,6 +291,9 @@ func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { if err != nil || tx == nil { return hexutil.Big{}, err } + if tx.Value() == nil { + return hexutil.Big{}, fmt.Errorf("invalid transaction value %x", t.hash) + } return hexutil.Big(*tx.Value()), nil } @@ -721,7 +729,11 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { } h = header.Hash() } - return hexutil.Big(*b.backend.GetTd(ctx, h)), nil + td := b.backend.GetTd(ctx, h) + if td == nil { + return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", b.hash) + } + return hexutil.Big(*td), nil } // BlockNumberArgs encapsulates arguments to accessors that specify a block number. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1674dcb0a..b65f98836 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -71,7 +71,7 @@ func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) return (*hexutil.Big)(tipcap), err } -// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic transactions. +// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions. func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) { tipcap, err := s.b.SuggestGasTipCap(ctx) if err != nil { @@ -80,6 +80,40 @@ func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil. return (*hexutil.Big)(tipcap), err } +type feeHistoryResult struct { + OldestBlock rpc.BlockNumber `json:"oldestBlock"` + Reward [][]*hexutil.Big `json:"reward,omitempty"` + BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` + GasUsedRatio []float64 `json:"gasUsedRatio"` +} + +func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { + oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) + if err != nil { + return nil, err + } + results := &feeHistoryResult{ + OldestBlock: oldest, + GasUsedRatio: gasUsed, + } + if reward != nil { + results.Reward = make([][]*hexutil.Big, len(reward)) + for i, w := range reward { + results.Reward[i] = make([]*hexutil.Big, len(w)) + for j, v := range w { + results.Reward[i][j] = (*hexutil.Big)(v) + } + } + } + if baseFee != nil { + results.BaseFee = make([]*hexutil.Big, len(baseFee)) + for i, v := range baseFee { + results.BaseFee[i] = (*hexutil.Big)(v) + } + } + return results, nil +} + // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not // yet received the latest block headers from its pears. In case it is synchronizing: // - startingBlock: block number this node started to synchronise from @@ -141,6 +175,29 @@ func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransac return content } +// ContentFrom returns the transactions contained within the transaction pool. +func (s *PublicTxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction { + content := make(map[string]map[string]*RPCTransaction, 2) + pending, queue := s.b.TxPoolContentFrom(addr) + curHeader := s.b.CurrentHeader() + + // Build the pending transactions + dump := make(map[string]*RPCTransaction, len(pending)) + for _, tx := range pending { + dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) + } + content["pending"] = dump + + // Build the queued transactions + dump = make(map[string]*RPCTransaction, len(queue)) + for _, tx := range queue { + dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()) + } + content["queued"] = dump + + return content +} + // Status returns the number of pending and queued transaction in the pool. func (s *PublicTxPoolAPI) Status() map[string]hexutil.Uint { pending, queue := s.b.Stats() @@ -414,14 +471,15 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args Transactio if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } - if args.GasPrice == nil { - return nil, fmt.Errorf("gasPrice not specified") + if args.GasPrice == nil && (args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil) { + return nil, fmt.Errorf("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") } if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } // Before actually sign the transaction, ensure the transaction fee is reasonable. - if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { + tx := args.toTransaction() + if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } signed, err := s.signTransaction(ctx, &args, passwd) @@ -1083,7 +1141,7 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes { if trace.Stack != nil { stack := make([]string, len(trace.Stack)) for i, stackValue := range trace.Stack { - stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) + stack[i] = stackValue.Hex() } formatted[index].Stack = &stack } @@ -1383,11 +1441,11 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Copy the original db so we don't modify it statedb := db.Copy() - msg := types.NewMessage(args.from(), args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, args.data(), accessList, false) + msg := types.NewMessage(args.from(), args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), big.NewInt(0), big.NewInt(0), args.data(), accessList, false) // Apply the transaction with the access list tracer tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles) - config := vm.Config{Tracer: tracer, Debug: true} + config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true} vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) if err != nil { return nil, 0, nil, err @@ -1663,8 +1721,9 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Tra return SubmitTransaction(ctx, s.b, signed) } -// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction, -// and returns it to the caller for further processing (signing + broadcast) +// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields) +// on a given unsigned transaction, and returns it to the caller for further +// processing (signing + broadcast). func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { @@ -1727,8 +1786,8 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Tra if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } - if args.GasPrice == nil { - return nil, fmt.Errorf("gasPrice not specified") + if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) { + return nil, fmt.Errorf("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") } if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") @@ -1737,18 +1796,19 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Tra return nil, err } // Before actually sign the transaction, ensure the transaction fee is reasonable. - if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { + tx := args.toTransaction() + if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { return nil, err } - tx, err := s.sign(args.from(), args.toTransaction()) + signed, err := s.sign(args.from(), tx) if err != nil { return nil, err } - data, err := tx.MarshalBinary() + data, err := signed.MarshalBinary() if err != nil { return nil, err } - return &SignTransactionResult{data, tx}, nil + return &SignTransactionResult{data, signed}, nil } // PendingTransactions returns the transactions that are in the transaction pool diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index a0e2cf089..fe55ec59c 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,6 +42,7 @@ type Backend interface { // General Ethereum API Downloader() *downloader.Downloader SuggestGasTipCap(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error) ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool @@ -76,6 +77,7 @@ type Backend interface { GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) Stats() (pending int, queued int) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) + TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription // Filter API diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 220eb22b7..1fbaaeacb 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -49,7 +49,7 @@ type TransactionArgs struct { Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input"` - // For non-legacy transactions + // Introduced by AccessListTxType transaction. AccessList *types.AccessList `json:"accessList,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"` } @@ -108,6 +108,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { return err } if b.ChainConfig().IsLondon(head.Number) { + // The legacy tx gas price suggestion should not add 2x base fee + // because all fees are consumed, so it would result in a spiral + // upwards. price.Add(price, head.BaseFee) } args.GasPrice = (*hexutil.Big)(price) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8072a3dab..927dba189 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -18,55 +18,20 @@ package web3ext var Modules = map[string]string{ - "accounting": AccountingJs, - "admin": AdminJs, - "chequebook": ChequebookJs, - "clique": CliqueJs, - "ethash": EthashJs, - "debug": DebugJs, - "eth": EthJs, - "miner": MinerJs, - "net": NetJs, - "personal": PersonalJs, - "rpc": RpcJs, - "shh": ShhJs, - "swarmfs": SwarmfsJs, - "txpool": TxpoolJs, - "les": LESJs, - "vflux": VfluxJs, + "admin": AdminJs, + "clique": CliqueJs, + "ethash": EthashJs, + "debug": DebugJs, + "eth": EthJs, + "miner": MinerJs, + "net": NetJs, + "personal": PersonalJs, + "rpc": RpcJs, + "txpool": TxpoolJs, + "les": LESJs, + "vflux": VfluxJs, } -const ChequebookJs = ` -web3._extend({ - property: 'chequebook', - methods: [ - new web3._extend.Method({ - name: 'deposit', - call: 'chequebook_deposit', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Property({ - name: 'balance', - getter: 'chequebook_balance', - outputFormatter: web3._extend.utils.toDecimal - }), - new web3._extend.Method({ - name: 'cash', - call: 'chequebook_cash', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'issue', - call: 'chequebook_issue', - params: 2, - inputFormatter: [null, null] - }), - ] -}); -` - const CliqueJs = ` web3._extend({ property: 'clique', @@ -108,6 +73,12 @@ web3._extend({ call: 'clique_status', params: 0 }), + new web3._extend.Method({ + name: 'getSigner', + call: 'clique_getSigner', + params: 1, + inputFormatter: [null] + }), ], properties: [ new web3._extend.Property({ @@ -581,6 +552,12 @@ web3._extend({ params: 2, inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter], }), + new web3._extend.Method({ + name: 'feeHistory', + call: 'eth_feeHistory', + params: 3, + inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter, null] + }), ], properties: [ new web3._extend.Property({ @@ -635,6 +612,12 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.utils.fromDecimal] }), + new web3._extend.Method({ + name: 'setGasLimit', + call: 'miner_setGasLimit', + params: 1, + inputFormatter: [web3._extend.utils.fromDecimal] + }), new web3._extend.Method({ name: 'setRecommitInterval', call: 'miner_setRecommitInterval', @@ -731,50 +714,6 @@ web3._extend({ }); ` -const ShhJs = ` -web3._extend({ - property: 'shh', - methods: [ - ], - properties: - [ - new web3._extend.Property({ - name: 'version', - getter: 'shh_version', - outputFormatter: web3._extend.utils.toDecimal - }), - new web3._extend.Property({ - name: 'info', - getter: 'shh_info' - }), - ] -}); -` - -const SwarmfsJs = ` -web3._extend({ - property: 'swarmfs', - methods: - [ - new web3._extend.Method({ - name: 'mount', - call: 'swarmfs_mount', - params: 2 - }), - new web3._extend.Method({ - name: 'unmount', - call: 'swarmfs_unmount', - params: 1 - }), - new web3._extend.Method({ - name: 'listmounts', - call: 'swarmfs_listmounts', - params: 0 - }), - ] -}); -` - const TxpoolJs = ` web3._extend({ property: 'txpool', @@ -798,49 +737,10 @@ web3._extend({ return status; } }), - ] -}); -` - -const AccountingJs = ` -web3._extend({ - property: 'accounting', - methods: [ - new web3._extend.Property({ - name: 'balance', - getter: 'account_balance' - }), - new web3._extend.Property({ - name: 'balanceCredit', - getter: 'account_balanceCredit' - }), - new web3._extend.Property({ - name: 'balanceDebit', - getter: 'account_balanceDebit' - }), - new web3._extend.Property({ - name: 'bytesCredit', - getter: 'account_bytesCredit' - }), - new web3._extend.Property({ - name: 'bytesDebit', - getter: 'account_bytesDebit' - }), - new web3._extend.Property({ - name: 'msgCredit', - getter: 'account_msgCredit' - }), - new web3._extend.Property({ - name: 'msgDebit', - getter: 'account_msgDebit' - }), - new web3._extend.Property({ - name: 'peerDrops', - getter: 'account_peerDrops' - }), - new web3._extend.Property({ - name: 'selfDrops', - getter: 'account_selfDrops' + new web3._extend.Method({ + name: 'contentFrom', + call: 'txpool_contentFrom', + params: 1, }), ] }); diff --git a/les/api_backend.go b/les/api_backend.go index a6ad7a38e..2a2d406d1 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -60,7 +60,10 @@ func (b *LesApiBackend) SetHead(number uint64) { } func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { + if number == rpc.PendingBlockNumber { + return nil, nil + } + if number == rpc.LatestBlockNumber { return b.eth.blockchain.CurrentHeader(), nil } return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number)) @@ -122,6 +125,10 @@ func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r return nil, errors.New("invalid arguments; neither block nor hash specified") } +func (b *LesApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return nil, nil +} + func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { header, err := b.HeaderByNumber(ctx, number) if err != nil { @@ -212,6 +219,10 @@ func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions, return b.eth.txPool.Content() } +func (b *LesApiBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + return b.eth.txPool.ContentFrom(addr) +} + func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { return b.eth.txPool.SubscribeNewTxsEvent(ch) } @@ -255,6 +266,10 @@ func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) return b.gpo.SuggestTipCap(ctx) } +func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { + return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) +} + func (b *LesApiBackend) ChainDb() ethdb.Database { return b.eth.chainDb } diff --git a/les/servingqueue.go b/les/servingqueue.go index 16e064cb3..10c7e6f48 100644 --- a/les/servingqueue.go +++ b/les/servingqueue.go @@ -159,27 +159,23 @@ func (sq *servingQueue) newTask(peer *clientPeer, maxTime uint64, priority int64 // run tokens from the token channel and allow the corresponding tasks to run // without entering the priority queue. func (sq *servingQueue) threadController() { + defer sq.wg.Done() for { token := make(runToken) select { case best := <-sq.queueBestCh: best.tokenCh <- token case <-sq.stopThreadCh: - sq.wg.Done() return case <-sq.quit: - sq.wg.Done() return } - <-token select { case <-sq.stopThreadCh: - sq.wg.Done() return case <-sq.quit: - sq.wg.Done() return - default: + case <-token: } } } @@ -298,6 +294,7 @@ func (sq *servingQueue) addTask(task *servingTask) { // and always tries to send the highest priority task to queueBestCh. Successfully sent // tasks are removed from the queue. func (sq *servingQueue) queueLoop() { + defer sq.wg.Done() for { if sq.best != nil { expTime := sq.best.expTime @@ -316,7 +313,6 @@ func (sq *servingQueue) queueLoop() { sq.best, _ = sq.queue.PopItem().(*servingTask) } case <-sq.quit: - sq.wg.Done() return } } else { @@ -324,7 +320,6 @@ func (sq *servingQueue) queueLoop() { case task := <-sq.queueAddCh: sq.addTask(task) case <-sq.quit: - sq.wg.Done() return } } @@ -335,6 +330,7 @@ func (sq *servingQueue) queueLoop() { // of active thread controller goroutines. func (sq *servingQueue) threadCountLoop() { var threadCountTarget int + defer sq.wg.Done() for { for threadCountTarget > sq.threadCount { sq.wg.Add(1) @@ -347,14 +343,12 @@ func (sq *servingQueue) threadCountLoop() { case sq.stopThreadCh <- struct{}{}: sq.threadCount-- case <-sq.quit: - sq.wg.Done() return } } else { select { case threadCountTarget = <-sq.setThreadsCh: case <-sq.quit: - sq.wg.Done() return } } diff --git a/les/state_accessor.go b/les/state_accessor.go index e276b06dc..112e6fd44 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -58,7 +58,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. msg, _ := tx.AsMessage(signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) - statedb.Prepare(tx.Hash(), block.Hash(), idx) + statedb.Prepare(tx.Hash(), idx) if idx == txIndex { return msg, context, statedb, nil } diff --git a/light/txpool.go b/light/txpool.go index 1296389e3..a7df4aeec 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -505,6 +505,25 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common return pending, queued } +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + // Retrieve the pending transactions and sort by nonce + var pending types.Transactions + for _, tx := range pool.pending { + account, _ := types.Sender(pool.signer, tx) + if account != addr { + continue + } + pending = append(pending, tx) + } + // There are no queued transactions in a light pool, just return an empty map + return pending, types.Transactions{} +} + // RemoveTransactions removes all given transactions from the pool. func (pool *TxPool) RemoveTransactions(txs types.Transactions) { pool.mu.Lock() diff --git a/miner/miner.go b/miner/miner.go index 00c3d0cb5..a4a01b9f4 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -194,11 +194,22 @@ func (miner *Miner) PendingBlock() *types.Block { return miner.worker.pendingBlock() } +// PendingBlockAndReceipts returns the currently pending block and corresponding receipts. +func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return miner.worker.pendingBlockAndReceipts() +} + func (miner *Miner) SetEtherbase(addr common.Address) { miner.coinbase = addr miner.worker.setEtherbase(addr) } +// SetGasCeil sets the gaslimit to strive for when mining blocks post 1559. +// For pre-1559 blocks, it sets the ceiling. +func (miner *Miner) SetGasCeil(ceil uint64) { + miner.worker.setGasCeil(ceil) +} + // EnablePreseal turns on the preseal mining feature. It's enabled by default. // Note this function shouldn't be exposed to API, it's unnecessary for users // (miners) to actually know the underlying detail. It's only for outside project diff --git a/miner/worker.go b/miner/worker.go index b0b676ad0..accf3dac9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -162,9 +162,10 @@ type worker struct { pendingMu sync.RWMutex pendingTasks map[common.Hash]*task - snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot - snapshotBlock *types.Block - snapshotState *state.StateDB + snapshotMu sync.RWMutex // The lock used to protect the snapshots below + snapshotBlock *types.Block + snapshotReceipts types.Receipts + snapshotState *state.StateDB // atomic status counters running int32 // The indicator whether the consensus engine is running or not. @@ -243,6 +244,12 @@ func (w *worker) setEtherbase(addr common.Address) { w.coinbase = addr } +func (w *worker) setGasCeil(ceil uint64) { + w.mu.Lock() + defer w.mu.Unlock() + w.config.GasCeil = ceil +} + // setExtra sets the content used to initialize the block extra field. func (w *worker) setExtra(extra []byte) { w.mu.Lock() @@ -284,6 +291,14 @@ func (w *worker) pendingBlock() *types.Block { return w.snapshotBlock } +// pendingBlockAndReceipts returns pending block and corresponding receipts. +func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { + // return a snapshot to avoid contention on currentMu mutex + w.snapshotMu.RLock() + defer w.snapshotMu.RUnlock() + return w.snapshotBlock, w.snapshotReceipts +} + // start sets the running status as 1 and triggers new work submitting. func (w *worker) start() { atomic.StoreInt32(&w.running, 1) @@ -730,6 +745,7 @@ func (w *worker) updateSnapshot() { w.current.receipts, trie.NewStackTrie(nil), ) + w.snapshotReceipts = copyReceipts(w.current.receipts) w.snapshotState = w.current.state.Copy() } @@ -805,7 +821,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin continue } // Start executing the transaction - w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount) + w.current.state.Prepare(tx.Hash(), w.current.tcount) logs, err := w.commitTransaction(tx, coinbase) switch { diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 081a8e1d5..9a24f6b17 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -102,7 +102,7 @@ compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/les Fuzz fuzzLes -compile_fuzzer tests/fuzzers/secp265k1 Fuzz fuzzSecp256k1 +compile_fuzzer tests/fuzzers/secp256k1 Fuzz fuzzSecp256k1 compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add diff --git a/p2p/enode/node.go b/p2p/enode/node.go index c2429e0e8..d747ca331 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -121,7 +121,7 @@ func (n *Node) UDP() int { return int(port) } -// UDP returns the TCP port of the node. +// TCP returns the TCP port of the node. func (n *Node) TCP() int { var port enr.TCP n.Load(&port) diff --git a/p2p/peer_error.go b/p2p/peer_error.go index ab61bfef0..393cc86b0 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -89,7 +89,7 @@ var discReasonToString = [...]string{ } func (d DiscReason) String() string { - if len(discReasonToString) < int(d) { + if len(discReasonToString) <= int(d) { return fmt.Sprintf("unknown disconnect reason %d", d) } return discReasonToString[d] diff --git a/p2p/server.go b/p2p/server.go index f70ebf721..04fdecaec 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -370,7 +370,7 @@ func (srv *Server) RemoveTrustedPeer(node *enode.Node) { } } -// SubscribePeers subscribes the given channel to peer events +// SubscribeEvents subscribes the given channel to peer events func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { return srv.peerFeed.Subscribe(ch) } diff --git a/params/config.go b/params/config.go index eb99e9dda..aa155c50d 100644 --- a/params/config.go +++ b/params/config.go @@ -69,15 +69,16 @@ var ( IstanbulBlock: big.NewInt(9_069_000), MuirGlacierBlock: big.NewInt(9_200_000), BerlinBlock: big.NewInt(12_244_000), + LondonBlock: big.NewInt(12_965_000), Ethash: new(EthashConfig), } // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 384, - SectionHead: common.HexToHash("0xb583a0ead70324849c4caf923476de3645c0d2f707c86221ec8e40078bdd6884"), - CHTRoot: common.HexToHash("0x6ecc993baad0c9f77fe9c4c13b89360112e5a0accae4d8502470b911211618b7"), - BloomRoot: common.HexToHash("0x66a30d8885c19921711704921de7b4bcbd1b49191b197ee79e34dafeed9a04d9"), + SectionIndex: 389, + SectionHead: common.HexToHash("0x8f96e510cf64abf34095c5aa3937acdf5316de5540945b9688f4a2e083cddc73"), + CHTRoot: common.HexToHash("0xa2362493848d6dbc50dcbbf74c017ea808b8938bfb129217d507bd276950d7ac"), + BloomRoot: common.HexToHash("0x72fc78a841bde7e08e1fb7c187b622c49dc8271db12db748ff5d0f27bdb41413"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -115,10 +116,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 279, - SectionHead: common.HexToHash("0x4a4912848d4c06090097073357c10015d11c6f4544a0f93cbdd584701c3b7d58"), - CHTRoot: common.HexToHash("0x9053b7867ae921e80a4e2f5a4b15212e4af3d691ca712fb33dc150e9c6ea221c"), - BloomRoot: common.HexToHash("0x3dc04cb1be7ddc271f3f83469b47b76184a79d7209ef51d85b1539ea6d25a645"), + SectionIndex: 322, + SectionHead: common.HexToHash("0xe3f2fb70acd752bbcac06b67688db8430815c788a31213011ed51b966108a5f4"), + CHTRoot: common.HexToHash("0xb2993a6bc28b23b84159cb477c38c0ec5607434faae6b3657ad44cbcf116f288"), + BloomRoot: common.HexToHash("0x871841e5c2ada9dab2011a550d38e9fe0a30047cfc81f1ffc7ebc09f4f230732"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -159,10 +160,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 266, - SectionHead: common.HexToHash("0xf5655caa59689790e9d7a8ed50081f0c4f447730d279f069ca64b29f075c8688"), - CHTRoot: common.HexToHash("0x2af6a663aa8c89d72f4140567c99b3c93c25ac3bb21f80a5d8380bd7c801f422"), - BloomRoot: common.HexToHash("0x367d2a2eb41e48c7c4983b93ed87781ef29bd2c6fcb7d4fd4bee96f1775f783f"), + SectionIndex: 270, + SectionHead: common.HexToHash("0x03ef8982c93bbf18c859bc1b20ae05b439f04cf1ff592656e941d2c3fcff5d68"), + CHTRoot: common.HexToHash("0x9eb80685e8ece479e105b170439779bc0f89997ab7f4dee425f85c4234e8a6b5"), + BloomRoot: common.HexToHash("0xc3673721c5697efe5fe4cb825d178f4a335dbfeda6a197fb75c9256a767379dc"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -201,10 +202,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 150, - SectionHead: common.HexToHash("0x5294a0cf00895e94f2e5c556ebed1cacab400ab5b8e62bf5ffc3e1d8d3def23e"), - CHTRoot: common.HexToHash("0x4cd8776300a2e8ea37d99d3a2e6381642dac53b0d3c5d5b1acaa1188601fd1ce"), - BloomRoot: common.HexToHash("0x2bd3b7626c6a23060ed2ea0f92fe5d8f7ee8adc689079e3ad6595b2354981691"), + SectionIndex: 154, + SectionHead: common.HexToHash("0xf4cb74cc0e3683589f4992902184241fb892d7c3859d0044c16ec864605ff80d"), + CHTRoot: common.HexToHash("0xead95f9f2504b2c7c6d82c51d30e50b40631c3ea2f590cddcc9721cfc0ae79de"), + BloomRoot: common.HexToHash("0xc6dd6cfe88ac9c4a6d19c9a8651944fa9d941a2340a8f5ddaf673d4d39779d81"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. @@ -246,16 +247,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -335,7 +336,6 @@ type ChainConfig struct { BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london) - EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) CatalystBlock *big.Int `json:"catalystBlock,omitempty"` // Catalyst switch block (nil = no fork, 0 = already on catalyst) // Various consensus engines @@ -459,11 +459,6 @@ func (c *ChainConfig) IsCatalyst(num *big.Int) bool { return isForked(c.CatalystBlock, num) } -// IsEWASM returns whether num represents a block number after the EWASM fork -func (c *ChainConfig) IsEWASM(num *big.Int) bool { - return isForked(c.EWASMBlock, num) -} - // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { @@ -573,9 +568,6 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.LondonBlock, newcfg.LondonBlock, head) { return newCompatError("London fork block", c.LondonBlock, newcfg.LondonBlock) } - if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { - return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) - } return nil } diff --git a/params/protocol_params.go b/params/protocol_params.go index a49c4489f..7abb2441b 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -38,7 +38,7 @@ const ( Sha3Gas uint64 = 30 // Once per SHA3 operation. Sha3WordGas uint64 = 6 // Once per word of the SHA3 operation's data. - SstoreSetGas uint64 = 20000 // Once per SLOAD operation. + SstoreSetGas uint64 = 20000 // Once per SSTORE operation. SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero. SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change. SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero. diff --git a/params/version.go b/params/version.go index 9ce389168..6e4e7fccc 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 10 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release + VersionPatch = 5 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) diff --git a/tests/init_test.go b/tests/init_test.go index dc923dc75..1638f863e 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -18,7 +18,6 @@ package tests import ( "encoding/json" - "flag" "fmt" "io" "io/ioutil" @@ -34,17 +33,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// Command line flags to configure the interpreters. -var ( - testEVM = flag.String("vm.evm", "", "EVM configuration") - testEWASM = flag.String("vm.ewasm", "", "EWASM configuration") -) - -func TestMain(m *testing.M) { - flag.Parse() - os.Exit(m.Run()) -} - var ( baseDir = filepath.Join(".", "testdata") blockTestDir = filepath.Join(baseDir, "BlockchainTests") diff --git a/tests/state_test.go b/tests/state_test.go index 43009afdd..2ed98b650 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -74,8 +74,10 @@ func TestState(t *testing.T) { t.Run(key+"/snap", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { snaps, statedb, err := test.Run(subtest, vmconfig, true) - if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil { - return err + if snaps != nil && statedb != nil { + if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil { + return err + } } return st.checkFailure(t, err) }) @@ -90,7 +92,7 @@ const traceErrorLimit = 400000 func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { // Use config from command line arguments. - config := vm.Config{EVMInterpreter: *testEVM, EWASMInterpreter: *testEWASM} + config := vm.Config{} err := test(config) if err == nil { return diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 227ef77cc..97fd3fb6a 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -328,6 +328,9 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (core.Messa gasPrice = math.BigMin(new(big.Int).Add(tx.MaxPriorityFeePerGas, baseFee), tx.MaxFeePerGas) } + if gasPrice == nil { + return nil, fmt.Errorf("no gas price provided") + } msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, gasPrice, tx.MaxFeePerGas, tx.MaxPriorityFeePerGas, data, accessList, true) diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 1afcce21d..49986fcf0 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -45,11 +45,12 @@ var ( // provided disk database on creation in a background thread and will only start // returning live results once that's finished. type SyncBloom struct { - bloom *bloomfilter.Filter - inited uint32 - closer sync.Once - closed uint32 - pend sync.WaitGroup + bloom *bloomfilter.Filter + inited uint32 + closer sync.Once + closed uint32 + pend sync.WaitGroup + closeCh chan struct{} } // NewSyncBloom creates a new bloom filter of the given size (in megabytes) and @@ -64,7 +65,8 @@ func NewSyncBloom(memory uint64, database ethdb.Iteratee) *SyncBloom { // Assemble the fast sync bloom and init it from previous sessions b := &SyncBloom{ - bloom: bloom, + bloom: bloom, + closeCh: make(chan struct{}), } b.pend.Add(2) go func() { @@ -125,16 +127,15 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { // meter periodically recalculates the false positive error rate of the bloom // filter and reports it in a metric. func (b *SyncBloom) meter() { + // check every second + tick := time.NewTicker(1 * time.Second) for { - // Report the current error ration. No floats, lame, scale it up. - bloomErrorGauge.Update(int64(b.bloom.FalsePosititveProbability() * 100000)) - - // Wait one second, but check termination more frequently - for i := 0; i < 10; i++ { - if atomic.LoadUint32(&b.closed) == 1 { - return - } - time.Sleep(100 * time.Millisecond) + select { + case <-tick.C: + // Report the current error ration. No floats, lame, scale it up. + bloomErrorGauge.Update(int64(b.bloom.FalsePosititveProbability() * 100000)) + case <-b.closeCh: + return } } } @@ -145,6 +146,7 @@ func (b *SyncBloom) Close() error { b.closer.Do(func() { // Ensure the initializer is stopped atomic.StoreUint32(&b.closed, 1) + close(b.closeCh) b.pend.Wait() // Wipe the bloom, but mark it "uninited" just in case someone attempts an access diff --git a/trie/trie.go b/trie/trie.go index 7ed235fa8..e492a532c 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -405,6 +405,14 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { n.flags = t.newFlag() n.Children[key[0]] = nn + // Because n is a full node, it must've contained at least two children + // before the delete operation. If the new child value is non-nil, n still + // has at least two children after the deletion, and cannot be reduced to + // a short node. + if nn != nil { + return true, n, nil + } + // Reduction: // Check how many non-nil entries are left after deleting and // reduce the full node to a short node if only one entry is // left. Since n must've contained at least two children