From 126f8718ded11060b250814d6a404e311c0c37fe Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Fri, 7 Jun 2024 16:02:55 -0300 Subject: [PATCH 01/30] init: define parts with manifest file --- cmd/nitro/init.go | 81 ++++++++++++++++++++++++++++-------------- cmd/nitro/init_test.go | 14 ++++---- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index a0b5304974..93597f91d8 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -15,6 +15,7 @@ import ( "net/http" "net/url" "os" + "path" "runtime" "strings" "sync" @@ -74,18 +75,21 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err return initFile, nil } log.Info("Downloading initial database", "url", initConfig.Url) - path, err := downloadFile(ctx, initConfig, initConfig.Url) - if errors.Is(err, notFoundError) { - return downloadInitInParts(ctx, initConfig) - } - return path, err -} - -func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string) (string, error) { - checksum, err := fetchChecksum(ctx, url+".sha256") + checksum, err := fetchChecksum(ctx, initConfig.Url+".sha256") if err != nil { + if errors.Is(err, notFoundError) { + return downloadInitInParts(ctx, initConfig) + } return "", fmt.Errorf("error fetching checksum: %w", err) } + file, err := downloadFile(ctx, initConfig, initConfig.Url, checksum) + if err != nil && errors.Is(err, notFoundError) { + return "", fmt.Errorf("file not found but checksum exists") + } + return file, err +} + +func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string, checksum []byte) (string, error) { grabclient := grab.NewClient() printTicker := time.NewTicker(time.Second) defer printTicker.Stop() @@ -122,7 +126,7 @@ func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string) case <-resp.Done: if err := resp.Err(); err != nil { if resp.HTTPResponse.StatusCode == http.StatusNotFound { - return "", fmt.Errorf("file not found but checksum exists") + return "", notFoundError } fmt.Printf("\n attempt %d failed: %v\n", attempt, err) break updateLoop @@ -190,39 +194,62 @@ func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (stri if err != nil || !fileInfo.IsDir() { return "", fmt.Errorf("download path must be a directory: %v", initConfig.DownloadPath) } - part := 0 - parts := []string{} + url, err := url.Parse(initConfig.Url) + if err != nil { + return "", fmt.Errorf("failed to parse init url \"%s\": %w", initConfig.Url, err) + } + + // Get parts from manifest file + manifest, err := httpGet(ctx, url.String()+".manifest.txt") + if err != nil { + return "", fmt.Errorf("failed to get manifest file: %w", err) + } + partNames := []string{} + checksums := [][]byte{} + lines := strings.Split(strings.TrimSpace(string(manifest)), "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) != 2 { + return "", fmt.Errorf("manifest file in wrong format") + } + checksum, err := hex.DecodeString(fields[0]) + if err != nil { + return "", fmt.Errorf("failed decoding checksum in manifest file: %w", err) + } + checksums = append(checksums, checksum) + partNames = append(partNames, fields[1]) + } + + partFiles := []string{} defer func() { // remove all temporary files. - for _, part := range parts { + for _, part := range partFiles { err := os.Remove(part) if err != nil { log.Warn("Failed to remove temporary file", "file", part) } } }() - for { - url := fmt.Sprintf("%s.part%d", initConfig.Url, part) - log.Info("Downloading database part", "url", url) - partFile, err := downloadFile(ctx, initConfig, url) - if errors.Is(err, notFoundError) { - log.Info("Part not found; concatenating archive into single file", "numParts", len(parts)) - break - } else if err != nil { - return "", err + + // Download parts + for i, partName := range partNames { + log.Info("Downloading database part", "part", partName) + partUrl := url.JoinPath("..", partName).String() + partFile, err := downloadFile(ctx, initConfig, partUrl, checksums[i]) + if err != nil { + return "", fmt.Errorf("error downloading part \"%s\": %w", partName, err) } - parts = append(parts, partFile) - part++ + partFiles = append(partFiles, partFile) } - return joinArchive(parts) + archivePath := path.Join(initConfig.DownloadPath, path.Base(url.Path)) + return joinArchive(partFiles, archivePath) } // joinArchive joins the archive parts into a single file and return its path. -func joinArchive(parts []string) (string, error) { +func joinArchive(parts []string, archivePath string) (string, error) { if len(parts) == 0 { return "", fmt.Errorf("no database parts found") } - archivePath := strings.TrimSuffix(parts[0], ".part0") archive, err := os.Create(archivePath) if err != nil { return "", fmt.Errorf("failed to create archive: %w", err) diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go index 17bac3d670..64e9c13edf 100644 --- a/cmd/nitro/init_test.go +++ b/cmd/nitro/init_test.go @@ -13,6 +13,7 @@ import ( "net" "net/http" "os" + "path" "path/filepath" "testing" "time" @@ -76,20 +77,21 @@ func TestDownloadInitInParts(t *testing.T) { // Create parts with random data serverDir := t.TempDir() data := testhelpers.RandomSlice(dataSize) + manifest := bytes.NewBuffer(nil) for i := 0; i < numParts; i++ { // Create part and checksum partData := data[partSize*i : partSize*(i+1)] + partName := fmt.Sprintf("%s.part%d", archiveName, i) checksumBytes := sha256.Sum256(partData) checksum := hex.EncodeToString(checksumBytes[:]) + fmt.Fprintf(manifest, "%s %s\n", checksum, partName) // Write part file - partFile := fmt.Sprintf("%s/%s.part%d", serverDir, archiveName, i) - err := os.WriteFile(partFile, partData, filePerm) + err := os.WriteFile(path.Join(serverDir, partName), partData, filePerm) Require(t, err, "failed to write part") - // Write checksum file - checksumFile := partFile + ".sha256" - err = os.WriteFile(checksumFile, []byte(checksum), filePerm) - Require(t, err, "failed to write checksum") } + manifestFile := fmt.Sprintf("%s/%s.manifest.txt", serverDir, archiveName) + err := os.WriteFile(manifestFile, manifest.Bytes(), filePerm) + Require(t, err, "failed to write manifest file") // Start HTTP server ctx, cancel := context.WithCancel(context.Background()) From 87a608f38eee54cdd903eb6e7563b905c86ea9d0 Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Wed, 12 Jun 2024 12:36:02 -0300 Subject: [PATCH 02/30] Add option to enable the checksum validation --- cmd/conf/init.go | 3 ++ cmd/nitro/init.go | 18 +++++++- cmd/nitro/init_test.go | 101 ++++++++++++++++++++++++++++++++++------- 3 files changed, 104 insertions(+), 18 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 5f25b02eeb..deb82a2de4 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -14,6 +14,7 @@ type InitConfig struct { Url string `koanf:"url"` Latest string `koanf:"latest"` LatestBase string `koanf:"latest-base"` + ValidateChecksum bool `koanf:"validate-checksum"` DownloadPath string `koanf:"download-path"` DownloadPoll time.Duration `koanf:"download-poll"` DevInit bool `koanf:"dev-init"` @@ -34,6 +35,7 @@ var InitConfigDefault = InitConfig{ Url: "", Latest: "", LatestBase: "https://snapshot.arbitrum.foundation/", + ValidateChecksum: true, DownloadPath: "/tmp/", DownloadPoll: time.Minute, DevInit: false, @@ -54,6 +56,7 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".url", InitConfigDefault.Url, "url to download initialization data - will poll if download fails") f.String(prefix+".latest", InitConfigDefault.Latest, "if set, searches for the latest snapshot of the given kind "+acceptedSnapshotKindsStr) f.String(prefix+".latest-base", InitConfigDefault.LatestBase, "base url used when searching for the latest") + f.Bool(prefix+".validate-checksum", InitConfigDefault.ValidateChecksum, "if true: validate the checksum after downloading the snapshot") f.String(prefix+".download-path", InitConfigDefault.DownloadPath, "path to save temp downloaded file") f.Duration(prefix+".download-poll", InitConfigDefault.DownloadPoll, "how long to wait between polling attempts") f.Bool(prefix+".dev-init", InitConfigDefault.DevInit, "init with dev data (1 account with balance) instead of file import") diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 6afd4cc842..a21fce8e98 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -75,6 +75,13 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err return initFile, nil } log.Info("Downloading initial database", "url", initConfig.Url) + if !initConfig.ValidateChecksum { + file, err := downloadFile(ctx, initConfig, initConfig.Url, nil) + if err != nil && errors.Is(err, notFoundError) { + return downloadInitInParts(ctx, initConfig) + } + return file, err + } checksum, err := fetchChecksum(ctx, initConfig.Url+".sha256") if err != nil { if errors.Is(err, notFoundError) { @@ -100,7 +107,10 @@ func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string, if err != nil { panic(err) } - req.SetChecksum(sha256.New(), checksum, false) + if checksum != nil { + const deleteOnError = true + req.SetChecksum(sha256.New(), checksum, deleteOnError) + } resp := grabclient.Do(req.WithContext(ctx)) firstPrintTime := time.Now().Add(time.Second * 2) updateLoop: @@ -235,7 +245,11 @@ func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (stri for i, partName := range partNames { log.Info("Downloading database part", "part", partName) partUrl := url.JoinPath("..", partName).String() - partFile, err := downloadFile(ctx, initConfig, partUrl, checksums[i]) + var checksum []byte + if initConfig.ValidateChecksum { + checksum = checksums[i] + } + partFile, err := downloadFile(ctx, initConfig, partUrl, checksum) if err != nil { return "", fmt.Errorf("error downloading part \"%s\": %w", partName, err) } diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go index 64e9c13edf..e56e5ee102 100644 --- a/cmd/nitro/init_test.go +++ b/cmd/nitro/init_test.go @@ -15,6 +15,7 @@ import ( "os" "path" "path/filepath" + "strings" "testing" "time" @@ -22,13 +23,47 @@ import ( "github.com/offchainlabs/nitro/util/testhelpers" ) -func TestDownloadInit(t *testing.T) { - const ( - archiveName = "random_data.tar.gz" - dataSize = 1024 * 1024 - filePerm = 0600 - ) +const ( + archiveName = "random_data.tar.gz" + numParts = 3 + partSize = 1024 * 1024 + dataSize = numParts * partSize + filePerm = 0600 + dirPerm = 0700 +) +func TestDownloadInitWithoutChecksum(t *testing.T) { + // Create archive with random data + serverDir := t.TempDir() + data := testhelpers.RandomSlice(dataSize) + + // Write archive file + archiveFile := fmt.Sprintf("%s/%s", serverDir, archiveName) + err := os.WriteFile(archiveFile, data, filePerm) + Require(t, err, "failed to write archive") + + // Start HTTP server + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + addr := startFileServer(t, ctx, serverDir) + + // Download file + initConfig := conf.InitConfigDefault + initConfig.Url = fmt.Sprintf("http://%s/%s", addr, archiveName) + initConfig.DownloadPath = t.TempDir() + initConfig.ValidateChecksum = false + receivedArchive, err := downloadInit(ctx, &initConfig) + Require(t, err, "failed to download") + + // Check archive contents + receivedData, err := os.ReadFile(receivedArchive) + Require(t, err, "failed to read received archive") + if !bytes.Equal(receivedData, data) { + t.Error("downloaded archive is different from generated one") + } +} + +func TestDownloadInitWithChecksum(t *testing.T) { // Create archive with random data serverDir := t.TempDir() data := testhelpers.RandomSlice(dataSize) @@ -65,15 +100,51 @@ func TestDownloadInit(t *testing.T) { } } -func TestDownloadInitInParts(t *testing.T) { - const ( - archiveName = "random_data.tar.gz" - numParts = 3 - partSize = 1024 * 1024 - dataSize = numParts * partSize - filePerm = 0600 - ) +func TestDownloadInitInPartsWithoutChecksum(t *testing.T) { + // Create parts with random data + serverDir := t.TempDir() + data := testhelpers.RandomSlice(dataSize) + manifest := bytes.NewBuffer(nil) + for i := 0; i < numParts; i++ { + partData := data[partSize*i : partSize*(i+1)] + partName := fmt.Sprintf("%s.part%d", archiveName, i) + fmt.Fprintf(manifest, "%s %s\n", strings.Repeat("0", 64), partName) + err := os.WriteFile(path.Join(serverDir, partName), partData, filePerm) + Require(t, err, "failed to write part") + } + manifestFile := fmt.Sprintf("%s/%s.manifest.txt", serverDir, archiveName) + err := os.WriteFile(manifestFile, manifest.Bytes(), filePerm) + Require(t, err, "failed to write manifest file") + + // Start HTTP server + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + addr := startFileServer(t, ctx, serverDir) + + // Download file + initConfig := conf.InitConfigDefault + initConfig.Url = fmt.Sprintf("http://%s/%s", addr, archiveName) + initConfig.DownloadPath = t.TempDir() + initConfig.ValidateChecksum = false + receivedArchive, err := downloadInit(ctx, &initConfig) + Require(t, err, "failed to download") + + // check database contents + receivedData, err := os.ReadFile(receivedArchive) + Require(t, err, "failed to read received archive") + if !bytes.Equal(receivedData, data) { + t.Error("downloaded archive is different from generated one") + } + + // Check if the function deleted the temporary files + entries, err := os.ReadDir(initConfig.DownloadPath) + Require(t, err, "failed to read temp dir") + if len(entries) != 1 { + t.Error("download function did not delete temp files") + } +} +func TestDownloadInitInPartsWithChecksum(t *testing.T) { // Create parts with random data serverDir := t.TempDir() data := testhelpers.RandomSlice(dataSize) @@ -126,8 +197,6 @@ func TestSetLatestSnapshotUrl(t *testing.T) { snapshotKind = "archive" latestDate = "2024/21" latestFile = "latest-" + snapshotKind + ".txt" - dirPerm = 0700 - filePerm = 0600 ) // Create latest file From 3b62f4345d1249dead34473969dd5dae8183d438 Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Tue, 18 Jun 2024 17:13:02 -0300 Subject: [PATCH 03/30] Rename url variable to avoid shadowing stdlib --- cmd/nitro/init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index a3582ec29b..7b00d8b0ec 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -205,13 +205,13 @@ func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (stri if err != nil || !fileInfo.IsDir() { return "", fmt.Errorf("download path must be a directory: %v", initConfig.DownloadPath) } - url, err := url.Parse(initConfig.Url) + archiveUrl, err := url.Parse(initConfig.Url) if err != nil { return "", fmt.Errorf("failed to parse init url \"%s\": %w", initConfig.Url, err) } // Get parts from manifest file - manifest, err := httpGet(ctx, url.String()+".manifest.txt") + manifest, err := httpGet(ctx, archiveUrl.String()+".manifest.txt") if err != nil { return "", fmt.Errorf("failed to get manifest file: %w", err) } @@ -245,7 +245,7 @@ func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (stri // Download parts for i, partName := range partNames { log.Info("Downloading database part", "part", partName) - partUrl := url.JoinPath("..", partName).String() + partUrl := archiveUrl.JoinPath("..", partName).String() var checksum []byte if initConfig.ValidateChecksum { checksum = checksums[i] @@ -256,7 +256,7 @@ func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (stri } partFiles = append(partFiles, partFile) } - archivePath := path.Join(initConfig.DownloadPath, path.Base(url.Path)) + archivePath := path.Join(initConfig.DownloadPath, path.Base(archiveUrl.Path)) return joinArchive(partFiles, archivePath) } From 9e729a21e6fa2214a3c625dec0165a488252d693 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 21 Jun 2024 14:59:15 -0600 Subject: [PATCH 04/30] set stylus to v2 on arbos 31 --- arbos/arbosState/arbosstate.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 0ac5d1380d..25aa7d3d9b 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -318,6 +318,12 @@ func (state *ArbosState) UpgradeArbosVersion( case 30: programs.Initialize(state.backingStorage.OpenSubStorage(programsSubspace)) + case 31: + params, err := state.Programs().Params() + ensure(err) + params.Version = 2 + ensure(params.Save()) + default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", From ab185a98356c1e2eaec2b47d5d1bb21d1d34fe19 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 15:27:29 -0600 Subject: [PATCH 05/30] Specify address when caching Stylus program --- arbos/programs/programs.go | 2 ++ contracts | 2 +- precompiles/ArbWasmCache.go | 18 ++++++++++++------ system_tests/program_test.go | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 320a48ebcf..7cb1a547ea 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -342,10 +342,12 @@ func (p Programs) ProgramCached(codeHash common.Hash) (bool, error) { } // Sets whether a program is cached. Errors if trying to cache an expired program. +// `address` must be present if setting cache to true, and must have the specified codeHash. func (p Programs) SetProgramCached( emitEvent func() error, db vm.StateDB, codeHash common.Hash, + address common.Address, cache bool, time uint64, params *StylusParams, diff --git a/contracts b/contracts index 77ee9de042..f1bb9a51b7 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 77ee9de042de225fab560096f7624f3d13bd12cb +Subproject commit f1bb9a51b770a4e5896dda4f761e2938bb791852 diff --git a/precompiles/ArbWasmCache.go b/precompiles/ArbWasmCache.go index 36b4e1ad31..15b7cc2089 100644 --- a/precompiles/ArbWasmCache.go +++ b/precompiles/ArbWasmCache.go @@ -3,6 +3,8 @@ package precompiles +import "github.com/ethereum/go-ethereum/common" + type ArbWasmCache struct { Address addr // 0x72 @@ -20,14 +22,18 @@ func (con ArbWasmCache) AllCacheManagers(c ctx, _ mech) ([]addr, error) { return c.State.Programs().CacheManagers().AllMembers(65536) } -// Caches all programs with the given codehash. Caller must be a cache manager or chain owner. -func (con ArbWasmCache) CacheCodehash(c ctx, evm mech, codehash hash) error { - return con.setProgramCached(c, evm, codehash, true) +// Caches all programs with a codehash equal to the given address. Caller must be a cache manager or chain owner. +func (con ArbWasmCache) CacheProgram(c ctx, evm mech, address addr) error { + codehash, err := c.GetCodeHash(address) + if err != nil { + return err + } + return con.setProgramCached(c, evm, address, codehash, true) } // Evicts all programs with the given codehash. Caller must be a cache manager or chain owner. func (con ArbWasmCache) EvictCodehash(c ctx, evm mech, codehash hash) error { - return con.setProgramCached(c, evm, codehash, false) + return con.setProgramCached(c, evm, common.Address{}, codehash, false) } // Gets whether a program is cached. Note that the program may be expired. @@ -36,7 +42,7 @@ func (con ArbWasmCache) CodehashIsCached(c ctx, evm mech, codehash hash) (bool, } // Caches all programs with the given codehash. -func (con ArbWasmCache) setProgramCached(c ctx, evm mech, codehash hash, cached bool) error { +func (con ArbWasmCache) setProgramCached(c ctx, evm mech, address addr, codehash hash, cached bool) error { if !con.hasAccess(c) { return c.BurnOut() } @@ -51,7 +57,7 @@ func (con ArbWasmCache) setProgramCached(c ctx, evm mech, codehash hash, cached return con.UpdateProgramCache(c, evm, c.caller, codehash, cached) } return programs.SetProgramCached( - emitEvent, evm.StateDB, codehash, cached, evm.Context.Time, params, txRunMode, debugMode, + emitEvent, evm.StateDB, codehash, address, cached, evm.Context.Time, params, txRunMode, debugMode, ) } diff --git a/system_tests/program_test.go b/system_tests/program_test.go index b20efe0740..2b66340bbd 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1248,7 +1248,7 @@ func TestProgramCacheManager(t *testing.T) { // check ownership assert(arbOwner.IsChainOwner(nil, ownerAuth.From)) ensure(arbWasmCache.EvictCodehash(&ownerAuth, codehash)) - ensure(arbWasmCache.CacheCodehash(&ownerAuth, codehash)) + ensure(arbWasmCache.CacheProgram(&ownerAuth, program)) // de-authorize manager ensure(arbOwner.RemoveWasmCacheManager(&ownerAuth, manager)) From 66f51c2b6350b682145db357123f4b9d95b0ef3f Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 15:40:44 -0600 Subject: [PATCH 06/30] Fix versioning --- arbos/programs/programs.go | 3 ++- contracts | 2 +- go-ethereum | 2 +- precompiles/ArbWasmCache.go | 5 +++++ precompiles/precompile.go | 16 ++++++++++------ precompiles/precompile_test.go | 1 + 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 7cb1a547ea..e907c6eb6a 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -342,7 +342,8 @@ func (p Programs) ProgramCached(codeHash common.Hash) (bool, error) { } // Sets whether a program is cached. Errors if trying to cache an expired program. -// `address` must be present if setting cache to true, and must have the specified codeHash. +// `address` must be present if setting cache to true as of ArbOS 31, +// and if `address` is present it must have the specified codeHash. func (p Programs) SetProgramCached( emitEvent func() error, db vm.StateDB, diff --git a/contracts b/contracts index f1bb9a51b7..cc53496c82 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit f1bb9a51b770a4e5896dda4f761e2938bb791852 +Subproject commit cc53496c827f1ecdc68ef2ccd0125059a92bd711 diff --git a/go-ethereum b/go-ethereum index da519ddc4f..1b1a1eff41 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit da519ddc4fd5113a46da734e41b37369a1dce098 +Subproject commit 1b1a1eff4171b016dcbc00a9aa639502024fabf5 diff --git a/precompiles/ArbWasmCache.go b/precompiles/ArbWasmCache.go index 15b7cc2089..31805ab24b 100644 --- a/precompiles/ArbWasmCache.go +++ b/precompiles/ArbWasmCache.go @@ -22,6 +22,11 @@ func (con ArbWasmCache) AllCacheManagers(c ctx, _ mech) ([]addr, error) { return c.State.Programs().CacheManagers().AllMembers(65536) } +// Deprecated, replaced with CacheProgram. +func (con ArbWasmCache) CacheCodehash(c ctx, evm mech, codehash hash) error { + return con.setProgramCached(c, evm, common.Address{}, codehash, true) +} + // Caches all programs with a codehash equal to the given address. Caller must be a cache manager or chain owner. func (con ArbWasmCache) CacheProgram(c ctx, evm mech, address addr) error { codehash, err := c.GetCodeHash(address) diff --git a/precompiles/precompile.go b/precompiles/precompile.go index c39f2bcb6d..9a6d8885ad 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -72,11 +72,12 @@ type Precompile struct { } type PrecompileMethod struct { - name string - template abi.Method - purity purity - handler reflect.Method - arbosVersion uint64 + name string + template abi.Method + purity purity + handler reflect.Method + arbosVersion uint64 + maxArbosVersion uint64 } type PrecompileEvent struct { @@ -226,6 +227,7 @@ func MakePrecompile(metadata *bind.MetaData, implementer interface{}) (addr, *Pr purity, handler, 0, + 0, } methods[id] = &method methodsByName[name] = &method @@ -575,6 +577,8 @@ func Precompiles() map[addr]ArbosPrecompile { for _, method := range ArbWasmCache.methods { method.arbosVersion = ArbWasmCache.arbosVersion } + ArbWasmCache.methodsByName["CacheCodehash"].maxArbosVersion = params.ArbosVersion_Stylus + ArbWasmCache.methodsByName["CacheProgram"].arbosVersion = params.ArbosVersion_StylusFixes ArbRetryableImpl := &ArbRetryableTx{Address: types.ArbRetryableTxAddress} ArbRetryable := insert(MakePrecompile(pgen.ArbRetryableTxMetaData, ArbRetryableImpl)) @@ -680,7 +684,7 @@ func (p *Precompile) Call( } id := *(*[4]byte)(input) method, ok := p.methods[id] - if !ok || arbosVersion < method.arbosVersion { + if !ok || arbosVersion < method.arbosVersion || (method.maxArbosVersion > 0 && arbosVersion > method.maxArbosVersion) { // method does not exist or hasn't yet been activated return nil, 0, vm.ErrExecutionReverted } diff --git a/precompiles/precompile_test.go b/precompiles/precompile_test.go index 86047038dc..ecce77088a 100644 --- a/precompiles/precompile_test.go +++ b/precompiles/precompile_test.go @@ -194,6 +194,7 @@ func TestPrecompilesPerArbosVersion(t *testing.T) { 11: 4, 20: 8, 30: 38, + 31: 1, } precompiles := Precompiles() From e9b4f8da630da1ea09015d8aceff8f95437a4b33 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 13 Jun 2024 00:24:39 -0600 Subject: [PATCH 07/30] Fix solgen paths with new foundry versions --- solgen/gen.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solgen/gen.go b/solgen/gen.go index 770fa08571..92511595d7 100644 --- a/solgen/gen.go +++ b/solgen/gen.go @@ -68,7 +68,7 @@ func main() { } root := filepath.Dir(filename) parent := filepath.Dir(root) - filePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "build", "contracts", "src", "*", "*", "*.json")) + filePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "build", "contracts", "src", "*", "*.sol", "*.json")) if err != nil { log.Fatal(err) } @@ -105,7 +105,7 @@ func main() { modInfo.addArtifact(artifact) } - yulFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "out", "yul", "*", "*.json")) + yulFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "out", "*", "*.yul", "*.json")) if err != nil { log.Fatal(err) } From 66989f5da019844edb787f68c9933f915c4ddc6c Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 16:12:44 -0600 Subject: [PATCH 08/30] Add a minimum cost to Stylus return data --- arbos/programs/programs.go | 26 +++++++++++++++++++++++++- arbos/tx_processor.go | 1 + go-ethereum | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 320a48ebcf..c4dd7b6298 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -13,6 +13,8 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + gethParams "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/addressSet" "github.com/offchainlabs/nitro/arbos/storage" @@ -22,6 +24,7 @@ import ( ) type Programs struct { + arbosVersion uint64 backingStorage *storage.Storage programs *storage.Storage moduleHashes *storage.Storage @@ -158,6 +161,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c func (p Programs) CallProgram( scope *vm.ScopeContext, statedb vm.StateDB, + arbosVersion uint64, interpreter *vm.EVMInterpreter, tracingInfo *util.TracingInfo, calldata []byte, @@ -166,6 +170,7 @@ func (p Programs) CallProgram( evm := interpreter.Evm() contract := scope.Contract codeHash := contract.CodeHash + startingGas := contract.Gas debugMode := evm.ChainConfig().DebugMode() params, err := p.Params() @@ -227,7 +232,26 @@ func (p Programs) CallProgram( if contract.CodeAddr != nil { address = *contract.CodeAddr } - return callProgram(address, moduleHash, scope, interpreter, tracingInfo, calldata, evmData, goParams, model) + ret, err := callProgram(address, moduleHash, scope, interpreter, tracingInfo, calldata, evmData, goParams, model) + if len(ret) > 0 && arbosVersion >= gethParams.ArbosVersion_Stylus { + // Ensure that return data costs as least as much as it would in the EVM. + evmCost := evmMemoryCost(uint64(len(ret))) + if startingGas < evmCost { + contract.Gas = 0 + return nil, vm.ErrOutOfGas + } + maxGasToReturn := startingGas - evmCost + contract.Gas = am.MinInt(contract.Gas, maxGasToReturn) + } + return ret, err +} + +func evmMemoryCost(size uint64) uint64 { + // It would take 100GB to overflow this calculation, so no need to worry about that + words := (size + 31) / 32 + linearCost := words * params.MemoryGas + squareCost := (words * words) / params.QuadCoeffDiv + return linearCost + squareCost } func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index b5fb64f695..92eed38c5b 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -123,6 +123,7 @@ func (p *TxProcessor) ExecuteWASM(scope *vm.ScopeContext, input []byte, interpre return p.state.Programs().CallProgram( scope, p.evm.StateDB, + p.state.ArbOSVersion(), interpreter, tracingInfo, input, diff --git a/go-ethereum b/go-ethereum index da519ddc4f..1b1a1eff41 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit da519ddc4fd5113a46da734e41b37369a1dce098 +Subproject commit 1b1a1eff4171b016dcbc00a9aa639502024fabf5 From ff87bf51cb31339d2f2a482be6d755cabb844084 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 16:14:42 -0600 Subject: [PATCH 09/30] Add support for ArbOS 31 --- arbos/arbosState/arbosstate.go | 7 +++++-- go-ethereum | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 0ac5d1380d..410088d5c0 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -74,8 +74,8 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error) } return &ArbosState{ arbosVersion, - 30, - 30, + 31, + 31, backingStorage.OpenStorageBackedUint64(uint64(upgradeVersionOffset)), backingStorage.OpenStorageBackedUint64(uint64(upgradeTimestampOffset)), backingStorage.OpenStorageBackedAddress(uint64(networkFeeAccountOffset)), @@ -318,6 +318,9 @@ func (state *ArbosState) UpgradeArbosVersion( case 30: programs.Initialize(state.backingStorage.OpenSubStorage(programsSubspace)) + case 31: + // no state changes needed + default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", diff --git a/go-ethereum b/go-ethereum index 1b1a1eff41..5a89d01223 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 1b1a1eff4171b016dcbc00a9aa639502024fabf5 +Subproject commit 5a89d012232039ab57e28f9628c8e50b9093edc7 From 45f50930018643300fa1ffd1cc2026bb8591f630 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 16:15:07 -0600 Subject: [PATCH 10/30] Bump geth --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 1b1a1eff41..5a89d01223 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 1b1a1eff4171b016dcbc00a9aa639502024fabf5 +Subproject commit 5a89d012232039ab57e28f9628c8e50b9093edc7 From a28e0347d38ce866ecd4d94979b4122b56f51a58 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 16:16:13 -0600 Subject: [PATCH 11/30] Fix linter --- precompiles/ArbWasmCache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/ArbWasmCache.go b/precompiles/ArbWasmCache.go index 31805ab24b..3cada9dd70 100644 --- a/precompiles/ArbWasmCache.go +++ b/precompiles/ArbWasmCache.go @@ -22,7 +22,7 @@ func (con ArbWasmCache) AllCacheManagers(c ctx, _ mech) ([]addr, error) { return c.State.Programs().CacheManagers().AllMembers(65536) } -// Deprecated, replaced with CacheProgram. +// Deprecated: replaced with CacheProgram. func (con ArbWasmCache) CacheCodehash(c ctx, evm mech, codehash hash) error { return con.setProgramCached(c, evm, common.Address{}, codehash, true) } From f6c1f2cf80b7943a532332146b07ff6c56920ea7 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Fri, 21 Jun 2024 16:31:41 -0600 Subject: [PATCH 12/30] Remove double import --- arbos/programs/programs.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index c4dd7b6298..445ac358b8 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" gethParams "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/addressSet" @@ -249,8 +248,8 @@ func (p Programs) CallProgram( func evmMemoryCost(size uint64) uint64 { // It would take 100GB to overflow this calculation, so no need to worry about that words := (size + 31) / 32 - linearCost := words * params.MemoryGas - squareCost := (words * words) / params.QuadCoeffDiv + linearCost := words * gethParams.MemoryGas + squareCost := (words * words) / gethParams.QuadCoeffDiv return linearCost + squareCost } From 0175b731cc839c5361343955c77dfa293096d296 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 21 Jun 2024 17:03:00 -0600 Subject: [PATCH 13/30] stylus config for v2 --- arbitrator/prover/src/programs/config.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arbitrator/prover/src/programs/config.rs b/arbitrator/prover/src/programs/config.rs index 0b5ce17475..1a37294b04 100644 --- a/arbitrator/prover/src/programs/config.rs +++ b/arbitrator/prover/src/programs/config.rs @@ -162,8 +162,7 @@ impl CompileConfig { match version { 0 => {} - 1 => { - // TODO: settle on reasonable values for the v1 release + 1 | 2 => { config.bounds.heap_bound = Pages(128); // 8 mb config.bounds.max_frame_size = 10 * 1024; config.bounds.max_frame_contention = 4096; From 5fa2c8803724fd4cfddbb0cf57ab295a29e17a61 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 21 Jun 2024 17:03:16 -0600 Subject: [PATCH 14/30] test stylus v2 in arbos 31 --- system_tests/program_test.go | 121 +++++++++++++++++++++++++++++++++-- system_tests/stylus_test.go | 4 ++ 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index b20efe0740..05d050308e 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -221,6 +221,106 @@ func testActivateTwice(t *testing.T, jit bool) { validateBlocks(t, 7, jit, builder) } +func TestStylusUpgrade(t *testing.T) { + t.Parallel() + testStylusUpgrade(t, true) +} + +func testStylusUpgrade(t *testing.T, jit bool) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.WithArbOSVersion(30) + + auth, cleanup := buildProgramTest(t, builder, jit) + defer cleanup() + + l2info := builder.L2Info + l2client := builder.L2.Client + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, l2client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&auth, 1)) + + wasm, _ := readWasmFile(t, rustFile("keccak")) + keccakAddr := deployContract(t, ctx, auth, l2client, wasm) + + colors.PrintBlue("keccak program deployed to ", keccakAddr) + + preimage := []byte("hello, you fool") + + keccakArgs := []byte{0x01} // keccak the preimage once + keccakArgs = append(keccakArgs, preimage...) + + checkFailWith := func(errMessage string) uint64 { + msg := ethereum.CallMsg{ + To: &keccakAddr, + Data: keccakArgs, + } + _, err = l2client.CallContract(ctx, msg, nil) + if err == nil || !strings.Contains(err.Error(), errMessage) { + Fatal(t, "call should have failed with "+errMessage, " got: "+err.Error()) + } + + // execute onchain for proving's sake + tx := l2info.PrepareTxTo("Owner", &keccakAddr, 1e9, nil, keccakArgs) + Require(t, l2client.SendTransaction(ctx, tx)) + return EnsureTxFailed(t, ctx, l2client, tx).BlockNumber.Uint64() + } + + checkSucceeds := func() uint64 { + msg := ethereum.CallMsg{ + To: &keccakAddr, + Data: keccakArgs, + } + _, err = l2client.CallContract(ctx, msg, nil) + if err != nil { + Fatal(t, err) + } + + // execute onchain for proving's sake + tx := l2info.PrepareTxTo("Owner", &keccakAddr, 1e9, nil, keccakArgs) + Require(t, l2client.SendTransaction(ctx, tx)) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + if err != nil { + Fatal(t, err) + } + return receipt.BlockNumber.Uint64() + } + + // Calling the contract pre-activation should fail. + blockFail1 := checkFailWith("ProgramNotActivated") + + activateWasm(t, ctx, auth, l2client, keccakAddr, "keccak") + + blockSuccess1 := checkSucceeds() + + tx, err := arbOwner.ScheduleArbOSUpgrade(&auth, 31, 0) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // generate traffic to perform the upgrade + TransferBalance(t, "Owner", "Owner", big.NewInt(1), builder.L2Info, builder.L2.Client, ctx) + + blockFail2 := checkFailWith("ProgramNeedsUpgrade") + + activateWasm(t, ctx, auth, l2client, keccakAddr, "keccak") + + blockSuccess2 := checkSucceeds() + + validateBlockRange(t, []uint64{blockFail1, blockSuccess1, blockFail2, blockSuccess2}, jit, builder) +} + func TestProgramErrors(t *testing.T) { t.Parallel() errorTest(t, true) @@ -1265,6 +1365,20 @@ func setupProgramTest(t *testing.T, jit bool) ( builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + auth, builderCleanup := buildProgramTest(t, builder, jit) + + cleanup := func() { + builderCleanup() + cancel() + } + return builder, auth, cleanup +} + +func buildProgramTest(t *testing.T, builder *NodeBuilder, jit bool) ( + bind.TransactOpts, func(), +) { + ctx := builder.ctx + builder.nodeConfig.BlockValidator.Enable = false builder.nodeConfig.Staker.Enable = true builder.nodeConfig.BatchPoster.Enable = true @@ -1280,11 +1394,6 @@ func setupProgramTest(t *testing.T, jit bool) ( builderCleanup := builder.Build(t) - cleanup := func() { - builderCleanup() - cancel() - } - auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) @@ -1306,7 +1415,7 @@ func setupProgramTest(t *testing.T, jit bool) ( ensure(arbDebug.BecomeChainOwner(&auth)) ensure(arbOwner.SetInkPrice(&auth, inkPrice)) - return builder, auth, cleanup + return auth, builderCleanup } func readWasmFile(t *testing.T, file string) ([]byte, []byte) { diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go index 46a9103b04..0c1ef01201 100644 --- a/system_tests/stylus_test.go +++ b/system_tests/stylus_test.go @@ -67,3 +67,7 @@ func TestProgramArbitratorActivateFails(t *testing.T) { func TestProgramArbitratorEarlyExit(t *testing.T) { testEarlyExit(t, false) } + +func TestProgramArbitratorStylusUpgrade(t *testing.T) { + testStylusUpgrade(t, false) +} From 8210523d625640303ddba7b60e5fa2c991d20656 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 21 Jun 2024 14:13:58 -0600 Subject: [PATCH 15/30] separate cached from call cost --- arbitrator/prover/src/binary.rs | 5 ++++- arbos/programs/programs.go | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index f6c3e9fe8f..821192ee38 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -616,7 +616,10 @@ impl<'a> WasmBinary<'a> { cached_init = cached_init.saturating_add(data_len.saturating_mul(75244) / 100_000); cached_init = cached_init.saturating_add(footprint as u64 * 5); - let mut init = cached_init; + let mut init: u64 = 0; + if compile.version == 1 { + init = cached_init; // in version 1 cached cost is part of call cost + } init = init.saturating_add(funcs.saturating_mul(8252) / 1000); init = init.saturating_add(type_len.saturating_mul(1059) / 1000); init = init.saturating_add(wasm_len.saturating_mul(1286) / 10_000); diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 320a48ebcf..f5e55e474a 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -194,9 +194,10 @@ func (p Programs) CallProgram( // pay for program init cached := program.cached || statedb.GetRecentWasms().Insert(codeHash, params.BlockCacheSize) - if cached { + if cached || params.Version > 1 { // in version 1 cached cost is part of called cost callCost = am.SaturatingUAdd(callCost, program.cachedGas(params)) - } else { + } + if !cached { callCost = am.SaturatingUAdd(callCost, program.initGas(params)) } if err := contract.BurnGas(callCost); err != nil { From 693f3d4d4a5070e7fb879eb7470ca6ce13a74e58 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 21 Jun 2024 20:16:27 -0600 Subject: [PATCH 16/30] fix ProgramInitGas --- arbos/programs/programs.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index f5e55e474a..d67ffb8e15 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -413,7 +413,12 @@ func (p Programs) ProgramTimeLeft(codeHash common.Hash, time uint64, params *Sty func (p Programs) ProgramInitGas(codeHash common.Hash, time uint64, params *StylusParams) (uint64, uint64, error) { program, err := p.getActiveProgram(codeHash, time, params) - return program.initGas(params), program.cachedGas(params), err + cachedGas := program.cachedGas(params) + initGas := program.initGas(params) + if params.Version > 1 { + initGas += cachedGas + } + return initGas, cachedGas, err } func (p Programs) ProgramMemoryFootprint(codeHash common.Hash, time uint64, params *StylusParams) (uint16, error) { From 3ef613c2a529b7ee79623425cbb8fe0ad5d4f2c4 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Sun, 23 Jun 2024 22:52:29 -0600 Subject: [PATCH 17/30] Cleanup unused field --- arbos/programs/programs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 6fb6e25838..d895fe2e71 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -23,7 +23,6 @@ import ( ) type Programs struct { - arbosVersion uint64 backingStorage *storage.Storage programs *storage.Storage moduleHashes *storage.Storage From 45bcd914cf3087b0ab73ba85f07f3cd8182d65e8 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Sun, 23 Jun 2024 23:32:40 -0600 Subject: [PATCH 18/30] Add test case for return data cost --- arbitrator/stylus/tests/return-size.wat | 71 ++++++++++++++++++++++++ arbos/programs/programs.go | 2 +- system_tests/program_test.go | 73 ++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 arbitrator/stylus/tests/return-size.wat diff --git a/arbitrator/stylus/tests/return-size.wat b/arbitrator/stylus/tests/return-size.wat new file mode 100644 index 0000000000..c7ec663fcc --- /dev/null +++ b/arbitrator/stylus/tests/return-size.wat @@ -0,0 +1,71 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "pay_for_memory_grow" (func (param i32))) + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (local $size i32) + + ;; read input + i32.const 0 + call $read_args + + ;; read the target size from the last 4 bytes of the input big endian + ;; byte 1 + local.get $args_len + i32.const 1 + i32.sub + local.tee $size + i32.load8_u + + ;; byte 2 + local.get $size + i32.const 1 + i32.sub + local.tee $size + i32.load8_u + i32.const 8 + i32.shl + i32.or + + ;; byte 3 + local.get $size + i32.const 1 + i32.sub + local.tee $size + i32.load8_u + i32.const 16 + i32.shl + i32.or + + ;; byte 4 + local.get $size + i32.const 1 + i32.sub + local.tee $size + i32.load8_u + i32.const 32 + i32.shl + i32.or + + local.tee $size + + ;; grow memory enough to handle the output + ;; we start with one page allocated, so no need to round up + i32.const 65536 + i32.div_u + memory.grow + drop + + ;; set return data + i32.const 0 + local.get $size + call $write_result + + ;; return success + i32.const 0 + ) + (memory (export "memory") 1) +) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index d895fe2e71..de60397b87 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -231,7 +231,7 @@ func (p Programs) CallProgram( address = *contract.CodeAddr } ret, err := callProgram(address, moduleHash, scope, interpreter, tracingInfo, calldata, evmData, goParams, model) - if len(ret) > 0 && arbosVersion >= gethParams.ArbosVersion_Stylus { + if len(ret) > 0 && arbosVersion >= gethParams.ArbosVersion_StylusFixes { // Ensure that return data costs as least as much as it would in the EVM. evmCost := evmMemoryCost(uint64(len(ret))) if startingGas < evmCost { diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 2b66340bbd..79586463a7 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -8,6 +8,7 @@ import ( "context" "encoding/binary" "fmt" + "math" "math/big" "os" "path/filepath" @@ -24,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" _ "github.com/ethereum/go-ethereum/eth/tracers/js" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbos/util" @@ -1258,13 +1260,82 @@ func TestProgramCacheManager(t *testing.T) { assert(len(all) == 0, err) } -func setupProgramTest(t *testing.T, jit bool) ( +func testReturnDataCost(t *testing.T, arbosVersion uint64) { + builder, auth, cleanup := setupProgramTest(t, false, func(b *NodeBuilder) { b.WithArbOSVersion(arbosVersion) }) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + // use a consistent ink price + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, l2client) + Require(t, err) + tx, err := arbOwner.SetInkPrice(&auth, 10000) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + + returnSize := big.NewInt(1024 * 1024) // 1MiB + returnSizeBytes := arbmath.U256Bytes(returnSize) + + testCall := func(to common.Address) uint64 { + msg := ethereum.CallMsg{ + To: &to, + Data: returnSizeBytes, + SkipL1Charging: true, + } + ret, err := l2client.CallContract(ctx, msg, nil) + Require(t, err) + + if !arbmath.BigEquals(big.NewInt(int64(len(ret))), returnSize) { + Fatal(t, "unexpected return length", len(ret), "expected", returnSize) + } + + gas, err := l2client.EstimateGas(ctx, msg) + Require(t, err) + + return gas + } + + stylusReturnSizeAddr := deployWasm(t, ctx, auth, l2client, watFile("return-size")) + + stylusGas := testCall(stylusReturnSizeAddr) + + // PUSH32 [returnSizeBytes] + evmBytecode := append([]byte{0x7F}, returnSizeBytes...) + // PUSH0 RETURN + evmBytecode = append(evmBytecode, 0x5F, 0xF3) + evmReturnSizeAddr := deployContract(t, ctx, auth, l2client, evmBytecode) + + evmGas := testCall(evmReturnSizeAddr) + + colors.PrintGrey(fmt.Sprintf("arbosVersion=%v stylusGas=%v evmGas=%v", arbosVersion, stylusGas, evmGas)) + // a bit of gas difference is expected due to EVM PUSH32 and PUSH0 cost (in practice this is 5 gas) + similarGas := math.Abs(float64(stylusGas)-float64(evmGas)) <= 100 + if arbosVersion >= params.ArbosVersion_StylusFixes { + if !similarGas { + Fatal(t, "unexpected gas difference for return data: stylus", stylusGas, ", evm", evmGas) + } + } else if similarGas { + Fatal(t, "gas unexpectedly similar for return data: stylus", stylusGas, ", evm", evmGas) + } +} + +func TestReturnDataCost(t *testing.T) { + testReturnDataCost(t, params.ArbosVersion_Stylus) + testReturnDataCost(t, params.ArbosVersion_StylusFixes) +} + +func setupProgramTest(t *testing.T, jit bool, builderOpts ...func(*NodeBuilder)) ( *NodeBuilder, bind.TransactOpts, func(), ) { ctx, cancel := context.WithCancel(context.Background()) builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + for _, opt := range builderOpts { + opt(builder) + } + builder.nodeConfig.BlockValidator.Enable = false builder.nodeConfig.Staker.Enable = true builder.nodeConfig.BatchPoster.Enable = true From 1baa7c645dc38071dca3ddb1fc168a963b16fc37 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Sun, 23 Jun 2024 23:48:22 -0600 Subject: [PATCH 19/30] Fix spacing in return-size.wat --- arbitrator/stylus/tests/return-size.wat | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/arbitrator/stylus/tests/return-size.wat b/arbitrator/stylus/tests/return-size.wat index c7ec663fcc..7f93f9d454 100644 --- a/arbitrator/stylus/tests/return-size.wat +++ b/arbitrator/stylus/tests/return-size.wat @@ -8,24 +8,24 @@ (func (export "user_entrypoint") (param $args_len i32) (result i32) (local $size i32) - ;; read input + ;; read input i32.const 0 call $read_args ;; read the target size from the last 4 bytes of the input big endian ;; byte 1 - local.get $args_len + local.get $args_len i32.const 1 i32.sub local.tee $size - i32.load8_u + i32.load8_u ;; byte 2 local.get $size i32.const 1 i32.sub local.tee $size - i32.load8_u + i32.load8_u i32.const 8 i32.shl i32.or @@ -35,7 +35,7 @@ i32.const 1 i32.sub local.tee $size - i32.load8_u + i32.load8_u i32.const 16 i32.shl i32.or @@ -45,23 +45,23 @@ i32.const 1 i32.sub local.tee $size - i32.load8_u + i32.load8_u i32.const 32 i32.shl i32.or local.tee $size - ;; grow memory enough to handle the output - ;; we start with one page allocated, so no need to round up - i32.const 65536 - i32.div_u - memory.grow + ;; grow memory enough to handle the output + ;; we start with one page allocated, so no need to round up + i32.const 65536 + i32.div_u + memory.grow drop ;; set return data i32.const 0 - local.get $size + local.get $size call $write_result ;; return success From d4bc16d9538b17345b9afb91422b9a447187bf11 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Sun, 23 Jun 2024 23:48:53 -0600 Subject: [PATCH 20/30] Fix copyright year in return-size.wat --- arbitrator/stylus/tests/return-size.wat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbitrator/stylus/tests/return-size.wat b/arbitrator/stylus/tests/return-size.wat index 7f93f9d454..52a2bc8ece 100644 --- a/arbitrator/stylus/tests/return-size.wat +++ b/arbitrator/stylus/tests/return-size.wat @@ -1,4 +1,4 @@ -;; Copyright 2023, Offchain Labs, Inc. +;; Copyright 2024, Offchain Labs, Inc. ;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE (module From d4b3917e16dc6f152dee2905be9ca967d3022f58 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 24 Jun 2024 22:40:09 -0600 Subject: [PATCH 21/30] Minor fixes to "separate cached from call cost" --- arbitrator/prover/src/binary.rs | 2 +- arbos/programs/programs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index 821192ee38..aa5537476c 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -618,7 +618,7 @@ impl<'a> WasmBinary<'a> { let mut init: u64 = 0; if compile.version == 1 { - init = cached_init; // in version 1 cached cost is part of call cost + init = cached_init; // in version 1 cached cost is part of init cost } init = init.saturating_add(funcs.saturating_mul(8252) / 1000); init = init.saturating_add(type_len.saturating_mul(1059) / 1000); diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 7ca33369c8..06943532b4 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -197,7 +197,7 @@ func (p Programs) CallProgram( // pay for program init cached := program.cached || statedb.GetRecentWasms().Insert(codeHash, params.BlockCacheSize) - if cached || params.Version > 1 { // in version 1 cached cost is part of called cost + if cached || program.version > 1 { // in version 1 cached cost is part of init cost callCost = am.SaturatingUAdd(callCost, program.cachedGas(params)) } if !cached { From 151744f5c631bcc1fd1fcbcb9b7a9f328a8227b0 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Wed, 26 Jun 2024 10:43:32 -0600 Subject: [PATCH 22/30] Require program.version == params.Version when caching --- arbos/programs/programs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 06943532b4..84d7450364 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -384,8 +384,8 @@ func (p Programs) SetProgramCached( } expired := program.ageSeconds > am.DaysToSeconds(params.ExpiryDays) - if program.version == 0 && cache { - return ProgramNeedsUpgradeError(0, params.Version) + if program.version != params.Version && cache { + return ProgramNeedsUpgradeError(program.version, params.Version) } if expired && cache { return ProgramExpiredError(program.ageSeconds) From 68663fa4d6e877962c441eca74c32217c8fdbf0b Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 26 Jun 2024 11:33:38 -0600 Subject: [PATCH 23/30] update minInitGas for stylus v2 --- arbos/arbosState/arbosstate.go | 2 +- arbos/programs/params.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index b213da9c82..9ff3dd3aa5 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -321,7 +321,7 @@ func (state *ArbosState) UpgradeArbosVersion( case 31: params, err := state.Programs().Params() ensure(err) - params.Version = 2 + ensure(params.UpgradeToVersion(2)) ensure(params.Save()) default: diff --git a/arbos/programs/params.go b/arbos/programs/params.go index 6138e36033..a0b8acd95c 100644 --- a/arbos/programs/params.go +++ b/arbos/programs/params.go @@ -5,6 +5,7 @@ package programs import ( "errors" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -29,6 +30,8 @@ const initialExpiryDays = 365 // deactivate after 1 year. const initialKeepaliveDays = 31 // wait a month before allowing reactivation. const initialRecentCacheSize = 32 // cache the 32 most recent programs. +const v2MinInitGas = 69 // charge 69 * 128 = 8832 gas (minCachedGas will also be charged in v2). + const MinCachedGasUnits = 32 /// 32 gas for each unit const MinInitGasUnits = 128 // 128 gas for each unit const CostScalarPercent = 2 // 2% for each unit @@ -137,6 +140,18 @@ func (p *StylusParams) Save() error { return nil } +func (p *StylusParams) UpgradeToVersion(version uint16) error { + if version != 2 { + return fmt.Errorf("dest version not supported for upgrade") + } + if p.Version != 1 { + return fmt.Errorf("existing version not supported for upgrade") + } + p.Version = 2 + p.MinInitGas = v2MinInitGas + return nil +} + func initStylusParams(sto *storage.Storage) { params := &StylusParams{ backingStorage: sto, From 022d5ee4f2efe13882cf234c9fd4b08d3c622a45 Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Tue, 2 Jul 2024 16:23:30 -0500 Subject: [PATCH 24/30] Add LOG opcodes to Stylus tracing --- arbos/programs/api.go | 3 ++ arbos/util/tracing.go | 21 +++++++++++++ system_tests/program_test.go | 61 +++++++++++++++++++++++++++++------- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/arbos/programs/api.go b/arbos/programs/api.go index 787f127ea4..65a58a47c2 100644 --- a/arbos/programs/api.go +++ b/arbos/programs/api.go @@ -228,6 +228,9 @@ func newApiClosures( return addr, res, cost, nil } emitLog := func(topics []common.Hash, data []byte) error { + if tracingInfo != nil { + tracingInfo.RecordEmitLog(topics, data) + } if readOnly { return vm.ErrWriteProtection } diff --git a/arbos/util/tracing.go b/arbos/util/tracing.go index 49b82d6d64..ed5acb33b2 100644 --- a/arbos/util/tracing.go +++ b/arbos/util/tracing.go @@ -4,6 +4,7 @@ package util import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -47,6 +48,26 @@ func NewTracingInfo(evm *vm.EVM, from, to common.Address, scenario TracingScenar } } +func (info *TracingInfo) RecordEmitLog(topics []common.Hash, data []byte) { + size := uint64(len(data)) + var args []uint256.Int + args = append(args, *uint256.NewInt(0)) + args = append(args, *uint256.NewInt(size)) + for _, topic := range topics { + args = append(args, HashToUint256(topic)) + } + memory := vm.NewMemory() + memory.Resize(size) + memory.Set(0, size, data) + scope := &vm.ScopeContext{ + Memory: memory, + Stack: TracingStackFromArgs(args...), + Contract: info.Contract, + } + logType := fmt.Sprintf("LOG%d", len(topics)) + info.Tracer.CaptureState(0, vm.StringToOp(logType), 0, 0, scope, []byte{}, info.Depth, nil) +} + func (info *TracingInfo) RecordStorageGet(key common.Hash) { tracer := info.Tracer if info.Scenario == TracingDuringEVM { diff --git a/system_tests/program_test.go b/system_tests/program_test.go index d8d9e05aa1..1687d74f04 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -641,10 +642,15 @@ func testReturnData(t *testing.T, jit bool) { func TestProgramLogs(t *testing.T) { t.Parallel() - testLogs(t, true) + testLogs(t, true, false) } -func testLogs(t *testing.T, jit bool) { +func TestProgramLogsWithTracing(t *testing.T) { + t.Parallel() + testLogs(t, true, true) +} + +func testLogs(t *testing.T, jit, tracing bool) { builder, auth, cleanup := setupProgramTest(t, jit) ctx := builder.ctx l2info := builder.L2Info @@ -653,6 +659,27 @@ func testLogs(t *testing.T, jit bool) { logAddr := deployWasm(t, ctx, auth, l2client, rustFile("log")) multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + type traceLog struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + } + traceTx := func(tx *types.Transaction) []traceLog { + type traceLogs struct { + Logs []traceLog `json:"logs"` + } + var trace traceLogs + traceConfig := map[string]interface{}{ + "tracer": "callTracer", + "tracerConfig": map[string]interface{}{ + "withLog": true, + }, + } + rpc := l2client.Client() + err := rpc.CallContext(ctx, &trace, "debug_traceTransaction", tx.Hash(), traceConfig) + Require(t, err) + return trace.Logs + } ensure := func(tx *types.Transaction, err error) *types.Receipt { t.Helper() Require(t, err) @@ -679,6 +706,20 @@ func testLogs(t *testing.T, jit bool) { topics[j] = testhelpers.RandomHash() } data := randBytes(0, 48) + verifyLogTopicsAndData := func(logData []byte, logTopics []common.Hash) { + if !bytes.Equal(logData, data) { + Fatal(t, "data mismatch", logData, data) + } + if len(logTopics) != len(topics) { + Fatal(t, "topics mismatch", len(logTopics), len(topics)) + } + for j := 0; j < i; j++ { + if logTopics[j] != topics[j] { + Fatal(t, "topic mismatch", logTopics, topics) + } + } + } + args := encode(topics, data) tx := l2info.PrepareTxTo("Owner", &logAddr, 1e9, nil, args) receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) @@ -687,16 +728,14 @@ func testLogs(t *testing.T, jit bool) { Fatal(t, "wrong number of logs", len(receipt.Logs)) } log := receipt.Logs[0] - if !bytes.Equal(log.Data, data) { - Fatal(t, "data mismatch", log.Data, data) - } - if len(log.Topics) != len(topics) { - Fatal(t, "topics mismatch", len(log.Topics), len(topics)) - } - for j := 0; j < i; j++ { - if log.Topics[j] != topics[j] { - Fatal(t, "topic mismatch", log.Topics, topics) + verifyLogTopicsAndData(log.Data, log.Topics) + if tracing { + logs := traceTx(tx) + if len(logs) != 1 { + Fatal(t, "wrong number of logs in trace", len(logs)) } + log := logs[0] + verifyLogTopicsAndData(log.Data, log.Topics) } } From 4c96aaf8e3f67d604d9f67137c4d22670285e361 Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Tue, 2 Jul 2024 16:56:29 -0500 Subject: [PATCH 25/30] fix missed invocation of testLogs in stylus_test --- system_tests/stylus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go index 97f3041196..a4bf4d8035 100644 --- a/system_tests/stylus_test.go +++ b/system_tests/stylus_test.go @@ -41,7 +41,7 @@ func TestProgramArbitratorReturnData(t *testing.T) { } func TestProgramArbitratorLogs(t *testing.T) { - testLogs(t, false) + testLogs(t, false, false) } func TestProgramArbitratorCreate(t *testing.T) { From 857d9db373364d18ae006d3ff4ccc1f15f6a285e Mon Sep 17 00:00:00 2001 From: Gabriel de Quadros Ligneul Date: Wed, 3 Jul 2024 11:53:23 -0300 Subject: [PATCH 26/30] init: allow classic node exported data in db dir --- cmd/nitro/init.go | 43 ++++++++++++++++++++-------------- cmd/nitro/init_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 9c23cdf852..db2f4abc20 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -327,6 +327,30 @@ func dirExists(path string) bool { return info.IsDir() } +func checkEmptyDatabaseDir(dir string, force bool) error { + entries, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to open database dir %s: %w", dir, err) + } + unexpectedFiles := []string{} + allowedFiles := map[string]bool{ + "LOCK": true, "classic-msg": true, "l2chaindata": true, + } + for _, entry := range entries { + if !allowedFiles[entry.Name()] { + unexpectedFiles = append(unexpectedFiles, entry.Name()) + } + } + if len(unexpectedFiles) > 0 { + if force { + return fmt.Errorf("trying to overwrite old database directory '%s' (delete the database directory and try again)", dir) + } + firstThreeFilenames := strings.Join(unexpectedFiles[:min(len(unexpectedFiles), 3)], ", ") + return fmt.Errorf("found %d unexpected files in database directory, including: %s", len(unexpectedFiles), firstThreeFilenames) + } + return nil +} + var pebbleNotExistErrorRegex = regexp.MustCompile("pebble: database .* does not exist") func isPebbleNotExistError(err error) bool { @@ -424,23 +448,8 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo return nil, nil, fmt.Errorf(errorFmt, stack.InstanceDir(), grandParentDir) } - // Check if database directory is empty - entries, err := os.ReadDir(stack.InstanceDir()) - if err != nil { - return nil, nil, fmt.Errorf("failed to open database dir %s: %w", stack.InstanceDir(), err) - } - unexpectedFiles := []string{} - for _, entry := range entries { - if entry.Name() != "LOCK" { - unexpectedFiles = append(unexpectedFiles, entry.Name()) - } - } - if len(unexpectedFiles) > 0 { - if config.Init.Force { - return nil, nil, fmt.Errorf("trying to overwrite old database directory '%s' (delete the database directory and try again)", stack.InstanceDir()) - } - firstThreeFilenames := strings.Join(unexpectedFiles[:min(len(unexpectedFiles), 3)], ", ") - return nil, nil, fmt.Errorf("found %d unexpected files in database directory, including: %s", len(unexpectedFiles), firstThreeFilenames) + if err := checkEmptyDatabaseDir(stack.InstanceDir(), config.Init.Force); err != nil { + return nil, nil, err } if err := setLatestSnapshotUrl(ctx, &config.Init, config.Chain.Name); err != nil { diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go index 47ab4b4491..546e1a096c 100644 --- a/cmd/nitro/init_test.go +++ b/cmd/nitro/init_test.go @@ -13,7 +13,9 @@ import ( "net" "net/http" "os" + "path" "path/filepath" + "strings" "testing" "time" @@ -210,3 +212,53 @@ func TestIsNotExistError(t *testing.T) { testIsNotExistError(t, "leveldb", isLeveldbNotExistError) }) } + +func TestEmptyDatabaseDir(t *testing.T) { + testCases := []struct { + name string + files []string + force bool + wantErr string + }{ + { + name: "succeed with empty dir", + }, + { + name: "succeed with expected files", + files: []string{"LOCK", "classic-msg", "l2chaindata"}, + }, + { + name: "fail with unexpected files", + files: []string{"LOCK", "a", "b", "c", "d"}, + wantErr: "found 4 unexpected files in database directory, including: a, b, c", + }, + { + name: "fail with unexpected files when forcing", + files: []string{"LOCK", "a", "b", "c", "d"}, + force: true, + wantErr: "trying to overwrite old database directory", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + for _, file := range tc.files { + const filePerm = 0600 + err := os.WriteFile(path.Join(dir, file), []byte{1, 2, 3}, filePerm) + Require(t, err) + } + err := checkEmptyDatabaseDir(dir, tc.force) + if tc.wantErr == "" { + if err != nil { + t.Errorf("expected nil error, got %q", err) + } + } else { + if err == nil { + t.Error("expected error, got nil") + } else if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("expected %q, got %q", tc.wantErr, err) + } + } + }) + } +} From 80cf3d424f1cb567d96a7cf6fb866b4917e7fe4c Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Mon, 8 Jul 2024 10:52:29 -0500 Subject: [PATCH 27/30] add descriptive comments to stack args --- arbos/util/tracing.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arbos/util/tracing.go b/arbos/util/tracing.go index ed5acb33b2..f0f101bc20 100644 --- a/arbos/util/tracing.go +++ b/arbos/util/tracing.go @@ -51,10 +51,10 @@ func NewTracingInfo(evm *vm.EVM, from, to common.Address, scenario TracingScenar func (info *TracingInfo) RecordEmitLog(topics []common.Hash, data []byte) { size := uint64(len(data)) var args []uint256.Int - args = append(args, *uint256.NewInt(0)) - args = append(args, *uint256.NewInt(size)) + args = append(args, *uint256.NewInt(0)) // offset: byte offset in the memory in bytes + args = append(args, *uint256.NewInt(size)) // size: byte size to copy (length of data) for _, topic := range topics { - args = append(args, HashToUint256(topic)) + args = append(args, HashToUint256(topic)) // topic: 32-byte value. Max topics count is 4 } memory := vm.NewMemory() memory.Resize(size) From e5244adc52cd951110898ca4ba3b110fac5f3b6b Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 8 Jul 2024 15:03:20 -0600 Subject: [PATCH 28/30] Upgrade to node 18 in CI to fix contracts --- .github/workflows/arbitrator-ci.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml index b2713bdfde..3ec3327392 100644 --- a/.github/workflows/arbitrator-ci.yml +++ b/.github/workflows/arbitrator-ci.yml @@ -64,7 +64,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '18' cache: 'yarn' cache-dependency-path: '**/yarn.lock' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d97b5bfd5..cc5cd68a90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '18' cache: 'yarn' cache-dependency-path: '**/yarn.lock' @@ -174,7 +174,7 @@ jobs: run: | packages=`go list ./...` stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run="TestProgramLong" > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") - + - name: Archive detailed run log uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index acaa97895d..1cde8f06b9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -66,7 +66,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '18' cache: 'yarn' cache-dependency-path: '**/yarn.lock' From e63b4b5c9b32362f2496b74b52a5c2629156e5dd Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 8 Jul 2024 15:04:50 -0600 Subject: [PATCH 29/30] Fix wasm cacheProgram stub --- arbos/programs/native.go | 4 ++-- arbos/programs/wasm.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 5366609942..f8e2696aad 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -222,10 +222,10 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out // Caches a program in Rust. We write a record so that we can undo on revert. // For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. -func cacheProgram(db vm.StateDB, module common.Hash, program Program, address common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { +func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { if runMode == core.MessageCommitMode { // address is only used for logging - asm, err := getLocalAsm(db, module, address, code, codeHash, params.PageLimit, time, debug, program) + asm, err := getLocalAsm(db, module, addressForLogging, code, codeHash, params.PageLimit, time, debug, program) if err != nil { panic("unable to recreate wasm") } diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index 0301a7e847..f7191dca8f 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -95,7 +95,7 @@ func activateProgram( } // stub any non-consensus, Rust-side caching updates -func cacheProgram(db vm.StateDB, module common.Hash, program Program, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { +func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { } func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, mode core.MessageRunMode, forever bool) { } From e10102b36b83052dc0d50a48b9c2f0d4c39be0c0 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 8 Jul 2024 15:43:52 -0600 Subject: [PATCH 30/30] Update Dockerfile to node 18 for contracts build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 01d9ef37f0..37c1020a42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN apt-get update && \ FROM scratch as brotli-library-export COPY --from=brotli-library-builder /workspace/install/ / -FROM node:16-bookworm-slim as contracts-builder +FROM node:18-bookworm-slim as contracts-builder RUN apt-get update && \ apt-get install -y git python3 make g++ curl RUN curl -L https://foundry.paradigm.xyz | bash && . ~/.bashrc && ~/.foundry/bin/foundryup