diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index 8902a128b5d..c7f209687c4 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -15,4 +15,4 @@ jobs: assign-author: runs-on: ubuntu-latest steps: - - uses: toshimaru/auto-author-assign@v1.6.2 + - uses: toshimaru/auto-author-assign@v2.0.1 diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d4b3079d612..b17c66d8e5a 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -50,7 +50,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno test --verbose ./examples + - run: go run ./gnovm/cmd/gno test --verbose ./examples/... lint: strategy: fail-fast: false diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8f57bec80a1..4e812eed5b7 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -21,9 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: "cd misc/devdeps && make install" - - run: "cd misc/gendocs && make gen" - - run: "find docs/ -type f -ls" + - uses: actions/setup-go@v4 + with: + go-version: "1.21.x" + - run: "cd misc/gendocs && make install gen" - uses: actions/configure-pages@v3 id: pages - uses: actions/upload-pages-artifact@v2 diff --git a/.github/workflows/monthly-snapshots.yml b/.github/workflows/monthly-snapshots.yml new file mode 100644 index 00000000000..85307074218 --- /dev/null +++ b/.github/workflows/monthly-snapshots.yml @@ -0,0 +1,22 @@ +name: Monthly Snapshots + +on: + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Generate tag name + id: tag_name + run: echo "::set-output name=tag_name::v0.0.1-dev.$(date +'%Y.%m.%d')" + - name: Release + uses: softprops/action-gh-release@v1 + with: + generate_release_notes: true + prerelease: true + tag_name: '${{ steps.tag_name.outputs.tag_name }}' diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..2c81b9938ea --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +# man 5 gitmailmap +# git log --mailmap --pretty=short | grep ^Author: | sort -u +Jae Kwon <53785+jaekwon@users.noreply.github.com> Jae Kwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> Jae Kwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> jaekwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> Naut Jae +Thomas Bruyelle Thomas Bruyelle +Thomas Bruyelle Thomas Bruyelle +Miloš Živković Miloš Živković +Hariom Verma Hariom Verma +Giancarlos Salas Giancarlos Salas +Morgan Morgan diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1e23f18273..1739d50f034 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ -# Contributing to GNO +# Contributing to Gno -Thank you for looking to contribute to the GNO project. -We appreciate every open-source contribution, as it helps us improve and enhance gno for the benefit of the community. +Thank you for looking to contribute to the Gno project. +We appreciate every open-source contribution, as it helps us improve and enhance Gno for the benefit of the community. This document outlines some basic pointers on making your future contribution a great experience. It outlines basic PR etiquette employed by the core gno team. It lays out coding styles, simple how-to guides and tools to get you up and @@ -20,7 +20,7 @@ Likewise, if you have an idea on how to improve this guide, go for it as well. - [Testing](#testing) - [Running locally](#running-locally) - [Running test workflows](#running-test-workflows) - - [Testing GNO code](#testing-gno-code) + - [Testing Gno code](#testing-gno-code) - [Repository Structure](#repository-structure) - [How do I?](#how-do-i) - [How do I submit changes?](#how-do-i-submit-changes) @@ -41,9 +41,9 @@ Likewise, if you have an idea on how to improve this guide, go for it as well. - **[Discord](https://discord.gg/YFtMjWwUN7)** - we are very active on Discord. Join today and start discussing all things gno with fellow engineers and enthusiasts. -- **[Awesome GNO](https://github.com/gnolang/awesome-gno)** - check out the list of compiled resources for helping you +- **[Awesome Gno](https://github.com/gnolang/awesome-gno)** - check out the list of compiled resources for helping you understand the gno ecosystem -- **[Active Staging](https://gno.land/)** - use the currently available staging environment to play around with a +- **[Active Staging](https://staging.gno.land/)** - use the currently available staging environment to play around with a production network. If you want to interact with a local instance, refer to the [Local Setup](#local-setup) guide. - **[Twitter](https://twitter.com/_gnoland)** - follow us on Twitter to get the latest scoop - **[Telegram](https://t.me/gnoland)** - join our official Telegram group to start a conversation about gno @@ -58,7 +58,6 @@ The primary tech stack for working on the repository: - Go (version 1.20+) - make (for using Makefile configurations) -- Docker (for using the official Docker setup files) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 for Windows / Linux / macOS). @@ -67,13 +66,21 @@ For Gno, there is no specific tooling that needs to be installed, that’s not a You can utilize the `gno` command to facilitate Gnolang support when writing Smart Contracts in Gno, by installing it with `make install_gno`. +If you are working on Go source code on this repository, `pkg.go.dev` will not +render our documentation as it has a license it does not recognise. Instead, use +the `go doc` command, or use our statically-generated documentation: +https://gnolang.github.io/gno/github.com/gnolang/gno.html + Additionally, you can also configure your editor to recognize `.gno` files as `.go` files, to get the benefit of syntax highlighting. -Currently, we support a [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno) extension -(eventually official in the future) for Gnolang. +#### Visual Studio Code + +There currently is an unofficial [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno) +extension (primarily developed by a core team member) for working with `*.gno` +files. -#### ViM Support +#### ViM Support (without LSP) Add to your `.vimrc` file: @@ -97,9 +104,68 @@ To use *gofumpt* instead of *gofmt*, as hinted in the comment, you may either ha cexpr system('go run -modfile /misc/devdeps/go.mod mvdan.cc/gofumpt -w ' . expand('%')) ``` +### ViM Support (with LSP) + There is an experimental and unofficial [Gno Language Server](https://github.com/jdkato/gnols) developed by the community, with an installation guide for Neovim. +For ViM purists, you have to install the [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) +plugin and then register the LSP server in your `.vimrc` file: + +```vim +augroup gno_autocmd + autocmd! + autocmd BufNewFile,BufRead *.gno + \ set filetype=gno | + \ set syntax=go +augroup END + +if (executable('gnols')) + au User lsp_setup call lsp#register_server({ + \ 'name': 'gnols', + \ 'cmd': ['gnols'], + \ 'allowlist': ['gno'], + \ 'config': {}, + \ 'workspace_config': { + \ 'root' : '/path/to/gno_repo', + \ 'gno' : '/path/to/gno_bin', + \ 'precompileOnSave' : v:true, + \ 'buildOnSave' : v:false, + \ }, + \ 'languageId': {server_info->'gno'}, + \ }) +else + echomsg 'gnols binary not found: LSP disabled for Gno files' +endif + +function! s:on_lsp_buffer_enabled() abort + " Autocompletion + setlocal omnifunc=lsp#complete + " Format on save + autocmd BufWritePre LspDocumentFormatSync + " Some optionnal mappings + nmap i (lsp-hover) + " Following mappings are not supported yet by gnols + " nmap gd (lsp-definition) + " nmap rr (lsp-rename) +endfunction +augroup lsp_install + au! + autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() +augroup END +``` + +Note that unlike the previous ViM setup without LSP, here it is required by +`vim-lsp` to have a specific `filetype=gno`. Syntax highlighting is preserved +thanks to `syntax=go`. + +Inside `lsp#register_server()`, you also have to replace +`workspace_config.root` and `workspace_config.gno` with the correct directories +from your machine. + +Additionaly, it's not possible to use `gofumpt` for code formatting with +`gnols` for now. + #### Emacs Support 1. Install [go-mode.el](https://github.com/dominikh/go-mode.el). @@ -122,9 +188,36 @@ Clone the repo: `git clone https://github.com/gnolang/gno.git` Build / install base commands: -`make build ` +`make install` + +If you haven't already, you may need to add the directory where [`go install` +places its binaries](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) +to your `PATH`. If you haven't configured `GOBIN` or `GOPATH` differently, this +command should suffice: + +``` +echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.profile +source ~/.profile # reload ~/.profile in the current shell +``` + +After that, you should be good to go to use `gno` and `gnokey`, straight from +your command line! The following commands should list the help messages for +each: -That’s it! +```console +$ gno --help +USAGE + [flags] [...] + +Runs the gno development toolkit +[...] +$ gnokey --help +USAGE + [flags] [...] + +Manages private keys for the node +[...] +``` ### Testing @@ -151,7 +244,7 @@ To run the entire test suite through workflow files, run the following command: act -v -j go-test -#### Testing GNO code +#### Testing Gno code If you wish to test a `.gno` Realm or Package, you can utilize the `gno` tool. @@ -169,24 +262,35 @@ subcommands by running: gno --help +#### Adding new tests + +Most packages will follow the convention established with Go: each package +contains within its file many files suffixed with `_test.go` which test its +functionality. As a general rule, you should follow this convention, and in +every PR you make you should ensure all the code you added is appropriately +covered by tests ([Codecov](https://about.codecov.io/) will loudly complain in +your PR's comments if you don't). + +Additionally, we have a few testing systems that stray from this general rule; +at the time of writing, these are for integration tests and language tests. You +can find more documentation about them [on this guide](gno/docs/testing-guide.md). + ### Repository Structure The repository structure can seem tricky at first, but it’s simple if you consider the philosophy that the gno project -employs (check out [PHILOSOPHY.md](https://github.com/gnolang/gno/blob/master/PHILOSOPHY.md)). +employs (check out [PHILOSOPHY.md](./PHILOSOPHY.md)). The gno project currently favors a mono-repo structure, as it’s easier to manage contributions and keep everyone aligned. In the future, this may change, but in the meantime the majority of gno resources and source code will be centralized here. -- `cmd` - contains the base command implementations for tools like `gnokey`, `gnotxport`, etc. The actual underlying - logic is located within the `pkgs` subdirectories. - `examples` - contains the example `.gno` realms and packages. This is the central point for adding user-defined realms and packages. -- `gnoland` - contains the base source code for bootstrapping the Gnoland node -- `pkgs` - contains the dev-audited packages used throughout the gno codebase -- `stdlibs` - contains the standard library packages used (imported) in `.gno` Smart Contracts. These packages are - themselves `.gno` files. -- `tests` - contains the standard language tests for Gnolang +- `gno.land` - contains the base source code for bootstrapping the Gnoland node, + using `tm2` and `gnovm`. +- `gnovm` - contains the implementation of the Gno programming language and its + Virtual Machine, together with their standard libraries and tests. +- `tm2` - contains a fork of the [Tendermint consensus engine](https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html) with different expectations. ## How do I? diff --git a/README.md b/README.md index 99634f90a0d..71c53b86f19 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,64 @@ # Gno -At first, there was Bitcoin, out of entropy soup of the greater All. -Then, there was Ethereum, which was created in the likeness of Bitcoin, -but made Turing complete. +> At first, there was Bitcoin, out of entropy soup of the greater All. +> Then, there was Ethereum, which was created in the likeness of Bitcoin, +> but made Turing complete. +> +> Among these were Tendermint and Cosmos to engineer robust PoS and IBC. +> Then came Gno upon Cosmos and there spring forth Gnoland, +> simulated by the Gnomes of the Greater Resistance. + +Gno is an interpreted and fully-deterministic implementation of the Go +programming language, designed to build succinct and composable smart contracts. +The first blockchain to use it is Gno.land, a +[Proof of Contribution](./docs/proof-of-contribution.md)-based chain, backed by +a variation of the [Tendermint](https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html) +consensus engine. -Among these were Tendermint and Cosmos to engineer robust PoS and IBC. -Then came Gno upon Cosmos and there spring forth Gnoland, -simulated by the Gnomes of the Greater Resistance. +## Getting started + +If you haven't already, take a moment to check out our [website](https://gno.land/). + +> The website is a deployment of our [gnoweb](./gno.land/cmd/gnoweb) frontend; you +> can use it to check out [some](https://test3.gno.land/r/demo/boards) [example](https://test3.gno.land/r/gnoland/blog) +> [contracts](https://test3.gno.land/r/demo/users). +> +> Use the `[source]` button in the header to inspect the program's source; use +> the `[help]` button to view how you can use [`gnokey`](./gno.land/cmd/gnokey) +> to interact with the chain from your command line. + +If you have already played around with the website, use our +[Getting Started](https://github.com/gnolang/getting-started) guide to learn how +to write and deploy your first smart contract. No local set-up required! + +Once you're done, learn how to set up your local environment with the +[quickstart guide](./examples/gno.land/r/demo/boards/README.md) and the +[contributing guide](./CONTRIBUTING.md). -## Discover +You can find out more existing tools & documentation for Gno on our +[awesome-gno](https://github.com/gnolang/awesome-gno) repository. +We look forward to seeing your first PR! + +## Repository structure * [examples](./examples) - smart-contract examples and guides for new Gno developers. * [gnovm](./gnovm) - GnoVM and Gnolang. * [gno.land](./gno.land) - Gno.land blockchain and tools. * [tm2](./tm2) - Tendermint2. -## Getting started - -Start your journey with Gno.land by: -- using the [`gnoweb`](./gno.land/cmd/gnoweb) interface on the [latest testnet (test3.gno.land)](https://test3.gno.land/), -- sending transactions with [`gnokey`](./gno.land/cmd/gnokey), -- writing smart-contracts with [`gno` (ex `gnodev`)](./gnovm/cmd/gno). - -Also, see the [quickstart guide](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/boards/README.md). +## Socials & Contact -## Contact - - * Discord: https://discord.gg/YFtMjWwUN7 <-- join now - * Gnoland: https://gno.land/r/demo/boards:testboard - * Telegram: https://t.me/gnoland - * Twitter: https://twitter.com/_gnoland +* [**Discord**](https://discord.gg/YFtMjWwUN7): good for general chat-based + conversations, as well as for asking support on developing with Gno. +* [**Reddit**](https://www.reddit.com/r/gnoland): more "permanent" and + forum-style discussions. Feel free to post anything Gno-related, as well as + any question related to Gno programming! +* [**Telegram**](https://t.me/gnoland): unofficial Telegram group. +* [**Twitter**](https://twitter.com/_gnoland): official Twitter account. Follow + us to know about new developments, events & official announcements about Gno! +* [**YouTube**](https://www.youtube.com/@_gnoland): here we post all of our + video content, like workshops, talks and public development calls. Follow + along on our development journey!
Short doc about all the commands @@ -52,28 +81,28 @@ Also, see the [quickstart guide](https://github.com/gnolang/gno/blob/master/exam
CI/CD/Tools badges and links GitHub Actions: - + * [![gno.land](https://github.com/gnolang/gno/actions/workflows/gnoland.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/gnoland.yml) * [![gnovm](https://github.com/gnolang/gno/actions/workflows/gnovm.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/gnovm.yml) * [![tm2](https://github.com/gnolang/gno/actions/workflows/tm2.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/tm2.yml) * [![examples](https://github.com/gnolang/gno/actions/workflows/examples.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/examples.yml) * [![docker](https://github.com/gnolang/gno/actions/workflows/docker.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/docker.yml) - + Codecov: - + * General: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4)](https://codecov.io/gh/gnolang/gno) * tm2: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=tm2)](https://codecov.io/gh/gnolang/gno/tree/master/tm2) * gnovm: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=gnovm)](https://codecov.io/gh/gnolang/gno/tree/master/gnovm) * gno.land: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=gno.land)](https://codecov.io/gh/gnolang/gno/tree/master/gno.land) * examples: TODO - + Go Report Card: - + * [![Go Report Card](https://goreportcard.com/badge/github.com/gnolang/gno)](https://goreportcard.com/report/github.com/gnolang/gno) * tm2, gnovm, gno.land: TODO (blocked by tm2 split, because we need go mod workspaces) - + Pkg.go.dev - - * [![Go Reference](https://pkg.go.dev/badge/github.com/gnolang/gno.svg)](https://pkg.go.dev/github.com/gnolang/gno) - * TODO: host custom docs on gh-pages, to bypass license limitation + + * [![Go Reference](https://pkg.go.dev/badge/hey/google)](https://gnolang.github.io/gno/github.com/gnolang/gno.html) \ + (pkg.go.dev will not show our repository as it has a license it doesn't recognise)
diff --git a/docs/testing_guide.md b/docs/testing-guide.md similarity index 100% rename from docs/testing_guide.md rename to docs/testing-guide.md diff --git a/examples/Makefile b/examples/Makefile index 5075df198ac..f20072d9df2 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -19,7 +19,7 @@ build: precompile .PHONY: test test: - go run ../gnovm/cmd/gno test --verbose . + go run ../gnovm/cmd/gno test --verbose ./... .PHONY: lint lint: @@ -27,7 +27,7 @@ lint: .PHONY: test.sync test.sync: - go run ../gnovm/cmd/gno test --verbose --update-golden-tests . + go run ../gnovm/cmd/gno test --verbose --update-golden-tests ./... .PHONY: clean clean: diff --git a/examples/gno.land/p/demo/blog/blog.gno b/examples/gno.land/p/demo/blog/blog.gno index 62103b52885..1cf37b7ad3a 100644 --- a/examples/gno.land/p/demo/blog/blog.gno +++ b/examples/gno.land/p/demo/blog/blog.gno @@ -13,13 +13,28 @@ import ( ) type Blog struct { - Title string - Prefix string // i.e. r/gnoland/blog: - Posts avl.Tree // slug -> Post + Title string + Prefix string // i.e. r/gnoland/blog: + Posts avl.Tree // slug -> Post + NoBreadcrumb bool +} + +func (b Blog) RenderLastPostsWidget(limit int) string { + output := "" + i := 0 + b.Posts.Iterate("", "", func(key string, value interface{}) bool { + p := value.(*Post) + output += ufmt.Sprintf("- [%s](%s)\n", p.Title, p.URL()) + i++ + return i >= limit + }) + return output } func (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) { - res.Write(breadcrumb([]string{b.Title})) + if !b.NoBreadcrumb { + res.Write(breadcrumb([]string{b.Title})) + } if b.Posts.Size() == 0 { res.Write("No posts.") @@ -47,12 +62,14 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { } p := post.(*Post) - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "p", - p.Title, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "p", + p.Title, + }) + res.Write(breadStr) + } // output += ufmt.Sprintf("## [%s](%s)\n", p.Title, p.URL()) res.Write(p.Body + "\n\n") @@ -75,12 +92,14 @@ func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) { return } - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "t", - slug, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "t", + slug, + }) + res.Write(breadStr) + } nb := 0 b.Posts.Iterate("", "", func(key string, value interface{}) bool { diff --git a/examples/gno.land/p/demo/ui/ui.gno b/examples/gno.land/p/demo/ui/ui.gno index efa185914a0..dd21d0510eb 100644 --- a/examples/gno.land/p/demo/ui/ui.gno +++ b/examples/gno.land/p/demo/ui/ui.gno @@ -1,6 +1,9 @@ package ui -import "strings" +import ( + "strconv" + "strings" +) type DOM struct { // metadata @@ -56,6 +59,17 @@ func (dom DOM) String() string { return output } +type Jumbotron []DomStringer + +func (j Jumbotron) String(dom DOM) string { + output := `
` + "\n\n" + for _, elem := range j { + output += elem.String(dom) + "\n" + } + output += `
` + "\n" + return output +} + // XXX: rename Element to Div? type Element []DomStringer @@ -88,6 +102,26 @@ func (b Breadcrumb) String(dom DOM) string { return output } +type Columns struct { + MaxWidth int + Columns []Element +} + +func (c *Columns) Append(elems ...Element) { + c.Columns = append(c.Columns, elems...) +} + +func (c Columns) String(dom DOM) string { + output := `
` + "\n" + for _, entry := range c.Columns { + output += `
` + "\n\n" + output += entry.String(dom) + output += "
\n" + } + output += "
\n" + return output +} + type Link struct { Text string Path string @@ -104,8 +138,14 @@ func (l Link) String(dom DOM) string { case l.Path != "" && l.URL != "": panic("a link should have a path or a URL, not both.") case l.Path != "": + if l.Text == "" { + l.Text = l.Path + } url = dom.Prefix + l.Path case l.URL != "": + if l.Text == "" { + l.Text = l.URL + } url = l.URL } @@ -151,6 +191,7 @@ type ( Italic string Code string Paragraph string + Quote string HR struct{} ) @@ -160,6 +201,7 @@ func (text H3) String(_ DOM) string { return "### " + string(text) + "\n" func (text H4) String(_ DOM) string { return "#### " + string(text) + "\n" } func (text H5) String(_ DOM) string { return "##### " + string(text) + "\n" } func (text H6) String(_ DOM) string { return "###### " + string(text) + "\n" } +func (text Quote) String(_ DOM) string { return "> " + string(text) + "\n" } func (text Bold) String(_ DOM) string { return "**" + string(text) + "**" } func (text Italic) String(_ DOM) string { return "_" + string(text) + "_" } func (text Paragraph) String(_ DOM) string { return "\n" + string(text) + "\n" } diff --git a/examples/gno.land/r/demo/groups/README.md b/examples/gno.land/r/demo/groups/README.md index 1db5ae56b51..ecdd5065903 100644 --- a/examples/gno.land/r/demo/groups/README.md +++ b/examples/gno.land/r/demo/groups/README.md @@ -8,7 +8,7 @@ ### - create group - ./build/gnokey maketx call -func "CreateGroup" -args "dao_trinity_ngo" -gas-fee "1000000ugnot" -gas-wanted 4000000 -broadcast true -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 + ./build/gnokey maketx call -func "CreateGroup" -args "dao_trinity_ngo" -gas-fee "1000000ugnot" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 ### - add member diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index fb49b2273ae..0094ad2ae35 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -44,7 +44,7 @@ func (t *TestRealmObject) Modify() { } //---------------------------------------- -// Test helpers to test a particualr realm bug. +// Test helpers to test a particular realm bug. type TestNode struct { Name string diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index 2982ea88489..cad84507614 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -23,3 +23,7 @@ func AddComment(postSlug, comment string) { func Render(path string) string { return b.Render(path) } + +func RenderLastPostsWidget(limit int) string { + return b.RenderLastPostsWidget(limit) +} diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod new file mode 100644 index 00000000000..9192b4364d0 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/home + +require ( + "gno.land/r/gnoland/blog" v0.0.0-latest + "gno.land/p/demo/ufmt" v0.0.0-latest + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/ui" v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno new file mode 100644 index 00000000000..5f2a5b9c4b5 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -0,0 +1,257 @@ +package home + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/demo/ui" + blog "gno.land/r/gnoland/blog" +) + +// XXX: p/demo/ui API is crappy, we need to make it more idiomatic +// XXX: use an updatable block system to update content from a DAO +// XXX: var blocks avl.Tree + +func Render(_ string) string { + dom := ui.DOM{Prefix: "r/gnoland/home:"} + dom.Title = "Welcome to Gno.land" + + // body + dom.Body.Append(introSection()...) + dom.Body.Append(ui.Jumbotron(worxDAO())) + dom.Body.Append(packageStaffPicks()...) + dom.Body.Append(ui.HR{}) + dom.Body.Append( + ui.Columns{3, []ui.Element{ + lastBlogposts(4), + upcomingEvents(4), + lastContributions(4), + }}, + ) + dom.Body.Append(ui.Jumbotron(discoverLinks())) + + // footer + dom.Footer.Append( + ui.Columns{2, []ui.Element{ + socialLinks(), + quoteOfTheBlock(), + }}, + ) + + // Testnet disclaimer + dom.Footer.Append( + ui.HR{}, + ui.Bold("This is a testnet."), + ui.Text("Package names are not guaranteed to be available for production."), + ) + + return dom.String() +} + +func lastBlogposts(limit int) ui.Element { + posts := blog.RenderLastPostsWidget(limit) + return ui.Element{ + ui.H3("Last Blogposts"), + ui.Text(posts), + } +} + +func lastContributions(limit int) ui.Element { + return ui.Element{ + ui.H3("Last Contributions"), + ui.Text("TODO: import r/gh"), + ui.Link{Text: "#1134", URL: "https://github.com/gnolang/gno/pull/1134"}, + } +} + +func upcomingEvents(limit int) ui.Element { + return ui.Element{ + ui.H3("Upcoming Events"), + ui.Text("TODO: import r/gnoland/events"), + } +} + +func introSection() ui.Element { + return ui.Element{ + ui.H3("An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination."), + ui.Paragraph("If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how."), + ui.Paragraph("Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today."), + } +} + +func worxDAO() ui.Element { + // WorxDAO + // XXX(manfred): please, let me finish a v0, then we can iterate + // highest level == highest responsibility + // teams are responsible for components they don't owne + // flag : realm maintainers VS facilitators + // teams + // committee of trustees to create the directory + // each directory is a name, has a parent and have groups + // homepage team - blocks aggregating events + // XXX: TODO + /*` + # Directory + + * gno.land (owned by group) + * + * gnovm + * gnolang (language) + * gnovm + - current challenges / concerns / issues + * tm2 + * amino + * + + ## Contributors + ``*/ + return ui.Element{ + ui.H3("WorxDAO (WIP)"), + ui.Text(`- A + - A1 + - A1A + - A1B + - A2 + - A3 + - A3A + - A3A1 +- B +- C`), + } +} + +func quoteOfTheBlock() ui.Element { + quotes := []string{ + "Gno is for Truth.", + "Gno is for Social Coordination.", + "Gno is _not only_ for DeFi.", + "Now, you Gno.", + "Come for the Go, Stay for the Gno.", + } + height := std.GetHeight() + idx := int(height) % len(quotes) + qotb := quotes[idx] + + return ui.Element{ + ui.H3(ufmt.Sprintf("Quote of the ~Day~Block#%d", height)), + ui.Quote(qotb), + } +} + +func socialLinks() ui.Element { + return ui.Element{ + ui.H3("Socials"), + ui.BulletList{ + // XXX: improve UI to support a nice GO api for such links + ui.Text("Check out our [community projects](https://github.com/gnolang/awesome-gno)"), + ui.Text("![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)"), + ui.Text("![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)"), + ui.Text("![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)"), + ui.Text("![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)"), + }, + } +} + +func packageStaffPicks() ui.Element { + // XXX: make it modifiable from a DAO + return ui.Element{ + ui.H3("Explore New Packages and Realms"), + ui.Columns{ + 3, + []ui.Element{ + { + ui.H4("r/gnoland"), + ui.BulletList{ + ui.Link{URL: "r/gnoland/blog"}, + ui.Link{URL: "r/gnoland/dao"}, + ui.Link{URL: "r/gnoland/faucet"}, + ui.Link{URL: "r/gnoland/home"}, + ui.Link{URL: "r/gnoland/pages"}, + }, + ui.H4("r/system"), + ui.BulletList{ + ui.Link{URL: "r/system/names"}, + ui.Link{URL: "r/system/rewards"}, + ui.Link{URL: "r/system/validators"}, + }, + }, { + ui.H4("r/demo"), + ui.BulletList{ + ui.Link{URL: "r/demo/boards"}, + ui.Link{URL: "r/demo/users"}, + ui.Link{URL: "r/demo/banktest"}, + ui.Link{URL: "r/demo/foo20"}, + ui.Link{URL: "r/demo/foo721"}, + ui.Link{URL: "r/demo/microblog"}, + ui.Link{URL: "r/demo/nft"}, + ui.Link{URL: "r/demo/types"}, + ui.Link{URL: "r/demo/art"}, + ui.Link{URL: "r/demo/groups"}, + ui.Text("..."), + }, + }, { + ui.H4("p/demo"), + ui.BulletList{ + ui.Link{URL: "p/demo/avl"}, + ui.Link{URL: "p/demo/blog"}, + ui.Link{URL: "p/demo/ui"}, + ui.Link{URL: "p/demo/ufmt"}, + ui.Link{URL: "p/demo/merkle"}, + ui.Link{URL: "p/demo/bf"}, + ui.Link{URL: "p/demo/flow"}, + ui.Link{URL: "p/demo/gnode"}, + ui.Link{URL: "p/demo/grc/grc20"}, + ui.Link{URL: "p/demo/grc/grc721"}, + ui.Text("..."), + }, + }, + }, + }, + } +} + +func discoverLinks() ui.Element { + return ui.Element{ + ui.Text(`
+
+ +### Learn about Gno.land + +- [About](/about) +- [GitHub](https://github.com/gnolang) +- [Subscribe](#subscribe) +- [Tokenomics (soon)](#) +- [Blog](/blog) +- [Events](/events) +- [Partners, Fund, Grants](/partners) + +
+ +
+ +### Build with Gnolang + +- [Gno dev with CLI (soon)](#) +- [Explore the Universe](/ecosystem) +- [Test in the browser (soon)](#) +- [About the Gnolang Language](/gnolang) +- [Docs/ Tutorials](https://github.com/gnolang) +- [Gno by example](https://gno-by-example.com/) +- [Getting started video (soon)](#) + +
+
+ +### Explore the universe + +- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +- [Install Gno Key instructions](/r/demo/boards:testboard/5) +- [Testnets 3](https://test3.gno.land/) +- [Testnets 2](https://test2.gno.land/) +- [Explorer links(soon)](#) +- [Testnet Tokens (faucet)](https://test3.gno.land/faucet) + +
+
`), + } +} diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno new file mode 100644 index 00000000000..1fffc11792f --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -0,0 +1,188 @@ +package main + +import "gno.land/r/gnoland/home" + +func main() { + println(home.Render("")) +} + +// Output: +// # Welcome to Gno.land +// +// ### An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination. +// +// +// If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how. +// +// +// Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today. +// +//
+// +// ### WorxDAO (WIP) +// +// - A +// - A1 +// - A1A +// - A1B +// - A2 +// - A3 +// - A3A +// - A3A1 +// - B +// - C +//
+// +// ### Explore New Packages and Realms +// +//
+//
+// +// #### r/gnoland +// +// - [r/gnoland/blog](r/gnoland/blog) +// - [r/gnoland/dao](r/gnoland/dao) +// - [r/gnoland/faucet](r/gnoland/faucet) +// - [r/gnoland/home](r/gnoland/home) +// - [r/gnoland/pages](r/gnoland/pages) +// +// #### r/system +// +// - [r/system/names](r/system/names) +// - [r/system/rewards](r/system/rewards) +// - [r/system/validators](r/system/validators) +// +//
+//
+// +// #### r/demo +// +// - [r/demo/boards](r/demo/boards) +// - [r/demo/users](r/demo/users) +// - [r/demo/banktest](r/demo/banktest) +// - [r/demo/foo20](r/demo/foo20) +// - [r/demo/foo721](r/demo/foo721) +// - [r/demo/microblog](r/demo/microblog) +// - [r/demo/nft](r/demo/nft) +// - [r/demo/types](r/demo/types) +// - [r/demo/art](r/demo/art) +// - [r/demo/groups](r/demo/groups) +// - ... +// +//
+//
+// +// #### p/demo +// +// - [p/demo/avl](p/demo/avl) +// - [p/demo/blog](p/demo/blog) +// - [p/demo/ui](p/demo/ui) +// - [p/demo/ufmt](p/demo/ufmt) +// - [p/demo/merkle](p/demo/merkle) +// - [p/demo/bf](p/demo/bf) +// - [p/demo/flow](p/demo/flow) +// - [p/demo/gnode](p/demo/gnode) +// - [p/demo/grc/grc20](p/demo/grc/grc20) +// - [p/demo/grc/grc721](p/demo/grc/grc721) +// - ... +// +//
+//
+// +// +// --- +// +//
+//
+// +// ### Last Blogposts +// +// +//
+//
+// +// ### Upcoming Events +// +// TODO: import r/gnoland/events +//
+//
+// +// ### Last Contributions +// +// TODO: import r/gh +// [#1134](https://github.com/gnolang/gno/pull/1134) +//
+//
+// +//
+// +//
+//
+// +// ### Learn about Gno.land +// +// - [About](/about) +// - [GitHub](https://github.com/gnolang) +// - [Subscribe](#subscribe) +// - [Tokenomics (soon)](#) +// - [Blog](/blog) +// - [Events](/events) +// - [Partners, Fund, Grants](/partners) +// +//
+// +//
+// +// ### Build with Gnolang +// +// - [Gno dev with CLI (soon)](#) +// - [Explore the Universe](/ecosystem) +// - [Test in the browser (soon)](#) +// - [About the Gnolang Language](/gnolang) +// - [Docs/ Tutorials](https://github.com/gnolang) +// - [Gno by example](https://gno-by-example.com/) +// - [Getting started video (soon)](#) +// +//
+//
+// +// ### Explore the universe +// +// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +// - [Install Gno Key instructions](/r/demo/boards:testboard/5) +// - [Testnets 3](https://test3.gno.land/) +// - [Testnets 2](https://test2.gno.land/) +// - [Explorer links(soon)](#) +// - [Testnet Tokens (faucet)](https://test3.gno.land/faucet) +// +//
+//
+//
+// +// +//
+//
+// +// ### Socials +// +// - Check out our [community projects](https://github.com/gnolang/awesome-gno) +// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn) +// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland) +// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland) +// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland) +// +//
+//
+// +// ### Quote of the ~Day~Block#123 +// +// > Now, you Gno. +// +//
+//
+// +// +// --- +// +// **This is a testnet.** +// Package names are not guaranteed to be available for production. diff --git a/gno.land/cmd/gnoweb/pages/ABOUT.md b/examples/gno.land/r/gnoland/pages/page_about.gno similarity index 73% rename from gno.land/cmd/gnoweb/pages/ABOUT.md rename to examples/gno.land/r/gnoland/pages/page_about.gno index a5678a7349a..9aba4e39f76 100644 --- a/gno.land/cmd/gnoweb/pages/ABOUT.md +++ b/examples/gno.land/r/gnoland/pages/page_about.gno @@ -1,4 +1,10 @@ -# About Gno.land +package gnopages + +func init() { + path := "about" + title := "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)" + // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem." + body := `# About Gno.land Gno.land is a platform to write smart contracts in Gnolang (Gno). Using an interpreted version of the general-purpose programming language Golang (Go), developers can write smart contracts and other blockchain apps without having to learn a language that’s exclusive to a single ecosystem. @@ -9,4 +15,6 @@ Proof of Contribution rewards contributors from technical and non-technical back This consensus mechanism also achieves higher security with fewer validators, optimizing resources for a greener, more sustainable, and enduring blockchain ecosystem. Any blockchain using Gnolang achieves succinctness, composability, expressivity, and completeness not found in any other smart contract platform. -By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today. +By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today.` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno new file mode 100644 index 00000000000..68969c44529 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -0,0 +1,35 @@ +package gnopages + +func init() { + var ( + path = "ecosystem" + title = "Discover Gno.land Ecosystem Projects & Initiatives" + // XXX: description = "Dive further into the Gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." + body = `# Gno Ecosystem + +## Gno.land Space + +For the best onboarding experience, head over to [Gno.land Space](https://www.gnoland.space/) open ecosystem. Here you can set up your Gno wallet, explore existing community-written Gno smart contracts (realms), and become part of our vibrant community by joining [Gno.land Discord](https://discord.com/invite/x76qK4ttHC). + +## Gno Studio (IDE) + +Gno IDE is a web-based application helping builders quickly spin up Gno realms and packages right on their browsers. Offering a smooth and intuitive UX for building on Gno, you’ll find multiple modes for customizability with all the features you’d expect from an IDE, such as auto compilation in the editor, debugging, and extensive testing capability. + +## Gnoscan + +Developed by the Onbloc team, Gnoscan is Gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find information that resides on the Gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. Gnoscan makes our on-chain data easy to read and intuitive to discover. [Go to Gnoscan.](https://gnoscan.io/) + +## Adena + +Adena is a user-friendly non-custodial wallet for Gno.land. Open-source and developed by Onbloc, Adena currently powers all transactions on Gno.land, allowing gnomes to interact easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a high-quality interface, support for NFTs and custom tokens, and seamless integration. [Get started here.](https://adena.app/) + +## Gnoswap + +Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on Gno.land and is an automated market maker (AMM) protocol written in Gnolang that allows for permissionless token exchanges on the platform. + +## Gno.land Developer Portal + +Through the Gno.land Developer Portal, new developers can explore the exciting world of Gnolang (Gno), a novel programming language that powers the Gno.land blockchain. If you want to interact with Gno.land, start writing a realm, build a dApp, or even port a Solidity contract to a Gnolang realm, you’ll find the resources to [get started here](https://docs.onbloc.xyz/).` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_events.gno b/examples/gno.land/r/gnoland/pages/page_events.gno new file mode 100644 index 00000000000..18e7faeb3d3 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_events.gno @@ -0,0 +1,151 @@ +package gnopages + +func init() { + var ( + path = "events" + title = "Gno.land Core Team Attends Industry Events & Meetups" + // XXX: description = "If you’re interested in learning more about Gno.land, you can join us at major blockchain industry events throughout the year either in person or virtually." + body = `# Events + +If you’re interested in building web3 with us, catch up with Gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform. + +--- + +## Upcoming Events + +
+
+ +### EthCC + +- **Come Meet Us at our Booth** +- Paris, July 17 - 20, 2023 +- Manfred Touron + +[Learn more](https://www.ethcc.io/) + +
+
+ +### Nebular Summit Gno.land for Developers + +- Paris, July 24 - 25, 2023 +- Manfred Touron + +[Learn more](https://www.nebular.builders/) + +
+
+ +### GopherCon EU + +- **Come Meet Us at our Booth** +- Berlin, July 26 - 29, 2023 + +[Learn more](https://gophercon.eu/) + +
+ +
+ +### GopherCon US + +- **Come Meet Us at our Booth** +- San Diego, September 26 - 29, 2023 + +[Learn more](https://www.gophercon.com/) + +
+
+ +--- + +## Past Events + +
+ +
+ +### Eth Seoul + +- **The Evolution of Smart Contracts: A Journey into Gno.land** +- Seoul, June 3, 2023 +- Manfred Touron + +[Learn more](https://2023.ethseoul.org/) + +
+
+ +### BUIDL Asia + +- **Proof of Contribution in Gno.land** +- Seoul, June 6, 2023 +- Manfred Touron + +[Learn more](https://www.buidl.asia/) + +
+
+ +### Game Developer Conference + +- **Side Event: Web3 Gaming Apps Powered by Gno** +- San Francisco, Mach 23, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### EthDenver + +- **Side Event: Discover Gno.land** +- Denver, Feb 24 - Mar 5, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### Istanbul Blockchain Week + +- Istanbul, Nov 14 - 17, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4) + +
+
+ +### Web Summit Buckle Up and Build with Cosmos + +- Lisbon, Nov 1 - 4, 2022 +- Manfred Touron + +
+
+ +### Cosmoverse + +- Medallin, Sept 26 - 28, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk) + +
+
+ +### Berlin Blockchain Week Buckle Up and Build with Cosmos + +- Berlin, Sept 11 - 18, 2022 + +[Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI) + +
+
` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno new file mode 100644 index 00000000000..f0c2bfe276d --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno @@ -0,0 +1,43 @@ +package gnopages + +func init() { + var ( + path = "gnolang" + title = "Gnolang (Gno) Is a Complete Language for Blockchain" + // XXX: description = "Gnolang (Gno) is an interpretation of the popular Golang (Go) language for blockchain created by Tendermint and Cosmos founder Jae Kwon." + body = `# About the Gnolang, the Gno Language + +[Gnolang](https://github.com/gnolang/gno/blob/master/LICENSE.md) (Gno) is an interpretation of the widely-used Golang (Go) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same. + +Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK). + +## How Gno Differs from Go + +![Gno and Go differences](static/img/gno-language/go-and-gno.jpg) + +The composable nature of Go/Gno allows for type-checked interactions between contracts, making Gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on Gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts. + +![Example of Gno code](static/img/gno-language/code-example.jpg) + +## Gno Inherits Go’s Built-in Security Features + +Go supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users. + +Another major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes. + +## Gno vs Solidity + +The most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies. + +Solidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base. + +## Gno Is Essential for the Wider Adoption of Web3 + +Gno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence. + +Using Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism. + +The Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gor.gno b/examples/gno.land/r/gnoland/pages/page_gor.gno new file mode 100644 index 00000000000..3a6bb022e09 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gor.gno @@ -0,0 +1,221 @@ +package gnopages + +func init() { + path := "gor" + title := "Game of Realms Content For The Best Contributors" + // XXX: description := "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool." + body := `# Game of Realms + +
+ +### Game of Realms + +The first high-stakes contest will see participants compete for tiered membership to co-own the Gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. + +
+ +The competition is currently in phase one – for advanced developers only. + +Once the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors. + +If you want to stack ATOM rewards and play a key role in the success of Gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today. + +
+ +
+
+
+ +## Phase I. (ongoing) + +- + +- + +- + +
+
+ +## Phase II. (Locked) + +
+
+
+ +
+ +
+ +## Evaluation DAO + +This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the Gno.land platform are fairly rewarded. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors are still in discussions, proposing additional ideas, and seeing how the proposal for the Evaluation DAO evolves over time. + +
+ + + +
+ +See [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most up-to-date discussion so far on how voting should work for the DAO, what the responsibilities are, how to join, etc. + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +## Tutorials + +To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to Gno.land. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +## Governance Module + +Can you define and implement a governance contract suite that rivals existing ones, such as the Cosmos Hub? Show us how! We’re looking for the fairest and most efficient governance solution possible. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors have made significant progress teaming up to complete this challenge but discussions and additional ideas are still ongoing. + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +## Register Now + + +
+
+ +
+
+ + +
+ +
+ + +
+ + +
+
+
+ +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_partners.gno b/examples/gno.land/r/gnoland/pages/page_partners.gno new file mode 100644 index 00000000000..440302437fa --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_partners.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "partners" + title := "Partners" + // XXX: description := """ + body := `## Partnerships + +### Fund and Grants Program + +Are you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, or smart contract libraries on Gno.land, you can apply for a grant. The Gno.land Ecosystem Fund and Grants program provides financial contributions for individuals and teams to innovate on the platform. + +
+ +[More information here](https://github.com/gnolang/ecosystem-fund-grants) + +
+` + + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_start.gno b/examples/gno.land/r/gnoland/pages/page_start.gno new file mode 100644 index 00000000000..a36ec6e52b1 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_start.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "start" + title := "Getting Started with Gno" + // XXX: description := "" + + // TODO: codegen to use README files here + + /* TODO: port previous message: This is a demo of Gno smart contract programming. This document was + constructed by Gno onto a smart contract hosted on the data Realm + name ["gno.land/r/demo/boards"](https://gno.land/r/demo/boards/) + ([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)). + */ + body := `## Getting Started with Gno + +- [Install Gno Key](/r/demo/boards:testboard/5) +- TODO: add more links +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_testnets.gno b/examples/gno.land/r/gnoland/pages/page_testnets.gno new file mode 100644 index 00000000000..b6c09ab71ee --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_testnets.gno @@ -0,0 +1,19 @@ +package gnopages + +func init() { + path := "testnets" + title := "Gno.land Testnets" + // XXX: description := """ + body := `## Other testnets + +- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** +- _[test3.gno.land](https://test3.gno.land) (latest)_ +- _[test2.gno.land](https://test2.gno.land) (archive)_ +- _[test1.gno.land](https://test1.gno.land) (archive)_ + +## Local devnet + +See CONTRIBUTING.md on GitHub. +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_tokenomics.gno b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno new file mode 100644 index 00000000000..de899ae0a70 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno @@ -0,0 +1,11 @@ +package gnopages + +func init() { + var ( + path = "tokenomics" + title = "Gno.land Tokenomics" + // XXX: description = """ + body = `Lorem Ipsum` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/pages.gno b/examples/gno.land/r/gnoland/pages/pages.gno index dbc3d855880..6e1f117d1d5 100644 --- a/examples/gno.land/r/gnoland/pages/pages.gno +++ b/examples/gno.land/r/gnoland/pages/pages.gno @@ -4,16 +4,12 @@ import ( "gno.land/p/demo/blog" ) -var b = &blog.Blog{ - Title: "Gnoland's Pages", - Prefix: "/r/gnoland/pages:", -} +// TODO: switch from p/blog to p/pages -func init() { - _ = b.NewPost("", "gor", "Game of Realms", "Lorem Ipsum", nil) - _ = b.NewPost("", "events", "Events", "Lorem Ipsum", nil) - _ = b.NewPost("", "tokenomics", "Tokenomics", "Lorem Ipsum", nil) - _ = b.NewPost("", "start", "Getting Started", "Lorem Ipsum", nil) +var b = &blog.Blog{ + Title: "Gnoland's Pages", + Prefix: "/r/gnoland/pages:", + NoBreadcrumb: true, } func Render(path string) string { diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index 1a43153e2c8..5a6fe84ad38 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -6,48 +6,42 @@ import ( "testing" ) -func TestPackage(t *testing.T) { - std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq")) - - author := std.GetOrigCaller() - - // by default, lorem ipsum posts - { - got := Render("") - expected := ` -# Gnoland's Pages - -
- -## [Events](/r/gnoland/pages:p/events) -**[Learn More](/r/gnoland/pages:p/events)** - -
- -## [Game of Realms](/r/gnoland/pages:p/gor) -**[Learn More](/r/gnoland/pages:p/gor)** - -
- -## [Getting Started](/r/gnoland/pages:p/start) -**[Learn More](/r/gnoland/pages:p/start)** - -
- -## [Tokenomics](/r/gnoland/pages:p/tokenomics) -**[Learn More](/r/gnoland/pages:p/tokenomics)** - -
-` - assertMDEquals(t, got, expected) +func TestHome(t *testing.T) { + printedOnce := false + got := Render("") + expectedSubtrings := []string{ + "/r/gnoland/pages:p/events", + "/r/gnoland/pages:p/tokenomics", + "/r/gnoland/pages:p/start", + "/r/gnoland/pages:p/gor", + "/r/gnoland/pages:p/about", + "/r/gnoland/pages:p/gnolang", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } -func assertMDEquals(t *testing.T, got, expected string) { - t.Helper() - expected = strings.TrimSpace(expected) - got = strings.TrimSpace(got) - if expected != got { - t.Errorf("invalid render output.\nexpected %q.\ngot %q.", expected, got) +func TestAbout(t *testing.T) { + printedOnce := false + got := Render("p/about") + expectedSubtrings := []string{ + "# About Gno.land", + "Gno.land is a platform to write smart contracts in Gnolang (Gno).", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } diff --git a/examples/gno.land/r/manfred/present/admin.gno b/examples/gno.land/r/manfred/present/admin.gno new file mode 100644 index 00000000000..ff0cb075656 --- /dev/null +++ b/examples/gno.land/r/manfred/present/admin.gno @@ -0,0 +1,92 @@ +package present + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" +) + +var ( + adminAddr std.Address + moderatorList avl.Tree + inPause bool +) + +func init() { + // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + adminAddr = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" +} + +func AdminSetAdminAddr(addr std.Address) { + assertIsAdmin() + adminAddr = addr +} + +func AdminSetInPause(state bool) { + assertIsAdmin() + inPause = state +} + +func AdminAddModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), true) +} + +func AdminRemoveModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), false) // XXX: delete instead? +} + +func ModAddPost(slug, title, body, tags string) { + assertIsModerator() + + caller := std.GetOrigCaller() + tagList := strings.Split(tags, ",") + err := b.NewPost(caller, slug, title, body, tagList) + checkErr(err) +} + +func ModEditPost(slug, title, body, tags string) { + assertIsModerator() + + tagList := strings.Split(tags, ",") + err := b.GetPost(slug).Update(title, body, tagList) + checkErr(err) +} + +func isAdmin(addr std.Address) bool { + return addr == adminAddr +} + +func isModerator(addr std.Address) bool { + _, found := moderatorList.Get(addr.String()) + return found +} + +func assertIsAdmin() { + caller := std.GetOrigCaller() + if !isAdmin(caller) { + panic("access restricted.") + } +} + +func assertIsModerator() { + caller := std.GetOrigCaller() + if isAdmin(caller) || isModerator(caller) { + return + } + panic("access restricted") +} + +func assertNotInPause() { + if inPause { + panic("access restricted (pause)") + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/manfred/present/gno.mod b/examples/gno.land/r/manfred/present/gno.mod new file mode 100644 index 00000000000..9d1ab5b0e56 --- /dev/null +++ b/examples/gno.land/r/manfred/present/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/manfred/present + +require ( + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/blog" v0.0.0-latest +) diff --git a/examples/gno.land/r/manfred/present/present_miami23.gno b/examples/gno.land/r/manfred/present/present_miami23.gno new file mode 100644 index 00000000000..36b1980bb0b --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23.gno @@ -0,0 +1,44 @@ +package present + +func init() { + path := "miami23" + title := "Portal Loop Demo (Miami 2023)" + body := ` +# Portal Loop Demo (Miami 2023) + +Rendered by Gno. + +[Source (WIP)](https://github.com/gnolang/gno/pull/1176) + +## Portal Loop + +- DONE: Dynamic homepage, key pages, aliases, and redirects. +- TODO: Deploy with history, complete worxdao v0. +- Will replace the static gno.land site. +- Enhances local development. + +[GitHub Issue](https://github.com/gnolang/gno/issues/1108) + +## Roadmap + +- Crafting the roadmap this week, open to collaboration. +- Combining onchain (portal loop) and offchain (GitHub). +- Next week: Unveiling the official v0 roadmap. + +## Teams, DAOs, Projects + +- Developing worxDAO contracts for directories of projects and teams. +- GitHub teams and projects align with this structure. +- CODEOWNER file updates coming. +- Initial teams announced next week. + +## Tech Team Retreat Plan + +- Continue Portal Loop. +- Consider dApp development. +- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +- Engage in workshops. +- Connect and have fun with colleagues. +` + _ = b.NewPost(adminAddr, path, title, body, []string{"demo", "portal-loop", "miami"}) +} diff --git a/examples/gno.land/r/manfred/present/present_miami23_filetest.gno b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno new file mode 100644 index 00000000000..05c41905060 --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno @@ -0,0 +1,57 @@ +package main + +import "gno.land/r/manfred/present" + +func main() { + println(present.Render("")) + println("------------------------------------") + println(present.Render("p/miami23")) +} + +// Output: +//
+// +// ## [Portal Loop Demo (Miami 2023)](/r/manfred/present:p/miami23) +// **[Learn More](/r/manfred/present:p/miami23)** +// +//
+// ------------------------------------ +// # Portal Loop Demo (Miami 2023) +// +// Rendered by Gno. +// +// [Source (WIP)](https://github.com/gnolang/gno/pull/1176) +// +// ## Portal Loop +// +// - DONE: Dynamic homepage, key pages, aliases, and redirects. +// - TODO: Deploy with history, complete worxdao v0. +// - Will replace the static gno.land site. +// - Enhances local development. +// +// [GitHub Issue](https://github.com/gnolang/gno/issues/1108) +// +// ## Roadmap +// +// - Crafting the roadmap this week, open to collaboration. +// - Combining onchain (portal loop) and offchain (GitHub). +// - Next week: Unveiling the official v0 roadmap. +// +// ## Teams, DAOs, Projects +// +// - Developing worxDAO contracts for directories of projects and teams. +// - GitHub teams and projects align with this structure. +// - CODEOWNER file updates coming. +// - Initial teams announced next week. +// +// ## Tech Team Retreat Plan +// +// - Continue Portal Loop. +// - Consider dApp development. +// - Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +// - Engage in workshops. +// - Connect and have fun with colleagues. +// +// [#demo](/r/manfred/present:t/demo) [#portal-loop](/r/manfred/present:t/portal-loop) [#miami](/r/manfred/present:t/miami) +// +// by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 1970-01-01 12:00am UTC diff --git a/examples/gno.land/r/manfred/present/presentations.gno b/examples/gno.land/r/manfred/present/presentations.gno new file mode 100644 index 00000000000..8a99f502e86 --- /dev/null +++ b/examples/gno.land/r/manfred/present/presentations.gno @@ -0,0 +1,17 @@ +package present + +import ( + "gno.land/p/demo/blog" +) + +// TODO: switch from p/blog to p/present + +var b = &blog.Blog{ + Title: "Manfred's Presentations", + Prefix: "/r/manfred/present:", + NoBreadcrumb: true, +} + +func Render(path string) string { + return b.Render(path) +} diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md b/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md index b1dc2de58df..4926713e8fa 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md @@ -51,7 +51,7 @@ An example of a category is a bounty, a chore, a defect, or a document. A contribution is associated with a pull request. A contribution has an evaluation life cycle. A submission time is set when a contribution is added. -A last evaluation time is set when a contribution is evaluated and approved by a memeber. +A last evaluation time is set when a contribution is evaluated and approved by a member. An approval time is set when a contribution is approved by all members (or when a future threshold is reached) #### Submission diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index ed939b56fdb..1ec801bb971 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -9,7 +9,7 @@ import ( type Committee struct { members []std.Address // TODO - use avl tree or address set? - categories avl.Tree // A catagory is mapped to a list of evaluation criteria + categories avl.Tree // A category is mapped to a list of evaluation criteria evaluation *Evaluation } @@ -84,7 +84,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c if !c.isMember(std.GetOrigCaller()) { return -1, false } - // Check the category of the PR matches a catagory this committee evaluates + // Check the category of the PR matches a category this committee evaluates // TODO check the category is an approved category if c.categories.Has(pr.category) { return c.evaluation.AddContribution(pr, contributor) diff --git a/gno.land/Makefile b/gno.land/Makefile index 1fd1aaa1f78..be1db280c40 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -5,6 +5,9 @@ help: rundep=go run -modfile ../misc/devdeps/go.mod +.PHONY: gnoland.start +gnoland.start:; go run ./cmd/gnoland start + .PHONY: build build: build.gnoland build.gnokey build.gnoweb build.gnofaucet build.gnotxsync diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index b2134d86ea9..a42e1df1bf0 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -1,9 +1,12 @@ package main import ( + "bufio" "context" + "errors" "flag" "fmt" + "os" "path/filepath" "strings" "time" @@ -17,6 +20,9 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/privval" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" + eventstorecfg "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -35,6 +41,10 @@ type startCfg struct { rootDir string genesisMaxVMCycles int64 config string + + txEventStoreType string + txEventStorePath string + nodeConfigPath string } func newStartCmd(io *commands.IO) *commands.Command { @@ -47,8 +57,8 @@ func newStartCmd(io *commands.IO) *commands.Command { ShortHelp: "Run the full node", }, cfg, - func(_ context.Context, args []string) error { - return execStart(cfg, args, io) + func(_ context.Context, _ []string) error { + return execStart(cfg, io) }, ) } @@ -114,18 +124,62 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { &c.config, "config", "", - "config file (optional)", + "the flag config file (optional)", + ) + + fs.StringVar( + &c.nodeConfigPath, + "tm2-node-config", + "", + "the node TOML config file path (optional)", + ) + + fs.StringVar( + &c.txEventStoreType, + "tx-event-store-type", + null.EventStoreType, + fmt.Sprintf( + "type of transaction event store [%s]", + strings.Join( + []string{ + null.EventStoreType, + file.EventStoreType, + }, + ", ", + ), + ), + ) + + fs.StringVar( + &c.txEventStorePath, + "tx-event-store-path", + "", + fmt.Sprintf("path for the file tx event store (required if event store is '%s')", file.EventStoreType), ) } -func execStart(c *startCfg, args []string, io *commands.IO) error { +func execStart(c *startCfg, io *commands.IO) error { logger := log.NewTMLogger(log.NewSyncWriter(io.Out)) rootDir := c.rootDir - cfg := config.LoadOrMakeConfigWithOptions(rootDir, func(cfg *config.Config) { - cfg.Consensus.CreateEmptyBlocks = true - cfg.Consensus.CreateEmptyBlocksInterval = 0 * time.Second - }) + var ( + cfg *config.Config + loadCfgErr error + ) + + // Set the node configuration + if c.nodeConfigPath != "" { + // Load the node configuration + // from the specified path + cfg, loadCfgErr = config.LoadConfigFile(c.nodeConfigPath) + } else { + // Load the default node configuration + cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(rootDir, nil) + } + + if loadCfgErr != nil { + return fmt.Errorf("unable to load node configuration, %w", loadCfgErr) + } // create priv validator first. // need it to generate genesis.json @@ -135,16 +189,34 @@ func execStart(c *startCfg, args []string, io *commands.IO) error { // write genesis file if missing. genesisFilePath := filepath.Join(rootDir, cfg.Genesis) + + genesisTxs, genesisTxsErr := loadGenesisTxs(c.genesisTxsFile, c.chainID, c.genesisRemote) + if genesisTxsErr != nil { + return fmt.Errorf("unable to load genesis txs, %w", genesisTxsErr) + } + if !osm.FileExists(genesisFilePath) { - genDoc := makeGenesisDoc( + genDoc, err := makeGenesisDoc( priv.GetPubKey(), c.chainID, c.genesisBalancesFile, - loadGenesisTxs(c.genesisTxsFile, c.chainID, c.genesisRemote), + genesisTxs, ) + if err != nil { + return fmt.Errorf("unable to generate genesis.json, %w", err) + } + writeGenesisFile(genDoc, genesisFilePath) } + // Initialize the indexer config + txEventStoreCfg, err := getTxEventStoreConfig(c) + if err != nil { + return fmt.Errorf("unable to parse indexer config, %w", err) + } + + cfg.TxEventStore = txEventStoreCfg + // create application and node. gnoApp, err := gnoland.NewApp(rootDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) if err != nil { @@ -180,13 +252,37 @@ func execStart(c *startCfg, args []string, io *commands.IO) error { select {} // run forever } +// getTxEventStoreConfig constructs an event store config from provided user options +func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) { + var cfg *eventstorecfg.Config + + switch c.txEventStoreType { + case file.EventStoreType: + if c.txEventStorePath == "" { + return nil, errors.New("unspecified file transaction indexer path") + } + + // Fill out the configuration + cfg = &eventstorecfg.Config{ + EventStoreType: file.EventStoreType, + Params: map[string]any{ + file.Path: c.txEventStorePath, + }, + } + default: + cfg = eventstorecfg.DefaultEventStoreConfig() + } + + return cfg, nil +} + // Makes a local test genesis doc with local privValidator. func makeGenesisDoc( pvPub crypto.PubKey, chainID string, genesisBalancesFile string, genesisTxs []std.Tx, -) *bft.GenesisDoc { +) (*bft.GenesisDoc, error) { gen := &bft.GenesisDoc{} gen.GenesisTime = time.Now() @@ -210,8 +306,10 @@ func makeGenesisDoc( } // Load distribution. - balances := loadGenesisBalances(genesisBalancesFile) - // debug: for _, balance := range balances { fmt.Println(balance) } + balances, err := loadGenesisBalances(genesisBalancesFile) + if err != nil { + return nil, fmt.Errorf("unable to load genesis balances, %w", err) + } // Load initial packages from examples. test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") @@ -257,7 +355,7 @@ func makeGenesisDoc( Balances: balances, Txs: txs, } - return gen + return gen, nil } func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { @@ -271,11 +369,24 @@ func loadGenesisTxs( path string, chainID string, genesisRemote string, -) []std.Tx { - txs := []std.Tx{} - txsBz := osm.MustReadFile(path) - txsLines := strings.Split(string(txsBz), "\n") - for _, txLine := range txsLines { +) ([]std.Tx, error) { + txs := make([]std.Tx, 0) + + if !osm.FileExists(path) { + // No initial transactions + return txs, nil + } + + txsFile, openErr := os.Open(path) + if openErr != nil { + return nil, fmt.Errorf("unable to open genesis txs file, %w", openErr) + } + + scanner := bufio.NewScanner(txsFile) + + for scanner.Scan() { + txLine := scanner.Text() + if txLine == "" { continue // skip empty line } @@ -285,19 +396,40 @@ func loadGenesisTxs( txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) var tx std.Tx - amino.MustUnmarshalJSON([]byte(txLine), &tx) + + if unmarshalErr := amino.UnmarshalJSON([]byte(txLine), &tx); unmarshalErr != nil { + return nil, fmt.Errorf("unable to amino unmarshal tx, %w", unmarshalErr) + } + txs = append(txs, tx) } - return txs + if scanErr := scanner.Err(); scanErr != nil { + return nil, fmt.Errorf("error encountered while scanning, %w", scanErr) + } + + return txs, nil } -func loadGenesisBalances(path string) []string { +func loadGenesisBalances(path string) ([]string, error) { // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot - balances := []string{} - content := osm.MustReadFile(path) - lines := strings.Split(string(content), "\n") - for _, line := range lines { + balances := make([]string, 0) + + if !osm.FileExists(path) { + // No initial balances + return balances, nil + } + + balancesFile, openErr := os.Open(path) + if openErr != nil { + return nil, fmt.Errorf("unable to open genesis balances file, %w", openErr) + } + + scanner := bufio.NewScanner(balancesFile) + + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) // remove comments. @@ -309,12 +441,16 @@ func loadGenesisBalances(path string) []string { continue } - parts := strings.Split(line, "=") - if len(parts) != 2 { - panic("invalid genesis_balance line: " + line) + if len(strings.Split(line, "=")) != 2 { + return nil, fmt.Errorf("invalid genesis_balance line: %s", line) } balances = append(balances, line) } - return balances + + if scanErr := scanner.Err(); scanErr != nil { + return nil, fmt.Errorf("error encountered while scanning, %w", scanErr) + } + + return balances, nil } diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index e8a2feac0d7..0d9398cb8e2 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -18,7 +18,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gorilla/mux" "github.com/gotuna/gotuna" @@ -32,59 +31,93 @@ const ( qFileStr = "vm/qfile" ) +var startedAt time.Time + var flags struct { - bindAddr string - remoteAddr string - captchaSite string - faucetURL string - viewsDir string - pagesDir string - helpChainID string - helpRemote string + BindAddr string + RemoteAddr string + CaptchaSite string + FaucetURL string + ViewsDir string + HelpChainID string + HelpRemote string + WithAnalytics bool } -var startedAt time.Time - func init() { - flag.StringVar(&flags.remoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") - flag.StringVar(&flags.bindAddr, "bind", "127.0.0.1:8888", "server listening address") - flag.StringVar(&flags.captchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") - flag.StringVar(&flags.faucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") - flag.StringVar(&flags.viewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") - flag.StringVar(&flags.pagesDir, "pages-dir", "./cmd/gnoweb/pages", "pages directory location") - flag.StringVar(&flags.helpChainID, "help-chainid", "dev", "help page's chainid") - flag.StringVar(&flags.helpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.StringVar(&flags.RemoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") + flag.StringVar(&flags.BindAddr, "bind", "127.0.0.1:8888", "server listening address") + flag.StringVar(&flags.CaptchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") + flag.StringVar(&flags.FaucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") + flag.StringVar(&flags.ViewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") // XXX: replace with goembed + flag.StringVar(&flags.HelpChainID, "help-chainid", "dev", "help page's chainid") + flag.StringVar(&flags.HelpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.BoolVar(&flags.WithAnalytics, "with-analytics", false, "enable privacy-first analytics") startedAt = time.Now() } func makeApp() gotuna.App { app := gotuna.App{ - ViewFiles: os.DirFS(flags.viewsDir), + ViewFiles: os.DirFS(flags.ViewsDir), Router: gotuna.NewMuxRouter(), Static: static.EmbeddedStatic, // StaticPrefix: "static/", } - app.Router.Handle("/", handlerHome(app)) - app.Router.Handle("/about", handlerAbout(app)) - app.Router.Handle("/game-of-realms", handlerGor(app)) - app.Router.Handle("/faucet", handlerFaucet(app)) - app.Router.Handle("/r/demo/boards:gnolang/6", handlerRedirect(app)) + + // realm aliases + aliases := map[string]string{ + "/": "/r/gnoland/home", + "/about": "/r/gnoland/pages:p/about", + "/gnolang": "/r/gnoland/pages:p/gnolang", + "/ecosystem": "/r/gnoland/pages:p/ecosystem", + "/partners": "/r/gnoland/pages:p/partners", + "/testnets": "/r/gnoland/pages:p/testnets", + "/start": "/r/gnoland/pages:p/start", + "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm + "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm + } + for from, to := range aliases { + app.Router.Handle(from, handlerRealmAlias(app, to)) + } + // http redirects + redirects := map[string]string{ + "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary + "/blog": "/r/gnoland/blog", + "/gor": "/game-of-realms", + "/grants": "/partners", + "/language": "/gnolang", + "/getting-started": "/start", + } + for from, to := range redirects { + app.Router.Handle(from, handlerRedirect(app, to)) + } + // realm routes // NOTE: see rePathPart. app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(app)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(app)) + + // other + app.Router.Handle("/faucet", handlerFaucet(app)) app.Router.Handle("/static/{path:.+}", handlerStaticFile(app)) app.Router.Handle("/favicon.ico", handlerFavicon(app)) + + // api app.Router.Handle("/status.json", handlerStatusJSON(app)) + + app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.RequestURI + handleNotFound(app, path, w, r) + }) return app } func main() { flag.Parse() - fmt.Printf("Running on http://%s\n", flags.bindAddr) + fmt.Printf("Running on http://%s\n", flags.BindAddr) server := &http.Server{ - Addr: flags.bindAddr, + Addr: flags.BindAddr, ReadHeaderTimeout: 60 * time.Second, Handler: makeApp().Router, } @@ -94,50 +127,59 @@ func main() { } } -func handlerHome(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "HOME.md") - homeContent := osm.MustReadFile(md) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Smart Contract Platform Using Gnolang (Gno)"). - Set("Description", "Gno.land is the only smart contract platform using the Gnolang (Gno) programming language, an interpretation of the widely-used Golang (Go)."). - Set("HomeContent", string(homeContent)). - Render(w, r, "home.html", "funcs.html") - }) -} - -func handlerAbout(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "ABOUT.md") - mainContent := osm.MustReadFile(md) - +// handlerRealmAlias is used to render official pages from realms. +// url is intended to be shorter. +// UX is intended to be more minimalistic. +// A link to the realm realm is added. +func handlerRealmAlias(app gotuna.App, rlmpath string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)"). - Set("Description", "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem."). - Set("MainContent", string(mainContent)). - Render(w, r, "generic.html", "funcs.html") - }) -} + rlmfullpath := "gno.land" + rlmpath + querystr := "" // XXX: "?gnoweb-alias=1" + parts := strings.Split(rlmpath, ":") + switch len(parts) { + case 1: // continue + case 2: // r/realm:querystr + rlmfullpath = "gno.land" + parts[0] + querystr = parts[1] + querystr + default: + panic("should not happen") + } + rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/") + qpath := "vm/qrender" + data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr)) + res, err := makeRequest(qpath, data) + if err != nil { + writeError(w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) + return + } -func handlerGor(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "GOR.md") - mainContent := osm.MustReadFile(md) + queryParts := strings.Split(querystr, "/") + pathLinks := []pathLink{} + for i, part := range queryParts { + pathLinks = append(pathLinks, pathLink{ + URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"), + Text: part, + }) + } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("MainContent", string(mainContent)). - Set("Title", "Game of Realms Content For The Best Contributors "). - Set("Description", "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool."). - Render(w, r, "generic.html", "funcs.html") + tmpl := app.NewTemplatingEngine() + // XXX: extract title from realm's output + // XXX: extract description from realm's output + tmpl.Set("RealmName", rlmname) + tmpl.Set("RealmPath", rlmpath) + tmpl.Set("Query", querystr) + tmpl.Set("PathLinks", pathLinks) + tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) + tmpl.Set("IsAlias", true) + tmpl.Render(w, r, "realm_render.html", "funcs.html") }) } func handlerFaucet(app gotuna.App) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { app.NewTemplatingEngine(). - Set("captchaSite", flags.captchaSite). - Set("faucetURL", flags.faucetURL). + Set("Flags", flags). Render(w, r, "faucet.html", "funcs.html") }) } @@ -192,12 +234,13 @@ func handlerStatusJSON(app gotuna.App) http.Handler { }) } -// XXX temporary. -func handlerRedirect(app gotuna.App) http.Handler { +func handlerRedirect(app gotuna.App, to string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/r/boards:gnolang/3", http.StatusFound) - app.NewTemplatingEngine(). - Render(w, r, "home.html", "funcs.html") + http.Redirect(w, r, to, http.StatusFound) + tmpl := app.NewTemplatingEngine() + tmpl.Set("To", to) + tmpl.Set("Flags", flags) + tmpl.Render(w, r, "redirect.html", "funcs.html") }) } @@ -232,10 +275,9 @@ func handlerRealmMain(app gotuna.App) http.Handler { tmpl := app.NewTemplatingEngine() tmpl.Set("FuncName", funcName) tmpl.Set("RealmPath", rlmpath) - tmpl.Set("Remote", flags.helpRemote) - tmpl.Set("ChainID", flags.helpChainID) tmpl.Set("DirPath", pathOf(rlmpath)) tmpl.Set("FunctionSignatures", fsigs) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_help.html", "funcs.html") } else { // Ensure realm exists. TODO optimize. @@ -297,12 +339,14 @@ func handleRealmRender(app gotuna.App, w http.ResponseWriter, r *http.Request) { } // Render template. tmpl := app.NewTemplatingEngine() - + // XXX: extract title from realm's output + // XXX: extract description from realm's output tmpl.Set("RealmName", rlmname) tmpl.Set("RealmPath", rlmpath) tmpl.Set("Query", querystr) tmpl.Set("PathLinks", pathLinks) tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_render.html", "funcs.html") } @@ -345,6 +389,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirURI", diruri) tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("Files", files) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_dir.html", "funcs.html") } else { // Request is for a file. @@ -362,6 +407,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("FileName", filename) tmpl.Set("FileContents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_file.html", "funcs.html") } } @@ -371,7 +417,7 @@ func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) // Height: height, XXX // Prove: false, XXX } - remote := flags.remoteAddr + remote := flags.RemoteAddr cli := client.NewHTTP(remote, "/websocket") qres, err := cli.ABCIQueryWithOptions( qpath, data, opts2) @@ -432,11 +478,20 @@ func handleNotFound(app gotuna.App, path string, w http.ResponseWriter, r *http. app.NewTemplatingEngine(). Set("title", "Not found"). Set("path", path). + Set("Flags", flags). Render(w, r, "404.html", "funcs.html") } func writeError(w http.ResponseWriter, err error) { + // XXX: writeError should return an error page template. w.WriteHeader(500) + + details := errors.Unwrap(err).Error() + main := err.Error() + + fmt.Println("main", main) + fmt.Println("details", details) + w.Write([]byte(err.Error())) } diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 8b5ecdc2927..974d3f987b7 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -12,8 +12,11 @@ import ( ) func TestRoutes(t *testing.T) { - t.Skip("depends on an inmemory gnoland node.") - ok := http.StatusOK + const ( + ok = http.StatusOK + found = http.StatusFound + notFound = http.StatusNotFound + ) routes := []struct { route string status int @@ -33,6 +36,10 @@ func TestRoutes(t *testing.T) { {"/r/demo/deep/very/deep?help", ok, "exposed"}, {"/r/demo/deep/very/deep/", ok, "render.gno"}, {"/r/demo/deep/very/deep/render.gno", ok, "func Render("}, + {"/game-of-realms", ok, "/r/gnoland/pages:p/gor"}, + {"/gor", found, "/game-of-realms"}, + {"/blog", found, "/r/gnoland/blog"}, + {"/404-not-found", notFound, "/404-not-found"}, } if wd, err := os.Getwd(); err == nil { if strings.HasSuffix(wd, "cmd/gnoweb") { @@ -41,6 +48,14 @@ func TestRoutes(t *testing.T) { } else { panic("os.Getwd() -> err: " + err.Error()) } + + // configure default values + flags.RemoteAddr = "127.0.0.1:26657" + flags.HelpRemote = "127.0.0.1:26657" + flags.HelpChainID = "dev" + flags.CaptchaSite = "" + flags.ViewsDir = "./cmd/gnoweb/views" + flags.WithAnalytics = false app := makeApp() for _, r := range routes { @@ -49,8 +64,57 @@ func TestRoutes(t *testing.T) { response := httptest.NewRecorder() app.Router.ServeHTTP(response, request) assert.Equal(t, r.status, response.Code) - assert.Equal(t, strings.Contains(response.Body.String(), r.substring), true) - println(response.Body.String()) + assert.Contains(t, response.Body.String(), r.substring) + // println(response.Body.String()) }) } } + +func TestAnalytics(t *testing.T) { + routes := []string{ + // special realms + "/", // home + "/about", + "/start", + + // redirects + "/game-of-realms", + "/getting-started", + "/blog", + "/boards", + + // realm, source, help page + "/r/gnoland/blog", + "/r/gnoland/blog/admin.gno", + "/r/demo/users:administrator", + "/r/gnoland/blog?help", + + // special pages + "/404-not-found", + } + + t.Run("with", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.WithAnalytics = true + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Contains(t, response.Body.String(), "simpleanalytics") + }) + } + }) + t.Run("without", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.WithAnalytics = false + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Equal(t, strings.Contains(response.Body.String(), "simpleanalytics"), false) + }) + } + }) +} diff --git a/gno.land/cmd/gnoweb/pages/GOR.md b/gno.land/cmd/gnoweb/pages/GOR.md deleted file mode 100644 index 95a155319fb..00000000000 --- a/gno.land/cmd/gnoweb/pages/GOR.md +++ /dev/null @@ -1,30 +0,0 @@ -# Game of Realms - -The first high-stakes contest will see participants compete for the tiered membership to co-own the Gno.land blockchain. -A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. -Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. - -You can start participating in the co-creation of the Game of Realms now by adding your contributions on the dedicated [GitHub page](https://github.com/gnolang/gno/issues/357) to help us formulate the challenges. We want to release the final challenges from your contributions. - -## Register Now - - -
-
- -
-
- - -
- -
- - -
- - -
-
-
- diff --git a/gno.land/cmd/gnoweb/pages/HOME.md b/gno.land/cmd/gnoweb/pages/HOME.md deleted file mode 100644 index 0f3bfaec685..00000000000 --- a/gno.land/cmd/gnoweb/pages/HOME.md +++ /dev/null @@ -1,50 +0,0 @@ -# Welcome to **Gno.land** - -- [About Gno.land](/about) -- [Blogs](/r/gnoland/blog) -- [Install `gnokey`](https://github.com/gnolang/gno/tree/master/gno.land/cmd/gnokey) -- [Acquire testnet tokens](/faucet) -- [Game of Realms](/game-of-realms) - An open worldwide competition for developers to build the best Gnolang smart-contracts. - -# Explore new packages. - -- r/gnoland - - [/r/gnoland/blog](/r/gnoland/blog) - - [/r/gnoland/faucet](/r/gnoland/faucet) -- r/system - - [/r/system/names](/r/system/names) - - [/r/system/rewards](/r/system/rewards) - - [/r/system/validators](/r/system/validators) -- r/demo - - [/r/demo/banktest](/r/demo/banktest) - - [/r/demo/boards](/r/demo/boards) - - [/r/demo/foo20](/r/demo/foo20) - - [/r/demo/nft](/r/demo/nft) - - [/r/demo/types](/r/demo/types) - - [/r/demo/users](/r/demo/users) - - [/r/demo/groups](/r/demo/groups) -- p/demo - - [/p/demo/avl](/p/demo/avl) - - [/p/demo/blog](/p/demo/blog) - - [/p/demo/flow](/p/demo/flow) - - [/p/demo/gnode](/p/demo/gnode) - - [/p/demo/grc/exts](/p/demo/grc/exts) - - [/p/demo/grc/grc20](/p/demo/grc/grc20) - - [/p/demo/grc/grc721](/p/demo/grc/grc721) - -# Other Testnets - -- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** -- _[test3.gno.land](https://test3.gno.land) (latest)_ -- _[test2.gno.land](https://test2.gno.land) (archive)_ -- _[test1.gno.land](https://test1.gno.land) (archive)_ - -**This is a testnet.** -Package names are not guaranteed to be available for production. - -# Social - -Check out our [community projects](https://github.com/gnolang/awesome-gno). - -Official channel: [Discord](https://discord.gg/S8nKUqwkPn)
-Other channels: [Telegram](https://t.me/gnoland) [Twitter](https://twitter.com/_gnoland) [Youtube](https://www.youtube.com/@_gnoland) diff --git a/gno.land/cmd/gnoweb/views/404.html b/gno.land/cmd/gnoweb/views/404.html index c51543ca1cd..fee4fff8689 100644 --- a/gno.land/cmd/gnoweb/views/404.html +++ b/gno.land/cmd/gnoweb/views/404.html @@ -2,8 +2,7 @@ - - + {{ template "html_head" . }} 404 - Not Found @@ -13,6 +12,7 @@

{{.Data.path}}

+ {{ template "analytics" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/faucet.html b/gno.land/cmd/gnoweb/views/faucet.html index 84bcc6f34e5..85d3d6780c5 100644 --- a/gno.land/cmd/gnoweb/views/faucet.html +++ b/gno.land/cmd/gnoweb/views/faucet.html @@ -2,8 +2,8 @@ + {{ template "html_head" . }} Gno.land - {{ template "html_head" }}
diff --git a/gno.land/cmd/gnoweb/views/funcs.html b/gno.land/cmd/gnoweb/views/funcs.html index 8ac2fe6328b..c8d643ef655 100644 --- a/gno.land/cmd/gnoweb/views/funcs.html +++ b/gno.land/cmd/gnoweb/views/funcs.html @@ -1,4 +1,4 @@ -{{ define "header_buttons" }} +{{- define "header_buttons" -}}
-{{ end }} {{ define "html_head" }} +{{- end -}} + +{{- define "html_head" -}} + +{{if .Data.Description}}{{end}} -{{ end }} {{ define "header_logo" }} +{{- end -}} + +{{- define "header_logo" -}} -{{ end }} {{ define "footer" }} +{{- end -}} + +{{- define "footer" -}}
Gno.land
-{{ end }} {{ define "js" }} +{{- end -}} + +{{- define "js" -}} + -{{ end }} {{ define "subscribe" }} +{{ template "analytics" .}} +{{- end -}} + +{{- define "analytics" -}} +{{- if .Data.Flags.WithAnalytics -}} + + + +{{- end -}} +{{- end -}} + +{{- define "subscribe" -}}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/generic.html b/gno.land/cmd/gnoweb/views/generic.html index 117ea9d59fe..e671625e26a 100644 --- a/gno.land/cmd/gnoweb/views/generic.html +++ b/gno.land/cmd/gnoweb/views/generic.html @@ -3,8 +3,7 @@ Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} + {{ template "html_head" . }}
@@ -16,7 +15,7 @@
{{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/home.html b/gno.land/cmd/gnoweb/views/home.html deleted file mode 100644 index a2bf78adb96..00000000000 --- a/gno.land/cmd/gnoweb/views/home.html +++ /dev/null @@ -1,21 +0,0 @@ -{{- define "app" -}} - - - - Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} - - -
- -
-
{{ .Data.HomeContent }}
-
-
{{ template "subscribe" }}
- {{ template "footer" }} -
- {{ template "js" }} - - -{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_dir.html b/gno.land/cmd/gnoweb/views/package_dir.html index 6b0bf5cfd48..efaf4d7ad0c 100644 --- a/gno.land/cmd/gnoweb/views/package_dir.html +++ b/gno.land/cmd/gnoweb/views/package_dir.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}
@@ -12,13 +12,14 @@ {{ .Data.DirPath }}/*
{{ template "dir_contents" . }}
- {{ template "footer" }} - {{ template "js" }} + {{ template "js" . }} -{{- end -}} {{ define "dir_contents" }} +{{- end -}} + +{{- define "dir_contents" -}}
{{ $dirPath := .Data.DirPath }}
    @@ -29,4 +30,4 @@ {{ end }}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_file.html b/gno.land/cmd/gnoweb/views/package_file.html index 7068854d16c..71aa8b68452 100644 --- a/gno.land/cmd/gnoweb/views/package_file.html +++ b/gno.land/cmd/gnoweb/views/package_file.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}/{{.Data.FileName}}
@@ -18,7 +18,7 @@ {{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} - + {{ template "js" . }} -{{- end -}} +{{ define "func_specs" }} +{{- end -}} + +{{- define "func_specs" -}}
{{ $funcName := .Data.FuncName }} {{ $found := false }} {{ if eq $funcName "" }} {{ range .Data.FunctionSignatures }} {{ template "func_spec" . }} {{ end }} {{ else }} {{ range .Data.FunctionSignatures }} {{ if eq .FuncName $funcName }} {{ $found = true }} {{ template "func_spec" . }} {{ end }} {{ end }} {{ if not $found }} {{ $funcName }} not found. {{ end }} {{ end }}
-{{ end }} {{ define "func_spec" }} +{{- end -}} + +{{- define "func_spec" -}}
@@ -67,7 +69,9 @@
-{{ end }} {{ define "func_param" }} +{{- end -}} + +{{- define "func_param" -}} {{ .Name }} @@ -75,7 +79,9 @@ {{ .Type }} -{{ end }} {{ define "func_result" }} +{{- end -}} + +{{- define "func_result" -}} {{ .Name }} {{ .Type }} diff --git a/gno.land/cmd/gnoweb/views/realm_render.html b/gno.land/cmd/gnoweb/views/realm_render.html index 8a2c35adeca..6337d77aafa 100644 --- a/gno.land/cmd/gnoweb/views/realm_render.html +++ b/gno.land/cmd/gnoweb/views/realm_render.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.RealmName}}
@@ -26,10 +26,7 @@
{{ template "footer" }} - {{ template "js" }} - - - + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/redirect.html b/gno.land/cmd/gnoweb/views/redirect.html new file mode 100644 index 00000000000..6fe43a7138b --- /dev/null +++ b/gno.land/cmd/gnoweb/views/redirect.html @@ -0,0 +1,16 @@ +{{- define "app" -}} + + + + + + + + Redirecting to {{.Data.To}} + + + {{.Data.To}} + {{ template "analytics" .}} + + +{{- end -}} diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index c4fee341bfc..318d76eea86 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -133,8 +133,6 @@ func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir cfg := config.TestConfig().SetRootDir(gnoDataDir) { cfg.EnsureDirs() - cfg.Consensus.CreateEmptyBlocks = true - cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" } diff --git a/gnovm/cmd/gno/build_test.go b/gnovm/cmd/gno/build_test.go index 89339ee8a6e..5bb03ef0d35 100644 --- a/gnovm/cmd/gno/build_test.go +++ b/gnovm/cmd/gno/build_test.go @@ -1,17 +1,11 @@ package main -import "testing" +import ( + "testing" -func TestBuildApp(t *testing.T) { - tc := []testMainCase{ - { - args: []string{"build"}, - errShouldBe: "flag: help requested", - }, + "github.com/rogpeppe/go-internal/testscript" +) - // {args: []string{"build", "..."}, stdoutShouldContain: "..."}, - // TODO: auto precompilation - // TODO: error handling - } - testMainCaseRun(t, tc) +func TestBuild(t *testing.T) { + testscript.Run(t, setupTestScript(t, "testdata/gno_build")) } diff --git a/gnovm/cmd/gno/doc_test.go b/gnovm/cmd/gno/doc_test.go index 3eb90e2a329..513862ad2dc 100644 --- a/gnovm/cmd/gno/doc_test.go +++ b/gnovm/cmd/gno/doc_test.go @@ -10,7 +10,7 @@ func TestGnoDoc(t *testing.T) { }, { args: []string{"doc", "avl"}, - stdoutShouldContain: "func NewNode", + stdoutShouldContain: "func NewTree", }, { args: []string{"doc", "-u", "avl.Node"}, diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 158b9d8db5d..7acd9877770 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -38,7 +38,7 @@ func newLintCmd(io *commands.IO) *commands.Command { func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar(&c.verbose, "verbose", false, "verbose output when lintning") fs.StringVar(&c.rootDir, "root-dir", "", "clone location of github.com/gnolang/gno (gno tries to guess it)") - fs.IntVar(&c.setExitStatus, "set_exit_status", 1, "set exit status to 1 if any issues are found") + fs.IntVar(&c.setExitStatus, "set-exit-status", 1, "set exit status to 1 if any issues are found") } func execLint(cfg *lintCfg, args []string, io *commands.IO) error { diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index ce200a1fedd..0a747a03778 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -8,16 +8,16 @@ func TestLintApp(t *testing.T) { args: []string{"lint"}, errShouldBe: "flag: help requested", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/run-main/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"}, stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/run-main/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"}, stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/minimalist-gnomod/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/minimalist-gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/invalid-module-name/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid-module-name/"}, // TODO: raise an error because gno.mod is invalid }, // TODO: 'gno mod' is valid? diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 8cacfd5623b..85fe3f7ee7d 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -50,7 +50,7 @@ func newTestCmd(io *commands.IO) *commands.Command { 'gno test' recompiles each package along with any files with names matching the file pattern "*_test.gno" or "*_filetest.gno". -The only supported for now is a directory (relative or absolute). +The can be directory or file path (relative or absolute). - "*_test.gno" files work like "*_test.go" files, but they contain only test functions. Benchmark and fuzz functions aren't supported yet. Similarly, only @@ -182,9 +182,9 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { cfg.rootDir = guessRootDir() } - paths, err := gnoPackagesFromArgs(args) + paths, err := targetsFromPatterns(args) if err != nil { - return fmt.Errorf("list package paths from args: %w", err) + return fmt.Errorf("list targets from patterns: %w", err) } if len(paths) == 0 { io.ErrPrintln("no packages to test") diff --git a/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar new file mode 100644 index 00000000000..d346b6ad46f --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar @@ -0,0 +1,6 @@ +# Run gno build on an empty dir + +gno build . + +! stdout .+ +! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar new file mode 100644 index 00000000000..617e12291be --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar @@ -0,0 +1,27 @@ +# Run gno build with invalid gno files (still success) + +gno build . + +! stdout .+ +! stderr .+ + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +-- main.gno -- +package main + +invalid + +func main() {} + +-- sub/sub.gno -- +package sub + +invalid + +-- sub/file2.go -- +package file2 diff --git a/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar b/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar new file mode 100644 index 00000000000..a7c8b51af49 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar @@ -0,0 +1,30 @@ +# Run gno build with invalid go files + +! gno build . + +! stdout .+ +stderr '\./file1\.go:3:1: syntax error: non-declaration statement outside function body' +stderr '\./\.: build pkg: std go compiler' +stderr 'sub/file2\.go:3:1: syntax error: non-declaration statement outside function body' +stderr '\./sub: build pkg: std go compiler' + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +invalid1 + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub + +-- sub/file2.go -- +package file2 + +invalid2 diff --git a/gnovm/cmd/gno/testdata/gno_build/no_args.txtar b/gnovm/cmd/gno/testdata/gno_build/no_args.txtar new file mode 100644 index 00000000000..b3f68676588 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_args.txtar @@ -0,0 +1,6 @@ +# Run gno build without args + +! gno build + +! stdout .+ +stderr 'flag: help requested' diff --git a/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar new file mode 100644 index 00000000000..58261e77cda --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar @@ -0,0 +1,12 @@ +# Run gno build on a dir w/o gno files + +gno build . + +! stdout .+ +! stderr .+ + +-- README -- +Hello world + +-- sub/README -- +Hello world diff --git a/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar b/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar new file mode 100644 index 00000000000..46559610ccf --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar @@ -0,0 +1,19 @@ +# Run gno build in a dir without go files + +! gno build . + +! stdout .+ +stderr -count=2 'no Go files in '$WORK +stderr '\./\.: build pkg: std go compiler' +stderr '\./sub: build pkg: std go compiler' + +-- go.mod -- +module gnobuild + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub diff --git a/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar b/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar new file mode 100644 index 00000000000..9e6cad05664 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar @@ -0,0 +1,16 @@ +# Run gno build on a dir without go.mod + +! gno build . + +! stdout .+ +stderr -count=2 'go: go.mod file not found in current directory or any parent directory' +stderr './.: build pkg: std go compiler' +stderr './sub: build pkg: std go compiler' + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub diff --git a/gnovm/cmd/gno/testdata/gno_build/ok.txtar b/gnovm/cmd/gno/testdata/gno_build/ok.txtar new file mode 100644 index 00000000000..9d70fd97904 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/ok.txtar @@ -0,0 +1,23 @@ +# Run gno build successfully + +gno build . + +! stdout .+ +! stderr .+ + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub + +-- sub/file2.go -- +package file2 diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar index 73f0da72dfe..ffed64ab9c7 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar @@ -2,5 +2,10 @@ gno test . +! stdout .+ +stderr '[no test files]' + +gno test ./... + ! stdout .+ stderr 'no packages to test' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar index ae73b3ab275..cc673bb38ff 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr '\? \./\. \[no test files\]' +stderr '\? \. \[no test files\]' ! gno test --precompile . diff --git a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar index c7d3187424c..a66d831b48c 100644 --- a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . stdout 'Machine\.RunMain\(\) panic: oups' stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar index bc3efc1a8c9..c739c1ce328 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar @@ -9,8 +9,8 @@ stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' ! gno test -verbose --precompile . stdout 'Machine.RunMain\(\) panic: beep boop' -stderr '=== PREC \./\.' -stderr '=== BUILD \./\.' +stderr '=== PREC \.' +stderr '=== BUILD \.' stderr '=== RUN file/failing_filetest.gno' stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' diff --git a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar b/gnovm/cmd/gno/testdata/gno_test/minim1.txtar index 231ef4a270c..b0a77186086 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim1.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr '\? \./\. \[no test files\]' +stderr '\? \. \[no test files\]' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar b/gnovm/cmd/gno/testdata/gno_test/minim2.txtar index 038dfd19289..3c4d1d085f0 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim2.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar b/gnovm/cmd/gno/testdata/gno_test/minim3.txtar index 8e657104801..ac8ae0c41d4 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim3.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar index ce12874f669..4e5495ab839 100644 --- a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar index 85fec4ab316..b21db788924 100644 --- a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar @@ -5,7 +5,7 @@ gno test -verbose . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index f376e61d9a4..c3d3b983e34 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- // PKGPATH: gno.land/r/x diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar index 918fb0b88c9..236e69f8641 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -5,7 +5,7 @@ gno test -verbose . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar index 51b5f323654..545607a9082 100644 --- a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar @@ -3,14 +3,14 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' gno test -verbose . ! stdout .+ stderr '=== RUN file/valid_filetest.gno' stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar b/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar index cb5f7286f60..9590626776c 100644 --- a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar @@ -3,7 +3,12 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' + +gno test ./... + +! stdout .+ +stderr 'ok \. \d\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 8288539c97b..73ee0f0323b 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "time" @@ -105,6 +106,89 @@ func gnoPackagesFromArgs(args []string) ([]string, error) { return paths, nil } +// targetsFromPatterns returns a list of target paths that match the patterns. +// Each pattern can represent a file or a directory, and if the pattern +// includes "/...", the "..." is treated as a wildcard, matching any string. +// Intended to be used by gno commands such as `gno test`. +func targetsFromPatterns(patterns []string) ([]string, error) { + paths := []string{} + for _, p := range patterns { + var match func(string) bool + patternLookup := false + dirToSearch := p + + // Check if the pattern includes `/...` + if strings.Contains(p, "/...") { + index := strings.Index(p, "/...") + if index != -1 { + dirToSearch = p[:index] // Extract the directory path to search + } + match = matchPattern(strings.TrimPrefix(p, "./")) + patternLookup = true + } + + info, err := os.Stat(dirToSearch) + if err != nil { + return nil, fmt.Errorf("invalid file or package path: %w", err) + } + + // If the pattern is a file or a directory + // without `/...`, add it to the list. + if !info.IsDir() || !patternLookup { + paths = append(paths, p) + continue + } + + // the pattern is a dir containing `/...`, walk the dir recursively and + // look for directories containing at least one .gno file and match pattern. + visited := map[string]bool{} // used to run the builder only once per folder. + err = filepath.WalkDir(dirToSearch, func(curpath string, f fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) + } + // Skip directories and non ".gno" files. + if f.IsDir() || !isGnoFile(f) { + return nil + } + + parentDir := filepath.Dir(curpath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = true + if match(parentDir) { + paths = append(paths, parentDir) + } + + return nil + }) + if err != nil { + return nil, err + } + } + return paths, nil +} + +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +// Simplified version of go source's matchPatternInternal +// (see $GOROOT/src/cmd/internal/pkgpattern) +func matchPattern(pattern string) func(name string) bool { + re := regexp.QuoteMeta(pattern) + re = strings.Replace(re, `\.\.\.`, `.*`, -1) + // Special case: foo/... matches foo too. + if strings.HasSuffix(re, `/.*`) { + re = re[:len(re)-len(`/.*`)] + `(/.*)?` + } + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go new file mode 100644 index 00000000000..9e9659bfe4f --- /dev/null +++ b/gnovm/cmd/gno/util_test.go @@ -0,0 +1,297 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatchPattern(t *testing.T) { + tests := []struct { + pattern string + names []string + expected []bool + }{ + { + pattern: "foo", + names: []string{"foo", "bar", "baz", "foo/bar"}, + expected: []bool{true, false, false, false}, + }, + { + pattern: "foo/...", + names: []string{"foo", "foo/bar", "foo/bar/baz", "bar", "baz"}, + expected: []bool{true, true, true, false, false}, + }, + { + pattern: "foo/bar/...", + names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, + expected: []bool{true, true, false, false, false}, + }, + { + pattern: "foo/.../baz", + names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, + expected: []bool{false, true, false, false, false}, + }, + { + pattern: "foo/.../baz/...", + names: []string{"foo/bar/baz", "foo/baz/bar", "foo/bar/baz/qux", "foo/baz/bar/qux"}, + expected: []bool{true, false, true, false}, + }, + { + pattern: "...", + names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, + expected: []bool{true, true, true, true, true}, + }, + { + pattern: ".../bar", + names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, + expected: []bool{false, false, false, true, false}, + }, + } + + for _, test := range tests { + t.Run(test.pattern, func(t *testing.T) { + matchFunc := matchPattern(test.pattern) + for i, name := range test.names { + res := matchFunc(name) + assert.Equal(t, test.expected[i], res, "Expected: %v, Got: %v", test.expected[i], res) + } + }) + } +} + +func TestTargetsFromPatterns(t *testing.T) { + tmpDir := t.TempDir() + createGnoPackages(t, tmpDir) + + for _, tc := range []struct { + desc string + in, expected []string + errorShouldContain string + }{ + { + desc: "valid1", + in: []string{ + tmpDir, + }, + expected: []string{ + tmpDir, + }, + }, + { + desc: "valid2", + in: []string{ + tmpDir + "/foo", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + }, + }, + { + desc: "valid_recursive1", + in: []string{ + tmpDir + "/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "baz"), + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "bar", "quux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "valid_recursive2", + in: []string{ + tmpDir + "/foo/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "valid_recursive2", + in: []string{ + tmpDir + "/.../qux", + }, + expected: []string{ + filepath.Join(tmpDir, "foo", "qux"), + }, + }, + { + desc: "valid_recursive3", + in: []string{ + tmpDir + "/.../qux/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "multiple_input", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar", + tmpDir + "/baz", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "baz"), + }, + }, + { + desc: "mixed_input1", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "bar", "quux"), + }, + }, + { + desc: "mixed_input2", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar/...", + tmpDir + "/baz/baz.gno", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "bar", "quux"), + filepath.Join(tmpDir, "baz", "baz.gno"), + }, + }, + { + desc: "not_exists1", + in: []string{ + tmpDir + "/notexists", // dir path + }, + errorShouldContain: "no such file or directory", + }, + { + desc: "not_exists2", + in: []string{ + tmpDir + "/foo/bar.gno", // file path + }, + errorShouldContain: "no such file or directory", + }, + { + desc: "not_exists3", // mixed + in: []string{ + tmpDir + "/foo", // exists + tmpDir + "/notexists", // not exists + }, + errorShouldContain: "no such file or directory", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + targets, err := targetsFromPatterns(tc.in) + if tc.errorShouldContain != "" { + assert.ErrorContains(t, err, tc.errorShouldContain) + return + } + assert.NoError(t, err) + require.Equal(t, len(tc.expected), len(targets)) + for _, tr := range targets { + assert.Contains(t, tc.expected, tr) + } + }) + } +} + +func createGnoPackages(t *testing.T, tmpDir string) { + t.Helper() + + type file struct { + name, data string + } + // Gno pkgs to create + pkgs := []struct { + dir string + files []file + }{ + // pkg 'foo', 'bar' and 'baz' + { + dir: filepath.Join(tmpDir, "foo"), + files: []file{ + { + name: "foo.gno", + data: `package foo`, + }, + }, + }, + { + dir: filepath.Join(tmpDir, "bar"), + files: []file{ + { + name: "bar.gno", + data: `package bar`, + }, + }, + }, + { + dir: filepath.Join(tmpDir, "baz"), + files: []file{ + { + name: "baz.gno", + data: `package baz`, + }, + }, + }, + + // pkg inside 'foo' pkg + { + dir: filepath.Join(tmpDir, "foo", "qux"), + files: []file{ + { + name: "qux.gno", + data: `package qux`, + }, + }, + }, + + // pkg inside 'bar' pkg + { + dir: filepath.Join(tmpDir, "bar", "quux"), + files: []file{ + { + name: "quux.gno", + data: `package quux`, + }, + }, + }, + + // pkg inside 'foo/qux' pkg + { + dir: filepath.Join(tmpDir, "foo", "qux", "corge"), + files: []file{ + { + name: "corge.gno", + data: `package corge`, + }, + }, + }, + } + + // Create pkgs + for _, p := range pkgs { + err := os.MkdirAll(p.dir, 0o700) + require.NoError(t, err) + for _, f := range p.files { + err = os.WriteFile(filepath.Join(p.dir, f.name), []byte(f.data), 0o644) + require.NoError(t, err) + } + } +} diff --git a/gnovm/docs/go-gno-compatibility.md b/gnovm/docs/go-gno-compatibility.md index 98f42aa9f29..a39cec533f4 100644 --- a/gnovm/docs/go-gno-compatibility.md +++ b/gnovm/docs/go-gno-compatibility.md @@ -234,8 +234,8 @@ Additional native types: | go/types | TBD | | go/types/testdata | TBD | | go/types/testdata/local | TBD | -| hash | TBD | -| hash/adler32 | TBD | +| hash | partial | +| hash/adler32 | full | | hash/crc32 | TBD | | hash/crc64 | TBD | | hash/fnv | TBD | diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index 9fc2ce4a567..cc7c0de9f09 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -1240,12 +1240,14 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { case Float32Kind: dst.T = t dst.V = nil - f64, _ := bd.Float64() + f64, err := bd.Float64() + if err != nil { + panic(fmt.Errorf("cannot convert untyped bigdec to float64: %w", err)) + } + bf := big.NewFloat(f64) - f32, acc := bf.Float32() - if f32 == 0 && (acc == big.Below || acc == big.Above) { - panic("cannot convert untyped bigdec to float32 -- too close to zero") - } else if math.IsInf(float64(f32), 0) { + f32, _ := bf.Float32() + if math.IsInf(float64(f32), 0) { panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") } dst.SetFloat32(f32) @@ -1253,10 +1255,11 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { case Float64Kind: dst.T = t dst.V = nil - f64, _ := bd.Float64() - if f64 == 0 && !bd.IsZero() { - panic("cannot convert untyped bigdec to float64 -- too close to zero") - } else if math.IsInf(f64, 0) { + f64, err := bd.Float64() + if err != nil { + panic(fmt.Errorf("cannot convert untyped bigdec to float64: %w", err)) + } + if math.IsInf(f64, 0) { panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") } dst.SetFloat64(f64) diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go new file mode 100644 index 00000000000..b9ac9e68d68 --- /dev/null +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -0,0 +1,25 @@ +package gnolang + +import ( + "math" + "testing" + + "github.com/cockroachdb/apd" + "github.com/stretchr/testify/require" +) + +func TestConvertUntypedBigdecToFloat(t *testing.T) { + dst := &TypedValue{} + + dec, err := apd.New(-math.MaxInt64, -4).SetFloat64(math.SmallestNonzeroFloat64 / 2) + require.NoError(t, err) + bd := BigdecValue{ + V: dec, + } + + typ := Float64Type + + ConvertUntypedBigdecTo(dst, bd, typ) + + require.Equal(t, float64(0), dst.GetFloat64()) +} diff --git a/gnovm/stdlibs/encoding/encoding.gno b/gnovm/stdlibs/encoding/encoding.gno new file mode 100644 index 00000000000..50acf3c23a1 --- /dev/null +++ b/gnovm/stdlibs/encoding/encoding.gno @@ -0,0 +1,54 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package encoding defines interfaces shared by other packages that +// convert data to and from byte-level and textual representations. +// Packages that check for these interfaces include encoding/gob, +// encoding/json, and encoding/xml. As a result, implementing an +// interface once can make a type useful in multiple encodings. +// Standard types that implement these interfaces include time.Time and net.IP. +// The interfaces come in pairs that produce and consume encoded data. +// +// Adding encoding/decoding methods to existing types may constitute a breaking change, +// as they can be used for serialization in communicating with programs +// written with different library versions. +// The policy for packages maintained by the Go project is to only allow +// the addition of marshaling functions if no existing, reasonable marshaling exists. +package encoding + +// BinaryMarshaler is the interface implemented by an object that can +// marshal itself into a binary form. +// +// MarshalBinary encodes the receiver into a binary form and returns the result. +type BinaryMarshaler interface { + MarshalBinary() (data []byte, err error) +} + +// BinaryUnmarshaler is the interface implemented by an object that can +// unmarshal a binary representation of itself. +// +// UnmarshalBinary must be able to decode the form generated by MarshalBinary. +// UnmarshalBinary must copy the data if it wishes to retain the data +// after returning. +type BinaryUnmarshaler interface { + UnmarshalBinary(data []byte) error +} + +// TextMarshaler is the interface implemented by an object that can +// marshal itself into a textual form. +// +// MarshalText encodes the receiver into UTF-8-encoded text and returns the result. +type TextMarshaler interface { + MarshalText() (text []byte, err error) +} + +// TextUnmarshaler is the interface implemented by an object that can +// unmarshal a textual representation of itself. +// +// UnmarshalText must be able to decode the form generated by MarshalText. +// UnmarshalText must copy the text if it wishes to retain the text +// after returning. +type TextUnmarshaler interface { + UnmarshalText(text []byte) error +} diff --git a/gnovm/stdlibs/hash/adler32/adler32.gno b/gnovm/stdlibs/hash/adler32/adler32.gno new file mode 100644 index 00000000000..38d644d1ee5 --- /dev/null +++ b/gnovm/stdlibs/hash/adler32/adler32.gno @@ -0,0 +1,135 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package adler32 implements the Adler-32 checksum. +// +// It is defined in RFC 1950: +// +// Adler-32 is composed of two sums accumulated per byte: s1 is +// the sum of all bytes, s2 is the sum of all s1 values. Both sums +// are done modulo 65521. s1 is initialized to 1, s2 to zero. The +// Adler-32 checksum is stored as s2*65536 + s1 in most- +// significant-byte first (network) order. +package adler32 + +import ( + "errors" + "hash" +) + +const ( + // mod is the largest prime that is less than 65536. + mod = 65521 + // nmax is the largest n such that + // 255 * n * (n+1) / 2 + (n+1) * (mod-1) <= 2^32-1. + // It is mentioned in RFC 1950 (search for "5552"). + nmax = 5552 +) + +// The size of an Adler-32 checksum in bytes. +const Size = 4 + +// digest represents the partial evaluation of a checksum. +// The low 16 bits are s1, the high 16 bits are s2. +type digest uint32 + +func (d *digest) Reset() { *d = 1 } + +// New returns a new hash.Hash32 computing the Adler-32 checksum. Its +// Sum method will lay the value out in big-endian byte order. The +// returned Hash32 also implements encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler to marshal and unmarshal the internal +// state of the hash. +func New() hash.Hash32 { + d := new(digest) + d.Reset() + return d +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return 4 } + +const ( + magic = "adl\x01" + marshaledSize = len(magic) + 4 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + b = appendUint32(b, uint32(*d)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("hash/adler32: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("hash/adler32: invalid hash state size") + } + *d = digest(readUint32(b[len(magic):])) + return nil +} + +func appendUint32(b []byte, x uint32) []byte { + a := [4]byte{ + byte(x >> 24), + byte(x >> 16), + byte(x >> 8), + byte(x), + } + return append(b, a[:]...) +} + +func readUint32(b []byte) uint32 { + _ = b[3] + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +// Add p to the running checksum d. +func update(d digest, p []byte) digest { + s1, s2 := uint32(d&0xffff), uint32(d>>16) + for len(p) > 0 { + var q []byte + if len(p) > nmax { + p, q = p[:nmax], p[nmax:] + } + for len(p) >= 4 { + s1 += uint32(p[0]) + s2 += s1 + s1 += uint32(p[1]) + s2 += s1 + s1 += uint32(p[2]) + s2 += s1 + s1 += uint32(p[3]) + s2 += s1 + p = p[4:] + } + for _, x := range p { + s1 += uint32(x) + s2 += s1 + } + s1 %= mod + s2 %= mod + p = q + } + return digest(s2<<16 | s1) +} + +func (d *digest) Write(p []byte) (nn int, err error) { + *d = update(*d, p) + return len(p), nil +} + +func (d *digest) Sum32() uint32 { return uint32(*d) } + +func (d *digest) Sum(in []byte) []byte { + s := uint32(*d) + return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +// Checksum returns the Adler-32 checksum of data. +func Checksum(data []byte) uint32 { return uint32(update(1, data)) } diff --git a/gnovm/stdlibs/hash/hash.gno b/gnovm/stdlibs/hash/hash.gno new file mode 100644 index 00000000000..62cf6a45184 --- /dev/null +++ b/gnovm/stdlibs/hash/hash.gno @@ -0,0 +1,58 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hash provides interfaces for hash functions. +package hash + +import "io" + +// Hash is the common interface implemented by all hash functions. +// +// Hash implementations in the standard library (e.g. hash/crc32 and +// crypto/sha256) implement the encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler interfaces. Marshaling a hash implementation +// allows its internal state to be saved and used for additional processing +// later, without having to re-write the data previously written to the hash. +// The hash state may contain portions of the input in its original form, +// which users are expected to handle for any possible security implications. +// +// Compatibility: Any future changes to hash or crypto packages will endeavor +// to maintain compatibility with state encoded using previous versions. +// That is, any released versions of the packages should be able to +// decode data written with any previously released version, +// subject to issues such as security fixes. +// See the Go compatibility document for background: https://golang.org/doc/go1compat +type Hash interface { + // Write (via the embedded io.Writer interface) adds more data to the running hash. + // It never returns an error. + io.Writer + + // Sum appends the current hash to b and returns the resulting slice. + // It does not change the underlying hash state. + Sum(b []byte) []byte + + // Reset resets the Hash to its initial state. + Reset() + + // Size returns the number of bytes Sum will return. + Size() int + + // BlockSize returns the hash's underlying block size. + // The Write method must be able to accept any amount + // of data, but it may operate more efficiently if all writes + // are a multiple of the block size. + BlockSize() int +} + +// Hash32 is the common interface implemented by all 32-bit hash functions. +type Hash32 interface { + Hash + Sum32() uint32 +} + +// Hash64 is the common interface implemented by all 64-bit hash functions. +type Hash64 interface { + Hash + Sum64() uint64 +} diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno new file mode 100644 index 00000000000..b31d35faa77 --- /dev/null +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -0,0 +1,87 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test that the hashes in the standard library implement +// BinaryMarshaler, BinaryUnmarshaler, +// and lock in the current representations. + +package hash + +import ( + "bytes" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding" + "encoding/hex" + "hash" + "hash/adler32" + "testing" +) + +func fromHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var marshalTests = []struct { + name string + new func() hash.Hash + golden []byte +}{ + {"adler32", func() hash.Hash { return adler32.New() }, fromHex("61646c01460a789d")}, +} + +func TestMarshalHash(t *testing.T) { + for _, tt := range marshalTests { + t.Run(tt.name, func(t *testing.T) { + buf := make([]byte, 256) + for i := range buf { + buf[i] = byte(i) + } + + h := tt.new() + h.Write(buf[:256]) + sum := h.Sum(nil) + + h2 := tt.new() + h3 := tt.new() + const split = 249 + for i := 0; i < split; i++ { + h2.Write(buf[i : i+1]) + } + h2m, ok := h2.(encoding.BinaryMarshaler) + if !ok { + t.Fatalf("Hash does not implement MarshalBinary") + } + enc, err := h2m.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary: %v", err) + } + if !bytes.Equal(enc, tt.golden) { + t.Errorf("MarshalBinary = %x, want %x", enc, tt.golden) + } + h3u, ok := h3.(encoding.BinaryUnmarshaler) + if !ok { + t.Fatalf("Hash does not implement UnmarshalBinary") + } + if err := h3u.UnmarshalBinary(enc); err != nil { + t.Fatalf("UnmarshalBinary: %v", err) + } + h2.Write(buf[split:]) + h3.Write(buf[split:]) + sum2 := h2.Sum(nil) + sum3 := h3.Sum(nil) + if !bytes.Equal(sum2, sum) { + t.Fatalf("Sum after MarshalBinary = %x, want %x", sum2, sum) + } + if !bytes.Equal(sum3, sum) { + t.Fatalf("Sum after UnmarshalBinary = %x, want %x", sum3, sum) + } + }) + } +} diff --git a/gnovm/tests/files/float6.gno b/gnovm/tests/files/float6.gno new file mode 100644 index 00000000000..680b9461975 --- /dev/null +++ b/gnovm/tests/files/float6.gno @@ -0,0 +1,13 @@ +package main + +const ( + SmallestNonzeroFloat64 = 0x1p-1022 * 0x1p-52 // 4.9406564584124654417656879286822137236505980e-324 + DividedByTwo = SmallestNonzeroFloat64 / 2 +) + +func main() { + println(DividedByTwo) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/float7.gno b/gnovm/tests/files/float7.gno new file mode 100644 index 00000000000..f519a963523 --- /dev/null +++ b/gnovm/tests/files/float7.gno @@ -0,0 +1,15 @@ +package main + +const ( + SmallestNonzeroFloat32 = 0x1p-126 * 0x1p-23 // 1.401298464324817070923729583289916131280e-45 + DividedByTwo = SmallestNonzeroFloat32 / 2 +) + +func main() { + var i float32 + i = DividedByTwo + println(i) +} + +// Output: +// 0 diff --git a/go.mod b/go.mod index dbb62a34e2d..0dc4114f405 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/dgraph-io/badger/v3 v3.2103.4 github.com/fortytw2/leaktest v1.3.0 - github.com/gdamore/tcell/v2 v2.1.0 + github.com/gdamore/tcell/v2 v2.6.0 github.com/gnolang/goleveldb v0.0.9 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 github.com/golang/protobuf v1.5.3 @@ -32,10 +32,10 @@ require ( github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c go.etcd.io/bbolt v1.3.7 go.uber.org/multierr v1.9.0 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/mod v0.13.0 - golang.org/x/net v0.15.0 - golang.org/x/term v0.12.0 + golang.org/x/net v0.17.0 + golang.org/x/term v0.13.0 golang.org/x/tools v0.13.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 @@ -60,13 +60,13 @@ require ( github.com/klauspost/compress v1.12.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.7 // indirect - github.com/lucasb-eyer/go-colorful v1.0.3 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.3 // indirect go.opencensus.io v0.22.5 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 3e9aac9b2e2..2b3d717a7e5 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.1.0 h1:UnSmozHgBkQi2PGsFr+rpdXuAPRRucMegpQp3Z3kDro= -github.com/gdamore/tcell/v2 v2.1.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= +github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= +github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= @@ -122,10 +122,10 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/linxGnu/grocksdb v1.8.4 h1:ZMsBpPpJNtRLHiKKp0mI7gW+NT4s7UgfD5xHxx1jVRo= github.com/linxGnu/grocksdb v1.8.4/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= -github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -146,8 +146,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= @@ -175,6 +176,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= @@ -189,14 +191,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -208,8 +212,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -217,22 +223,32 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -242,6 +258,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/misc/deployments/staging.gno.land/docker-compose.yml b/misc/deployments/staging.gno.land/docker-compose.yml index 268002902ab..af5e747e653 100644 --- a/misc/deployments/staging.gno.land/docker-compose.yml +++ b/misc/deployments/staging.gno.land/docker-compose.yml @@ -36,8 +36,8 @@ services: - --faucet-url=https://faucet-staging.gno.land/ - --help-chainid=staging - --help-remote=staging.gno.land:36657 - - --pages-dir=/overlay/pages - --views-dir=./gno.land/cmd/gnoweb/views + - --with-analytics volumes: - "./overlay:/overlay:ro" links: diff --git a/misc/devdeps/Makefile b/misc/devdeps/Makefile index 99aea512ed0..54df62cc031 100644 --- a/misc/devdeps/Makefile +++ b/misc/devdeps/Makefile @@ -1,4 +1,3 @@ install: go install mvdan.cc/gofumpt go install google.golang.org/protobuf/cmd/protoc-gen-go - go install golang.org/x/tools/cmd/godoc diff --git a/misc/gendocs/Makefile b/misc/gendocs/Makefile index 8a4f3ba2de5..0f56d83693f 100644 --- a/misc/gendocs/Makefile +++ b/misc/gendocs/Makefile @@ -1,5 +1,8 @@ all: clean gen +install: + go install golang.org/x/pkgsite/cmd/pkgsite@latest + gen: ./gendocs.sh @@ -7,4 +10,4 @@ clean: rm -rf godoc kill_zombies: - kill -9 `lsof -t -i tcp:6060 -s TCP:LISTEN` || true + kill -9 `lsof -t -i tcp:8080 -s TCP:LISTEN` || true diff --git a/misc/gendocs/gendocs.sh b/misc/gendocs/gendocs.sh index d7621acd5a0..4336516aba1 100755 --- a/misc/gendocs/gendocs.sh +++ b/misc/gendocs/gendocs.sh @@ -1,35 +1,51 @@ -#!/bin/sh - -GODOC_PORT=${GODOC_PORT:-6060} -GO_MODULE=${GO_MODULE:-github.com/gnolang/gno} -GODOC_OUT=${GODOC_OUT:-godoc} -URL=http://localhost:${GODOC_PORT}/pkg/github.com/gnolang/gno/ - -echo "[+] Starting godoc server..." -go run \ - -modfile ../devdeps/go.mod \ - golang.org/x/tools/cmd/godoc \ - -http="localhost:${GODOC_PORT}" & -PID=$! -# Waiting for godoc server -while ! curl --fail --silent "$URL" > /dev/null 2>&1; do - sleep 0.1 +#!/bin/bash +# Heavily modified version of the following script: +# https://gist.github.com/Kegsay/84ce060f237cb9ab4e0d2d321a91d920 +set -u + +DOC_DIR=godoc +PKG=github.com/gnolang/gno + +# Run a pkgsite server which we will scrape. Use env to run it from our repo's root directory. +env -C ../.. pkgsite & +DOC_PID=$! + +# Wait for the server to init +while : +do + curl -s "http://localhost:8080" > /dev/null + if [ $? -eq 0 ] # exit code is 0 if we connected + then + break + fi done -echo "[+] Downloading godoc pages..." +# Scrape the pkg directory for the API docs. Scrap lib for the CSS/JS. Ignore everything else. wget \ - --recursive \ - --no-verbose \ - --convert-links \ - --page-requisites \ - --adjust-extension \ - --execute=robots=off \ - --include-directories="/lib,/pkg/$GO_MODULE,/src/$GO_MODULE" \ - --exclude-directories="*" \ - --directory-prefix="${GODOC_OUT}" \ - --no-host-directories \ - "$URL?m=all" - -echo "[+] Killing godoc server..." -kill -9 "$PID" + --verbose \ + --recursive \ + --mirror \ + --convert-links \ + --adjust-extension \ + --page-requisites \ + -erobots=off \ + --accept-regex='8080/((search|license-policy|about|)$|(static|images)/|github.com/gnolang/)' \ + http://localhost:8080/ + +# Stop the pkgsite server +kill -9 $DOC_PID + +# Delete the old directory or else mv will put the localhost dir into +# the DOC_DIR if it already exists. +rm -rf $DOC_DIR +mv localhost\:8080 $DOC_DIR + +# Perform various replacements to fix broken links/UI. +# /files/ will point to their github counterparts; we make links to importedby/version go nowhere; +# any other link will point to pkg.go.dev, and fix the /files/... text when viewing a pkg. +find godoc -type f -exec sed -ri 's#http://localhost:8080/files/[^"]*/github.com/gnolang/([^/"]+)/([^"]*)#https://github.com/gnolang/\1/blob/master/\2#g +s#http://localhost:8080/[^"?]*\?tab=(importedby|versions)#\##g +s#http://localhost:8080([^")]*)#https://pkg.go.dev\1#g +s#/files/[^" ]*/(github.com/[^" ]*)/#\1#g' {} + +echo "Docs can be found in $DOC_DIR" diff --git a/misc/list-gnophers/main.sh b/misc/list-gnophers/main.sh new file mode 100755 index 00000000000..beb90d4c767 --- /dev/null +++ b/misc/list-gnophers/main.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +main() { + cd ../.. + for file in $(list_gno_files); do + extract_file_metadata $file + done > gno_file_commits.csv + echo + cat gno_file_commits.csv | sort_by_date | unique_by_author +} + +list_gno_files() { + # list .gno file in examples/, remove tests and unit tests + find ./examples -name "*.gno" | grep -v _filetest.gno | grep -v _test.gno | grep -v gno.land/r/demo/tests +} + +extract_file_metadata() { + file=$1 + # get the first commit date of the file + first_commit_date=$(git log --pretty=format:%ct --follow $file | tail -n 1) + # get the email of the first contributor of the file + email=$(git log --mailmap --pretty=format:%aE --follow $file | tail -n 1) + # print the file name, first commit date, and email + echo "$first_commit_date,$email,$file" +} + +sort_by_date() { + sort -t, -k1 +} + +unique_by_author() { + awk -F, '!seek[$2]++' +} + +main diff --git a/tm2/README.md b/tm2/README.md index 101fa793e82..c4d6aa8d287 100644 --- a/tm2/README.md +++ b/tm2/README.md @@ -22,7 +22,7 @@ * Minimal code - keep total footprint small. * Minimal dependencies - all dependencies must get audited, and become part of the repo. -* Modular dependencies - whereever reasonable, make components modular. +* Modular dependencies - wherever reasonable, make components modular. * Completeness - software projects that don't become finished are projects that are forever vulnerable. One of the primary goals of the Gno language and related works is to become finished within a reasonable timeframe. diff --git a/tm2/pkg/autofile/group.go b/tm2/pkg/autofile/group.go index 3350e1e62c5..189d7818bd8 100644 --- a/tm2/pkg/autofile/group.go +++ b/tm2/pkg/autofile/group.go @@ -125,7 +125,11 @@ func (g *Group) OnStart() error { // OnStop implements service.Service by stopping the goroutine described above. // NOTE: g.Head must be closed separately using Close. func (g *Group) OnStop() { - g.FlushAndSync() + if err := g.FlushAndSync(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully flush data, %s", err.Error()), + ) + } } // Wait blocks until all internal goroutines are finished. Supposed to be @@ -136,11 +140,20 @@ func (g *Group) Wait() { // Close closes the head file. The group must be stopped by this moment. func (g *Group) Close() { - g.FlushAndSync() + if err := g.FlushAndSync(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully flush data, %s", err.Error()), + ) + } g.mtx.Lock() - _ = g.Head.Close() - g.mtx.Unlock() + defer g.mtx.Unlock() + + if err := g.Head.Close(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully close group head, %s", err.Error()), + ) + } } // HeadSizeLimit returns the current head size limit. diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index 6f148c3b5c1..3785759c960 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -9,6 +9,7 @@ import ( cns "github.com/gnolang/gno/tm2/pkg/bft/consensus/config" mem "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" rpc "github.com/gnolang/gno/tm2/pkg/bft/rpc/config" + eventstore "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" "github.com/gnolang/gno/tm2/pkg/errors" osm "github.com/gnolang/gno/tm2/pkg/os" p2p "github.com/gnolang/gno/tm2/pkg/p2p/config" @@ -20,59 +21,70 @@ type Config struct { BaseConfig `toml:",squash"` // Options for services - RPC *rpc.RPCConfig `toml:"rpc"` - P2P *p2p.P2PConfig `toml:"p2p"` - Mempool *mem.MempoolConfig `toml:"mempool"` - Consensus *cns.ConsensusConfig `toml:"consensus"` + RPC *rpc.RPCConfig `toml:"rpc"` + P2P *p2p.P2PConfig `toml:"p2p"` + Mempool *mem.MempoolConfig `toml:"mempool"` + Consensus *cns.ConsensusConfig `toml:"consensus"` + TxEventStore *eventstore.Config `toml:"tx_event_store"` } // DefaultConfig returns a default configuration for a Tendermint node func DefaultConfig() *Config { return &Config{ - BaseConfig: DefaultBaseConfig(), - RPC: rpc.DefaultRPCConfig(), - P2P: p2p.DefaultP2PConfig(), - Mempool: mem.DefaultMempoolConfig(), - Consensus: cns.DefaultConsensusConfig(), + BaseConfig: DefaultBaseConfig(), + RPC: rpc.DefaultRPCConfig(), + P2P: p2p.DefaultP2PConfig(), + Mempool: mem.DefaultMempoolConfig(), + Consensus: cns.DefaultConsensusConfig(), + TxEventStore: eventstore.DefaultEventStoreConfig(), } } -// Like LoadOrMakeConfigWithOptions() but without overriding any defaults. -func LoadOrMakeDefaultConfig(root string) (cfg *Config) { - return LoadOrMakeConfigWithOptions(root, nil) -} - type ConfigOptions func(cfg *Config) -// LoadOrMakeConfigWithOptions() loads configuration or saves one +// LoadOrMakeConfigWithOptions loads configuration or saves one // made by modifying the default config with override options -func LoadOrMakeConfigWithOptions(root string, options ConfigOptions) (cfg *Config) { +func LoadOrMakeConfigWithOptions(root string, options ConfigOptions) (*Config, error) { + var cfg *Config + configPath := join(root, defaultConfigFilePath) if osm.FileExists(configPath) { - cfg = LoadConfigFile(configPath) + var loadErr error + + // Load the configuration + if cfg, loadErr = LoadConfigFile(configPath); loadErr != nil { + return nil, loadErr + } + cfg.SetRootDir(root) cfg.EnsureDirs() } else { cfg = DefaultConfig() - options(cfg) + if options != nil { + options(cfg) + } cfg.SetRootDir(root) cfg.EnsureDirs() WriteConfigFile(configPath, cfg) + + // Validate the configuration + if validateErr := cfg.ValidateBasic(); validateErr != nil { + return nil, fmt.Errorf("unable to validate config, %w", validateErr) + } } - if err := cfg.ValidateBasic(); err != nil { - panic(err) - } - return cfg + + return cfg, nil } // TestConfig returns a configuration that can be used for testing func TestConfig() *Config { return &Config{ - BaseConfig: TestBaseConfig(), - RPC: rpc.TestRPCConfig(), - P2P: p2p.TestP2PConfig(), - Mempool: mem.TestMempoolConfig(), - Consensus: cns.TestConsensusConfig(), + BaseConfig: TestBaseConfig(), + RPC: rpc.TestRPCConfig(), + P2P: p2p.TestP2PConfig(), + Mempool: mem.TestMempoolConfig(), + Consensus: cns.TestConsensusConfig(), + TxEventStore: eventstore.DefaultEventStoreConfig(), } } @@ -121,7 +133,7 @@ func (cfg *Config) ValidateBasic() error { return nil } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // BaseConfig const ( diff --git a/tm2/pkg/bft/config/toml.go b/tm2/pkg/bft/config/toml.go index a35e5674631..fdaa1295342 100644 --- a/tm2/pkg/bft/config/toml.go +++ b/tm2/pkg/bft/config/toml.go @@ -24,17 +24,27 @@ func init() { } } -func LoadConfigFile(configFilePath string) *Config { - bz, err := os.ReadFile(configFilePath) - if err != nil { - panic(err) +// LoadConfigFile loads the TOML node configuration from the specified path +func LoadConfigFile(path string) (*Config, error) { + // Read the config file + content, readErr := os.ReadFile(path) + if readErr != nil { + return nil, readErr } - var config Config - err = toml.Unmarshal(bz, &config) - if err != nil { - panic(err) + + // Parse the node config + var nodeConfig Config + + if unmarshalErr := toml.Unmarshal(content, &nodeConfig); unmarshalErr != nil { + return nil, unmarshalErr + } + + // Validate the config + if validateErr := nodeConfig.ValidateBasic(); validateErr != nil { + return nil, fmt.Errorf("unable to validate config, %w", validateErr) } - return &config + + return &nodeConfig, nil } /****** these are for production settings ***********/ diff --git a/tm2/pkg/bft/config/toml_test.go b/tm2/pkg/bft/config/toml_test.go index 0fe78285997..da6a720ecd4 100644 --- a/tm2/pkg/bft/config/toml_test.go +++ b/tm2/pkg/bft/config/toml_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/pelletier/go-toml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -95,3 +97,63 @@ func checkConfig(configFile string) bool { } return valid } + +func TestTOML_LoadConfig(t *testing.T) { + t.Parallel() + + t.Run("config does not exist", func(t *testing.T) { + t.Parallel() + + cfg, loadErr := LoadConfigFile("dummy-path") + + assert.Error(t, loadErr) + assert.Nil(t, cfg) + }) + + t.Run("config is not valid toml", func(t *testing.T) { + t.Parallel() + + // Create config file + configFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Write invalid TOML + _, writeErr := configFile.WriteString("invalid TOML") + require.NoError(t, writeErr) + + cfg, loadErr := LoadConfigFile(configFile.Name()) + + assert.Error(t, loadErr) + assert.Nil(t, cfg) + }) + + t.Run("valid config", func(t *testing.T) { + t.Parallel() + + // Create config file + configFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Create the default config + defaultConfig := DefaultConfig() + + // Marshal the default config + defaultConfigRaw, marshalErr := toml.Marshal(defaultConfig) + require.NoError(t, marshalErr) + + // Write valid TOML + _, writeErr := configFile.Write(defaultConfigRaw) + require.NoError(t, writeErr) + + cfg, loadErr := LoadConfigFile(configFile.Name()) + require.NoError(t, loadErr) + + assert.EqualValues(t, defaultConfig.BaseConfig, cfg.BaseConfig) + assert.EqualValues(t, defaultConfig.RPC, cfg.RPC) + assert.EqualValues(t, defaultConfig.P2P, cfg.P2P) + assert.EqualValues(t, defaultConfig.Mempool, cfg.Mempool) + assert.EqualValues(t, defaultConfig.Consensus, cfg.Consensus) + assert.Equal(t, defaultConfig.TxEventStore.EventStoreType, cfg.TxEventStore.EventStoreType) + assert.Empty(t, defaultConfig.TxEventStore.Params, cfg.TxEventStore.Params) + }) +} diff --git a/tm2/pkg/bft/consensus/ticker.go b/tm2/pkg/bft/consensus/ticker.go index c4f660aae16..f1a33dd9c38 100644 --- a/tm2/pkg/bft/consensus/ticker.go +++ b/tm2/pkg/bft/consensus/ticker.go @@ -86,7 +86,7 @@ func (t *timeoutTicker) stopTimer() { } // send on tickChan to start a new timer. -// timers are interupted and replaced by new ticks from later steps +// timers are interrupted and replaced by new ticks from later steps // timeouts of 0 on the tickChan will be immediately relayed to the tockChan func (t *timeoutTicker) timeoutRoutine() { t.Logger.Debug("Starting timeout routine") diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 23b42cec6b9..bdeb5061540 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" @@ -25,8 +26,8 @@ import ( _ "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" rpcserver "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/server" sm "github.com/gnolang/gno/tm2/pkg/bft/state" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex/null" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" "github.com/gnolang/gno/tm2/pkg/bft/store" "github.com/gnolang/gno/tm2/pkg/bft/types" tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" @@ -154,18 +155,18 @@ type Node struct { isListening bool // services - evsw events.EventSwitch - stateDB dbm.DB - blockStore *store.BlockStore // store the blockchain to disk - bcReactor p2p.Reactor // for fast-syncing - mempoolReactor *mempl.Reactor // for gossipping transactions - mempool mempl.Mempool - consensusState *cs.ConsensusState // latest consensus state - consensusReactor *cs.ConsensusReactor // for participating in the consensus - proxyApp proxy.AppConns // connection to the application - rpcListeners []net.Listener // rpc servers - txIndexer txindex.TxIndexer - indexerService *txindex.IndexerService + evsw events.EventSwitch + stateDB dbm.DB + blockStore *store.BlockStore // store the blockchain to disk + bcReactor p2p.Reactor // for fast-syncing + mempoolReactor *mempl.Reactor // for gossipping transactions + mempool mempl.Mempool + consensusState *cs.ConsensusState // latest consensus state + consensusReactor *cs.ConsensusReactor // for participating in the consensus + proxyApp proxy.AppConns // connection to the application + rpcListeners []net.Listener // rpc servers + txEventStore eventstore.TxEventStore + eventStoreService *eventstore.Service } func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) { @@ -193,36 +194,36 @@ func createAndStartProxyAppConns(clientCreator proxy.ClientCreator, logger log.L return proxyApp, nil } -func createAndStartIndexerService(config *cfg.Config, dbProvider DBProvider, - evsw events.EventSwitch, logger log.Logger, -) (*txindex.IndexerService, txindex.TxIndexer, error) { - var txIndexer txindex.TxIndexer = &null.TxIndex{} - /* - switch config.TxIndex.Indexer { - case "kv": - store, err := dbProvider(&DBContext{"tx_index", config}) - if err != nil { - return nil, nil, err - } - switch { - case config.TxIndex.IndexTags != "": - txIndexer = kv.NewTxIndex(store, kv.IndexTags(splitAndTrimEmpty(config.TxIndex.IndexTags, ",", " "))) - case config.TxIndex.IndexAllTags: - txIndexer = kv.NewTxIndex(store, kv.IndexAllTags()) - default: - txIndexer = kv.NewTxIndex(store) - } - default: - txIndexer = &null.TxIndex{} +func createAndStartEventStoreService( + cfg *cfg.Config, + evsw events.EventSwitch, + logger log.Logger, +) (*eventstore.Service, eventstore.TxEventStore, error) { + var ( + err error + txEventStore eventstore.TxEventStore + ) + + // Instantiate the event store based on the configuration + switch cfg.TxEventStore.EventStoreType { + case file.EventStoreType: + // Transaction events should be logged to files + txEventStore, err = file.NewTxEventStore(cfg.TxEventStore) + if err != nil { + return nil, nil, fmt.Errorf("unable to create file tx event store, %w", err) } - */ + default: + // Transaction event storing should be omitted + txEventStore = null.NewNullEventStore() + } - indexerService := txindex.NewIndexerService(txIndexer, evsw) - indexerService.SetLogger(logger.With("module", "txindex")) + indexerService := eventstore.NewEventStoreService(txEventStore, evsw) + indexerService.SetLogger(logger.With("module", "eventstore")) if err := indexerService.Start(); err != nil { return nil, nil, err } - return indexerService, txIndexer, nil + + return indexerService, txEventStore, nil } func doHandshake(stateDB dbm.DB, state sm.State, blockStore sm.BlockStore, @@ -431,14 +432,14 @@ func NewNode(config *cfg.Config, return nil, err } - // EventSwitch and IndexerService must be started before the handshake because - // we might need to index the txs of the replayed block as this might not have happened + // EventSwitch and EventStoreService must be started before the handshake because + // we might need to store the txs of the replayed block as this might not have happened // when the node stopped last time (i.e. the node stopped after it saved the block // but before it indexed the txs, or, endblocker panicked) evsw := events.NewEventSwitch() - // Transaction indexing - indexerService, txIndexer, err := createAndStartIndexerService(config, dbProvider, evsw, logger) + // Transaction event storing + eventStoreService, txEventStore, err := createAndStartEventStoreService(config, evsw, logger) if err != nil { return nil, err } @@ -500,7 +501,7 @@ func NewNode(config *cfg.Config, privValidator, fastSync, evsw, consensusLogger, ) - nodeInfo, err := makeNodeInfo(config, nodeKey, txIndexer, genDoc, state) + nodeInfo, err := makeNodeInfo(config, nodeKey, txEventStore, genDoc, state) if err != nil { return nil, errors.Wrap(err, "error making NodeInfo") } @@ -541,17 +542,17 @@ func NewNode(config *cfg.Config, nodeInfo: nodeInfo, nodeKey: nodeKey, - evsw: evsw, - stateDB: stateDB, - blockStore: blockStore, - bcReactor: bcReactor, - mempoolReactor: mempoolReactor, - mempool: mempool, - consensusState: consensusState, - consensusReactor: consensusReactor, - proxyApp: proxyApp, - txIndexer: txIndexer, - indexerService: indexerService, + evsw: evsw, + stateDB: stateDB, + blockStore: blockStore, + bcReactor: bcReactor, + mempoolReactor: mempoolReactor, + mempool: mempool, + consensusState: consensusState, + consensusReactor: consensusReactor, + proxyApp: proxyApp, + txEventStore: txEventStore, + eventStoreService: eventStoreService, } node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -626,7 +627,7 @@ func (n *Node) OnStop() { // first stop the non-reactor services n.evsw.Stop() - n.indexerService.Stop() + n.eventStoreService.Stop() // now stop the reactors n.sw.Stop() @@ -664,7 +665,7 @@ func (n *Node) ConfigureRPC() { rpccore.SetPubKey(pubKey) rpccore.SetGenesisDoc(n.genesisDoc) rpccore.SetProxyAppQuery(n.proxyApp.Query()) - rpccore.SetTxIndexer(n.txIndexer) + rpccore.SetTxEventStore(n.txEventStore) rpccore.SetConsensusReactor(n.consensusReactor) rpccore.SetLogger(n.Logger.With("module", "rpc")) rpccore.SetEventSwitch(n.evsw) @@ -839,15 +840,13 @@ func (n *Node) NodeInfo() p2p.NodeInfo { func makeNodeInfo( config *cfg.Config, nodeKey *p2p.NodeKey, - txIndexer txindex.TxIndexer, + txEventStore eventstore.TxEventStore, genDoc *types.GenesisDoc, state sm.State, ) (p2p.NodeInfo, error) { - txIndexerStatus := "on" - if _, ok := txIndexer.(*null.TxIndex); ok { - txIndexerStatus = "off" - } else if txIndexer == nil { - txIndexerStatus = "none" + txIndexerStatus := eventstore.StatusOff + if txEventStore.GetType() != null.EventStoreType { + txIndexerStatus = eventstore.StatusOn } bcChannel := bc.BlockchainChannel diff --git a/tm2/pkg/bft/rpc/client/mock/client.go b/tm2/pkg/bft/rpc/client/mock/client.go index f7a617da9fe..46db69debb3 100644 --- a/tm2/pkg/bft/rpc/client/mock/client.go +++ b/tm2/pkg/bft/rpc/client/mock/client.go @@ -50,7 +50,7 @@ type Call struct { Error error } -// GetResponse will generate the apporiate response for us, when +// GetResponse will generate the appropriate response for us, when // using the Call struct to configure a Mock handler. // // When configuring a response, if only one of Response or Error is diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index fd6c8fc0692..a8b102d9ab7 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -10,7 +10,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/proxy" cfg "github.com/gnolang/gno/tm2/pkg/bft/rpc/config" sm "github.com/gnolang/gno/tm2/pkg/bft/state" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" @@ -25,7 +25,7 @@ const ( maxPerPage = 100 ) -//---------------------------------------------- +// ---------------------------------------------- // These interfaces are used by RPC and must be thread safe type Consensus interface { @@ -50,7 +50,7 @@ type peers interface { Peers() p2p.IPeerSet } -//---------------------------------------------- +// ---------------------------------------------- // These package level globals come with setters // that are expected to be called only once, on startup @@ -68,7 +68,7 @@ var ( // objects pubKey crypto.PubKey genDoc *types.GenesisDoc // cache the genesis structure - txIndexer txindex.TxIndexer + txEventStore eventstore.TxEventStore consensusReactor *consensus.ConsensusReactor evsw events.EventSwitch gTxDispatcher *txDispatcher @@ -115,8 +115,8 @@ func SetProxyAppQuery(appConn proxy.AppConnQuery) { proxyAppQuery = appConn } -func SetTxIndexer(indexer txindex.TxIndexer) { - txIndexer = indexer +func SetTxEventStore(indexer eventstore.TxEventStore) { + txEventStore = indexer } func SetConsensusReactor(conR *consensus.ConsensusReactor) { diff --git a/tm2/pkg/bft/state/eventstore/file/file.go b/tm2/pkg/bft/state/eventstore/file/file.go new file mode 100644 index 00000000000..f4cd74721f5 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/file/file.go @@ -0,0 +1,92 @@ +package file + +import ( + "fmt" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/autofile" + storetypes "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/errors" +) + +const ( + EventStoreType = "file" + Path = "path" +) + +var ( + errMissingPath = errors.New("missing path param") + errInvalidType = errors.New("invalid config for file event store specified") +) + +// TxEventStore is the implementation of a transaction event store +// that outputs to the local filesystem +type TxEventStore struct { + headPath string + group *autofile.Group +} + +// NewTxEventStore creates a new file-based tx event store +func NewTxEventStore(cfg *storetypes.Config) (*TxEventStore, error) { + // Parse config params + if EventStoreType != cfg.EventStoreType { + return nil, errInvalidType + } + + headPath, ok := cfg.GetParam(Path).(string) + if !ok { + return nil, errMissingPath + } + + return &TxEventStore{ + headPath: headPath, + }, nil +} + +// Start starts the file transaction event store, by opening the autofile group +func (t *TxEventStore) Start() error { + // Open the group + group, err := autofile.OpenGroup(t.headPath) + if err != nil { + return fmt.Errorf("unable to open file group for writing, %w", err) + } + + t.group = group + + return nil +} + +// Stop stops the file transaction event store, by closing the autofile group +func (t *TxEventStore) Stop() error { + // Close off the group + t.group.Close() + + return nil +} + +// GetType returns the file transaction event store type +func (t *TxEventStore) GetType() string { + return EventStoreType +} + +// Append marshals the transaction using amino, and writes it to the disk +func (t *TxEventStore) Append(tx types.TxResult) error { + // Serialize the transaction using amino + txRaw, err := amino.MarshalJSON(tx) + if err != nil { + return fmt.Errorf("unable to marshal transaction, %w", err) + } + + // Write the serialized transaction info to the file group + if err = t.group.WriteLine(string(txRaw)); err != nil { + return fmt.Errorf("unable to save transaction event, %w", err) + } + + // Flush output to storage + if err := t.group.FlushAndSync(); err != nil { + return fmt.Errorf("unable to flush and sync transaction event, %w", err) + } + + return nil +} diff --git a/tm2/pkg/bft/state/eventstore/file/file_test.go b/tm2/pkg/bft/state/eventstore/file/file_test.go new file mode 100644 index 00000000000..46d87582ce4 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/file/file_test.go @@ -0,0 +1,140 @@ +package file + +import ( + "bufio" + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + storetypes "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" +) + +// generateTestTransactions generates random transaction results +func generateTestTransactions(count int) []types.TxResult { + txs := make([]types.TxResult, count) + + for i := 0; i < count; i++ { + txs[i] = types.TxResult{} + } + + return txs +} + +func TestTxEventStore_New(t *testing.T) { + t.Parallel() + + t.Run("invalid file path specified", func(t *testing.T) { + t.Parallel() + + cfg := &storetypes.Config{ + EventStoreType: "invalid", + } + + i, err := NewTxEventStore(cfg) + + assert.Nil(t, i) + assert.ErrorIs(t, err, errInvalidType) + }) + + t.Run("invalid file path specified", func(t *testing.T) { + t.Parallel() + + cfg := &storetypes.Config{ + EventStoreType: EventStoreType, + Params: nil, + } + + i, err := NewTxEventStore(cfg) + + assert.Nil(t, i) + assert.ErrorIs(t, err, errMissingPath) + }) + + t.Run("valid file path specified", func(t *testing.T) { + t.Parallel() + + headPath := "." + + cfg := &storetypes.Config{ + EventStoreType: EventStoreType, + Params: map[string]any{ + Path: headPath, + }, + } + + i, err := NewTxEventStore(cfg) + if i == nil { + t.Fatalf("unable to create event store") + } + + assert.NoError(t, err) + assert.Equal(t, headPath, i.headPath) + assert.Equal(t, EventStoreType, i.GetType()) + }) +} + +func TestTxEventStore_Append(t *testing.T) { + t.Parallel() + + headFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(func() { + cleanup() + }) + + eventStore, err := NewTxEventStore(&storetypes.Config{ + EventStoreType: EventStoreType, + Params: map[string]any{ + Path: headFile.Name(), + }, + }) + if err != nil { + t.Fatalf("unable to create tx event store, %v", err) + } + + // Start the event store + if err = eventStore.Start(); err != nil { + t.Fatalf("unable to start event store, %v", err) + } + + t.Cleanup(func() { + // Stop the event store + if err = eventStore.Stop(); err != nil { + t.Fatalf("unable to stop event store gracefully, %v", err) + } + }) + + numTxs := 10 + txs := generateTestTransactions(numTxs) + + for _, tx := range txs { + if err = eventStore.Append(tx); err != nil { + t.Fatalf("unable to store transaction, %v", err) + } + } + + // Make sure the file group's size is valid + if eventStore.group.ReadGroupInfo().TotalSize == 0 { + t.Fatalf("invalid group size") + } + + // Open file for reading + scanner := bufio.NewScanner(headFile) + + linesRead := 0 + for scanner.Scan() { + line := scanner.Bytes() + + var txRes types.TxResult + if err = amino.UnmarshalJSON(line, &txRes); err != nil { + t.Fatalf("unable to read store line") + } + + assert.Equal(t, txs[linesRead], txRes) + + linesRead++ + } + + assert.Equal(t, numTxs, linesRead) +} diff --git a/tm2/pkg/bft/state/eventstore/mock_test.go b/tm2/pkg/bft/state/eventstore/mock_test.go new file mode 100644 index 00000000000..087d8f6e3e9 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/mock_test.go @@ -0,0 +1,89 @@ +package eventstore + +import ( + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/service" +) + +// TxEventStore // + +type ( + startDelegate func() error + stopDelegate func() error + getTypeDelegate func() string + appendDelegate func(types.TxResult) error +) + +type mockEventStore struct { + startFn startDelegate + stopFn stopDelegate + getTypeFn getTypeDelegate + appendFn appendDelegate +} + +func (m mockEventStore) Start() error { + if m.startFn != nil { + return m.startFn() + } + + return nil +} + +func (m mockEventStore) Stop() error { + if m.stopFn != nil { + return m.stopFn() + } + + return nil +} + +func (m mockEventStore) GetType() string { + if m.getTypeFn != nil { + return m.getTypeFn() + } + + return "" +} + +func (m mockEventStore) Append(result types.TxResult) error { + if m.appendFn != nil { + return m.appendFn(result) + } + + return nil +} + +// EventSwitch // + +type ( + fireEventDelegate func(events.Event) + addListenerDelegate func(string, events.EventCallback) + removeListenerDelegate func(string) +) + +type mockEventSwitch struct { + service.BaseService + + fireEventFn fireEventDelegate + addListenerFn addListenerDelegate + removeListenerFn removeListenerDelegate +} + +func (m *mockEventSwitch) FireEvent(ev events.Event) { + if m.fireEventFn != nil { + m.fireEventFn(ev) + } +} + +func (m *mockEventSwitch) AddListener(listenerID string, cb events.EventCallback) { + if m.addListenerFn != nil { + m.addListenerFn(listenerID, cb) + } +} + +func (m *mockEventSwitch) RemoveListener(listenerID string) { + if m.removeListenerFn != nil { + m.removeListenerFn(listenerID) + } +} diff --git a/tm2/pkg/bft/state/eventstore/null/null.go b/tm2/pkg/bft/state/eventstore/null/null.go new file mode 100644 index 00000000000..40e3566d89e --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/null/null.go @@ -0,0 +1,35 @@ +package null + +import ( + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" + "github.com/gnolang/gno/tm2/pkg/bft/types" +) + +var _ eventstore.TxEventStore = (*TxEventStore)(nil) + +const ( + EventStoreType = "none" +) + +// TxEventStore acts as a /dev/null +type TxEventStore struct{} + +func NewNullEventStore() *TxEventStore { + return &TxEventStore{} +} + +func (t TxEventStore) Start() error { + return nil +} + +func (t TxEventStore) Stop() error { + return nil +} + +func (t TxEventStore) Append(_ types.TxResult) error { + return nil +} + +func (t TxEventStore) GetType() string { + return EventStoreType +} diff --git a/tm2/pkg/bft/state/eventstore/store.go b/tm2/pkg/bft/state/eventstore/store.go new file mode 100644 index 00000000000..10ef9eefc9b --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store.go @@ -0,0 +1,24 @@ +package eventstore + +import "github.com/gnolang/gno/tm2/pkg/bft/types" + +const ( + StatusOn = "on" + StatusOff = "off" +) + +// TxEventStore stores transaction events for later processing +type TxEventStore interface { + // Start starts the transaction event store + Start() error + + // Stop stops the transaction event store + Stop() error + + // GetType returns the event store type + GetType() string + + // Append analyzes and appends a single transaction + // to the event store + Append(result types.TxResult) error +} diff --git a/tm2/pkg/bft/state/eventstore/store_service.go b/tm2/pkg/bft/state/eventstore/store_service.go new file mode 100644 index 00000000000..d6ed40c4151 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store_service.go @@ -0,0 +1,84 @@ +package eventstore + +import ( + "context" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/service" +) + +// Service connects the event bus and event store together in order +// to store events coming from event bus +type Service struct { + service.BaseService + + cancelFn context.CancelFunc + + txEventStore TxEventStore + evsw events.EventSwitch +} + +// NewEventStoreService returns a new service instance +func NewEventStoreService(idr TxEventStore, evsw events.EventSwitch) *Service { + is := &Service{txEventStore: idr, evsw: evsw} + is.BaseService = *service.NewBaseService(nil, "EventStoreService", is) + + return is +} + +func (is *Service) OnStart() error { + // Create a context for the intermediary monitor service + ctx, cancelFn := context.WithCancel(context.Background()) + is.cancelFn = cancelFn + + // Start the event store + if err := is.txEventStore.Start(); err != nil { + return fmt.Errorf("unable to start transaction event store, %w", err) + } + + // Start the intermediary monitor service + go is.monitorTxEvents(ctx) + + return nil +} + +func (is *Service) OnStop() { + // Close off any routines + is.cancelFn() + + // Attempt to gracefully stop the event store + if err := is.txEventStore.Stop(); err != nil { + is.Logger.Error( + fmt.Sprintf("unable to gracefully stop event store, %v", err), + ) + } +} + +// monitorTxEvents acts as an intermediary feed service for the supplied +// event store. It relays transaction events that come from the event stream +func (is *Service) monitorTxEvents(ctx context.Context) { + // Create a subscription for transaction events + subCh := events.SubscribeToEvent(is.evsw, "tx-event-store", types.EventTx{}) + + for { + select { + case <-ctx.Done(): + return + case evRaw := <-subCh: + // Cast the event + ev, ok := evRaw.(types.EventTx) + if !ok { + is.Logger.Error("invalid transaction result type cast") + + continue + } + + // Alert the actual tx event store + if err := is.txEventStore.Append(ev.Result); err != nil { + is.Logger.Error("unable to store transaction", "err", err) + } + } + } +} diff --git a/tm2/pkg/bft/state/eventstore/store_service_test.go b/tm2/pkg/bft/state/eventstore/store_service_test.go new file mode 100644 index 00000000000..3fa5e8a7941 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store_service_test.go @@ -0,0 +1,159 @@ +package eventstore + +import ( + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/stretchr/testify/assert" +) + +// generateTxEvents generates random transaction events +func generateTxEvents(count int) []types.EventTx { + txEvents := make([]types.EventTx, count) + + for i := 0; i < count; i++ { + txEvents[i] = types.EventTx{ + Result: types.TxResult{}, + } + } + + return txEvents +} + +func TestEventStoreService_Monitor(t *testing.T) { + t.Parallel() + + const defaultTimeout = 5 * time.Second + + var ( + startCalled = false + stopCalled = false + receivedResults = make([]types.TxResult, 0) + receivedSize atomic.Int64 + + cb events.EventCallback + cbSet atomic.Bool + + mockEventStore = &mockEventStore{ + startFn: func() error { + startCalled = true + + return nil + }, + stopFn: func() error { + stopCalled = true + + return nil + }, + appendFn: func(result types.TxResult) error { + receivedResults = append(receivedResults, result) + + // Atomic because we are accessing this size from a routine + receivedSize.Store(int64(len(receivedResults))) + + return nil + }, + } + mockEventSwitch = &mockEventSwitch{ + fireEventFn: func(event events.Event) { + // Exec the callback on event fire + cb(event) + }, + addListenerFn: func(_ string, callback events.EventCallback) { + // Attach callback + cb = callback + + // Atomic because we are accessing this info from a routine + cbSet.Store(true) + }, + } + ) + + // Create a new event store instance + i := NewEventStoreService(mockEventStore, mockEventSwitch) + if i == nil { + t.Fatal("unable to create event store service") + } + + // Start the event store + if err := i.OnStart(); err != nil { + t.Fatalf("unable to start event store, %v", err) + } + + assert.True(t, startCalled) + + t.Cleanup(func() { + // Stop the event store + i.OnStop() + + assert.True(t, stopCalled) + }) + + // Fire off the events so the event store can catch them + numEvents := 1000 + txEvents := generateTxEvents(numEvents) + + var wg sync.WaitGroup + + // Start a routine that asynchronously pushes events + wg.Add(1) + go func() { + defer wg.Done() + + timeout := time.After(defaultTimeout) + + for { + select { + case <-timeout: + return + default: + // If the callback is set, fire the events + if !cbSet.Load() { + // Listener not set yet + continue + } + + for _, event := range txEvents { + mockEventSwitch.FireEvent(event) + } + + return + } + } + }() + + // Start a routine that monitors received results + wg.Add(1) + go func() { + defer wg.Done() + + timeout := time.After(defaultTimeout) + + for { + select { + case <-timeout: + return + default: + if int(receivedSize.Load()) == numEvents { + return + } + } + } + }() + + wg.Wait() + + // Make sure all results were received + if len(receivedResults) != numEvents { + t.Fatalf("invalid number of results received, %d", len(receivedResults)) + } + + // Make sure all results match + for index, event := range txEvents { + assert.Equal(t, event.Result, receivedResults[index]) + } +} diff --git a/tm2/pkg/bft/state/eventstore/types/config.go b/tm2/pkg/bft/state/eventstore/types/config.go new file mode 100644 index 00000000000..08e25870b4d --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/types/config.go @@ -0,0 +1,29 @@ +package types + +import "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" + +// EventStoreParams defines the arbitrary event store config params +type EventStoreParams map[string]any + +// Config defines the specific event store configuration +type Config struct { + EventStoreType string + Params EventStoreParams +} + +// GetParam fetches the specific config param, if any. +// Returns nil if the param is not present +func (c *Config) GetParam(name string) any { + if c.Params != nil { + return c.Params[name] + } + + return nil +} + +// DefaultEventStoreConfig returns the default event store config +func DefaultEventStoreConfig() *Config { + return &Config{ + EventStoreType: null.EventStoreType, + } +} diff --git a/tm2/pkg/bft/state/eventstore/types/config_test.go b/tm2/pkg/bft/state/eventstore/types/config_test.go new file mode 100644 index 00000000000..0f5683b7c61 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/types/config_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_GetParam(t *testing.T) { + t.Parallel() + + const paramName = "param" + + testTable := []struct { + name string + cfg *Config + + expectedParam any + }{ + { + "param not set", + &Config{}, + nil, + }, + { + "valid param set", + &Config{ + Params: map[string]any{ + paramName: 10, + }, + }, + 10, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, testCase.expectedParam, testCase.cfg.GetParam(paramName)) + }) + } +} diff --git a/tm2/pkg/bft/state/txindex/indexer.go b/tm2/pkg/bft/state/txindex/indexer.go deleted file mode 100644 index 2b5b4aae220..00000000000 --- a/tm2/pkg/bft/state/txindex/indexer.go +++ /dev/null @@ -1,18 +0,0 @@ -package txindex - -// TxIndexer interface defines methods to index and search transactions. -type TxIndexer interface { /* - // AddBatch analyzes, indexes and stores a batch of transactions. - AddBatch(b *Batch) error - - // Index analyzes, indexes and stores a single transaction. - Index(result *types.TxResult) error - - // Get returns the transaction specified by hash or nil if the transaction is not indexed - // or stored. - Get(hash []byte) (*types.TxResult, error) - - // Search allows you to query for transactions. - Search(q *query.Query) ([]*types.TxResult, error) - */ -} diff --git a/tm2/pkg/bft/state/txindex/indexer_service.go b/tm2/pkg/bft/state/txindex/indexer_service.go deleted file mode 100644 index fb5c3068ae4..00000000000 --- a/tm2/pkg/bft/state/txindex/indexer_service.go +++ /dev/null @@ -1,31 +0,0 @@ -package txindex - -import ( - "github.com/gnolang/gno/tm2/pkg/events" - "github.com/gnolang/gno/tm2/pkg/service" -) - -// IndexerService connects event bus and transaction indexer together in order -// to index transactions coming from event bus. -type IndexerService struct { - service.BaseService - - idr TxIndexer - evsw events.EventSwitch -} - -// NewIndexerService returns a new service instance. -func NewIndexerService(idr TxIndexer, evsw events.EventSwitch) *IndexerService { - is := &IndexerService{idr: idr, evsw: evsw} - is.BaseService = *service.NewBaseService(nil, "IndexerService", is) - return is -} - -func (is *IndexerService) OnStart() error { - // TODO - return nil -} - -func (is *IndexerService) OnStop() { - // TODO -} diff --git a/tm2/pkg/bft/state/txindex/null/null.go b/tm2/pkg/bft/state/txindex/null/null.go deleted file mode 100644 index ed90013b9a9..00000000000 --- a/tm2/pkg/bft/state/txindex/null/null.go +++ /dev/null @@ -1,31 +0,0 @@ -package null - -import ( - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" -) - -var _ txindex.TxIndexer = (*TxIndex)(nil) - -// TxIndex acts as a /dev/null. -type TxIndex struct{} - -/* -// Get on a TxIndex is disabled and panics when invoked. -func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { - return nil, errors.New(`Indexing is disabled (set 'tx_index = "kv"' in config)`) -} - -// AddBatch is a noop and always returns nil. -func (txi *TxIndex) AddBatch(batch *txindex.Batch) error { - return nil -} - -// Index is a noop and always returns nil. -func (txi *TxIndex) Index(result *types.TxResult) error { - return nil -} - -func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { - return []*types.TxResult{}, nil -} -*/ diff --git a/tm2/pkg/bft/types/evidence.go b/tm2/pkg/bft/types/evidence.go index 26cf1aaa8ca..c11021e3976 100644 --- a/tm2/pkg/bft/types/evidence.go +++ b/tm2/pkg/bft/types/evidence.go @@ -65,7 +65,7 @@ const ( ) // MaxEvidencePerBlock returns the maximum number of evidences -// allowed in the block and their maximum total size (limitted to 1/10th +// allowed in the block and their maximum total size (limited to 1/10th // of the maximum block size). // TODO: change to a constant, or to a fraction of the validator set size. // See https://github.com/tendermint/classic/issues/2590 diff --git a/tm2/pkg/crypto/keys/armor/armor.go b/tm2/pkg/crypto/keys/armor/armor.go index 7233b27123a..22315f8c521 100644 --- a/tm2/pkg/crypto/keys/armor/armor.go +++ b/tm2/pkg/crypto/keys/armor/armor.go @@ -128,7 +128,7 @@ func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privK } key = crypto.Sha256(key) // Get 32 bytes privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) - if err != nil && err.Error() == "Ciphertext decryption failed" { + if err != nil && err.Error() == "ciphertext decryption failed" { return privKey, keyerror.NewErrWrongPassword() } else if err != nil { return privKey, err diff --git a/tm2/pkg/crypto/keys/keyerror/errors.go b/tm2/pkg/crypto/keys/keyerror/errors.go index 93eb63d2bf3..f7dc97e972d 100644 --- a/tm2/pkg/crypto/keys/keyerror/errors.go +++ b/tm2/pkg/crypto/keys/keyerror/errors.go @@ -1,6 +1,7 @@ package keyerror import ( + "errors" "fmt" ) @@ -40,7 +41,8 @@ func IsErrKeyNotFound(err error) bool { if err == nil { return false } - if keyErr, ok := err.(keybaseError); ok { + var keyErr keybaseError + if errors.As(err, &keyErr) { if keyErr.Code() == codeKeyNotFound { return true } @@ -72,7 +74,8 @@ func IsErrWrongPassword(err error) bool { if err == nil { return false } - if keyErr, ok := err.(keybaseError); ok { + var keyErr keybaseError + if errors.As(err, &keyErr) { if keyErr.Code() == codeWrongPassword { return true } diff --git a/tm2/pkg/db/prefix_db.go b/tm2/pkg/db/prefix_db.go index ced82f922d1..29ed53639e8 100644 --- a/tm2/pkg/db/prefix_db.go +++ b/tm2/pkg/db/prefix_db.go @@ -329,7 +329,7 @@ func stripPrefix(key []byte, prefix []byte) (stripped []byte) { panic("should not happen") } if !bytes.Equal(key[:len(prefix)], prefix) { - panic("should not happne") + panic("should not happen") } return key[len(prefix):] } diff --git a/tm2/pkg/p2p/conn/secret_connection_test.go b/tm2/pkg/p2p/conn/secret_connection_test.go index 521f651e78b..a2560af34eb 100644 --- a/tm2/pkg/p2p/conn/secret_connection_test.go +++ b/tm2/pkg/p2p/conn/secret_connection_test.go @@ -234,7 +234,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { // A helper that will run with (fooConn, fooWrites, fooReads) and vice versa genNodeRunner := func(id string, nodeConn kvstoreConn, nodeWrites []string, nodeReads *[]string) async.Task { return func(_ int) (interface{}, error, bool) { - // Initiate cryptographic private key and secret connection trhough nodeConn. + // Initiate cryptographic private key and secret connection through nodeConn. nodePrvKey := ed25519.GenPrivKey() nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey) if err != nil { diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 9653a83c38a..48ba8f7776b 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -3,6 +3,7 @@ package p2p import ( "fmt" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" "github.com/gnolang/gno/tm2/pkg/strings" "github.com/gnolang/gno/tm2/pkg/versionset" ) @@ -100,7 +101,7 @@ func (info NodeInfo) Validate() error { other := info.Other txIndex := other.TxIndex switch txIndex { - case "", "on", "off": + case "", eventstore.StatusOn, eventstore.StatusOff: default: return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) }