diff --git a/common/utils.go b/common/utils.go index 2f53ff5e..6c2504c2 100644 --- a/common/utils.go +++ b/common/utils.go @@ -48,6 +48,10 @@ var ( Name: "geth", Usage: "Location of go-ethereum 'evm' binary", } + GethBatchFlag = &cli.StringSliceFlag{ + Name: "gethbatch", + Usage: "Location of go-ethereum 'evm' binary", + } NethermindFlag = &cli.StringSliceFlag{ Name: "nethermind", Usage: "Location of nethermind 'nethtest' binary", @@ -104,6 +108,7 @@ var ( } VmFlags = []cli.Flag{ GethFlag, + GethBatchFlag, NethermindFlag, NethBatchFlag, BesuFlag, @@ -118,6 +123,7 @@ var ( func initVMs(c *cli.Context) []evms.Evm { var ( gethBins = c.StringSlice(GethFlag.Name) + gethBatchBins = c.StringSlice(GethBatchFlag.Name) nethBins = c.StringSlice(NethermindFlag.Name) nethBatchBins = c.StringSlice(NethBatchFlag.Name) besuBins = c.StringSlice(BesuFlag.Name) @@ -130,6 +136,9 @@ func initVMs(c *cli.Context) []evms.Evm { for i, bin := range gethBins { vms = append(vms, evms.NewGethEVM(bin, fmt.Sprintf("%d", i))) } + for i, bin := range gethBatchBins { + vms = append(vms, evms.NewGethBatchVM(bin, fmt.Sprintf("%d", i))) + } for i, bin := range nethBins { vms = append(vms, evms.NewNethermindVM(bin, fmt.Sprintf("%d", i))) } diff --git a/evms/geth.go b/evms/geth.go index 38fc6d93..3058b435 100644 --- a/evms/geth.go +++ b/evms/geth.go @@ -102,9 +102,15 @@ func (evm *GethEVM) RunStateTest(path string, out io.Writer, speedTest bool) (st func (vm *GethEVM) Close() { } -// feed reads from the reader, does some geth-specific filtering and +// Copy reads from the reader, does some geth-specific filtering and // outputs items onto the channel func (evm *GethEVM) Copy(out io.Writer, input io.Reader) { + evm.copyUntilEnd(out, input) +} + +// copyUntilEnd reads from the reader, does some geth-specific filtering and +// outputs items onto the channel +func (evm *GethEVM) copyUntilEnd(out io.Writer, input io.Reader) stateRoot { var stateRoot stateRoot scanner := bufio.NewScanner(input) // Start with 1MB buffer, allow up to 32 MB @@ -158,6 +164,10 @@ func (evm *GethEVM) Copy(out io.Writer, input io.Reader) { if stateRoot.StateRoot == "" { _ = json.Unmarshal(data, &stateRoot) } + // If we have a stateroot, we're done + if len(stateRoot.StateRoot) > 0 { + break + } continue } // When geth encounters end of code, it continues anyway, on a 'virtual' STOP. @@ -173,6 +183,6 @@ func (evm *GethEVM) Copy(out io.Writer, input io.Reader) { root, _ := json.Marshal(stateRoot) if _, err := out.Write(append(root, '\n')); err != nil { fmt.Fprintf(os.Stderr, "Error writing to out: %v\n", err) - return } + return stateRoot } diff --git a/evms/gethbatcher.go b/evms/gethbatcher.go new file mode 100644 index 00000000..b59eb58b --- /dev/null +++ b/evms/gethbatcher.go @@ -0,0 +1,108 @@ +// Copyright 2023 Martin Holst Swende +// This file is part of the goevmlab library. +// +// The 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. +// +// This 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 goevmlab library. If not, see . + +package evms + +import ( + "fmt" + "io" + "os/exec" + "sync" +) + +// The GethBatchVM spins up one 'master' instance of the VM, and uses that to execute tests +type GethBatchVM struct { + GethEVM + cmd *exec.Cmd // the 'master' process + stdout io.ReadCloser + stdin io.WriteCloser + mu sync.Mutex +} + +func NewGethBatchVM(path, name string) *GethBatchVM { + return &GethBatchVM{ + GethEVM: GethEVM{path, name}, + } +} + +func (evm *GethBatchVM) Name() string { + return fmt.Sprintf("gethbatch-%v", evm.name) +} + +// RunStateTest implements the Evm interface +func (evm *GethBatchVM) RunStateTest(path string, out io.Writer, speedTest bool) (string, error) { + var ( + err error + cmd *exec.Cmd + stdout io.ReadCloser + stdin io.WriteCloser + ) + if evm.cmd == nil { + if speedTest { + cmd = exec.Command(evm.path, "--nomemory", "--noreturndata", "--nostack", "statetest") + } else { + cmd = exec.Command(evm.path, "--json", "--noreturndata", "--nomemory", "statetest") + } + if stdout, err = cmd.StderrPipe(); err != nil { + return cmd.String(), err + } + if stdin, err = cmd.StdinPipe(); err != nil { + return cmd.String(), err + } + if err = cmd.Start(); err != nil { + return cmd.String(), err + } + evm.cmd = cmd + evm.stdout = stdout + evm.stdin = stdin + } + evm.mu.Lock() + defer evm.mu.Unlock() + _, _ = evm.stdin.Write([]byte(fmt.Sprintf("%v\n", path))) + // copy everything for the _current_ statetest to the given writer + evm.copyUntilEnd(out, evm.stdout) + // release resources, handle error but ignore non-zero exit codes + return evm.cmd.String(), nil +} + +func (vm *GethBatchVM) Close() { + if vm.stdin != nil { + vm.stdin.Close() + } + if vm.cmd != nil { + _ = vm.cmd.Wait() + } +} + +func (evm *GethBatchVM) GetStateRoot(path string) (root, command string, err error) { + if evm.cmd == nil { + evm.cmd = exec.Command(evm.path) + if evm.stdout, err = evm.cmd.StdoutPipe(); err != nil { + return "", evm.cmd.String(), err + } + if evm.stdin, err = evm.cmd.StdinPipe(); err != nil { + return "", evm.cmd.String(), err + } + if err = evm.cmd.Start(); err != nil { + return "", evm.cmd.String(), err + } + } + evm.mu.Lock() + defer evm.mu.Unlock() + _, _ = evm.stdin.Write([]byte(fmt.Sprintf("%v\n", path))) + sRoot := evm.copyUntilEnd(devNull{}, evm.stdout) + return sRoot.StateRoot, evm.cmd.String(), nil +}