From 817e71f7f5a21498429f74f1dc5e83ce0d6b4896 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 9 Jan 2025 12:11:42 -0800 Subject: [PATCH 1/9] perf: use fmt.Fprintf to avoid unnecessary string+args + WriteString (#3434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a bid to remove unnecessary CPU and memory bloat for the gnovm which takes the order of minutes to run certain code, I noticed the pattern: io.StringWriter.WriteString(fmt.Sprintf(...)) in which fmt.Sprintf(...) has to create a string by inserting all arguments into the format specifiers then pass that into .WriteString which defeats the entire purpose of io.StringWriter.WriteString that *bytes.Buffer and *strings.Builder implement. Just from picking a single benchmark that already exists results in improvements in all dimensions: ```shell name old time/op new time/op delta StringLargeData-8 8.87ms ± 1% 8.28ms ± 3% -6.68% (p=0.000 n=17+19) name old alloc/op new alloc/op delta StringLargeData-8 8.44MB ± 0% 7.78MB ± 0% -7.75% (p=0.000 n=20+19) name old allocs/op new allocs/op delta StringLargeData-8 94.1k ± 0% 70.1k ± 0% -25.51% (p=0.000 n=15+20) ``` for heavily used code this is going to reduce on garbage collection cycles too. Fixes #3433 --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Morgan --- contribs/gnomigrate/internal/txs/txs.go | 2 +- gnovm/pkg/gnolang/gno_test.go | 2 + gnovm/pkg/gnolang/machine.go | 39 ++++++------ gnovm/pkg/gnolang/machine_test.go | 59 +++++++++++++++++++ go.mod | 1 + misc/docs-linter/jsx.go | 2 +- misc/docs-linter/links.go | 2 +- misc/docs-linter/main.go | 4 +- misc/docs-linter/urls.go | 2 +- tm2/pkg/bft/rpc/lib/server/handlers.go | 4 +- .../rpc/lib/server/write_endpoints_test.go | 33 +++++++++++ tm2/pkg/sdk/auth/params.go | 17 +++--- tm2/pkg/sdk/auth/params_test.go | 24 ++++++++ 13 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go diff --git a/contribs/gnomigrate/internal/txs/txs.go b/contribs/gnomigrate/internal/txs/txs.go index 4c65ca6ef0b..231428d5064 100644 --- a/contribs/gnomigrate/internal/txs/txs.go +++ b/contribs/gnomigrate/internal/txs/txs.go @@ -184,7 +184,7 @@ func processFile(ctx context.Context, io commands.IO, source, destination string continue } - if _, err = outputFile.WriteString(fmt.Sprintf("%s\n", string(marshaledData))); err != nil { + if _, err = fmt.Fprintf(outputFile, "%s\n", marshaledData); err != nil { io.ErrPrintfln("unable to save to output file, %s", err) } } diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 89458667997..5a8c6faf315 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -36,6 +36,8 @@ func setupMachine(b *testing.B, numValues, numStmts, numExprs, numBlocks, numFra func BenchmarkStringLargeData(b *testing.B) { m := setupMachine(b, 10000, 5000, 5000, 2000, 3000, 1000) + b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { _ = m.String() diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 4480a89d16f..75d12ac5402 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -2117,6 +2117,10 @@ func (m *Machine) Printf(format string, args ...interface{}) { } func (m *Machine) String() string { + if m == nil { + return "Machine:nil" + } + // Calculate some reasonable total length to avoid reallocation // Assuming an average length of 32 characters per string var ( @@ -2131,25 +2135,26 @@ func (m *Machine) String() string { totalLength = vsLength + ssLength + xsLength + bsLength + obsLength + fsLength + exceptionsLength ) - var builder strings.Builder + var sb strings.Builder + builder := &sb // Pointer for use in fmt.Fprintf. builder.Grow(totalLength) - builder.WriteString(fmt.Sprintf("Machine:\n PreprocessorMode: %v\n Op: %v\n Values: (len: %d)\n", m.PreprocessorMode, m.Ops[:m.NumOps], m.NumValues)) + fmt.Fprintf(builder, "Machine:\n PreprocessorMode: %v\n Op: %v\n Values: (len: %d)\n", m.PreprocessorMode, m.Ops[:m.NumOps], m.NumValues) for i := m.NumValues - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Values[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Values[i]) } builder.WriteString(" Exprs:\n") for i := len(m.Exprs) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Exprs[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Exprs[i]) } builder.WriteString(" Stmts:\n") for i := len(m.Stmts) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Stmts[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Stmts[i]) } builder.WriteString(" Blocks:\n") @@ -2166,17 +2171,17 @@ func (m *Machine) String() string { if pv, ok := b.Source.(*PackageNode); ok { // package blocks have too much, so just // print the pkgpath. - builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, pv.PkgPath)) + fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, pv.PkgPath) } else { bsi := b.StringIndented(" ") - builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, bsi)) + fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, bsi) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" "))) + fmt.Fprintf(builder, " (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" ")) sts := b.GetSource(m.Store).GetStaticBlock().Types - builder.WriteString(fmt.Sprintf(" (s typs) %s(%d) %s\n", gens, gen, sts)) + fmt.Fprintf(builder, " (s typs) %s(%d) %s\n", gens, gen, sts) } } @@ -2187,7 +2192,7 @@ func (m *Machine) String() string { case *Block: b = bp case RefValue: - builder.WriteString(fmt.Sprintf(" (block ref %v)\n", bp.ObjectID)) + fmt.Fprintf(builder, " (block ref %v)\n", bp.ObjectID) b = nil default: panic("should not happen") @@ -2206,12 +2211,12 @@ func (m *Machine) String() string { if _, ok := b.Source.(*PackageNode); ok { break // done, skip *PackageNode. } else { - builder.WriteString(fmt.Sprintf(" #%d %s\n", i, - b.StringIndented(" "))) + fmt.Fprintf(builder, " #%d %s\n", i, + b.StringIndented(" ")) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (static) #%d %s\n", i, - sb.StringIndented(" "))) + fmt.Fprintf(builder, " (static) #%d %s\n", i, + sb.StringIndented(" ")) } } } @@ -2219,17 +2224,17 @@ func (m *Machine) String() string { builder.WriteString(" Frames:\n") for i := len(m.Frames) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %s\n", i, m.Frames[i])) + fmt.Fprintf(builder, " #%d %s\n", i, m.Frames[i]) } if m.Realm != nil { - builder.WriteString(fmt.Sprintf(" Realm:\n %s\n", m.Realm.Path)) + fmt.Fprintf(builder, " Realm:\n %s\n", m.Realm.Path) } builder.WriteString(" Exceptions:\n") for _, ex := range m.Exceptions { - builder.WriteString(fmt.Sprintf(" %s\n", ex.Sprint(m))) + fmt.Fprintf(builder, " %s\n", ex.Sprint(m)) } return builder.String() diff --git a/gnovm/pkg/gnolang/machine_test.go b/gnovm/pkg/gnolang/machine_test.go index c3b2118f099..c2ab8ea12c5 100644 --- a/gnovm/pkg/gnolang/machine_test.go +++ b/gnovm/pkg/gnolang/machine_test.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/iavl" stypes "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" ) @@ -56,3 +57,61 @@ func TestRunMemPackageWithOverrides_revertToOld(t *testing.T) { assert.Equal(t, StringKind, v.T.Kind()) assert.Equal(t, StringValue("1"), v.V) } + +func TestMachineString(t *testing.T) { + cases := []struct { + name string + in *Machine + want string + }{ + { + "nil Machine", + nil, + "Machine:nil", + }, + { + "created with defaults", + NewMachineWithOptions(MachineOptions{}), + "Machine:\n PreprocessorMode: false\n Op: []\n Values: (len: 0)\n Exprs:\n Stmts:\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + { + "created with store and defaults", + func() *Machine { + db := memdb.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + return NewMachine("std", store) + }(), + "Machine:\n PreprocessorMode: false\n Op: []\n Values: (len: 0)\n Exprs:\n Stmts:\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + { + "filled in", + func() *Machine { + db := memdb.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + m := NewMachine("std", store) + m.PushOp(OpHalt) + m.PushExpr(&BasicLitExpr{ + Kind: INT, + Value: "100", + }) + m.Blocks = make([]*Block, 1, 1) + m.PushStmts(S(Call(X("Redecl"), 11))) + return m + }(), + "Machine:\n PreprocessorMode: false\n Op: [OpHalt]\n Values: (len: 0)\n Exprs:\n #0 100\n Stmts:\n #0 Redecl(11)\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + got := tt.in.String() + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Fatalf("Mismatch: got - want +\n%s", diff) + } + }) + } +} diff --git a/go.mod b/go.mod index 280ca3ae602..cd038e2ae65 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/fortytw2/leaktest v1.3.0 + github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.0 github.com/gorilla/websocket v1.5.3 github.com/libp2p/go-buffer-pool v0.1.0 diff --git a/misc/docs-linter/jsx.go b/misc/docs-linter/jsx.go index d0307680a0c..eb041a78386 100644 --- a/misc/docs-linter/jsx.go +++ b/misc/docs-linter/jsx.go @@ -50,7 +50,7 @@ func lintJSX(filepathToJSX map[string][]string) (string, error) { found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", tag, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", tag, filePath) } } diff --git a/misc/docs-linter/links.go b/misc/docs-linter/links.go index 744917d8dfb..e34d35d9f58 100644 --- a/misc/docs-linter/links.go +++ b/misc/docs-linter/links.go @@ -80,7 +80,7 @@ func lintLocalLinks(filepathToLinks map[string][]string, docsPath string) (strin found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", link, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", link, filePath) } } } diff --git a/misc/docs-linter/main.go b/misc/docs-linter/main.go index 97d80316108..5d7cdf37982 100644 --- a/misc/docs-linter/main.go +++ b/misc/docs-linter/main.go @@ -61,8 +61,8 @@ func execLint(cfg *cfg, ctx context.Context) (string, error) { } // Main buffer to write to the end user after linting - var output bytes.Buffer - output.WriteString(fmt.Sprintf("Linting %s...\n", absPath)) + var output bytes.Buffer + fmt.Fprintf(&output, "Linting %s...\n", absPath) // Find docs files to lint mdFiles, err := findFilePaths(cfg.docsPath) diff --git a/misc/docs-linter/urls.go b/misc/docs-linter/urls.go index 093e624d81e..098d0a05524 100644 --- a/misc/docs-linter/urls.go +++ b/misc/docs-linter/urls.go @@ -66,7 +66,7 @@ func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", url, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", url, filePath) lock.Unlock() } diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index 88ee26da4a9..9e10596a975 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -939,7 +939,7 @@ func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[st for _, name := range noArgNames { link := fmt.Sprintf("//%s/%s", r.Host, name) - buf.WriteString(fmt.Sprintf("%s
", link, link)) + fmt.Fprintf(buf, "%s
", link, link) } buf.WriteString("
Endpoints that require arguments:
") @@ -952,7 +952,7 @@ func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[st link += "&" } } - buf.WriteString(fmt.Sprintf("%s
", link, link)) + fmt.Fprintf(buf, "%s
", link, link) } buf.WriteString("") w.Header().Set("Content-Type", "text/html") diff --git a/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go b/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go new file mode 100644 index 00000000000..b01144f9273 --- /dev/null +++ b/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go @@ -0,0 +1,33 @@ +package rpcserver + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + + types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" +) + +func TestWriteListOfEndpoints(t *testing.T) { + funcMap := map[string]*RPCFunc{ + "c": NewWSRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), + "d": {}, + } + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + rec := httptest.NewRecorder() + writeListOfEndpoints(rec, req, funcMap) + res := rec.Result() + assert.Equal(t, res.StatusCode, 200, "Should always return 200") + blob, err := io.ReadAll(res.Body) + assert.NoError(t, err) + gotResp := string(blob) + wantResp := `
Available endpoints:
//localhost/d

Endpoints that require arguments:
//localhost/c?s=_&i=_
` + if diff := cmp.Diff(gotResp, wantResp); diff != "" { + t.Fatalf("Mismatch response: got - want +\n%s", diff) + } +} diff --git a/tm2/pkg/sdk/auth/params.go b/tm2/pkg/sdk/auth/params.go index 3fe08ed444d..fda85c7a3d6 100644 --- a/tm2/pkg/sdk/auth/params.go +++ b/tm2/pkg/sdk/auth/params.go @@ -69,15 +69,16 @@ func DefaultParams() Params { // String implements the stringer interface. func (p Params) String() string { - var sb strings.Builder + var builder strings.Builder + sb := &builder // Pointer for use with fmt.Fprintf sb.WriteString("Params: \n") - sb.WriteString(fmt.Sprintf("MaxMemoBytes: %d\n", p.MaxMemoBytes)) - sb.WriteString(fmt.Sprintf("TxSigLimit: %d\n", p.TxSigLimit)) - sb.WriteString(fmt.Sprintf("TxSizeCostPerByte: %d\n", p.TxSizeCostPerByte)) - sb.WriteString(fmt.Sprintf("SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519)) - sb.WriteString(fmt.Sprintf("SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1)) - sb.WriteString(fmt.Sprintf("GasPricesChangeCompressor: %d\n", p.GasPricesChangeCompressor)) - sb.WriteString(fmt.Sprintf("TargetGasRatio: %d\n", p.TargetGasRatio)) + fmt.Fprintf(sb, "MaxMemoBytes: %d\n", p.MaxMemoBytes) + fmt.Fprintf(sb, "TxSigLimit: %d\n", p.TxSigLimit) + fmt.Fprintf(sb, "TxSizeCostPerByte: %d\n", p.TxSizeCostPerByte) + fmt.Fprintf(sb, "SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519) + fmt.Fprintf(sb, "SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1) + fmt.Fprintf(sb, "GasPricesChangeCompressor: %d\n", p.GasPricesChangeCompressor) + fmt.Fprintf(sb, "TargetGasRatio: %d\n", p.TargetGasRatio) return sb.String() } diff --git a/tm2/pkg/sdk/auth/params_test.go b/tm2/pkg/sdk/auth/params_test.go index 4b5a6b15789..36a52ac9001 100644 --- a/tm2/pkg/sdk/auth/params_test.go +++ b/tm2/pkg/sdk/auth/params_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" ) @@ -105,3 +106,26 @@ func TestNewParams(t *testing.T) { t.Errorf("NewParams() = %+v, want %+v", params, expectedParams) } } + +func TestParamsString(t *testing.T) { + cases := []struct { + name string + params Params + want string + }{ + {"blank params", Params{}, "Params: \nMaxMemoBytes: 0\nTxSigLimit: 0\nTxSizeCostPerByte: 0\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\n"}, + {"some values", Params{ + MaxMemoBytes: 1_000_000, + TxSizeCostPerByte: 8192, + }, "Params: \nMaxMemoBytes: 1000000\nTxSigLimit: 0\nTxSizeCostPerByte: 8192\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\n"}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + got := tt.params.String() + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Fatalf("Mismatch: got - want +\n%s", diff) + } + }) + } +} From b92a6a43dc70ecea11f3ffad525d4a2e200247c3 Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Thu, 9 Jan 2025 21:18:55 +0100 Subject: [PATCH 2/9] feat(gnovm/pkg/packages): categorize imports (#3323) This makes the imports utils split imports by file kinds, allowing to make explicit decisions about what imports to use at the various callsites - Create `FileKind` enum to categorize gno files, with variants `PackageSource`, `Test`, `XTest` and `Filetest` - Create `GetFileKind` util to derive the `FileKind` from a file name and body - Create `ImportsMap` type that maps `FileKind`s to lists of imports. It has a single method `Merge` to select and merge various imports from multiple `FileKind`s - Modify the`packages.Imports` helper to return an `ImportsMap` instead of a `[]string` and adapt callsites by using`ImportMap.Merge` to preserve existing behavior This is something I need for #3304 and #2932 but to help reviews I made an atomic PR here instead --------- Signed-off-by: Norman Meier Co-authored-by: Morgan --- gno.land/pkg/integration/pkgloader.go | 3 +- gnovm/cmd/gno/download_deps.go | 3 +- gnovm/pkg/doc/dirs.go | 5 ++- gnovm/pkg/gnomod/pkg.go | 6 ++- gnovm/pkg/packages/filekind.go | 56 ++++++++++++++++++++++++ gnovm/pkg/packages/filekind_test.go | 63 +++++++++++++++++++++++++++ gnovm/pkg/packages/imports.go | 59 ++++++++++++++++++++----- gnovm/pkg/packages/imports_test.go | 59 +++++++++++++++++++------ gnovm/pkg/test/imports.go | 3 +- 9 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 gnovm/pkg/packages/filekind.go create mode 100644 gnovm/pkg/packages/filekind_test.go diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 7e7e817dd92..e40e8ff1eb5 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -131,10 +131,11 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { if err != nil { return fmt.Errorf("unable to read package at %q: %w", currentPkg.Dir, err) } - imports, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) for _, imp := range imports { if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { continue diff --git a/gnovm/cmd/gno/download_deps.go b/gnovm/cmd/gno/download_deps.go index 5a8c50be20b..4e638eb4970 100644 --- a/gnovm/cmd/gno/download_deps.go +++ b/gnovm/cmd/gno/download_deps.go @@ -25,10 +25,11 @@ func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pk if err != nil { return fmt.Errorf("read package at %q: %w", pkgDir, err) } - imports, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { return fmt.Errorf("read imports at %q: %w", pkgDir, err) } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, pkgPath := range imports { resolved := gnoMod.Resolve(module.Version{Path: pkgPath.PkgPath}) diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index b287fd20708..4c481324e9a 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -105,11 +105,12 @@ func packageImportsRecursive(root string, pkgPath string) []string { pkg = &gnovm.MemPackage{} } - resRaw, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { // ignore packages with invalid imports - resRaw = nil + importsMap = nil } + resRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) res := make([]string, len(resRaw)) for idx, imp := range resRaw { res[idx] = imp.PkgPath diff --git a/gnovm/pkg/gnomod/pkg.go b/gnovm/pkg/gnomod/pkg.go index a0831d494b0..85f1d31442d 100644 --- a/gnovm/pkg/gnomod/pkg.go +++ b/gnovm/pkg/gnomod/pkg.go @@ -121,11 +121,13 @@ func ListPkgs(root string) (PkgList, error) { pkg = &gnovm.MemPackage{} } - importsRaw, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { // ignore imports on error - importsRaw = nil + importsMap = nil } + importsRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) + imports := make([]string, 0, len(importsRaw)) for _, imp := range importsRaw { // remove self and standard libraries from imports diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go new file mode 100644 index 00000000000..ed2ca84b7d0 --- /dev/null +++ b/gnovm/pkg/packages/filekind.go @@ -0,0 +1,56 @@ +package packages + +import ( + "fmt" + "go/parser" + "go/token" + "strings" +) + +// FileKind represent the category a gno source file falls in, can be one of: +// +// - [FileKindPackageSource] -> A *.gno file that will be included in the gnovm package +// +// - [FileKindTest] -> A *_test.gno file that will be used for testing +// +// - [FileKindXTest] -> A *_test.gno file with a package name ending in _test that will be used for blackbox testing +// +// - [FileKindFiletest] -> A *_filetest.gno file that will be used for snapshot testing +type FileKind uint + +const ( + FileKindUnknown FileKind = iota + FileKindPackageSource + FileKindTest + FileKindXTest + FileKindFiletest +) + +// GetFileKind analyzes a file's name and body to get it's [FileKind], fset is optional +func GetFileKind(filename string, body string, fset *token.FileSet) (FileKind, error) { + if !strings.HasSuffix(filename, ".gno") { + return FileKindUnknown, fmt.Errorf("%s:1:1: not a gno file", filename) + } + + if strings.HasSuffix(filename, "_filetest.gno") { + return FileKindFiletest, nil + } + + if !strings.HasSuffix(filename, "_test.gno") { + return FileKindPackageSource, nil + } + + if fset == nil { + fset = token.NewFileSet() + } + ast, err := parser.ParseFile(fset, filename, body, parser.PackageClauseOnly) + if err != nil { + return FileKindUnknown, err + } + packageName := ast.Name.Name + + if strings.HasSuffix(packageName, "_test") { + return FileKindXTest, nil + } + return FileKindTest, nil +} diff --git a/gnovm/pkg/packages/filekind_test.go b/gnovm/pkg/packages/filekind_test.go new file mode 100644 index 00000000000..bd06b49fb45 --- /dev/null +++ b/gnovm/pkg/packages/filekind_test.go @@ -0,0 +1,63 @@ +package packages + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetFileKind(t *testing.T) { + tcs := []struct { + name string + filename string + body string + fileKind FileKind + errorContains string + }{ + { + name: "compiled", + filename: "foo.gno", + fileKind: FileKindPackageSource, + }, + { + name: "test", + filename: "foo_test.gno", + body: "package foo", + fileKind: FileKindTest, + }, + { + name: "xtest", + filename: "foo_test.gno", + body: "package foo_test", + fileKind: FileKindXTest, + }, + { + name: "filetest", + filename: "foo_filetest.gno", + fileKind: FileKindFiletest, + }, + { + name: "err_badpkgclause", + filename: "foo_test.gno", + body: "pakage foo", + errorContains: "foo_test.gno:1:1: expected 'package', found pakage", + }, + { + name: "err_notgnofile", + filename: "foo.gno.bck", + errorContains: `foo.gno.bck:1:1: not a gno file`, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + out, err := GetFileKind(tc.filename, tc.body, nil) + if len(tc.errorContains) != 0 { + require.ErrorContains(t, err, tc.errorContains) + } else { + require.NoError(t, err) + } + require.Equal(t, tc.fileKind, out) + }) + } +} diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index 201965bc588..3bc60be6664 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -14,33 +14,40 @@ import ( // Imports returns the list of gno imports from a [gnovm.MemPackage]. // fset is optional. -func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) ([]FileImport, error) { - allImports := make([]FileImport, 0, 16) - seen := make(map[string]struct{}, 16) +func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { + res := make(ImportsMap, 16) + seen := make(map[FileKind]map[string]struct{}, 16) + for _, file := range pkg.Files { if !strings.HasSuffix(file.Name, ".gno") { continue } - if strings.HasSuffix(file.Name, "_filetest.gno") { - continue + + fileKind, err := GetFileKind(file.Name, file.Body, fset) + if err != nil { + return nil, err } imports, err := FileImports(file.Name, file.Body, fset) if err != nil { return nil, err } for _, im := range imports { - if _, ok := seen[im.PkgPath]; ok { + if _, ok := seen[fileKind][im.PkgPath]; ok { continue } - allImports = append(allImports, im) - seen[im.PkgPath] = struct{}{} + res[fileKind] = append(res[fileKind], im) + if _, ok := seen[fileKind]; !ok { + seen[fileKind] = make(map[string]struct{}, 16) + } + seen[fileKind][im.PkgPath] = struct{}{} } } - sort.Slice(allImports, func(i, j int) bool { - return allImports[i].PkgPath < allImports[j].PkgPath - }) - return allImports, nil + for _, imports := range res { + sortImports(imports) + } + + return res, nil } // FileImport represents an import @@ -75,3 +82,31 @@ func FileImports(filename string, src string, fset *token.FileSet) ([]FileImport } return res, nil } + +type ImportsMap map[FileKind][]FileImport + +// Merge merges imports, it removes duplicates and sorts the result +func (imap ImportsMap) Merge(kinds ...FileKind) []FileImport { + res := make([]FileImport, 0, 16) + seen := make(map[string]struct{}, 16) + + for _, kind := range kinds { + for _, im := range imap[kind] { + if _, ok := seen[im.PkgPath]; ok { + continue + } + seen[im.PkgPath] = struct{}{} + + res = append(res, im) + } + } + + sortImports(res) + return res +} + +func sortImports(imports []FileImport) { + sort.Slice(imports, func(i, j int) bool { + return imports[i].PkgPath < imports[j].PkgPath + }) +} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 3750aa9108c..f9f58b967c8 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -58,13 +58,26 @@ func TestImports(t *testing.T) { ) `, }, + { + name: "file2_test.gno", + data: ` + package tmp_test + + import ( + "testing" + + "gno.land/p/demo/testpkg" + "gno.land/p/demo/xtestdep" + ) + `, + }, { name: "z_0_filetest.gno", data: ` package main import ( - "gno.land/p/demo/filetestpkg" + "gno.land/p/demo/filetestdep" ) `, }, @@ -95,17 +108,28 @@ func TestImports(t *testing.T) { }, } - // Expected list of imports + // Expected lists of imports // - ignore subdirs // - ignore duplicate - // - ignore *_filetest.gno // - should be sorted - expected := []string{ - "gno.land/p/demo/pkg1", - "gno.land/p/demo/pkg2", - "gno.land/p/demo/testpkg", - "std", - "testing", + expected := map[FileKind][]string{ + FileKindPackageSource: { + "gno.land/p/demo/pkg1", + "gno.land/p/demo/pkg2", + "std", + }, + FileKindTest: { + "gno.land/p/demo/testpkg", + "testing", + }, + FileKindXTest: { + "gno.land/p/demo/testpkg", + "gno.land/p/demo/xtestdep", + "testing", + }, + FileKindFiletest: { + "gno.land/p/demo/filetestdep", + }, } // Create subpkg dir @@ -120,12 +144,19 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - imports, err := Imports(pkg, nil) + + importsMap, err := Imports(pkg, nil) require.NoError(t, err) - importsStrings := make([]string, len(imports)) - for idx, imp := range imports { - importsStrings[idx] = imp.PkgPath + + // ignore specs + got := map[FileKind][]string{} + for key, vals := range importsMap { + stringVals := make([]string, len(vals)) + for i, val := range vals { + stringVals[i] = val.PkgPath + } + got[key] = stringVals } - require.Equal(t, expected, importsStrings) + require.Equal(t, expected, got) } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 8b24fdeaa77..a8dd709e501 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -242,10 +242,11 @@ func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { }() fset := token.NewFileSet() - imports, err := packages.Imports(memPkg, fset) + importsMap, err := packages.Imports(memPkg, fset) if err != nil { return err } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, imp := range imports { if gno.IsRealmPath(imp.PkgPath) { // Don't eagerly load realms. From 384d2beae48b93db9da9022b530c488837820a2c Mon Sep 17 00:00:00 2001 From: matijamarjanovic <93043005+matijamarjanovic@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:39:30 +0100 Subject: [PATCH 3/9] feat: add btree_dao to demo (#3388) ## Description BTree DAO is a little organisation of people who used BTree by @wyhaines. It serves the purpose of demonstrating some of the functionalities like iterating through the list from start and the end, adding nodes and general implementation of the BTree. Besides that, it encourages developers to use BTree and join the DAO. ### How it works: Currently the realm has 2 ways of members joining: - Either by submiting the BTree instance used in some other realm - Or by sending a direct transaction using gnokey or Studio Connect having string as argument Both ways allow members to become a part of the DAO but at different levels: the idea is that if a user decides to submit his own BTree he becomes a true member, and if a user supports the cause and just want to hang around until he gets the hang of the BTree implementation he can do so by joining thorugh a 'seed'. In the realm joining functions are PlantTree and PlantSeed for different roles. When a member joins he gets minted an NFT made using basic_nft.gno from GRC721 which is like his little proof of membership. ### Concerns To become a 'tree' member developer needs to submit his whole BTree which might be a little unsafe as that BTree might contain some sensitive information. I would like to hear an opinion on this of someone more experienced, as it is right now I have been extra careful not to expose any of the information (besides size) from the submitted BTrees. ### Contributors checklist: - [x] Create a BTree to store all the member info - [x] Implement Record interface, make nodes' creation times be compared - [x] Implement DAO joining process for 2 types of members - [x] Make a Render() function to display member addresses - [x] Demonstrate more of BTree functionalities - [x] Add tests --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../gno.land/r/demo/btree_dao/btree_dao.gno | 209 ++++++++++++++++++ .../r/demo/btree_dao/btree_dao_test.gno | 97 ++++++++ examples/gno.land/r/demo/btree_dao/gno.mod | 1 + 3 files changed, 307 insertions(+) create mode 100644 examples/gno.land/r/demo/btree_dao/btree_dao.gno create mode 100644 examples/gno.land/r/demo/btree_dao/btree_dao_test.gno create mode 100644 examples/gno.land/r/demo/btree_dao/gno.mod diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao.gno b/examples/gno.land/r/demo/btree_dao/btree_dao.gno new file mode 100644 index 00000000000..c90742eb29b --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/btree_dao.gno @@ -0,0 +1,209 @@ +package btree_dao + +import ( + "errors" + "std" + "strings" + "time" + + "gno.land/p/demo/btree" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" +) + +// RegistrationDetails holds the details of a user's registration in the BTree DAO. +// It stores the user's address, registration time, their B-Tree if they planted one, +// and their NFT ID. +type RegistrationDetails struct { + Address std.Address + RegTime time.Time + UserBTree *btree.BTree + NFTID string +} + +// Less implements the btree.Record interface for RegistrationDetails. +// It compares two RegistrationDetails based on their registration time. +// Returns true if the current registration time is before the other registration time. +func (rd *RegistrationDetails) Less(than btree.Record) bool { + other := than.(*RegistrationDetails) + return rd.RegTime.Before(other.RegTime) +} + +var ( + dao = grc721.NewBasicNFT("BTree DAO", "BTDAO") + tokenID = 0 + members = btree.New() +) + +// PlantTree allows a user to plant their B-Tree in the DAO forest. +// It mints an NFT to the user and registers their tree in the DAO. +// Returns an error if the tree is already planted, empty, or if NFT minting fails. +func PlantTree(userBTree *btree.BTree) error { + return plantImpl(userBTree, "") +} + +// PlantSeed allows a user to register as a seed in the DAO with a message. +// It mints an NFT to the user and registers them as a seed member. +// Returns an error if the message is empty or if NFT minting fails. +func PlantSeed(message string) error { + return plantImpl(nil, message) +} + +// plantImpl is the internal implementation that handles both tree planting and seed registration. +// For tree planting (userBTree != nil), it verifies the tree isn't already planted and isn't empty. +// For seed planting (userBTree == nil), it verifies the seed message isn't empty. +// In both cases, it mints an NFT to the user and adds their registration details to the members tree. +// Returns an error if any validation fails or if NFT minting fails. +func plantImpl(userBTree *btree.BTree, seedMessage string) error { + // Get the caller's address + userAddress := std.GetOrigCaller() + + var nftID string + var regDetails *RegistrationDetails + + if userBTree != nil { + // Handle tree planting + var treeExists bool + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == userBTree { + treeExists = true + return false + } + return true + }) + if treeExists { + return errors.New("tree is already planted in the forest") + } + + if userBTree.Len() == 0 { + return errors.New("cannot plant an empty tree") + } + + nftID = ufmt.Sprintf("%d", tokenID) + regDetails = &RegistrationDetails{ + Address: userAddress, + RegTime: time.Now(), + UserBTree: userBTree, + NFTID: nftID, + } + } else { + // Handle seed planting + if seedMessage == "" { + return errors.New("seed message cannot be empty") + } + nftID = "seed_" + ufmt.Sprintf("%d", tokenID) + regDetails = &RegistrationDetails{ + Address: userAddress, + RegTime: time.Now(), + UserBTree: nil, + NFTID: nftID, + } + } + + // Mint an NFT to the user + err := dao.Mint(userAddress, grc721.TokenID(nftID)) + if err != nil { + return err + } + + members.Insert(regDetails) + tokenID++ + return nil +} + +// Render generates a Markdown representation of the DAO members. +// It displays: +// - Total number of NFTs minted +// - Total number of members +// - Size of the biggest planted tree +// - The first 3 members (OGs) +// - The latest 10 members +// Each member entry includes their address and owned NFTs (🌳 for trees, 🌱 for seeds). +// The path parameter is currently unused. +// Returns a formatted Markdown string. +func Render(path string) string { + var latestMembers []string + var ogMembers []string + + // Get total size and first member + totalSize := members.Len() + biggestTree := 0 + if maxMember := members.Max(); maxMember != nil { + if userBTree := maxMember.(*RegistrationDetails).UserBTree; userBTree != nil { + biggestTree = userBTree.Len() + } + } + + // Collect the latest 10 members + members.Descend(func(record btree.Record) bool { + if len(latestMembers) < 10 { + regDetails := record.(*RegistrationDetails) + addr := regDetails.Address + nftList := "" + balance, err := dao.BalanceOf(addr) + if err == nil && balance > 0 { + nftList = " (NFTs: " + for i := uint64(0); i < balance; i++ { + if i > 0 { + nftList += ", " + } + if regDetails.UserBTree == nil { + nftList += "🌱#" + regDetails.NFTID + } else { + nftList += "🌳#" + regDetails.NFTID + } + } + nftList += ")" + } + latestMembers = append(latestMembers, string(addr)+nftList) + return true + } + return false + }) + + // Collect the first 3 members (OGs) + members.Ascend(func(record btree.Record) bool { + if len(ogMembers) < 3 { + regDetails := record.(*RegistrationDetails) + addr := regDetails.Address + nftList := "" + balance, err := dao.BalanceOf(addr) + if err == nil && balance > 0 { + nftList = " (NFTs: " + for i := uint64(0); i < balance; i++ { + if i > 0 { + nftList += ", " + } + if regDetails.UserBTree == nil { + nftList += "🌱#" + regDetails.NFTID + } else { + nftList += "🌳#" + regDetails.NFTID + } + } + nftList += ")" + } + ogMembers = append(ogMembers, string(addr)+nftList) + return true + } + return false + }) + + var sb strings.Builder + + sb.WriteString(md.H1("B-Tree DAO Members")) + sb.WriteString(md.H2("Total NFTs Minted")) + sb.WriteString(ufmt.Sprintf("Total NFTs minted: %d\n\n", dao.TokenCount())) + sb.WriteString(md.H2("Member Stats")) + sb.WriteString(ufmt.Sprintf("Total members: %d\n", totalSize)) + if biggestTree > 0 { + sb.WriteString(ufmt.Sprintf("Biggest tree size: %d\n", biggestTree)) + } + sb.WriteString(md.H2("OG Members")) + sb.WriteString(md.BulletList(ogMembers)) + sb.WriteString(md.H2("Latest Members")) + sb.WriteString(md.BulletList(latestMembers)) + + return sb.String() +} diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno new file mode 100644 index 00000000000..0514f52f7b4 --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno @@ -0,0 +1,97 @@ +package btree_dao + +import ( + "std" + "strings" + "testing" + "time" + + "gno.land/p/demo/btree" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func setupTest() { + std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + members = btree.New() +} + +type TestElement struct { + value int +} + +func (te *TestElement) Less(than btree.Record) bool { + return te.value < than.(*TestElement).value +} + +func TestPlantTree(t *testing.T) { + setupTest() + + tree := btree.New() + elements := []int{30, 10, 50, 20, 40} + for _, val := range elements { + tree.Insert(&TestElement{value: val}) + } + + err := PlantTree(tree) + urequire.NoError(t, err) + + found := false + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == tree { + found = true + return false + } + return true + }) + uassert.True(t, found) + + err = PlantTree(tree) + uassert.Error(t, err) + + emptyTree := btree.New() + err = PlantTree(emptyTree) + uassert.Error(t, err) +} + +func TestPlantSeed(t *testing.T) { + setupTest() + + err := PlantSeed("Hello DAO!") + urequire.NoError(t, err) + + found := false + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == nil { + found = true + uassert.NotEmpty(t, regDetails.NFTID) + uassert.True(t, strings.Contains(regDetails.NFTID, "seed_")) + return false + } + return true + }) + uassert.True(t, found) + + err = PlantSeed("") + uassert.Error(t, err) +} + +func TestRegistrationDetailsOrdering(t *testing.T) { + setupTest() + + rd1 := &RegistrationDetails{ + Address: std.Address("test1"), + RegTime: time.Now(), + NFTID: "0", + } + rd2 := &RegistrationDetails{ + Address: std.Address("test2"), + RegTime: time.Now().Add(time.Hour), + NFTID: "1", + } + + uassert.True(t, rd1.Less(rd2)) + uassert.False(t, rd2.Less(rd1)) +} diff --git a/examples/gno.land/r/demo/btree_dao/gno.mod b/examples/gno.land/r/demo/btree_dao/gno.mod new file mode 100644 index 00000000000..01b99acc300 --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/btree_dao From 24385052d54be962581d989cc9349a7b9250d4c7 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:44:25 +0000 Subject: [PATCH 4/9] feat(examples): add p/moul/addrset (#3448) See https://github.com/gnolang/gno/pull/3166#discussion_r1904564428 for context. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/moul/addrset/addrset.gno | 100 ++++++++++ .../gno.land/p/moul/addrset/addrset_test.gno | 174 ++++++++++++++++++ examples/gno.land/p/moul/addrset/gno.mod | 1 + 3 files changed, 275 insertions(+) create mode 100644 examples/gno.land/p/moul/addrset/addrset.gno create mode 100644 examples/gno.land/p/moul/addrset/addrset_test.gno create mode 100644 examples/gno.land/p/moul/addrset/gno.mod diff --git a/examples/gno.land/p/moul/addrset/addrset.gno b/examples/gno.land/p/moul/addrset/addrset.gno new file mode 100644 index 00000000000..0bb8165f9fe --- /dev/null +++ b/examples/gno.land/p/moul/addrset/addrset.gno @@ -0,0 +1,100 @@ +// Package addrset provides a specialized set data structure for managing unique Gno addresses. +// +// It is built on top of an AVL tree for efficient operations and maintains addresses in sorted order. +// This package is particularly useful when you need to: +// - Track a collection of unique addresses (e.g., for whitelists, participants, etc.) +// - Efficiently check address membership +// - Support pagination when displaying addresses +// +// Example usage: +// +// import ( +// "std" +// "gno.land/p/moul/addrset" +// ) +// +// func MyHandler() { +// // Create a new address set +// var set addrset.Set +// +// // Add some addresses +// addr1 := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") +// addr2 := std.Address("g1sss5g0rkqr88k4u648yd5d3l9t4d8vvqwszqth") +// +// set.Add(addr1) // returns true (newly added) +// set.Add(addr2) // returns true (newly added) +// set.Add(addr1) // returns false (already exists) +// +// // Check membership +// if set.Has(addr1) { +// // addr1 is in the set +// } +// +// // Get size +// size := set.Size() // returns 2 +// +// // Iterate with pagination (10 items per page, starting at offset 0) +// set.IterateByOffset(0, 10, func(addr std.Address) bool { +// // Process addr +// return false // continue iteration +// }) +// +// // Remove an address +// set.Remove(addr1) // returns true (was present) +// set.Remove(addr1) // returns false (not present) +// } +package addrset + +import ( + "std" + + "gno.land/p/demo/avl" +) + +type Set struct { + tree avl.Tree +} + +// Add inserts an address into the set. +// Returns true if the address was newly added, false if it already existed. +func (s *Set) Add(addr std.Address) bool { + return !s.tree.Set(string(addr), nil) +} + +// Remove deletes an address from the set. +// Returns true if the address was found and removed, false if it didn't exist. +func (s *Set) Remove(addr std.Address) bool { + _, removed := s.tree.Remove(string(addr)) + return removed +} + +// Has checks if an address exists in the set. +func (s *Set) Has(addr std.Address) bool { + return s.tree.Has(string(addr)) +} + +// Size returns the number of addresses in the set. +func (s *Set) Size() int { + return s.tree.Size() +} + +// IterateByOffset walks through addresses starting at the given offset. +// The callback should return true to stop iteration. +func (s *Set) IterateByOffset(offset int, count int, cb func(addr std.Address) bool) { + s.tree.IterateByOffset(offset, count, func(key string, _ interface{}) bool { + return cb(std.Address(key)) + }) +} + +// ReverseIterateByOffset walks through addresses in reverse order starting at the given offset. +// The callback should return true to stop iteration. +func (s *Set) ReverseIterateByOffset(offset int, count int, cb func(addr std.Address) bool) { + s.tree.ReverseIterateByOffset(offset, count, func(key string, _ interface{}) bool { + return cb(std.Address(key)) + }) +} + +// Tree returns the underlying AVL tree for advanced usage. +func (s *Set) Tree() avl.ITree { + return &s.tree +} diff --git a/examples/gno.land/p/moul/addrset/addrset_test.gno b/examples/gno.land/p/moul/addrset/addrset_test.gno new file mode 100644 index 00000000000..c3e27eab1df --- /dev/null +++ b/examples/gno.land/p/moul/addrset/addrset_test.gno @@ -0,0 +1,174 @@ +package addrset + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +func TestSet(t *testing.T) { + addr1 := std.Address("addr1") + addr2 := std.Address("addr2") + addr3 := std.Address("addr3") + + tests := []struct { + name string + actions func(s *Set) + size int + has map[std.Address]bool + addrs []std.Address // for iteration checks + }{ + { + name: "empty set", + actions: func(s *Set) {}, + size: 0, + has: map[std.Address]bool{addr1: false}, + }, + { + name: "single address", + actions: func(s *Set) { + s.Add(addr1) + }, + size: 1, + has: map[std.Address]bool{ + addr1: true, + addr2: false, + }, + addrs: []std.Address{addr1}, + }, + { + name: "multiple addresses", + actions: func(s *Set) { + s.Add(addr1) + s.Add(addr2) + s.Add(addr3) + }, + size: 3, + has: map[std.Address]bool{ + addr1: true, + addr2: true, + addr3: true, + }, + addrs: []std.Address{addr1, addr2, addr3}, + }, + { + name: "remove address", + actions: func(s *Set) { + s.Add(addr1) + s.Add(addr2) + s.Remove(addr1) + }, + size: 1, + has: map[std.Address]bool{ + addr1: false, + addr2: true, + }, + addrs: []std.Address{addr2}, + }, + { + name: "duplicate adds", + actions: func(s *Set) { + uassert.True(t, s.Add(addr1)) // first add returns true + uassert.False(t, s.Add(addr1)) // second add returns false + uassert.True(t, s.Remove(addr1)) // remove existing returns true + uassert.False(t, s.Remove(addr1)) // remove non-existing returns false + }, + size: 0, + has: map[std.Address]bool{ + addr1: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var set Set + + // Execute test actions + tt.actions(&set) + + // Check size + uassert.Equal(t, tt.size, set.Size()) + + // Check existence + for addr, expected := range tt.has { + uassert.Equal(t, expected, set.Has(addr)) + } + + // Check iteration if addresses are specified + if tt.addrs != nil { + collected := []std.Address{} + set.IterateByOffset(0, 10, func(addr std.Address) bool { + collected = append(collected, addr) + return false + }) + + // Check length + uassert.Equal(t, len(tt.addrs), len(collected)) + + // Check each address + for i, addr := range tt.addrs { + uassert.Equal(t, addr, collected[i]) + } + } + }) + } +} + +func TestSetIterationLimits(t *testing.T) { + tests := []struct { + name string + addrs []std.Address + offset int + limit int + expected int + }{ + { + name: "zero offset full list", + addrs: []std.Address{"a1", "a2", "a3"}, + offset: 0, + limit: 10, + expected: 3, + }, + { + name: "offset with limit", + addrs: []std.Address{"a1", "a2", "a3", "a4"}, + offset: 1, + limit: 2, + expected: 2, + }, + { + name: "offset beyond size", + addrs: []std.Address{"a1", "a2"}, + offset: 3, + limit: 1, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var set Set + for _, addr := range tt.addrs { + set.Add(addr) + } + + // Test forward iteration + count := 0 + set.IterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool { + count++ + return false + }) + uassert.Equal(t, tt.expected, count) + + // Test reverse iteration + count = 0 + set.ReverseIterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool { + count++ + return false + }) + uassert.Equal(t, tt.expected, count) + }) + } +} diff --git a/examples/gno.land/p/moul/addrset/gno.mod b/examples/gno.land/p/moul/addrset/gno.mod new file mode 100644 index 00000000000..45bb53b399c --- /dev/null +++ b/examples/gno.land/p/moul/addrset/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/addrset From 15e58960326612bb7943197e5307fd109b6742f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:13:30 +0100 Subject: [PATCH 5/9] fix: add support for signing lazily loaded transactions in genesis (#3468) ## Description This PR fixes an issue where genesis transactions added to `genesis.json` through `--lazy` fail, since the signatures are missing. It also introduces support for disabling genesis sig verification altogether. Why this was needed: - Portal Loop transactions are signed with a valid account number and sequence (not 0), and when they are replayed (they are shoved into a new aggregated `genesis.json`), their signatures are also migrated. Upon initializing the chain, this would cause the signature verification to fail (the sig verification process for genesis txs expects account number and sequence values of 0, but this is not the case) @moul, the transaction signatures in `gno.land/genesis/genesis_txs.jsonl` are invalid, and will always fail when being verified --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- .github/workflows/portal-loop.yml | 1 + .../internal/txs/txs_add_packages.go | 1 - gno.land/cmd/gnoland/start.go | 104 +++++++++++++----- gno.land/pkg/gnoland/app.go | 22 +++- gno.land/pkg/gnoland/app_test.go | 2 +- gno.land/pkg/gnoland/genesis.go | 9 +- gno.land/pkg/gnoland/types.go | 29 +++++ gno.land/pkg/gnoland/types_test.go | 27 +++++ gno.land/pkg/integration/node_testing.go | 2 +- gno.land/pkg/integration/pkgloader.go | 3 +- gno.land/pkg/integration/signer.go | 33 ------ .../testdata/event_multi_msg.txtar | 23 ++-- .../pkg/integration/testdata/gnokey.txtar | 10 +- .../testdata/gnoweb_airgapped.txtar | 21 ++-- .../testdata/restart_missing_type.txtar | 16 ++- .../integration/testdata/simulate_gas.txtar | 4 +- misc/loop/scripts/start.sh | 3 +- tm2/pkg/sdk/auth/ante.go | 6 +- tm2/pkg/sdk/auth/ante_test.go | 4 +- 19 files changed, 207 insertions(+), 113 deletions(-) delete mode 100644 gno.land/pkg/integration/signer.go diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index b5cafa459a7..aeb59d4dc77 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -45,6 +45,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} test-portal-loop-docker-compose: + if: ${{ false }} runs-on: ubuntu-latest timeout-minutes: 10 steps: diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index 0ab5724154e..53c0bb4b686 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -18,7 +18,6 @@ import ( const ( defaultAccount_Name = "test1" - defaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" defaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" defaultAccount_publicKey = "gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pq0skzdkmzu0r9h6gny6eg8c9dc303xrrudee6z4he4y7cs5rnjwmyf40yaj" ) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index cb5d54a513a..4f380031be4 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -45,22 +45,20 @@ var startGraphic = strings.ReplaceAll(` /___/ `, "'", "`") -var ( - // Keep in sync with contribs/gnogenesis/internal/txs/txs_add_packages.go - genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 - genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) -) +// Keep in sync with contribs/gnogenesis/internal/txs/txs_add_packages.go +var genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) type startCfg struct { - gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisFile string - chainID string - dataDir string - lazyInit bool + gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipGenesisSigVerification bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisFile string + chainID string + dataDir string + lazyInit bool logLevel string logFormat string @@ -86,7 +84,6 @@ func newStartCmd(io commands.IO) *commands.Command { func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { gnoroot := gnoenv.RootDir() defaultGenesisBalancesFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt") - defaultGenesisTxsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.jsonl") fs.BoolVar( &c.skipFailingGenesisTxs, @@ -95,6 +92,13 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "don't panic when replaying invalid genesis txs", ) + fs.BoolVar( + &c.skipGenesisSigVerification, + "skip-genesis-sig-verification", + false, + "don't panic when replaying invalidly signed genesis txs", + ) + fs.StringVar( &c.genesisBalancesFile, "genesis-balances-file", @@ -105,7 +109,7 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.genesisTxsFile, "genesis-txs-file", - defaultGenesisTxsFile, + "", "initial txs to replay", ) @@ -218,7 +222,7 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { ) // Init a new genesis.json - if err := lazyInitGenesis(io, c, genesisPath, privateKey.GetPubKey()); err != nil { + if err := lazyInitGenesis(io, c, genesisPath, privateKey.Key.PrivKey); err != nil { return fmt.Errorf("unable to initialize genesis.json, %w", err) } } @@ -238,7 +242,16 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { minGasPrices := cfg.Application.MinGasPrices // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger, minGasPrices) + cfg.LocalApp, err = gnoland.NewApp( + nodeDir, + gnoland.GenesisAppConfig{ + SkipFailingTxs: c.skipFailingGenesisTxs, + SkipSigVerification: c.skipGenesisSigVerification, + }, + evsw, + logger, + minGasPrices, + ) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } @@ -334,7 +347,7 @@ func lazyInitGenesis( io commands.IO, c *startCfg, genesisPath string, - publicKey crypto.PubKey, + privateKey crypto.PrivKey, ) error { // Check if the genesis.json is present if osm.FileExists(genesisPath) { @@ -342,7 +355,7 @@ func lazyInitGenesis( } // Generate the new genesis.json file - if err := generateGenesisFile(genesisPath, publicKey, c); err != nil { + if err := generateGenesisFile(genesisPath, privateKey, c); err != nil { return fmt.Errorf("unable to generate genesis file, %w", err) } @@ -367,7 +380,21 @@ func initializeLogger(io io.WriteCloser, logLevel, logFormat string) (*zap.Logge return log.GetZapLoggerFn(format)(io, level), nil } -func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { +func generateGenesisFile(genesisFile string, privKey crypto.PrivKey, c *startCfg) error { + var ( + pubKey = privKey.PubKey() + // There is an active constraint for gno.land transactions: + // + // All transaction messages' (MsgSend, MsgAddPkg...) "author" field, + // specific to the message type ("creator", "sender"...), must match + // the signature address contained in the transaction itself. + // This means that if MsgSend is originating from address A, + // the owner of the private key for address A needs to sign the transaction + // containing the message. Every message in a transaction needs to + // originate from the same account that signed the transaction + txSender = pubKey.Address() + ) + gen := &bft.GenesisDoc{} gen.GenesisTime = time.Now() gen.ChainID = c.chainID @@ -383,8 +410,8 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro gen.Validators = []bft.GenesisValidator{ { - Address: pk.Address(), - PubKey: pk, + Address: pubKey.Address(), + PubKey: pubKey, Power: 10, Name: "testvalidator", }, @@ -398,22 +425,43 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, genesisDeployAddress, genesisDeployFee) + pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, txSender, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) } // Load Genesis TXs - genesisTxs, err := gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote) - if err != nil { - return fmt.Errorf("unable to load genesis txs file: %w", err) + var genesisTxs []gnoland.TxWithMetadata + + if c.genesisTxsFile != "" { + genesisTxs, err = gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote) + if err != nil { + return fmt.Errorf("unable to load genesis txs file: %w", err) + } } genesisTxs = append(pkgsTxs, genesisTxs...) + // Sign genesis transactions, with the default key (test1) + if err = gnoland.SignGenesisTxs(genesisTxs, privKey, c.chainID); err != nil { + return fmt.Errorf("unable to sign genesis txs: %w", err) + } + + // Make sure the genesis transaction author has sufficient + // balance to cover transaction deployments in genesis. + // + // During the init-chainer process, the account that authors the + // genesis transactions needs to have a sufficient balance + // to cover outstanding transaction costs. + // Since the cost can't be estimated upfront at this point, the balance + // set is an arbitrary value based on a "best guess" basis. + // There should be a larger discussion if genesis transactions should consume gas, at all + deployerBalance := int64(len(genesisTxs)) * 10_000_000 // ~10 GNOT per tx + balances.Set(txSender, std.NewCoins(std.NewCoin("ugnot", deployerBalance))) + // Construct genesis AppState. defaultGenState := gnoland.DefaultGenState() - defaultGenState.Balances = balances + defaultGenState.Balances = balances.List() defaultGenState.Txs = genesisTxs gen.AppState = defaultGenState diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 80c58e9e982..0826071b9f5 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -182,10 +182,25 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { return baseApp, nil } +// GenesisAppConfig wraps the most important +// genesis params relating to the App +type GenesisAppConfig struct { + SkipFailingTxs bool // does not stop the chain from starting if any tx fails + SkipSigVerification bool // does not verify the transaction signatures in genesis +} + +// NewTestGenesisAppConfig returns a testing genesis app config +func NewTestGenesisAppConfig() GenesisAppConfig { + return GenesisAppConfig{ + SkipFailingTxs: true, + SkipSigVerification: true, + } +} + // NewApp creates the gno.land application. func NewApp( dataRootDir string, - skipFailingGenesisTxs bool, + genesisCfg GenesisAppConfig, evsw events.EventSwitch, logger *slog.Logger, minGasPrices string, @@ -199,9 +214,10 @@ func NewApp( GenesisTxResultHandler: PanicOnFailingTxResultHandler, StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), }, - MinGasPrices: minGasPrices, + MinGasPrices: minGasPrices, + SkipGenesisVerification: genesisCfg.SkipSigVerification, } - if skipFailingGenesisTxs { + if genesisCfg.SkipFailingTxs { cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler } diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 56a15fed5a9..361d7505157 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -134,7 +134,7 @@ func TestNewApp(t *testing.T) { // NewApp should have good defaults and manage to run InitChain. td := t.TempDir() - app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger(), "") + app, err := NewApp(td, NewTestGenesisAppConfig(), events.NewEventSwitch(), log.NewNoopLogger(), "") require.NoError(t, err, "NewApp should be successful") resp := app.InitChain(abci.RequestInitChain{ diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d7844d77b57..a754e7a4644 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -20,7 +20,7 @@ import ( const initGasPrice = "1ugnot/1000gas" // LoadGenesisBalancesFile loads genesis balances from the provided file path. -func LoadGenesisBalancesFile(path string) ([]Balance, error) { +func LoadGenesisBalancesFile(path string) (Balances, error) { // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot content, err := osm.ReadFile(path) if err != nil { @@ -28,7 +28,7 @@ func LoadGenesisBalancesFile(path string) ([]Balance, error) { } lines := strings.Split(string(content), "\n") - balances := make([]Balance, 0, len(lines)) + balances := make(Balances, len(lines)) for _, line := range lines { line = strings.TrimSpace(line) @@ -56,10 +56,7 @@ func LoadGenesisBalancesFile(path string) ([]Balance, error) { return nil, fmt.Errorf("invalid balance coins %s: %w", parts[1], err) } - balances = append(balances, Balance{ - Address: addr, - Amount: coins, - }) + balances.Set(addr, coins) } return balances, nil diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index ed35c4141f4..66fb2f54e8a 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -8,6 +8,7 @@ import ( "os" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk/auth" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -86,3 +87,31 @@ func ReadGenesisTxs(ctx context.Context, path string) ([]TxWithMetadata, error) return txs, nil } + +// SignGenesisTxs will sign all txs passed as argument using the private key. +// This signature is only valid for genesis transactions as the account number and sequence are 0 +func SignGenesisTxs(txs []TxWithMetadata, privKey crypto.PrivKey, chainID string) error { + for index, tx := range txs { + // Upon verifying genesis transactions, the account number and sequence are considered to be 0. + // The reason for this is that it is not possible to know the account number (or sequence!) in advance + // when generating the genesis transaction signature + bytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to get sign bytes for transaction, %w", err) + } + + signature, err := privKey.Sign(bytes) + if err != nil { + return fmt.Errorf("unable to sign genesis transaction, %w", err) + } + + txs[index].Tx.Signatures = []std.Signature{ + { + PubKey: privKey.PubKey(), + Signature: signature, + }, + } + } + + return nil +} diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go index b4625d6d7d6..c501325bc3e 100644 --- a/gno.land/pkg/gnoland/types_test.go +++ b/gno.land/pkg/gnoland/types_test.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" @@ -129,3 +130,29 @@ func TestReadGenesisTxs(t *testing.T) { } }) } + +func TestSignGenesisTx(t *testing.T) { + t.Parallel() + + var ( + txs = generateTxs(t, 100) + privKey = secp256k1.GenPrivKey() + pubKey = privKey.PubKey() + chainID = "testing" + ) + + // Make sure the transactions are properly signed + require.NoError(t, SignGenesisTxs(txs, privKey, chainID)) + + // Make sure the signatures are valid + for _, tx := range txs { + payload, err := tx.Tx.GetSignBytes(chainID, 0, 0) + require.NoError(t, err) + + sigs := tx.Tx.GetSignatures() + require.Len(t, sigs, 1) + + assert.True(t, pubKey.Equals(sigs[0].PubKey)) + assert.True(t, pubKey.VerifyBytes(payload, sigs[0].Signature)) + } +} diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index 7965f228fc2..edcf53de5d3 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -148,7 +148,7 @@ func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balanc genesisBalances, err := gnoland.LoadGenesisBalancesFile(balanceFile) require.NoError(t, err) - return genesisBalances + return genesisBalances.List() } // LoadDefaultGenesisParamFile loads the default genesis balance file for testing. diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index e40e8ff1eb5..71b1491b2a8 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -77,8 +77,7 @@ func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, depos } } - err = SignTxs(txs, creatorKey, "tendermint_test") - if err != nil { + if err = gnoland.SignGenesisTxs(txs, creatorKey, "tendermint_test"); err != nil { return nil, fmt.Errorf("unable to sign txs: %w", err) } diff --git a/gno.land/pkg/integration/signer.go b/gno.land/pkg/integration/signer.go deleted file mode 100644 index b32cd9c59bc..00000000000 --- a/gno.land/pkg/integration/signer.go +++ /dev/null @@ -1,33 +0,0 @@ -package integration - -import ( - "fmt" - - "github.com/gnolang/gno/gno.land/pkg/gnoland" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" -) - -// SignTxs will sign all txs passed as argument using the private key -// this signature is only valid for genesis transactions as accountNumber and sequence are 0 -func SignTxs(txs []gnoland.TxWithMetadata, privKey crypto.PrivKey, chainID string) error { - for index, tx := range txs { - bytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) - if err != nil { - return fmt.Errorf("unable to get sign bytes for transaction, %w", err) - } - signature, err := privKey.Sign(bytes) - if err != nil { - return fmt.Errorf("unable to sign transaction, %w", err) - } - - txs[index].Tx.Signatures = []std.Signature{ - { - PubKey: privKey.PubKey(), - Signature: signature, - }, - } - } - return nil -} diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 13a448e7f8c..4c8de856f03 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -1,29 +1,30 @@ # load the package from $WORK directory loadpkg gno.land/r/demo/simple_event $WORK/event +# add a random user +adduserfrom user1 'success myself purchase tray reject demise scene little legend someone lunar hope media goat regular test area smart save flee surround attack rapid smoke' +stdout 'g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0' + # start a new node gnoland start -## test1 account should be available on default -gnokey query auth/accounts/${USER_ADDR_test1} +## account should be available since it has an initial balance +gnokey query auth/accounts/g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0 stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": {' -stdout ' "@type": "/tm.PubKeySecp256k1",' -stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' -stdout ' },' -stdout ' "account_number": "0",' -stdout ' "sequence": "1"' +stdout ' "public_key": null,' +stdout ' "account_number": "57",' +stdout ' "sequence": "0"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 0 -account-sequence 1 test1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 57 -account-sequence 0 user1 stdout 'Tx successfully signed and saved to ' ## broadcast @@ -49,5 +50,5 @@ func Event(value string) { } -- multi/multi_msg.tx -- -{"msg":[{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index 123a0ce291c..35759fa25dd 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -1,18 +1,22 @@ # test basic gnokey integrations commands # golden files have been generated using UPDATE_SCRIPTS=true +# add a random user +adduserfrom user1 'alpha ability feed thrive color fee grace message chief helmet laundry inmate index brave luxury toddler spawn vague index able zone shoe collect escape' +stdout 'g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m' + # start gnoland gnoland start ## test1 account should be available on default -gnokey query auth/accounts/${USER_ADDR_test1} +gnokey query auth/accounts/g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "0",' +stdout ' "account_number": "57",' stdout ' "sequence": "0"' stdout ' }' stdout '}' diff --git a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar index 02bd8058214..838db121442 100644 --- a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar +++ b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar @@ -4,32 +4,33 @@ # load the package from $WORK directory loadpkg gno.land/r/demo/echo +# add a random user +adduserfrom user1 'lamp any denial pulse used shoot gap error denial mansion hurry foot solution grab winner congress drastic cat bamboo chicken color digital coffee unknown' +stdout 'g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva' + # start the node gnoland start # Query account -gnokey query auth/accounts/${USER_ADDR_test1} +gnokey query auth/accounts/g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": {' -stdout ' "@type": "/tm.PubKeySecp256k1",' -stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' -stdout ' },' -stdout ' "account_number": "0",' -stdout ' "sequence": "4"' +stdout ' "public_key": null,' +stdout ' "account_number": "57",' +stdout ' "sequence": "0"' stdout ' }' stdout '}' ! stderr '.+' # empty # Create transaction -gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -args "HELLO" test1 +gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -args "HELLO" user1 cp stdout call.tx # Sign -gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 0 -account-sequence 4 test1 +gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 57 -account-sequence 0 user1 cmpenv stdout sign.stdout.golden gnokey broadcast $WORK/call.tx diff --git a/gno.land/pkg/integration/testdata/restart_missing_type.txtar b/gno.land/pkg/integration/testdata/restart_missing_type.txtar index 09e1a27d6f4..cc8ed702734 100644 --- a/gno.land/pkg/integration/testdata/restart_missing_type.txtar +++ b/gno.land/pkg/integration/testdata/restart_missing_type.txtar @@ -1,3 +1,7 @@ +# add a random user +adduserfrom user1 'bone make joy hospital hawk crew civil relief maple alter always frozen category emerge fun inflict room sphere casino vital scheme basket omit wrap' +stdout 'g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4' + # This txtar is a regression test for a bug, whereby a type is committed to # the defaultStore.cacheTypes map, but not to the underlying store (due to a # failing transaction). @@ -5,15 +9,15 @@ loadpkg gno.land/p/demo/avl gnoland start -gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 1 test1 +gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 -account-number 57 user1 ! gnokey broadcast $WORK/tx1.tx stderr 'out of gas' -gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 2 test1 +gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 -account-number 57 user1 gnokey broadcast $WORK/tx2.tx stdout 'OK!' -gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 3 test1 +gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 -account-number 57 user1 gnokey broadcast $WORK/tx3.tx stdout 'OK!' @@ -24,7 +28,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic", "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", @@ -99,7 +103,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic", "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", @@ -174,7 +178,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic_core", "path": "gno.land/r/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic_core", diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 4c5213da345..57be82b75ff 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 99015' +stdout 'GAS USED: 99339' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 99015' # same as simulate only +stdout 'GAS USED: 99339' # same as simulate only -- package/package.gno -- diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index db36de39f2a..fd753324f5d 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -36,4 +36,5 @@ gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" exec gnoland start \ --chainid="${CHAIN_ID}" \ --lazy \ - --skip-failing-genesis-txs + --skip-failing-genesis-txs \ + --skip-genesis-sig-verification diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index f05a8eff0a7..f941f398b17 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -215,7 +215,7 @@ func processSig( ctx sdk.Context, acc std.Account, sig std.Signature, signBytes []byte, simulate bool, params Params, sigGasConsumer SignatureVerificationGasConsumer, ) (updatedAcc std.Account, res sdk.Result) { - pubKey, res := ProcessPubKey(acc, sig, simulate) + pubKey, res := ProcessPubKey(acc, sig) if !res.IsOK() { return nil, res } @@ -243,7 +243,7 @@ func processSig( // ProcessPubKey verifies that the given account address matches that of the // std.Signature. In addition, it will set the public key of the account if it // has not been set. -func ProcessPubKey(acc std.Account, sig std.Signature, simulate bool) (crypto.PubKey, sdk.Result) { +func ProcessPubKey(acc std.Account, sig std.Signature) (crypto.PubKey, sdk.Result) { // If pubkey is not known for account, set it from the std.Signature. pubKey := acc.GetPubKey() if pubKey == nil { @@ -271,7 +271,7 @@ func DefaultSigVerificationGasConsumer( switch pubkey := pubkey.(type) { case ed25519.PubKeyEd25519: meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") - return abciResult(std.ErrInvalidPubKey("ED25519 public keys are unsupported")) + return sdk.Result{} case secp256k1.PubKeySecp256k1: meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 7c6ace51e4e..430954a0867 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -623,7 +623,7 @@ func TestProcessPubKey(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, err := ProcessPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + _, err := ProcessPubKey(tt.args.acc, tt.args.sig) require.Equal(t, tt.wantErr, !err.IsOK()) }) } @@ -655,7 +655,7 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { gasConsumed int64 shouldErr bool }{ - {"PubKeyEd25519", args{store.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, true}, + {"PubKeyEd25519", args{store.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, false}, {"PubKeySecp256k1", args{store.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostSecp256k1, false}, {"Multisig", args{store.NewInfiniteGasMeter(), amino.MustMarshal(multisignature1), multisigKey1, params}, expectedCost1, false}, {"unknown key", args{store.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, From a57311bd67984628eaec5206698e26d7f58b27b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:27:24 +0100 Subject: [PATCH 6/9] fix: re-enable the portal loop CI (#3474) ## Description This PR re-enables the portal loop "CI". I won't go into details on why this was needed in the first place, with the intention to not lose absolutely all credibility for our deployment and testing workflows --- .github/workflows/portal-loop.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index aeb59d4dc77..b5cafa459a7 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -45,7 +45,6 @@ jobs: labels: ${{ steps.meta.outputs.labels }} test-portal-loop-docker-compose: - if: ${{ false }} runs-on: ubuntu-latest timeout-minutes: 10 steps: From 1b89166af37dd2ee63f5a30b81769532fd0bb1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:40:28 +0100 Subject: [PATCH 7/9] fix: use the `genesis/genesis_txs.jsonl` for the portal loop (#3475) ## Description This PR specifies the base file for the Portal Loop genesis transactions, as it is empty by default. --- misc/loop/scripts/start.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index fd753324f5d..bdabd2ac40f 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -11,10 +11,11 @@ GENESIS_BALANCES_FILE=${GENESIS_BALANCES_FILE:-""} SEEDS=${SEEDS:-""} PERSISTENT_PEERS=${PERSISTENT_PEERS:-""} +FINAL_GENESIS_TXS_SHEET="/gnoroot/gno.land/genesis/genesis_txs.jsonl" -echo "" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl +echo "" >> $FINAL_GENESIS_TXS_SHEET echo "" >> /gnoroot/gno.land/genesis/genesis_balances.jsonl -cat "${GENESIS_BACKUP_FILE}" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl +cat "${GENESIS_BACKUP_FILE}" >> $FINAL_GENESIS_TXS_SHEET cat "${GENESIS_BALANCES_FILE}" >> /gnoroot/gno.land/genesis/genesis_balances.jsonl # Initialize the secrets @@ -35,6 +36,7 @@ gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" # reading and piping to the gnoland genesis commands exec gnoland start \ --chainid="${CHAIN_ID}" \ + --genesis-txs-file="${FINAL_GENESIS_TXS_SHEET}" \ --lazy \ --skip-failing-genesis-txs \ --skip-genesis-sig-verification From 36c8f0e643c82fa157fe373d9a0442dfa7e67afb Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:10:04 +0100 Subject: [PATCH 8/9] feat: add account number / sequence as env in txtar (#3477) --- gno.land/pkg/integration/doc.go | 14 +- .../pkg/integration/testdata/addpkg.txtar | 4 +- .../testdata/addpkg_namespace.txtar | 12 +- .../integration/testdata/adduserfrom.txtar | 4 +- .../testdata/event_multi_msg.txtar | 9 +- .../pkg/integration/testdata/gnokey.txtar | 11 +- .../testdata/gnokey_simulate.txtar | 20 +- .../pkg/integration/testdata/patchpkg.txtar | 4 +- .../pkg/integration/testdata/prevrealm.txtar | 30 +-- .../realm_banker_issued_coin_denom.txtar | 16 +- .../pkg/integration/testscript_gnoland.go | 205 +++++++++--------- gno.land/pkg/integration/utils.go | 73 +++++++ ...stscript_gnoland_test.go => utils_test.go} | 0 13 files changed, 236 insertions(+), 166 deletions(-) create mode 100644 gno.land/pkg/integration/utils.go rename gno.land/pkg/integration/{testscript_gnoland_test.go => utils_test.go} (100%) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index 3e09d627c9a..d93d4607a59 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -102,11 +102,17 @@ // The path where the gnoland node stores its configuration and data. It's // set only if the node has started. // -// - USER_SEED_test1: -// Contains the seed for the test1 account. +// - xxx_user_seed: +// Where `xxx` is the account name; Contains the seed for the test1 account. // -// - USER_ADDR_test1: -// Contains the address for the test1 account. +// - xxx_user_addr: +// Where `xxx` is the account name; Contains the address for the test1 account. +// +// - xxx_account_num: +// Where `xxx` is the account name; Contains the account number for the test1 account. +// +// - xxx_account_seq: +// Where `xxx` is the account name; Contains the address for the test1 account. // // - RPC_ADDR: // Points to the gnoland node's remote address. It's set only if the node has started. diff --git a/gno.land/pkg/integration/testdata/addpkg.txtar b/gno.land/pkg/integration/testdata/addpkg.txtar index 8594e6596ce..15e8ad222f2 100644 --- a/gno.land/pkg/integration/testdata/addpkg.txtar +++ b/gno.land/pkg/integration/testdata/addpkg.txtar @@ -4,7 +4,7 @@ gnoland start ## deploy realm -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## check output stdout OK! @@ -15,7 +15,7 @@ stdout 'EVENTS: \[\]' stdout 'TX HASH: ' ## call added realm -gnokey maketx call -pkgpath gno.land/r/$USER_ADDR_test1/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 +gnokey maketx call -pkgpath gno.land/r/$test1_user_addr/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 ## check output stdout '\("hello world!" string\)' diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index 89da8a51820..f529c176f36 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -4,7 +4,7 @@ loadpkg gno.land/r/sys/users adduser admin adduser gui -patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $USER_ADDR_admin # use our custom admin +patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $admin_user_addr # use our custom admin gnoland start @@ -20,7 +20,7 @@ stdout 'false' # Gui should be able to addpkg on test1 addr # gui addpkg -> gno.land/r//mysuperpkg -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui stdout 'OK!' # Gui should be able to addpkg on random name @@ -43,12 +43,12 @@ stdout 'true' # Try to add a pkg an with unregistered user # gui addpkg -> gno.land/r//one -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stderr 'unauthorized user' # Try to add a pkg with an unregistered user, on their own address as namespace # gui addpkg -> gno.land/r//one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_gui/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stdout 'OK!' ## Test unregistered namespace @@ -63,12 +63,12 @@ stderr 'unauthorized user' # Test admin invites gui # admin call -> demo/users.Invite -gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_gui admin +gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $gui_user_addr admin stdout 'OK!' # test gui register namespace # gui call -> demo/users.Register -gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_admin -args 'guiland' -args 'im gui' gui +gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $admin_user_addr -args 'guiland' -args 'im gui' gui stdout 'OK!' # Test gui publishing on guiland/one diff --git a/gno.land/pkg/integration/testdata/adduserfrom.txtar b/gno.land/pkg/integration/testdata/adduserfrom.txtar index 47ec70b00e6..8bbfaa738fd 100644 --- a/gno.land/pkg/integration/testdata/adduserfrom.txtar +++ b/gno.land/pkg/integration/testdata/adduserfrom.txtar @@ -14,13 +14,13 @@ stdout 'g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp' gnoland start ## check users initial balance -gnokey query bank/balances/${USER_ADDR_user1} +gnokey query bank/balances/$user1_user_addr stdout '10000000ugnot' gnokey query bank/balances/g18e22n23g462drp4pyszyl6e6mwxkaylthgeeq4 stdout '10000000ugnot' -gnokey query auth/accounts/${USER_ADDR_user3} +gnokey query auth/accounts/$user3_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 4c8de856f03..3c5667b73b0 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -13,18 +13,18 @@ gnokey query auth/accounts/g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0 stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 57 -account-sequence 0 user1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number $user1_account_num -account-sequence $user1_account_seq user1 stdout 'Tx successfully signed and saved to ' ## broadcast @@ -51,4 +51,3 @@ func Event(value string) { -- multi/multi_msg.tx -- {"msg":[{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} - diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index 35759fa25dd..3268782b1ca 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -2,22 +2,21 @@ # golden files have been generated using UPDATE_SCRIPTS=true # add a random user -adduserfrom user1 'alpha ability feed thrive color fee grace message chief helmet laundry inmate index brave luxury toddler spawn vague index able zone shoe collect escape' -stdout 'g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m' +adduser user1 # start gnoland gnoland start ## test1 account should be available on default -gnokey query auth/accounts/g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m +gnokey query auth/accounts/$user1_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index db3cd527eb3..31b2249f8bb 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -6,41 +6,41 @@ loadpkg gno.land/r/hello $WORK/hello gnoland start # Initial state: assert that sequence == 0. -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # attempt adding the "test" package. # the package has a syntax error; simulation should catch this ahead of time and prevent the tx. # -simulate test ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate only ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate skip ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "2"' # attempt calling hello.SetName correctly. # -simulate test and skip should do it successfully, -simulate only should not. # -simulate test gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' @@ -50,19 +50,19 @@ stdout 'Hello, George!' # none should change the name (ie. panic rollbacks). # -simulate test ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' diff --git a/gno.land/pkg/integration/testdata/patchpkg.txtar b/gno.land/pkg/integration/testdata/patchpkg.txtar index c5962709625..0a1a7fa993d 100644 --- a/gno.land/pkg/integration/testdata/patchpkg.txtar +++ b/gno.land/pkg/integration/testdata/patchpkg.txtar @@ -2,13 +2,13 @@ loadpkg gno.land/r/dev/admin $WORK adduser dev -patchpkg "g1abcde" $USER_ADDR_dev +patchpkg "g1abcde" $dev_user_addr gnoland start gnokey maketx call -pkgpath gno.land/r/dev/admin -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ! stdout g1abcde -stdout $USER_ADDR_dev +stdout $dev_user_addr -- admin.gno -- package admin diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 20317d87345..4bbe16c3205 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -30,65 +30,65 @@ loadpkg gno.land/p/demo/bar $WORK/p/demo/bar ## start a new node gnoland start -env RFOO_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 +env RFOO_USER_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 # Test cases ## 1. MsgCall -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 2. MsgCall -> myrealm.B -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 3. MsgCall -> r/foo.A -> myrlm.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 4. MsgCall -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## remove due to update to maketx call can only call realm (case 5, 6, 13) ## 5. MsgCall -> p/demo/bar.A: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 6. MsgCall -> p/demo/bar.B: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 7. MsgRun -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 8. MsgRun -> myrealm.B -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 9. MsgRun -> r/foo.A -> myrlm.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 10. MsgRun -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 13. MsgCall -> std.PrevRealm(): user address ## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 14. MsgRun -> std.PrevRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} -- r/myrlm/myrlm.gno -- package myrlm diff --git a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar index be9a686bac6..a55604267ae 100644 --- a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar @@ -19,31 +19,31 @@ gnokey maketx addpkg -pkgdir $WORK/invalid_realm_denom -pkgpath gno.land/r/test/ gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check test2 balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '' ## mint coin from banker -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${USER_ADDR_test2} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${test2_user_addr} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after minting, without patching banker will return '31337ugnot' -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31337/gno.land/r/test/realm_banker:ugnot"' ## burn coin -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${USER_ADDR_test2} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${test2_user_addr} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after burning -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31330/gno.land/r/test/realm_banker:ugnot"' ## transfer 1ugnot to test2 for gas-fee of below tx -gnokey maketx send -send "1ugnot" -to ${USER_ADDR_test2} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx send -send "1ugnot" -to ${test2_user_addr} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## transfer coin gnokey maketx send -send "1330/gno.land/r/test/realm_banker:ugnot" -to g1yr0dpfgthph7y6mepdx8afuec4q3ga2lg8tjt0 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check sender balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"30000/gno.land/r/test/realm_banker:ugnot"' ## check receiver balance @@ -121,4 +121,4 @@ func Mint(addr std.Address, denom string, amount int64) { func Burn(addr std.Address, denom string, amount int64) { banker := std.GetBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, denom, amount) -} \ No newline at end of file +} diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index 9781799ea7d..1531b83dfef 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -20,6 +20,9 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/amino" + rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -45,6 +48,7 @@ const ( envKeyPrivValKey envKeyExecCommand envKeyExecBin + envKeyBase ) type commandkind int @@ -158,13 +162,18 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { } kb.ImportPrivKey(DefaultAccount_Name, defaultPK, "") - env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) - env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) + env.Setenv(DefaultAccount_Name+"_user_seed", DefaultAccount_Seed) + env.Setenv(DefaultAccount_Name+"_user_addr", DefaultAccount_Address) // New private key env.Values[envKeyPrivValKey] = ed25519.GenPrivKey() + + // Set gno dbdir env.Setenv("GNO_DBDIR", dbdir) + // Setup account store + env.Values[envKeyBase] = kb + // Generate node short id var sid string { @@ -215,6 +224,7 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { "adduserfrom": adduserfromCmd(nodesManager), "patchpkg": patchpkgCmd(), "loadpkg": loadpkgCmd(gnoRootDir), + "scanf": loadpkgCmd(gnoRootDir), } // Initialize cmds map if needed @@ -305,8 +315,11 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun }) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: cfg}) - ts.Setenv("RPC_ADDR", nodep.Address()) + + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node started successfully") case "restart": @@ -337,6 +350,9 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun ts.Setenv("RPC_ADDR", nodep.Address()) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: node.cfg}) + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node restarted successfully") case "stop": @@ -534,6 +550,64 @@ func loadpkgCmd(gnoRootDir string) func(ts *testscript.TestScript, neg bool, arg } } +func loadUserEnv(ts *testscript.TestScript, remote string) error { + const path = "auth/accounts" + + // List all accounts + kb := ts.Value(envKeyBase).(keys.Keybase) + accounts, err := kb.List() + if err != nil { + ts.Fatalf("query accounts: unable to list keys: %s", err) + } + + cli, err := rpcclient.NewHTTPClient(remote) + if err != nil { + return fmt.Errorf("unable create rpc client %q: %w", remote, err) + } + + batch := cli.NewBatch() + for _, account := range accounts { + accountPath := filepath.Join(path, account.GetAddress().String()) + if err := batch.ABCIQuery(accountPath, []byte{}); err != nil { + return fmt.Errorf("unable to create query request: %w", err) + } + } + + batchRes, err := batch.Send(context.Background()) + if err != nil { + return fmt.Errorf("unable to query accounts: %w", err) + } + + if len(batchRes) != len(accounts) { + ts.Fatalf("query accounts: len(res) != len(accounts)") + } + + for i, res := range batchRes { + account := accounts[i] + name := account.GetName() + qres := res.(*ctypes.ResultABCIQuery) + + if err := qres.Response.Error; err != nil { + ts.Fatalf("query account %q error: %s", account.GetName(), err.Error()) + } + + var qret struct{ BaseAccount std.BaseAccount } + if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil { + ts.Fatalf("query account %q unarmshal error: %s", account.GetName(), err.Error()) + } + + strAccountNumber := strconv.Itoa(int(qret.BaseAccount.GetAccountNumber())) + ts.Setenv(name+"_account_num", strAccountNumber) + ts.Logf("[%q] account number: %s", name, strAccountNumber) + + strAccountSequence := strconv.Itoa(int(qret.BaseAccount.GetSequence())) + ts.Setenv(name+"_account_seq", strAccountSequence) + ts.Logf("[%q] account sequence: %s", name, strAccountNumber) + } + + return nil +} + type tsLogWriter struct { ts *testscript.TestScript } @@ -589,94 +663,8 @@ func setupNode(ts *testscript.TestScript, ctx context.Context, cfg *ProcessNodeC return nil } -// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and -// processes them. The function handles quoted phrases and escape characters within these strings. -func unquote(args []string) ([]string, error) { - const quote = '"' - - parts := []string{} - var inQuote bool - - var part strings.Builder - for _, arg := range args { - var escaped bool - for _, c := range arg { - if escaped { - // If the character is meant to be escaped, it is processed with Unquote. - // We use `Unquote` here for two main reasons: - // 1. It will validate that the escape sequence is correct - // 2. It converts the escaped string to its corresponding raw character. - // For example, "\\t" becomes '\t'. - uc, err := strconv.Unquote(`"\` + string(c) + `"`) - if err != nil { - return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) - } - - part.WriteString(uc) - escaped = false - continue - } - - // If we are inside a quoted string and encounter an escape character, - // flag the next character as `escaped` - if inQuote && c == '\\' { - escaped = true - continue - } - - // Detect quote and toggle inQuote state - if c == quote { - inQuote = !inQuote - continue - } - - // Handle regular character - part.WriteRune(c) - } - - // If we're inside a quote, add a single space. - // It reflects one or multiple spaces between args in the original string. - if inQuote { - part.WriteRune(' ') - continue - } - - // Finalize part, add to parts, and reset for next part - parts = append(parts, part.String()) - part.Reset() - } - - // Check if a quote is left open - if inQuote { - return nil, errors.New("unfinished quote") - } - - return parts, nil -} - -func getNodeSID(ts *testscript.TestScript) string { - return ts.Getenv("SID") -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %q command failure: %s", cmd, err) - } - } else { - if neg { - ts.Fatalf("unexpected %q command success", cmd) - } - } -} - -type envSetter interface { - Setenv(key, value string) -} - // createAccount creates a new account with the given name and adds it to the keybase. -func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland.Balance, error) { +func createAccount(ts *testscript.TestScript, kb keys.Keybase, accountName string) (gnoland.Balance, error) { var balance gnoland.Balance entropy, err := bip39.NewEntropy(256) if err != nil { @@ -688,23 +676,11 @@ func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland. return balance, fmt.Errorf("error generating mnemonic: %w", err) } - var keyInfo keys.Info - if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { - return balance, fmt.Errorf("unable to create account: %w", err) - } - - address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) - - return gnoland.Balance{ - Address: address, - Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, - }, nil + return createAccountFrom(ts, kb, accountName, mnemonic, 0, 0) } // createAccountFrom creates a new account with the given metadata and adds it to the keybase. -func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { +func createAccountFrom(ts *testscript.TestScript, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { var balance gnoland.Balance // check if mnemonic is valid @@ -718,8 +694,8 @@ func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic str } address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) + ts.Setenv(accountName+"_user_seed", mnemonic) + ts.Setenv(accountName+"_user_addr", address.String()) return gnoland.Balance{ Address: address, @@ -787,3 +763,20 @@ func GeneratePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, inde privKey := secp256k1.PrivKeySecp256k1(derivedPriv) return privKey, nil } + +func getNodeSID(ts *testscript.TestScript) string { + return ts.Getenv("SID") +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %q command failure: %s", cmd, err) + } + } else { + if neg { + ts.Fatalf("unexpected %q command success", cmd) + } + } +} diff --git a/gno.land/pkg/integration/utils.go b/gno.land/pkg/integration/utils.go new file mode 100644 index 00000000000..bc9e7f1e220 --- /dev/null +++ b/gno.land/pkg/integration/utils.go @@ -0,0 +1,73 @@ +package integration + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and +// processes them. The function handles quoted phrases and escape characters within these strings. +func unquote(args []string) ([]string, error) { + const quote = '"' + + parts := []string{} + var inQuote bool + + var part strings.Builder + for _, arg := range args { + var escaped bool + for _, c := range arg { + if escaped { + // If the character is meant to be escaped, it is processed with Unquote. + // We use `Unquote` here for two main reasons: + // 1. It will validate that the escape sequence is correct + // 2. It converts the escaped string to its corresponding raw character. + // For example, "\\t" becomes '\t'. + uc, err := strconv.Unquote(`"\` + string(c) + `"`) + if err != nil { + return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) + } + + part.WriteString(uc) + escaped = false + continue + } + + // If we are inside a quoted string and encounter an escape character, + // flag the next character as `escaped` + if inQuote && c == '\\' { + escaped = true + continue + } + + // Detect quote and toggle inQuote state + if c == quote { + inQuote = !inQuote + continue + } + + // Handle regular character + part.WriteRune(c) + } + + // If we're inside a quote, add a single space. + // It reflects one or multiple spaces between args in the original string. + if inQuote { + part.WriteRune(' ') + continue + } + + // Finalize part, add to parts, and reset for next part + parts = append(parts, part.String()) + part.Reset() + } + + // Check if a quote is left open + if inQuote { + return nil, errors.New("unfinished quote") + } + + return parts, nil +} diff --git a/gno.land/pkg/integration/testscript_gnoland_test.go b/gno.land/pkg/integration/utils_test.go similarity index 100% rename from gno.land/pkg/integration/testscript_gnoland_test.go rename to gno.land/pkg/integration/utils_test.go From ddfff6096fc96631f15a9fae1bd6f72e15d40e13 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 13 Jan 2025 11:35:05 +0100 Subject: [PATCH 9/9] chore(gnovm): remove unused attributes (#3492) --- gnovm/pkg/gnolang/nodes.go | 6 ++---- gnovm/pkg/gnolang/preprocess.go | 12 +----------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 0496d37ed72..b85d1ac7026 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -153,10 +153,8 @@ const ( ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE" ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE" ATTR_IOTA GnoAttribute = "ATTR_IOTA" - ATTR_LOCATIONED GnoAttribute = "ATTR_LOCATIONE" // XXX DELETE - ATTR_GOTOLOOP_STMT GnoAttribute = "ATTR_GOTOLOOP_STMT" // XXX delete? - ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. - ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. + ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. + ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" ) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 79695d8888a..ddfd1851989 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2723,17 +2723,10 @@ func findGotoLoopDefines(ctx BlockNode, bn BlockNode) { // Otherwise mark stmt as gotoloop. case Stmt: // we're done if we - // re-encounter origGotoStmtm. + // re-encounter origGotoStmt. if n == origGoto { - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_EXIT // done } - // otherwise set attribute. - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_CONTINUE // Special case, maybe convert // NameExprTypeDefine to @@ -4804,9 +4797,6 @@ func setNodeLines(n Node) { // based on sparse expectations on block nodes, and ensures uniqueness of BlockNode.Locations. // Ensures uniqueness of BlockNode.Locations. func setNodeLocations(pkgPath string, fileName string, n Node) { - if n.GetAttribute(ATTR_LOCATIONED) == true { - return // locations already set (typically n is a filenode). - } if pkgPath == "" || fileName == "" { panic("missing package path or file name") }