From 307b3d905250c9d6237df686e3a06e7defbdeae9 Mon Sep 17 00:00:00 2001
From: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Date: Wed, 22 May 2024 12:41:15 +0200
Subject: [PATCH 01/49] chore: add Golang Serbia meetup to events page (#2163)
## Description
This PR adds the Golang Serbia Gno meetup to the "Upcoming events" page
on `gnoweb`.
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
.../gno.land/r/gnoland/pages/page_events.gno | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/examples/gno.land/r/gnoland/pages/page_events.gno b/examples/gno.land/r/gnoland/pages/page_events.gno
index 9728319dad8..80676ddc8d4 100644
--- a/examples/gno.land/r/gnoland/pages/page_events.gno
+++ b/examples/gno.land/r/gnoland/pages/page_events.gno
@@ -17,6 +17,17 @@ We’re looking to connect with developers and like-minded thinkers who can cont
+### Gno @ Golang Serbia
+
+- Join the meetup
+- Belgrade, May 23, 2024
+
+[Learn more](https://www.meetup.com/golang-serbia/events/300975006/)
+
+
+
+
+
### GopherCon EU
- Come Meet Us at our Booth
- Berlin, June 17 - 20, 2024
@@ -43,6 +54,12 @@ We’re looking to connect with developers and like-minded thinkers who can cont
[Learn More](https://nebular.builders/)
+
+
+
+
+
+
---
From e1586a5017189cb6394efd5cc13ba6bfb72130b8 Mon Sep 17 00:00:00 2001
From: Antonio Navarro Perez
Date: Wed, 22 May 2024 18:11:59 +0200
Subject: [PATCH 02/49] feat: Add goreleaser (#2101)
---
.github/goreleaser.yaml | 481 +++++++++++++++++++++++++++++
.github/workflows/DELETEdocker.yml | 78 -----
.github/workflows/nightlies.yml | 50 +++
.github/workflows/releaser.yml | 45 +++
.gitignore | 1 +
Dockerfile | 66 ----
Dockerfile.gno.release | 11 +
Dockerfile.gnokey.release | 6 +
Dockerfile.gnoland.release | 13 +
Dockerfile.gnoweb.release | 7 +
gnovm/tests/imports.go | 6 +-
11 files changed, 617 insertions(+), 147 deletions(-)
create mode 100644 .github/goreleaser.yaml
delete mode 100644 .github/workflows/DELETEdocker.yml
create mode 100644 .github/workflows/nightlies.yml
create mode 100644 .github/workflows/releaser.yml
delete mode 100644 Dockerfile
create mode 100644 Dockerfile.gno.release
create mode 100644 Dockerfile.gnokey.release
create mode 100644 Dockerfile.gnoland.release
create mode 100644 Dockerfile.gnoweb.release
diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml
new file mode 100644
index 00000000000..bffbf9fb0d6
--- /dev/null
+++ b/.github/goreleaser.yaml
@@ -0,0 +1,481 @@
+project_name: gno
+
+before:
+ hooks:
+ - go mod tidy
+
+builds:
+ - id: gno
+ main: ./gnovm/cmd/gno
+ binary: gno
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ - arm
+ goarm:
+ - 6
+ - 7
+ - id: gnoland
+ main: ./gno.land/cmd/gnoland
+ binary: gnoland
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ - arm
+ goarm:
+ - 6
+ - 7
+ - id: gnokey
+ main: ./gno.land/cmd/gnokey
+ binary: gnokey
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ - arm
+ goarm:
+ - 6
+ - 7
+ - id: gnoweb
+ main: ./gno.land/cmd/gnoweb
+ binary: gnoweb
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - darwin
+ goarch:
+ - amd64
+ - arm64
+ - arm
+ goarm:
+ - 6
+ - 7
+gomod:
+ proxy: true
+
+archives:
+ # https://goreleaser.com/customization/archive/
+ - files:
+ # Standard Release Files
+ - LICENSE.md
+ - README.md
+
+signs:
+ - cmd: cosign
+ env:
+ - COSIGN_EXPERIMENTAL=1
+ certificate: '${artifact}.pem'
+ args:
+ - sign-blob
+ - '--output-certificate=${certificate}'
+ - '--output-signature=${signature}'
+ - '${artifact}'
+ - "--yes" # needed on cosign 2.0.0+
+ artifacts: checksum
+ output: true
+
+dockers:
+ # https://goreleaser.com/customization/docker/
+
+ # gno
+ - use: buildx
+ dockerfile: Dockerfile.gno.release
+ goos: linux
+ goarch: amd64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64"
+ - "ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64"
+ build_flag_templates:
+ - "--platform=linux/amd64"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gno
+ extra_files:
+ - examples
+ - gnovm/stdlibs
+ - gnovm/tests/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gno.release
+ goos: linux
+ goarch: arm64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8"
+ - "ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8"
+ build_flag_templates:
+ - "--platform=linux/arm64/v8"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gno
+ extra_files:
+ - examples
+ - gnovm/stdlibs
+ - gnovm/tests/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gno.release
+ goos: linux
+ goarch: arm
+ goarm: 6
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6"
+ - "ghcr.io/gnolang/{{ .ProjectName }}:latest-armv6"
+ build_flag_templates:
+ - "--platform=linux/arm/v6"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gno
+ extra_files:
+ - examples
+ - gnovm/stdlibs
+ - gnovm/tests/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gno.release
+ goos: linux
+ goarch: arm
+ goarm: 7
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv7"
+ - "ghcr.io/gnolang/{{ .ProjectName }}:latest-armv7"
+ build_flag_templates:
+ - "--platform=linux/arm/v7"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gno
+ extra_files:
+ - examples
+ - gnovm/stdlibs
+ - gnovm/tests/stdlibs
+
+ # gnoland
+ - use: buildx
+ dockerfile: Dockerfile.gnoland.release
+ goos: linux
+ goarch: amd64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-amd64"
+ build_flag_templates:
+ - "--platform=linux/amd64"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoland
+ extra_files:
+ - gno.land/genesis/genesis_balances.txt
+ - gno.land/genesis/genesis_txs.jsonl
+ - examples
+ - gnovm/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gnoland.release
+ goos: linux
+ goarch: arm64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-arm64v8"
+ build_flag_templates:
+ - "--platform=linux/arm64/v8"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoland
+ extra_files:
+ - gno.land/genesis/genesis_balances.txt
+ - gno.land/genesis/genesis_txs.jsonl
+ - examples
+ - gnovm/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gnoland.release
+ goos: linux
+ goarch: arm
+ goarm: 6
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv6"
+ build_flag_templates:
+ - "--platform=linux/arm/v6"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoland
+ extra_files:
+ - gno.land/genesis/genesis_balances.txt
+ - gno.land/genesis/genesis_txs.jsonl
+ - examples
+ - gnovm/stdlibs
+ - use: buildx
+ dockerfile: Dockerfile.gnoland.release
+ goos: linux
+ goarch: arm
+ goarm: 7
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv7"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv7"
+ build_flag_templates:
+ - "--platform=linux/arm/v7"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoland
+ extra_files:
+ - gno.land/genesis/genesis_balances.txt
+ - gno.land/genesis/genesis_txs.jsonl
+ - examples
+ - gnovm/stdlibs
+ # gnokey
+ - use: buildx
+ dockerfile: Dockerfile.gnokey.release
+ goos: linux
+ goarch: amd64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-amd64"
+ build_flag_templates:
+ - "--platform=linux/amd64"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnokey
+ - use: buildx
+ dockerfile: Dockerfile.gnokey.release
+ goos: linux
+ goarch: arm64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-arm64v8"
+ build_flag_templates:
+ - "--platform=linux/arm64/v8"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnokey
+ - use: buildx
+ dockerfile: Dockerfile.gnokey.release
+ goos: linux
+ goarch: arm
+ goarm: 6
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv6"
+ build_flag_templates:
+ - "--platform=linux/arm/v6"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnokey
+ - use: buildx
+ dockerfile: Dockerfile.gnokey.release
+ goos: linux
+ goarch: arm
+ goarm: 7
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv7"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv7"
+ build_flag_templates:
+ - "--platform=linux/arm/v7"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnokey
+
+ # gnoweb
+ - use: buildx
+ dockerfile: Dockerfile.gnoweb.release
+ goos: linux
+ goarch: amd64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-amd64"
+ build_flag_templates:
+ - "--platform=linux/amd64"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoweb
+ - use: buildx
+ dockerfile: Dockerfile.gnoweb.release
+ goos: linux
+ goarch: arm64
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-arm64v8"
+ build_flag_templates:
+ - "--platform=linux/arm64/v8"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoweb
+ - use: buildx
+ dockerfile: Dockerfile.gnoweb.release
+ goos: linux
+ goarch: arm
+ goarm: 6
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv6"
+ build_flag_templates:
+ - "--platform=linux/arm/v6"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoweb
+ - use: buildx
+ dockerfile: Dockerfile.gnoweb.release
+ goos: linux
+ goarch: arm
+ goarm: 7
+ image_templates:
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv7"
+ - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv7"
+ build_flag_templates:
+ - "--platform=linux/arm/v7"
+ - "--label=org.opencontainers.image.created={{.Date}}"
+ - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb"
+ - "--label=org.opencontainers.image.revision={{.FullCommit}}"
+ - "--label=org.opencontainers.image.version={{.Version}}"
+ ids:
+ - gnoweb
+
+docker_manifests:
+ # https://goreleaser.com/customization/docker_manifest/
+
+ # gno
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv7
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}:latest
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}:latest-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}:latest-armv7
+
+ # gnoland
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv7
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:latest-armv7
+
+ # gnokey
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv7
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:latest-armv7
+
+ # gnoweb
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv7
+ - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest
+ image_templates:
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-amd64
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-arm64v8
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv6
+ - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:latest-armv7
+
+docker_signs:
+ - cmd: cosign
+ env:
+ - COSIGN_EXPERIMENTAL=1
+ artifacts: images
+ output: true
+ args:
+ - 'sign'
+ - '${artifact}'
+ - "--yes" # needed on cosign 2.0.0+
+
+checksum:
+ name_template: 'checksums.txt'
+
+changelog:
+ sort: asc
+
+source:
+ enabled: true
+
+sboms:
+ - artifacts: archive
+ - id: source # Two different sbom configurations need two different IDs
+ artifacts: source
+
+release:
+ draft: true
+ replace_existing_draft: true
+ prerelease: auto
+ mode: append
+ footer: |
+ ### Container Images
+
+ You can find all docker images at:
+
+ https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }}
+
diff --git a/.github/workflows/DELETEdocker.yml b/.github/workflows/DELETEdocker.yml
deleted file mode 100644
index 374eab763c9..00000000000
--- a/.github/workflows/DELETEdocker.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-name: docker
-on:
- push:
- branches: [ "master" ]
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build-main:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Get commit SHA
- id: commit
- run: echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Login to GitHub Container Registry
- uses: docker/login-action@v3
- if: (github.event_name != 'pull_request')
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Build and push
- uses: docker/build-push-action@v3
- with:
- context: .
- platforms: linux/amd64,linux/arm64
- push: ${{ github.event_name != 'pull_request' }}
- tags: |
- ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
- ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ steps.commit.outputs.sha }}
-
- build-slim:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- target: [ gnoland-slim, gnokey-slim, gno-slim, gnofaucet-slim, gnoweb-slim ]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Get commit SHA
- id: commit
- run: echo "sha=${GITHUB_SHA::8}" >> "$GITHUB_OUTPUT"
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Login to GitHub Container Registry
- uses: docker/login-action@v3
- if: (github.event_name != 'pull_request')
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Build and push
- uses: docker/build-push-action@v3
- with:
- context: .
- platforms: linux/amd64,linux/arm64
- target: ${{ matrix.target }}
- push: ${{ github.event_name != 'pull_request' }}
- tags: |
- ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ matrix.target }}:latest
- ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ matrix.target }}:${{ steps.commit.outputs.sha }}
diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml
new file mode 100644
index 00000000000..b16c358522b
--- /dev/null
+++ b/.github/workflows/nightlies.yml
@@ -0,0 +1,50 @@
+name: Trigger nightly build
+
+on:
+ schedule:
+ - cron: '0 0 * * 2-6'
+ workflow_dispatch:
+
+jobs:
+ trigger-nightly:
+ name: Push tag for nightly build
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Checkout'
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ fetch-depth: 0
+ - name: 'Push new tag'
+ run: |
+ git config user.name "${GITHUB_ACTOR}"
+ git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
+
+ # A previous release was created using a lightweight tag
+ # git describe by default includes only annotated tags
+ # git describe --tags includes lightweight tags as well
+ DESCRIBE=`git tag -l --sort=-v:refname | grep -v nightly | head -n 1`
+ MAJOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[1]}'`
+ MINOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[2]}'`
+ MINOR_VERSION="$((${MINOR_VERSION} + 1))"
+ TAG="${MAJOR_VERSION}.${MINOR_VERSION}.0-nightly.$(date +'%Y%m%d')"
+ git tag -a $TAG -m "$TAG: nightly build"
+ git push origin $TAG
+ - name: 'Clean up nightly releases'
+ uses: dev-drprasad/delete-older-releases@v0.3.3
+ with:
+ keep_latest: 5
+ delete_tags: true
+ delete_tag_pattern: nightly
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: 'Delete nightly containers older than a week'
+ uses: snok/container-retention-policy@v2.1.2
+ with:
+ image-names: gnolang/gno*
+ cut-off: 1 week ago UTC
+ account-type: org
+ org-name: gnolang
+ keep-at-least: 5
+ token: ${{ secrets.GITHUB_TOKEN }}
+ filter-tags: '*-nightly*'
\ No newline at end of file
diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml
new file mode 100644
index 00000000000..8baa16254f3
--- /dev/null
+++ b/.github/workflows/releaser.yml
@@ -0,0 +1,45 @@
+name: Go Releaser
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write # needed to write releases
+ id-token: write # needed for keyless signing
+ packages: write # needed for ghcr access
+
+jobs:
+ goreleaser:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.22.x"
+ cache: true
+
+ - uses: sigstore/cosign-installer@v3.5.0
+ - uses: anchore/sbom-action/download-syft@v0.15.10
+
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - uses: goreleaser/goreleaser-action@v5
+ with:
+ distribution: goreleaser-pro
+ version: latest
+ args: release --clean --config ./.github/goreleaser.yaml
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GORELEASER_KEY: ${{ secrets.GORELEASER_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 0a06c2cf055..019c0be3c98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ genesis.json
# Build leftovers
build
+dist
# Legacy .gitignore
data/*
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index a18c7125a82..00000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,66 +0,0 @@
-# build gno
-FROM golang:1.22 AS build-gno
-RUN mkdir -p /opt/gno/src /opt/build
-WORKDIR /opt/build
-ADD go.mod go.sum .
-RUN go mod download
-ADD . ./
-RUN go build -o ./build/gnoland ./gno.land/cmd/gnoland
-RUN go build -o ./build/gnokey ./gno.land/cmd/gnokey
-RUN go build -o ./build/gnoweb ./gno.land/cmd/gnoweb
-RUN go build -o ./build/gno ./gnovm/cmd/gno
-RUN ls -la ./build
-ADD . /opt/gno/src/
-RUN rm -rf /opt/gno/src/.git
-
-# build faucet
-FROM golang:1.22 AS build-faucet
-RUN mkdir -p /opt/gno/src /opt/build
-WORKDIR /opt/build
-ADD contribs/gnofaucet/go.mod contribs/gnofaucet/go.sum .
-RUN go mod download
-ADD contribs/gnofaucet ./
-RUN go build -o ./build/gnofaucet .
-
-
-# runtime-base + runtime-tls
-FROM debian:stable-slim AS runtime-base
-ENV PATH="${PATH}:/opt/gno/bin" \
- GNOROOT="/opt/gno/src"
-WORKDIR /opt/gno/src
-FROM runtime-base AS runtime-tls
-RUN apt-get update && apt-get install -y expect ca-certificates && update-ca-certificates
-
-# slim images
-FROM runtime-base AS gnoland-slim
-WORKDIR /opt/gno/src/gno.land/
-COPY --from=build-gno /opt/build/build/gnoland /opt/gno/bin/
-ENTRYPOINT ["gnoland"]
-EXPOSE 26657 36657
-
-FROM runtime-base AS gnokey-slim
-COPY --from=build-gno /opt/build/build/gnokey /opt/gno/bin/
-ENTRYPOINT ["gnokey"]
-
-FROM runtime-base AS gno-slim
-COPY --from=build-gno /opt/build/build/gno /opt/gno/bin/
-ENTRYPOINT ["gno"]
-
-FROM runtime-tls AS gnofaucet-slim
-COPY --from=build-faucet /opt/build/build/gnofaucet /opt/gno/bin/
-ENTRYPOINT ["gnofaucet"]
-EXPOSE 5050
-
-FROM runtime-tls AS gnoweb-slim
-COPY --from=build-gno /opt/build/build/gnoweb /opt/gno/bin/
-COPY --from=build-gno /opt/gno/src/gno.land/cmd/gnoweb /opt/gno/src/gnoweb
-ENTRYPOINT ["gnoweb"]
-EXPOSE 8888
-
-# all, contains everything.
-FROM runtime-tls AS all
-COPY --from=build-gno /opt/build/build/* /opt/gno/bin/
-COPY --from=build-faucet /opt/build/build/* /opt/gno/bin/
-COPY --from=build-gno /opt/gno/src /opt/gno/src
-# gofmt is required by `gnokey maketx addpkg`
-COPY --from=build-gno /usr/local/go/bin/gofmt /usr/bin
diff --git a/Dockerfile.gno.release b/Dockerfile.gno.release
new file mode 100644
index 00000000000..874d427661f
--- /dev/null
+++ b/Dockerfile.gno.release
@@ -0,0 +1,11 @@
+FROM busybox
+
+COPY ./gno /gno
+COPY ./examples /gnoroot/examples/
+COPY ./gnovm/stdlibs /gnoroot/gnovm/stdlibs/
+COPY ./gnovm/tests/stdlibs /gnoroot/gnovm/tests/stdlibs/
+
+ENV GNOROOT="/gnoroot/"
+
+ENTRYPOINT [ "/gno" ]
+CMD [ "" ]
\ No newline at end of file
diff --git a/Dockerfile.gnokey.release b/Dockerfile.gnokey.release
new file mode 100644
index 00000000000..4261f9272d6
--- /dev/null
+++ b/Dockerfile.gnokey.release
@@ -0,0 +1,6 @@
+FROM busybox
+
+COPY . /
+
+ENTRYPOINT [ "/gnokey" ]
+CMD [ "" ]
\ No newline at end of file
diff --git a/Dockerfile.gnoland.release b/Dockerfile.gnoland.release
new file mode 100644
index 00000000000..f8bd321f67f
--- /dev/null
+++ b/Dockerfile.gnoland.release
@@ -0,0 +1,13 @@
+FROM busybox
+
+COPY ./gnoland /gnoland
+COPY ./examples /gnoroot/examples/
+COPY ./gnovm/stdlibs /gnoroot/gnovm/stdlibs/
+COPY ./gno.land/genesis/genesis_balances.txt /gnoroot/gno.land/genesis/genesis_balances.txt
+COPY ./gno.land/genesis/genesis_txs.jsonl /gnoroot/gno.land/genesis/genesis_txs.jsonl
+
+ENV GNOROOT="/gnoroot/"
+
+EXPOSE 26657 36657
+ENTRYPOINT [ "/gnoland" ]
+CMD [ "" ]
\ No newline at end of file
diff --git a/Dockerfile.gnoweb.release b/Dockerfile.gnoweb.release
new file mode 100644
index 00000000000..9bdfd8ddce2
--- /dev/null
+++ b/Dockerfile.gnoweb.release
@@ -0,0 +1,7 @@
+FROM busybox
+
+COPY . /
+
+EXPOSE 8888
+ENTRYPOINT [ "/gnoweb" ]
+CMD [ "" ]
\ No newline at end of file
diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go
index d5541fb0554..befaec7d026 100644
--- a/gnovm/tests/imports.go
+++ b/gnovm/tests/imports.go
@@ -258,16 +258,16 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri
pkg.DefineGoNativeValue("Pi", math.Pi)
pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32)
pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64)
- pkg.DefineGoNativeValue("MaxUint32", math.MaxUint32)
+ pkg.DefineGoNativeValue("MaxUint32", uint32(math.MaxUint32))
pkg.DefineGoNativeValue("MaxUint64", uint64(math.MaxUint64))
pkg.DefineGoNativeValue("MinInt8", math.MinInt8)
pkg.DefineGoNativeValue("MinInt16", math.MinInt16)
pkg.DefineGoNativeValue("MinInt32", math.MinInt32)
- pkg.DefineGoNativeValue("MinInt64", math.MinInt64)
+ pkg.DefineGoNativeValue("MinInt64", int64(math.MinInt64))
pkg.DefineGoNativeValue("MaxInt8", math.MaxInt8)
pkg.DefineGoNativeValue("MaxInt16", math.MaxInt16)
pkg.DefineGoNativeValue("MaxInt32", math.MaxInt32)
- pkg.DefineGoNativeValue("MaxInt64", math.MaxInt64)
+ pkg.DefineGoNativeValue("MaxInt64", int64(math.MaxInt64))
return pkg, pkg.NewPackage()
case "math/rand":
// XXX only expose for tests.
From f165df7cdc2277befa22040e28aff74506ac977b Mon Sep 17 00:00:00 2001
From: Thomas Bruyelle
Date: Thu, 23 May 2024 11:31:41 +0200
Subject: [PATCH 03/49] feat(std): PrevRealm ignores user realms in MsgRun
(#1719)
Fix #1664
As commented in the IsRealm() method, a better format of user realms
should emerge in the form of `gno.land/u/user_address`, which would
remove the confusion between standard realms and the realm forged under
the MsgRun transaction.
BREAKING CHANGE: `std.PrevRealm` is not returning the user realm any
more when invoked under a transaction broadcasted by `MsgRun`.
To run the txtar test:
```
$ go test ./gno.land/cmd/gnoland/ -v -run TestTestdata/prevrealm
```
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
gno.land/cmd/gnoland/testdata/prevrealm.txtar | 183 ++++++++++++++++++
gno.land/pkg/sdk/vm/keeper.go | 5 +-
gnovm/pkg/gnolang/realm.go | 7 +-
gnovm/pkg/gnolang/values.go | 1 +
gnovm/pkg/transpiler/transpiler.go | 4 +-
5 files changed, 193 insertions(+), 7 deletions(-)
create mode 100644 gno.land/cmd/gnoland/testdata/prevrealm.txtar
diff --git a/gno.land/cmd/gnoland/testdata/prevrealm.txtar b/gno.land/cmd/gnoland/testdata/prevrealm.txtar
new file mode 100644
index 00000000000..ac7988616a4
--- /dev/null
+++ b/gno.land/cmd/gnoland/testdata/prevrealm.txtar
@@ -0,0 +1,183 @@
+# This tests ensure the consistency of the std.PrevRealm function, in the
+# following situations:
+#
+#
+# | Num | Msg Type | Call from | Entry Point | Result |
+# |-----|:--------:|:-------------------:|:---------------:|:------------:|
+# | 1 | MsgCall | wallet direct | myrlm.A() | user address |
+# | 2 | | | myrlm.B() | user address |
+# | 3 | | through /r/foo | myrlm.A() | r/foo |
+# | 4 | | | myrlm.B() | r/foo |
+# | 5 | | through /p/demo/bar | myrlm.A() | user address |
+# | 6 | | | myrlm.B() | user address |
+# | 7 | MsgRun | wallet direct | myrlm.A() | user address |
+# | 8 | | | myrlm.B() | user address |
+# | 9 | | through /r/foo | myrlm.A() | r/foo |
+# | 10 | | | myrlm.B() | r/foo |
+# | 11 | | through /p/demo/bar | myrlm.A() | user address |
+# | 12 | | | myrlm.B() | user address |
+# | 13 | MsgCall | wallet direct | std.PrevRealm() | user address |
+# | 14 | MsgRun | wallet direct | std.PrevRealm() | user address |
+
+# Init
+## deploy myrlm
+loadpkg gno.land/r/myrlm $WORK/r/myrlm
+## deploy r/foo
+loadpkg gno.land/r/foo $WORK/r/foo
+## deploy p/demo/bar
+loadpkg gno.land/p/demo/bar $WORK/p/demo/bar
+
+## start a new node
+gnoland start
+
+env RFOO_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469
+
+# Test cases
+## 1. MsgCall -> myrlm.A: user address
+gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${USER_ADDR_test1}
+
+## 2. MsgCall -> myrealm.B -> myrlm.A: user address
+gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${USER_ADDR_test1}
+
+## 3. MsgCall -> r/foo.A -> myrlm.A: r/foo
+gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${RFOO_ADDR}
+
+## 4. MsgCall -> r/foo.B -> myrlm.B -> r/foo.A: r/foo
+gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${RFOO_ADDR}
+
+## 5. MsgCall -> p/demo/bar.A -> myrlm.A: user address
+gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${USER_ADDR_test1}
+
+## 6. MsgCall -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address
+gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${USER_ADDR_test1}
+
+## 7. MsgRun -> myrlm.A: user address
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno
+stdout ${USER_ADDR_test1}
+
+## 8. MsgRun -> myrealm.B -> myrlm.A: user address
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno
+stdout ${USER_ADDR_test1}
+
+## 9. MsgRun -> r/foo.A -> myrlm.A: r/foo
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno
+stdout ${RFOO_ADDR}
+
+## 10. MsgRun -> r/foo.B -> myrlm.B -> r/foo.A: r/foo
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno
+stdout ${RFOO_ADDR}
+
+## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno
+stdout ${USER_ADDR_test1}
+
+## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno
+stdout ${USER_ADDR_test1}
+
+## 13. MsgCall -> std.PrevRealm(): user address
+gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
+stdout ${USER_ADDR_test1}
+
+## 14. MsgRun -> std.PrevRealm(): user address
+gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno
+stdout ${USER_ADDR_test1}
+
+-- r/myrlm/myrlm.gno --
+package myrlm
+
+import "std"
+
+func A() string {
+ return std.PrevRealm().Addr().String()
+}
+
+func B() string {
+ return A()
+}
+-- r/foo/foo.gno --
+package foo
+
+import "gno.land/r/myrlm"
+
+func A() string {
+ return myrlm.A()
+}
+
+func B() string {
+ return myrlm.B()
+}
+-- p/demo/bar/bar.gno --
+package bar
+
+import "gno.land/r/myrlm"
+
+func A() string {
+ return myrlm.A()
+}
+
+func B() string {
+ return myrlm.B()
+}
+-- run/myrlmA.gno --
+package main
+
+import myrlm "gno.land/r/myrlm"
+
+func main() {
+ println(myrlm.A())
+}
+-- run/myrlmB.gno --
+package main
+
+import "gno.land/r/myrlm"
+
+func main() {
+ println(myrlm.B())
+}
+-- run/fooA.gno --
+package main
+
+import "gno.land/r/foo"
+
+func main() {
+ println(foo.A())
+}
+-- run/fooB.gno --
+package main
+
+import "gno.land/r/foo"
+
+func main() {
+ println(foo.B())
+}
+-- run/barA.gno --
+package main
+
+import "gno.land/p/demo/bar"
+
+func main() {
+ println(bar.A())
+}
+-- run/barB.gno --
+package main
+
+import "gno.land/p/demo/bar"
+
+func main() {
+ println(bar.B())
+}
+-- run/baz.gno --
+package main
+
+import "std"
+
+func main() {
+ println(std.PrevRealm().Addr().String())
+}
diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go
index ef260bd3c42..ab7681bc3e1 100644
--- a/gno.land/pkg/sdk/vm/keeper.go
+++ b/gno.land/pkg/sdk/vm/keeper.go
@@ -6,7 +6,6 @@ import (
"bytes"
"fmt"
"os"
- "regexp"
"strings"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
@@ -131,8 +130,6 @@ func (vm *VMKeeper) getGnoStore(ctx sdk.Context) gno.Store {
}
}
-var reRunPath = regexp.MustCompile(`gno\.land/r/g[a-z0-9]+/run`)
-
// AddPackage adds a package with given fileset.
func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) {
creator := msg.Creator
@@ -156,7 +153,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) {
return ErrInvalidPkgPath("package already exists: " + pkgPath)
}
- if reRunPath.MatchString(pkgPath) {
+ if gno.ReGnoRunPath.MatchString(pkgPath) {
return ErrInvalidPkgPath("reserved package name: " + pkgPath)
}
diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go
index 3f615772426..17b655e6352 100644
--- a/gnovm/pkg/gnolang/realm.go
+++ b/gnovm/pkg/gnolang/realm.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
+ "regexp"
"strings"
)
@@ -1517,10 +1518,14 @@ func isUnsaved(oo Object) bool {
// be realms and as such to have their state persisted. This is used by [IsRealmPath].
const realmPathPrefix = "gno.land/r/"
+var ReGnoRunPath = regexp.MustCompile(`gno\.land/r/g[a-z0-9]+/run`)
+
// IsRealmPath determines whether the given pkgpath is for a realm, and as such
// should persist the global state.
func IsRealmPath(pkgPath string) bool {
- return strings.HasPrefix(pkgPath, realmPathPrefix)
+ return strings.HasPrefix(pkgPath, realmPathPrefix) &&
+ // MsgRun pkgPath aren't realms
+ !ReGnoRunPath.MatchString(pkgPath)
}
func prettyJSON(jstr []byte) []byte {
diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go
index 948730c4697..fda0a06f3d2 100644
--- a/gnovm/pkg/gnolang/values.go
+++ b/gnovm/pkg/gnolang/values.go
@@ -817,6 +817,7 @@ type PackageValue struct {
fBlocksMap map[Name]*Block
}
+// IsRealm returns true if pv represents a realm.
func (pv *PackageValue) IsRealm() bool {
return IsRealmPath(pv.PkgPath)
}
diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go
index cc49aac4c78..e3d817700d0 100644
--- a/gnovm/pkg/transpiler/transpiler.go
+++ b/gnovm/pkg/transpiler/transpiler.go
@@ -231,7 +231,7 @@ func TranspileBuildPackage(fileOrPkg, goBinary string) error {
return err
}
-var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`)
+var reGoBuildError = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`)
// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found
// in out, which is supposed to be the output of the `go build` command.
@@ -240,7 +240,7 @@ var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`)
// See https://github.com/golang/go/issues/62067
func parseGoBuildErrors(out string) error {
var errList goscanner.ErrorList
- matches := errorRe.FindAllStringSubmatch(out, -1)
+ matches := reGoBuildError.FindAllStringSubmatch(out, -1)
for _, match := range matches {
filename := match[1]
line, err := strconv.Atoi(match[2])
From 11649f61cd5379c076ea8861fd8bdb406295884a Mon Sep 17 00:00:00 2001
From: sunspirit99 <167175638+linhpn99@users.noreply.github.com>
Date: Thu, 23 May 2024 17:34:16 +0700
Subject: [PATCH 04/49] feat(stdlibs/std)!: remove `std.CurrentRealmPath`
(#2087)
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Co-authored-by: Morgan Bazalgette
---
docs/concepts/stdlibs/gnopher-hole.md | 2 +-
docs/reference/stdlibs/std/chain.md | 12 ------------
docs/reference/stdlibs/std/testing.md | 11 -----------
examples/gno.land/p/demo/tests/tests.gno | 2 +-
examples/gno.land/r/demo/tests/tests.gno | 2 +-
gnovm/stdlibs/native.go | 19 -------------------
gnovm/stdlibs/std/emit_event.go | 2 +-
gnovm/stdlibs/std/emit_event_test.go | 5 ++++-
gnovm/stdlibs/std/native.gno | 9 ++++-----
gnovm/stdlibs/std/native.go | 15 +++++++--------
gnovm/stdlibs/stdshim/stdshim.gno | 5 -----
gnovm/tests/files/zrealm12.gno | 16 ++++------------
gnovm/tests/files/zrealm_natbind0.gno | 10 +++++-----
gnovm/tests/files/zrealm_std3.gno | 4 ++--
gnovm/tests/stdlibs/native.go | 19 -------------------
gnovm/tests/stdlibs/std/std.gno | 2 +-
gnovm/tests/stdlibs/std/std.go | 4 ----
17 files changed, 31 insertions(+), 108 deletions(-)
diff --git a/docs/concepts/stdlibs/gnopher-hole.md b/docs/concepts/stdlibs/gnopher-hole.md
index b8795cc5af7..b9ce0c700af 100644
--- a/docs/concepts/stdlibs/gnopher-hole.md
+++ b/docs/concepts/stdlibs/gnopher-hole.md
@@ -12,7 +12,7 @@ in Go. There are generally three reasons why a function should be natively
defined:
1. It relies on inspecting the Gno Virtual Machine itself, i.e. `std.AssertOriginCall`
- or `std.CurrentRealmPath`.
+ or `std.CurrentRealm`.
2. It relies on `unsafe`, or other features which are not planned to be
available in the GnoVM, i.e. `math.Float64frombits`.
3. Its native Go performance significantly outperforms the Gno counterpart by
diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md
index 06c40d63afc..f3dddaba938 100644
--- a/docs/reference/stdlibs/std/chain.md
+++ b/docs/reference/stdlibs/std/chain.md
@@ -41,18 +41,6 @@ std.Emit("MyEvent", "myKey1", "myValue1", "myKey2", "myValue2")
```
---
-## CurrentRealmPath
-```go
-func CurrentRealmPath() string
-```
-Returns the path of the realm it is called in.
-
-#### Usage
-```go
-realmPath := std.CurrentRealmPath() // gno.land/r/demo/users
-```
----
-
## GetChainID
```go
func GetChainID() string
diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md
index 8c9146c81a1..102b9ed6d70 100644
--- a/docs/reference/stdlibs/std/testing.md
+++ b/docs/reference/stdlibs/std/testing.md
@@ -5,7 +5,6 @@ id: testing
# Testing
```go
-func TestCurrentRealm() string
func TestSkipHeights(count int64)
func TestSetOrigCaller(addr Address)
func TestSetOrigPkgAddr(addr Address)
@@ -13,16 +12,6 @@ func TestSetOrigSend(sent, spent Coins)
func TestIssueCoins(addr Address, coins Coins)
```
-## TestCurrentRealm
-```go
-func TestCurrentRealm() string
-```
-Returns the current realm path.
-
-#### Usage
-```go
-currentRealmPath := std.TestCurrentRealm()
-```
---
## TestSkipHeights
diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno
index 1a2c2526d01..43732d82dac 100644
--- a/examples/gno.land/p/demo/tests/tests.gno
+++ b/examples/gno.land/p/demo/tests/tests.gno
@@ -18,7 +18,7 @@ func IncCounter() {
}
func CurrentRealmPath() string {
- return std.CurrentRealmPath()
+ return std.CurrentRealm().PkgPath()
}
//----------------------------------------
diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno
index 2062df6903d..14adad7355c 100644
--- a/examples/gno.land/r/demo/tests/tests.gno
+++ b/examples/gno.land/r/demo/tests/tests.gno
@@ -17,7 +17,7 @@ func Counter() int {
}
func CurrentRealmPath() string {
- return std.CurrentRealmPath()
+ return std.CurrentRealm().PkgPath()
}
var initOrigCaller = std.GetOrigCaller()
diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go
index 3b1ab719e72..7319e393c35 100644
--- a/gnovm/stdlibs/native.go
+++ b/gnovm/stdlibs/native.go
@@ -425,25 +425,6 @@ var nativeFuncs = [...]nativeFunc{
))
},
},
- {
- "std",
- "CurrentRealmPath",
- []gno.FieldTypeExpr{},
- []gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
- },
- func(m *gno.Machine) {
- r0 := libs_std.CurrentRealmPath(
- m,
- )
-
- m.PushValue(gno.Go2GnoValue(
- m.Alloc,
- m.Store,
- reflect.ValueOf(&r0).Elem(),
- ))
- },
- },
{
"std",
"GetChainID",
diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go
index 46fea79d43c..8e61f67d58a 100644
--- a/gnovm/stdlibs/std/emit_event.go
+++ b/gnovm/stdlibs/std/emit_event.go
@@ -17,7 +17,7 @@ func X_emit(m *gno.Machine, typ string, attrs []string) {
m.Panic(typedString(err.Error()))
}
- pkgPath := CurrentRealmPath(m)
+ _, pkgPath := currentRealm(m)
fnIdent := getPrevFunctionNameFromTarget(m, "Emit")
evt := gnoEvent{
diff --git a/gnovm/stdlibs/std/emit_event_test.go b/gnovm/stdlibs/std/emit_event_test.go
index 10bd8ecacd9..147ad75dbb5 100644
--- a/gnovm/stdlibs/std/emit_event_test.go
+++ b/gnovm/stdlibs/std/emit_event_test.go
@@ -13,7 +13,10 @@ import (
func TestEmit(t *testing.T) {
m := gno.NewMachine("emit", nil)
- pkgPath := CurrentRealmPath(m)
+
+ m.Context = ExecContext{}
+
+ _, pkgPath := X_getRealm(m, 0)
tests := []struct {
name string
eventType string
diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno
index 8043df49882..ef2601eeca3 100644
--- a/gnovm/stdlibs/std/native.gno
+++ b/gnovm/stdlibs/std/native.gno
@@ -1,10 +1,9 @@
package std
-func AssertOriginCall() // injected
-func IsOriginCall() bool // injected
-func CurrentRealmPath() string // injected
-func GetChainID() string // injected
-func GetHeight() int64 // injected
+func AssertOriginCall() // injected
+func IsOriginCall() bool // injected
+func GetChainID() string // injected
+func GetHeight() int64 // injected
func GetOrigSend() Coins {
den, amt := origSend()
diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go
index deb0f1268d2..7148a4ba4f4 100644
--- a/gnovm/stdlibs/std/native.go
+++ b/gnovm/stdlibs/std/native.go
@@ -17,13 +17,6 @@ func IsOriginCall(m *gno.Machine) bool {
return len(m.Frames) == 2
}
-func CurrentRealmPath(m *gno.Machine) string {
- if m.Realm != nil {
- return m.Realm.Path
- }
- return ""
-}
-
func GetChainID(m *gno.Machine) string {
return m.Context.(ExecContext).ChainID
}
@@ -98,7 +91,7 @@ func X_callerAt(m *gno.Machine, n int) string {
return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32())
}
-func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) {
+func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) {
var (
ctx = m.Context.(ExecContext)
currentCaller crypto.Bech32Address
@@ -130,6 +123,12 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) {
return string(ctx.OrigCaller), ""
}
+// currentRealm retrieves the current realm's address and pkgPath.
+// It's not a native binding; but is used within this package to clarify usage.
+func currentRealm(m *gno.Machine) (address, pkgPath string) {
+ return X_getRealm(m, 0)
+}
+
func X_derivePkgAddr(pkgPath string) string {
return string(gno.DerivePkgAddr(pkgPath).Bech32())
}
diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno
index 62e97088209..efffea59dae 100644
--- a/gnovm/stdlibs/stdshim/stdshim.gno
+++ b/gnovm/stdlibs/stdshim/stdshim.gno
@@ -16,11 +16,6 @@ func Hash(bz []byte) (hash [20]byte) {
return
}
-func CurrentRealmPath() string {
- panic(shimWarn)
- return ""
-}
-
func GetChainID() string {
panic(shimWarn)
return ""
diff --git a/gnovm/tests/files/zrealm12.gno b/gnovm/tests/files/zrealm12.gno
index 0049cba6073..ee9e85d827b 100644
--- a/gnovm/tests/files/zrealm12.gno
+++ b/gnovm/tests/files/zrealm12.gno
@@ -3,31 +3,23 @@ package test
import (
"std"
-
- "gno.land/r/demo/tests"
)
func main() {
- if std.TestCurrentRealm() != "gno.land/r/test" {
- panic("should not happen")
- }
tests.InitTestNodes()
- if std.TestCurrentRealm() != "gno.land/r/test" {
+ if std.CurrentRealm().PkgPath() != "gno.land/r/test" {
panic("should not happen")
}
tests.ModTestNodes()
- if std.TestCurrentRealm() != "gno.land/r/test" {
+ if std.CurrentRealm().PkgPath() != "gno.land/r/test" {
panic("should not happen")
}
std.ClearStoreCache()
- if std.TestCurrentRealm() != "gno.land/r/test" {
+ if std.CurrentRealm().PkgPath() != "gno.land/r/test" {
panic("should not happen")
}
tests.PrintTestNodes()
- if std.TestCurrentRealm() != "gno.land/r/test" {
+ if std.CurrentRealm().PkgPath() != "gno.land/r/test" {
panic("should not happen")
}
}
-
-// Output:
-// second's child
diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno
index 084ddd3d18f..9cf0e809ece 100644
--- a/gnovm/tests/files/zrealm_natbind0.gno
+++ b/gnovm/tests/files/zrealm_natbind0.gno
@@ -12,19 +12,19 @@ func init() {
}
func main() {
- // NOTE: this test uses GetHeight and CurrentRealmPath, which are "pure"
+ // NOTE: this test uses GetHeight and GetChainID, which are "pure"
// natively bound functions (ie. not indirections through a wrapper fn,
// to convert the types to builtin go/gno identifiers).
f := node.(func() int64)
println(f())
- node = std.CurrentRealmPath
+ node = std.GetChainID
g := node.(func() string)
println(g())
}
// Output:
// 123
-// gno.land/r/test
+// dev
// Realm:
// switchrealm["gno.land/r/test"]
@@ -145,8 +145,8 @@ func main() {
// },
// "FileName": "native.gno",
// "IsMethod": false,
-// "Name": "CurrentRealmPath",
-// "NativeName": "CurrentRealmPath",
+// "Name": "GetChainID",
+// "NativeName": "GetChainID",
// "NativePkg": "std",
// "PkgPath": "std",
// "Source": {
diff --git a/gnovm/tests/files/zrealm_std3.gno b/gnovm/tests/files/zrealm_std3.gno
index c13feffa42c..4f1d1bc827a 100644
--- a/gnovm/tests/files/zrealm_std3.gno
+++ b/gnovm/tests/files/zrealm_std3.gno
@@ -6,11 +6,11 @@ import (
)
func foo() {
- println("foo", std.CurrentRealmPath())
+ println("foo", std.CurrentRealm().PkgPath())
}
func main() {
- println("main", std.CurrentRealmPath())
+ println("main", std.CurrentRealm().PkgPath())
foo()
}
diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go
index 81100838784..6d0e3caa1f1 100644
--- a/gnovm/tests/stdlibs/native.go
+++ b/gnovm/tests/stdlibs/native.go
@@ -50,25 +50,6 @@ var nativeFuncs = [...]nativeFunc{
))
},
},
- {
- "std",
- "TestCurrentRealm",
- []gno.FieldTypeExpr{},
- []gno.FieldTypeExpr{
- {Name: gno.N("r0"), Type: gno.X("string")},
- },
- func(m *gno.Machine) {
- r0 := testlibs_std.TestCurrentRealm(
- m,
- )
-
- m.PushValue(gno.Go2GnoValue(
- m.Alloc,
- m.Store,
- reflect.ValueOf(&r0).Elem(),
- ))
- },
- },
{
"std",
"TestSkipHeights",
diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno
index 0a4f9cc6eff..91c47fed2fd 100644
--- a/gnovm/tests/stdlibs/std/std.gno
+++ b/gnovm/tests/stdlibs/std/std.gno
@@ -2,7 +2,6 @@ package std
func AssertOriginCall() // injected
func IsOriginCall() bool // injected
-func TestCurrentRealm() string // injected
func TestSkipHeights(count int64) // injected
func ClearStoreCache() // injected
@@ -15,6 +14,7 @@ func TestSetOrigSend(sent, spent Coins) {
spentDenom, spentAmt := spent.expandNative()
testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt)
}
+
func TestIssueCoins(addr Address, coins Coins) {
denom, amt := coins.expandNative()
testIssueCoins(string(addr), denom, amt)
diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go
index 72a2a7734ed..f5c30a08868 100644
--- a/gnovm/tests/stdlibs/std/std.go
+++ b/gnovm/tests/stdlibs/std/std.go
@@ -41,10 +41,6 @@ func IsOriginCall(m *gno.Machine) bool {
panic("unable to determine if test is a _test or a _filetest")
}
-func TestCurrentRealm(m *gno.Machine) string {
- return m.Realm.Path
-}
-
func TestSkipHeights(m *gno.Machine, count int64) {
ctx := m.Context.(std.ExecContext)
ctx.Height += count
From e4b39f94766e47caefb74f7827fe2ba4797cc0bc Mon Sep 17 00:00:00 2001
From: Antonio Navarro Perez
Date: Thu, 23 May 2024 12:54:54 +0200
Subject: [PATCH 05/49] fix: Change goreleaser secret name (#2171)
---
.github/workflows/releaser.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml
index 8baa16254f3..ad369e5cc56 100644
--- a/.github/workflows/releaser.yml
+++ b/.github/workflows/releaser.yml
@@ -42,4 +42,4 @@ jobs:
args: release --clean --config ./.github/goreleaser.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GORELEASER_KEY: ${{ secrets.GORELEASER_TOKEN }}
+ GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
From dbeafec7a4ca59c0a8b3025970647aaacecd6ec2 Mon Sep 17 00:00:00 2001
From: Antonio Navarro Perez
Date: Thu, 23 May 2024 13:47:45 +0200
Subject: [PATCH 06/49] fix: refactor nightly workflow to use goreleaser pro
(#2172)
Workflows are not triggered if the change was generated by a workflow to
avoid loop workflow executions. Changing that to directly trigger a
nightly using goreleaser pro.
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [x] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
Signed-off-by: Antonio Navarro
---
.github/goreleaser.yaml | 4 +++
.github/workflows/nightlies.yml | 63 +++++++++++++++------------------
2 files changed, 33 insertions(+), 34 deletions(-)
diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml
index bffbf9fb0d6..d82b31ea9fb 100644
--- a/.github/goreleaser.yaml
+++ b/.github/goreleaser.yaml
@@ -479,3 +479,7 @@ release:
https://github.com/orgs/gnolang/packages?repo_name={{ .ProjectName }}
+nightly:
+ tag_name: nightly
+ publish_release: true
+ keep_single_release: true
\ No newline at end of file
diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml
index b16c358522b..df3c8047b8d 100644
--- a/.github/workflows/nightlies.yml
+++ b/.github/workflows/nightlies.yml
@@ -5,46 +5,41 @@ on:
- cron: '0 0 * * 2-6'
workflow_dispatch:
+permissions:
+ contents: write # needed to write releases
+ id-token: write # needed for keyless signing
+ packages: write # needed for ghcr access
+
jobs:
- trigger-nightly:
- name: Push tag for nightly build
+ goreleaser:
runs-on: ubuntu-latest
steps:
- - name: 'Checkout'
- uses: actions/checkout@v4
+ - uses: actions/checkout@v4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- - name: 'Push new tag'
- run: |
- git config user.name "${GITHUB_ACTOR}"
- git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
- # A previous release was created using a lightweight tag
- # git describe by default includes only annotated tags
- # git describe --tags includes lightweight tags as well
- DESCRIBE=`git tag -l --sort=-v:refname | grep -v nightly | head -n 1`
- MAJOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[1]}'`
- MINOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[2]}'`
- MINOR_VERSION="$((${MINOR_VERSION} + 1))"
- TAG="${MAJOR_VERSION}.${MINOR_VERSION}.0-nightly.$(date +'%Y%m%d')"
- git tag -a $TAG -m "$TAG: nightly build"
- git push origin $TAG
- - name: 'Clean up nightly releases'
- uses: dev-drprasad/delete-older-releases@v0.3.3
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.22.x"
+ cache: true
+
+ - uses: sigstore/cosign-installer@v3.5.0
+ - uses: anchore/sbom-action/download-syft@v0.15.10
+
+ - uses: docker/login-action@v3
with:
- keep_latest: 5
- delete_tags: true
- delete_tag_pattern: nightly
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - uses: goreleaser/goreleaser-action@v5
+ with:
+ distribution: goreleaser-pro
+ version: latest
+ args: release --clean --nightly --config ./.github/goreleaser.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: 'Delete nightly containers older than a week'
- uses: snok/container-retention-policy@v2.1.2
- with:
- image-names: gnolang/gno*
- cut-off: 1 week ago UTC
- account-type: org
- org-name: gnolang
- keep-at-least: 5
- token: ${{ secrets.GITHUB_TOKEN }}
- filter-tags: '*-nightly*'
\ No newline at end of file
+ GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
From dc9acde00569266fddc15b8532150468229f474e Mon Sep 17 00:00:00 2001
From: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Date: Thu, 23 May 2024 14:55:28 +0200
Subject: [PATCH 07/49] docs: add disclaimer that `maketx call` is a
state-changing call (#2134)
## Description
After discussions in #1523, we decided to add a disclaimer to `maketx
call` which will let people know that a `call` to `Render()` will appliy
state changes.
Closes: #1523
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
docs/gno-tooling/cli/gnokey.md | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/docs/gno-tooling/cli/gnokey.md b/docs/gno-tooling/cli/gnokey.md
index 8479e9c112d..46026a3deab 100644
--- a/docs/gno-tooling/cli/gnokey.md
+++ b/docs/gno-tooling/cli/gnokey.md
@@ -190,7 +190,7 @@ gnokey maketx addpkg \
### `call`
-This subcommand lets you call a public function.
+This subcommand lets you call any exported function.
```bash
# Register
@@ -207,6 +207,20 @@ gnokey maketx call \
> unsigned.tx
```
+:::warn `call` is a state-changing message
+
+All exported functions, including `Render()`, can be called in two main ways:
+`call` and [`query vm/qeval`](#query).
+
+With `call`, any state change that happened in the function being called will be
+applied and persisted in on the blockchain, and the gas used for this call will
+be subtracted from the caller balance.
+
+As opposed to this, an ABCI query, such as `vm/qeval` will not persist state
+changes and does not cost gas, only evaluating the expression in read-only mode.
+
+:::
+
#### **SignBroadcast Options**
| Name | Type | Description |
From 11e13f88663a0f714cd7a003badb8dc720ccc294 Mon Sep 17 00:00:00 2001
From: Morgan
Date: Thu, 23 May 2024 14:55:58 +0200
Subject: [PATCH 08/49] refactor(tm2/crypto): do not use build tags to mock
ledger (#2173)
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
tm2/pkg/crypto/internal/ledger/discover.go | 83 +++++++++++++++++++
.../keys/client/add_ledger_skipped_test.go | 10 ---
tm2/pkg/crypto/keys/client/add_ledger_test.go | 15 +---
.../keys/keybase_ledger_skipped_test.go | 18 ----
tm2/pkg/crypto/keys/keybase_ledger_test.go | 10 +--
tm2/pkg/crypto/ledger/discover.go | 19 -----
tm2/pkg/crypto/ledger/discover_mock.go | 69 ---------------
tm2/pkg/crypto/ledger/ledger_secp256k1.go | 29 ++-----
8 files changed, 98 insertions(+), 155 deletions(-)
create mode 100644 tm2/pkg/crypto/internal/ledger/discover.go
delete mode 100644 tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go
delete mode 100644 tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go
delete mode 100644 tm2/pkg/crypto/ledger/discover.go
delete mode 100644 tm2/pkg/crypto/ledger/discover_mock.go
diff --git a/tm2/pkg/crypto/internal/ledger/discover.go b/tm2/pkg/crypto/internal/ledger/discover.go
new file mode 100644
index 00000000000..a3402323938
--- /dev/null
+++ b/tm2/pkg/crypto/internal/ledger/discover.go
@@ -0,0 +1,83 @@
+// Package ledger contains the internals for package crypto/keys/ledger,
+// primarily existing so that the Discover function can be mocked elsewhere.
+package ledger
+
+import (
+ "github.com/btcsuite/btcd/btcec/v2"
+ ledger_go "github.com/cosmos/ledger-cosmos-go"
+ "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
+)
+
+// SECP256K1 reflects an interface a Ledger API must implement for SECP256K1
+type SECP256K1 interface {
+ Close() error
+ // Returns an uncompressed pubkey
+ GetPublicKeySECP256K1([]uint32) ([]byte, error)
+ // Returns a compressed pubkey and bech32 address (requires user confirmation)
+ GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error)
+ // Signs a message (requires user confirmation)
+ SignSECP256K1([]uint32, []byte, byte) ([]byte, error)
+}
+
+// Discover defines a function to be invoked at runtime for discovering
+// a connected Ledger device.
+var Discover DiscoverFn = DiscoverDefault
+
+// DiscoverDefault is the default function for [Discover].
+func DiscoverDefault() (SECP256K1, error) {
+ device, err := ledger_go.FindLedgerCosmosUserApp()
+ if err != nil {
+ return nil, err
+ }
+
+ return device, nil
+}
+
+// DiscoverMock can be used as a mock [DiscoverFn].
+func DiscoverMock() (SECP256K1, error) {
+ privateKey := secp256k1.GenPrivKey()
+
+ _, pubKeyObject := btcec.PrivKeyFromBytes(privateKey[:])
+ return discoverMock{
+ pubKey: pubKeyObject.SerializeCompressed(),
+ address: privateKey.PubKey().Address().String(),
+ }, nil
+}
+
+type discoverMock struct {
+ MockLedger
+ pubKey []byte
+ address string
+}
+
+func (m discoverMock) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) {
+ return m.pubKey, m.address, nil
+}
+
+// MockLedger is an interface that can be used to create mock [Ledger].
+// Embed it in another type, and implement the method you want to mock:
+//
+// type MyMock struct { MockLedger }
+// func (MyMock) SignSECP256K1(d1, d2 []byte, d3 byte) ([]byte, error) { ... }
+type MockLedger struct{}
+
+func (MockLedger) Close() error {
+ return nil
+}
+
+func (MockLedger) GetPublicKeySECP256K1(data []uint32) ([]byte, error) {
+ return nil, nil
+}
+
+func (MockLedger) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) {
+ return nil, "", nil
+}
+
+func (MockLedger) SignSECP256K1(d1 []uint32, d2 []byte, d3 byte) ([]byte, error) {
+ return nil, nil
+}
+
+// DiscoverFn defines a Ledger discovery function that returns a
+// connected device or an error upon failure. Its allows a method to avoid CGO
+// dependencies when Ledger support is potentially not enabled.
+type DiscoverFn func() (SECP256K1, error)
diff --git a/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go b/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go
deleted file mode 100644
index 8a09d060b16..00000000000
--- a/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build !ledger_suite
-// +build !ledger_suite
-
-package client
-
-import "testing"
-
-func TestAdd_Ledger(t *testing.T) {
- t.Skip("Please enable the 'ledger_suite' build tags")
-}
diff --git a/tm2/pkg/crypto/keys/client/add_ledger_test.go b/tm2/pkg/crypto/keys/client/add_ledger_test.go
index c1384efcb79..676c4bff6b4 100644
--- a/tm2/pkg/crypto/keys/client/add_ledger_test.go
+++ b/tm2/pkg/crypto/keys/client/add_ledger_test.go
@@ -1,6 +1,3 @@
-//go:build ledger_suite
-// +build ledger_suite
-
package client
import (
@@ -10,19 +7,17 @@ import (
"time"
"github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-// Make sure to run these tests with the following tag enabled:
-// -tags='ledger_suite'
func TestAdd_Ledger(t *testing.T) {
- t.Parallel()
+ ledger.Discover = ledger.DiscoverMock
+ t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault })
t.Run("valid ledger reference added", func(t *testing.T) {
- t.Parallel()
-
var (
kbHome = t.TempDir()
baseOptions = BaseOptions{
@@ -63,8 +58,6 @@ func TestAdd_Ledger(t *testing.T) {
})
t.Run("valid ledger reference added, overwrite", func(t *testing.T) {
- t.Parallel()
-
var (
kbHome = t.TempDir()
baseOptions = BaseOptions{
@@ -116,8 +109,6 @@ func TestAdd_Ledger(t *testing.T) {
})
t.Run("valid ledger reference added, no overwrite permission", func(t *testing.T) {
- t.Parallel()
-
var (
kbHome = t.TempDir()
baseOptions = BaseOptions{
diff --git a/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go b/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go
deleted file mode 100644
index d406f10f2ed..00000000000
--- a/tm2/pkg/crypto/keys/keybase_ledger_skipped_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build !ledger_suite
-// +build !ledger_suite
-
-package keys
-
-import "testing"
-
-func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
- t.Parallel()
-
- t.Skip("this test needs to be run with the `ledger_suite` tag enabled")
-}
-
-func TestCreateLedger(t *testing.T) {
- t.Parallel()
-
- t.Skip("this test needs to be run with the `ledger_suite` tag enabled")
-}
diff --git a/tm2/pkg/crypto/keys/keybase_ledger_test.go b/tm2/pkg/crypto/keys/keybase_ledger_test.go
index 0f2fca79f90..ea980cf4f02 100644
--- a/tm2/pkg/crypto/keys/keybase_ledger_test.go
+++ b/tm2/pkg/crypto/keys/keybase_ledger_test.go
@@ -1,17 +1,16 @@
-//go:build ledger_suite
-// +build ledger_suite
-
package keys
import (
"testing"
+ "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
- t.Parallel()
+ ledger.Discover = ledger.DiscoverMock
+ t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault })
kb := NewInMemory()
_, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1)
@@ -20,7 +19,8 @@ func TestCreateLedgerUnsupportedAlgo(t *testing.T) {
}
func TestCreateLedger(t *testing.T) {
- t.Parallel()
+ ledger.Discover = ledger.DiscoverMock
+ t.Cleanup(func() { ledger.Discover = ledger.DiscoverDefault })
kb := NewInMemory()
diff --git a/tm2/pkg/crypto/ledger/discover.go b/tm2/pkg/crypto/ledger/discover.go
deleted file mode 100644
index d610b56635e..00000000000
--- a/tm2/pkg/crypto/ledger/discover.go
+++ /dev/null
@@ -1,19 +0,0 @@
-//go:build !ledger_suite
-// +build !ledger_suite
-
-package ledger
-
-import (
- ledger_go "github.com/cosmos/ledger-cosmos-go"
-)
-
-// discoverLedger defines a function to be invoked at runtime for discovering
-// a connected Ledger device.
-var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) {
- device, err := ledger_go.FindLedgerCosmosUserApp()
- if err != nil {
- return nil, err
- }
-
- return device, nil
-}
diff --git a/tm2/pkg/crypto/ledger/discover_mock.go b/tm2/pkg/crypto/ledger/discover_mock.go
deleted file mode 100644
index 1f5bdbafdf3..00000000000
--- a/tm2/pkg/crypto/ledger/discover_mock.go
+++ /dev/null
@@ -1,69 +0,0 @@
-//go:build ledger_suite
-// +build ledger_suite
-
-package ledger
-
-import (
- btcec "github.com/btcsuite/btcd/btcec/v2"
- "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
-)
-
-// discoverLedger defines a function to be invoked at runtime for discovering
-// a connected Ledger device.
-var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) {
- privateKey := secp256k1.GenPrivKey()
-
- _, pubKeyObject := btcec.PrivKeyFromBytes(privateKey[:])
-
- return &MockLedger{
- GetAddressPubKeySECP256K1Fn: func(data []uint32, str string) ([]byte, string, error) {
- return pubKeyObject.SerializeCompressed(), privateKey.PubKey().Address().String(), nil
- },
- }, nil
-}
-
-type (
- closeDelegate func() error
- getPublicKeySECP256K1Delegate func([]uint32) ([]byte, error)
- getAddressPubKeySECP256K1Delegate func([]uint32, string) ([]byte, string, error)
- signSECP256K1Delegate func([]uint32, []byte, byte) ([]byte, error)
-)
-
-type MockLedger struct {
- CloseFn closeDelegate
- GetPublicKeySECP256K1Fn getPublicKeySECP256K1Delegate
- GetAddressPubKeySECP256K1Fn getAddressPubKeySECP256K1Delegate
- SignSECP256K1Fn signSECP256K1Delegate
-}
-
-func (m *MockLedger) Close() error {
- if m.CloseFn != nil {
- return m.CloseFn()
- }
-
- return nil
-}
-
-func (m *MockLedger) GetPublicKeySECP256K1(data []uint32) ([]byte, error) {
- if m.GetPublicKeySECP256K1Fn != nil {
- return m.GetPublicKeySECP256K1Fn(data)
- }
-
- return nil, nil
-}
-
-func (m *MockLedger) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) {
- if m.GetAddressPubKeySECP256K1Fn != nil {
- return m.GetAddressPubKeySECP256K1Fn(data, str)
- }
-
- return nil, "", nil
-}
-
-func (m *MockLedger) SignSECP256K1(d1 []uint32, d2 []byte, d3 byte) ([]byte, error) {
- if m.SignSECP256K1Fn != nil {
- return m.SignSECP256K1Fn(d1, d2, d3)
- }
-
- return nil, nil
-}
diff --git a/tm2/pkg/crypto/ledger/ledger_secp256k1.go b/tm2/pkg/crypto/ledger/ledger_secp256k1.go
index 56877b813a5..1d0ac8b05f0 100644
--- a/tm2/pkg/crypto/ledger/ledger_secp256k1.go
+++ b/tm2/pkg/crypto/ledger/ledger_secp256k1.go
@@ -12,27 +12,12 @@ import (
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/hd"
+ "github.com/gnolang/gno/tm2/pkg/crypto/internal/ledger"
"github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
"github.com/gnolang/gno/tm2/pkg/errors"
)
type (
- // discoverLedgerFn defines a Ledger discovery function that returns a
- // connected device or an error upon failure. Its allows a method to avoid CGO
- // dependencies when Ledger support is potentially not enabled.
- discoverLedgerFn func() (LedgerSECP256K1, error)
-
- // LedgerSECP256K1 reflects an interface a Ledger API must implement for SECP256K1
- LedgerSECP256K1 interface {
- Close() error
- // Returns an uncompressed pubkey
- GetPublicKeySECP256K1([]uint32) ([]byte, error)
- // Returns a compressed pubkey and bech32 address (requires user confirmation)
- GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error)
- // Signs a message (requires user confirmation)
- SignSECP256K1([]uint32, []byte, byte) ([]byte, error)
- }
-
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we
// cache the PubKey from the first call to use it later.
PrivKeyLedgerSecp256k1 struct {
@@ -191,8 +176,8 @@ func convertDERtoBER(signatureDER []byte) ([]byte, error) {
return sigBytes, nil
}
-func getLedgerDevice() (LedgerSECP256K1, error) {
- device, err := discoverLedger()
+func getLedgerDevice() (ledger.SECP256K1, error) {
+ device, err := ledger.Discover()
if err != nil {
return nil, errors.Wrap(err, "ledger nano S")
}
@@ -200,7 +185,7 @@ func getLedgerDevice() (LedgerSECP256K1, error) {
return device, nil
}
-func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error {
+func validateKey(device ledger.SECP256K1, pkl PrivKeyLedgerSecp256k1) error {
pub, err := getPubKeyUnsafe(device, pkl.Path)
if err != nil {
return err
@@ -219,7 +204,7 @@ func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error {
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
// an error, so this should only trigger if the private key is held in memory
// for a while before use.
-func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
+func sign(device ledger.SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) {
err := validateKey(device, pkl)
if err != nil {
return nil, err
@@ -240,7 +225,7 @@ func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byt
//
// since this involves IO, it may return an error, which is not exposed
// in the PubKey interface, so this function allows better error handling
-func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (crypto.PubKey, error) {
+func getPubKeyUnsafe(device ledger.SECP256K1, path hd.BIP44Params) (crypto.PubKey, error) {
publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath())
if err != nil {
return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %w", err)
@@ -264,7 +249,7 @@ func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (crypto.PubKey
//
// Since this involves IO, it may return an error, which is not exposed
// in the PubKey interface, so this function allows better error handling.
-func getPubKeyAddrSafe(device LedgerSECP256K1, path hd.BIP44Params, hrp string) (crypto.PubKey, string, error) {
+func getPubKeyAddrSafe(device ledger.SECP256K1, path hd.BIP44Params, hrp string) (crypto.PubKey, string, error) {
publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp)
if err != nil {
return nil, "", fmt.Errorf("address %s rejected", addr)
From 16ed32f5db34c020cf2c6af9e8d1cf010af972fa Mon Sep 17 00:00:00 2001
From: Morgan
Date: Thu, 23 May 2024 15:01:21 +0200
Subject: [PATCH 09/49] fix(gnovm): remove sort from gonative packages (#2168)
Fixes #2139.
Removing `sort` from the list of native packages allows tests to use the
full-Gno `sort` implementation, which has support for `sort.Sort` and
will not cause the panic mentioned by @leohhhn.
---
gnovm/tests/imports.go | 6 ------
1 file changed, 6 deletions(-)
diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go
index befaec7d026..d5e63532875 100644
--- a/gnovm/tests/imports.go
+++ b/gnovm/tests/imports.go
@@ -29,7 +29,6 @@ import (
"os"
"path/filepath"
"reflect"
- "sort"
"strconv"
"strings"
"sync"
@@ -332,11 +331,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri
pkg := gno.NewPackageNode("big", pkgPath, nil)
pkg.DefineGoNativeValue("NewInt", big.NewInt)
return pkg, pkg.NewPackage()
- case "sort":
- pkg := gno.NewPackageNode("sort", pkgPath, nil)
- pkg.DefineGoNativeValue("Strings", sort.Strings)
- // pkg.DefineGoNativeValue("Sort", sort.Sort)
- return pkg, pkg.NewPackage()
case "flag":
pkg := gno.NewPackageNode("flag", pkgPath, nil)
pkg.DefineGoNativeType(reflect.TypeOf(flag.Flag{}))
From f3ddc448cb7dfb93311e572c7fbc0b351b89f540 Mon Sep 17 00:00:00 2001
From: 6h057 <15034695+omarsy@users.noreply.github.com>
Date: Thu, 23 May 2024 15:37:25 +0200
Subject: [PATCH 10/49] fix(gnovm): fix type equality with native values in
binary expressions (#2016)
fixes #1966
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
gnovm/pkg/gnolang/preprocess.go | 13 ++++--
gnovm/pkg/gnolang/preprocess_test.go | 60 ++++++++++++++++++++++++++++
gnovm/tests/files/op7.gno | 2 +-
gnovm/tests/files/time16_native.gno | 14 +++++++
gnovm/tests/files/type31.gno | 2 +-
gnovm/tests/files/type32.gno | 2 +-
6 files changed, 86 insertions(+), 7 deletions(-)
create mode 100644 gnovm/pkg/gnolang/preprocess_test.go
create mode 100644 gnovm/tests/files/time16_native.gno
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index ddc72c3a048..4331e2aed9e 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -884,17 +884,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
// Left not const, Right not const ------------------
if n.Op == EQL || n.Op == NEQ {
// If == or !=, no conversions.
- } else if lnt, ok := lt.(*NativeType); ok {
+ } else if lnt, ok := lt.(*NativeType); ok && isNative(rt) {
if debug {
if !isShift {
assertSameTypes(lt, rt)
}
}
- // If left and right are native type,
+ // If left and right are native type, and same type
// convert left and right to gno, then
// convert result back to native.
//
// get concrete native base type.
+ if lt.TypeID() != rt.TypeID() {
+ panic(fmt.Sprintf(
+ "incompatible types in binary expression: %v %v %v",
+ lt.TypeID(), n.Op, rt.TypeID()))
+ }
pt := go2GnoBaseType(lnt.Type).(PrimitiveType)
// convert n.Left to (gno) pt type,
ln := Expr(Call(pt.String(), n.Left))
@@ -932,7 +937,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
if lt.TypeID() != rt.TypeID() {
panic(fmt.Sprintf(
"incompatible types in binary expression: %v %v %v",
- n.Left, n.Op, n.Right))
+ lt.TypeID(), n.Op, rt.TypeID()))
}
} else {
checkOrConvertType(store, last, &n.Left, rt, false)
@@ -945,7 +950,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
if lt.TypeID() != rt.TypeID() {
panic(fmt.Sprintf(
"incompatible types in binary expression: %v %v %v",
- n.Left, n.Op, n.Right))
+ lt.TypeID(), n.Op, rt.TypeID()))
}
}
}
diff --git a/gnovm/pkg/gnolang/preprocess_test.go b/gnovm/pkg/gnolang/preprocess_test.go
new file mode 100644
index 00000000000..2419a385e14
--- /dev/null
+++ b/gnovm/pkg/gnolang/preprocess_test.go
@@ -0,0 +1,60 @@
+package gnolang
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPreprocess_BinaryExpressionOneNative(t *testing.T) {
+ pn := NewPackageNode("time", "time", nil)
+ pn.DefineGoNativeValue("Millisecond", time.Millisecond)
+ pn.DefineGoNativeValue("Second", time.Second)
+ pn.DefineGoNativeType(reflect.TypeOf(time.Duration(0)))
+ pv := pn.NewPackage()
+ store := gonativeTestStore(pn, pv)
+ store.SetBlockNode(pn)
+
+ const src = `package main
+ import "time"
+func main() {
+ var a int64 = 2
+ println(time.Second * a)
+
+}`
+ n := MustParseFile("main.go", src)
+
+ defer func() {
+ err := recover()
+ assert.Contains(t, fmt.Sprint(err), "incompatible types in binary expression")
+ }()
+ Preprocess(store, pn, n)
+}
+
+func TestPreprocess_BinaryExpressionBothNative(t *testing.T) {
+ pn := NewPackageNode("time", "time", nil)
+ pn.DefineGoNativeValue("March", time.March)
+ pn.DefineGoNativeValue("Wednesday", time.Wednesday)
+ pn.DefineGoNativeType(reflect.TypeOf(time.Month(0)))
+ pn.DefineGoNativeType(reflect.TypeOf(time.Weekday(0)))
+ pv := pn.NewPackage()
+ store := gonativeTestStore(pn, pv)
+ store.SetBlockNode(pn)
+
+ const src = `package main
+ import "time"
+func main() {
+ println(time.March * time.Wednesday)
+
+}`
+ n := MustParseFile("main.go", src)
+
+ defer func() {
+ err := recover()
+ assert.Contains(t, fmt.Sprint(err), "incompatible types in binary expression")
+ }()
+ Preprocess(store, pn, n)
+}
diff --git a/gnovm/tests/files/op7.gno b/gnovm/tests/files/op7.gno
index c92a567110d..7167171035b 100644
--- a/gnovm/tests/files/op7.gno
+++ b/gnovm/tests/files/op7.gno
@@ -14,4 +14,4 @@ func main() {
}
// Error:
-// main/files/op7.gno:11: incompatible types in binary expression: err GTR invalidT
+// main/files/op7.gno:11: incompatible types in binary expression: .uverse.error GTR main.T
diff --git a/gnovm/tests/files/time16_native.gno b/gnovm/tests/files/time16_native.gno
new file mode 100644
index 00000000000..1789fcbef4f
--- /dev/null
+++ b/gnovm/tests/files/time16_native.gno
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ var a int64 = 2
+ fmt.Println(time.Second * a)
+}
+
+// Error:
+// main/files/time16_native.gno:10: incompatible types in binary expression: go:time.Duration MUL int64
diff --git a/gnovm/tests/files/type31.gno b/gnovm/tests/files/type31.gno
index a80d07d70f2..a613c67525c 100644
--- a/gnovm/tests/files/type31.gno
+++ b/gnovm/tests/files/type31.gno
@@ -9,4 +9,4 @@ func main() {
}
// Error:
-// main/files/type31.gno:8: incompatible types in binary expression: x ADD y
+// main/files/type31.gno:8: incompatible types in binary expression: string ADD main.String
diff --git a/gnovm/tests/files/type32.gno b/gnovm/tests/files/type32.gno
index b679543cf9f..bc943d90186 100644
--- a/gnovm/tests/files/type32.gno
+++ b/gnovm/tests/files/type32.gno
@@ -12,4 +12,4 @@ func main() {
}
// Error:
-// main/files/type32.gno:9#1: incompatible types in binary expression: a + (const (":" string)) ADD b
+// main/files/type32.gno:9#1: incompatible types in binary expression: string ADD main.S
From f24690e7ebf325bffcfaf9e328c3df8e6b21e50c Mon Sep 17 00:00:00 2001
From: Miguel Victoria Villaquiran
Date: Thu, 23 May 2024 23:33:08 +0200
Subject: [PATCH 11/49] feat(gnovm/cmd): printing all the errors from goparser
(#2011)
This PR aims to print all the errors that are found by the go/parser
package.
Currently the `parser.ParseFile` function returns an `scanner.ErrorList`
object that only prints the first error and a count of the remaining
errors. #1933 everything is better explained here.
**For the changes this PR introduces**:
- Create **issueWithError** function. This function will wrap the lines
of code that processed an error (before this PR changes) in order to
convert it on an **lintIssue** structure. This existent code applies a
regex to localize the parse error more precisely.
- The PR changes the behavior of run, test, and lint commands. Not they
exit with a non-zero status code as soon as we found some error while
parsing the files. This in order to not having the whole stacktrace for
some parsing file error, improving visibility.
- Add a new case on `gnovm/cmd/gno/lint.go.catchRuntimeError` to handle
and print all errors of an error with type `scanner.ErrorList`
- remove some wrapping during panic calls. This was done by simplicity
some of the calls were doing `panic(errors.Wrap(err, "parsing file
"+mfile.Name))` here err has the type scanner.ErrorList but as we're
wrapping it it would be more complex and difficult to read on
**catchRuntimeError** to identify an error with the type ErrorList
**Results:** (Using issue example)
- lint:
```cmd
-- NOW
./.: missing 'gno.mod' file (code=1).
test.gno:6: expected ';', found example (code=2).
test.gno:7: expected '}', found 'EOF' (code=2).
exit status 1
---- BEFORE
./.: missing 'gno.mod' file (code=1).
test.gno:6: expected ';', found example (and 1 more errors) (code=2).
```
- run:
```cmd
-- NOW
test.gno:6: expected ';', found example (code=2).
test.gno:7: expected '}', found 'EOF' (code=2).
exit status 1
---- BEFORE
panic: test.gno:6:5: expected ';', found example (and 1 more errors)
goroutine 1 [running]:
github.com/gnolang/gno/gnovm/pkg/gnolang.MustReadFile(...)
/Users/miguel/gnorigin/gnovm/pkg/gnolang/go2gno.go:49
main.parseFiles({0x140003b1720, 0x1, 0x1400031fb48?})
/Users/miguel/gnorigin/gnovm/cmd/gno/run.go:132 +0x27c
main.parseFiles({0x140003b09a0, 0x1, 0x0?})
/Users/miguel/gnorigin/gnovm/cmd/gno/run.go:121 +0x158
main.execRun(0x14000373230, {0x140003b09a0, 0x1, 0x1}, {0x100ca3e68, 0x140003b3bd0})
/Users/miguel/gnorigin/gnovm/cmd/gno/run.go:89 +0x174
main.newRunCmd.func1({0x0?, 0x1400019c140?}, {0x140003b09a0?, 0x0?, 0x0?})
/Users/miguel/gnorigin/gnovm/cmd/gno/run.go:35 +0x3c
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0x1400031fdc8?, {0x100c9af18?, 0x101249700?})
/Users/miguel/gnorigin/tm2/pkg/commands/command.go:247 +0x17c
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0x140003ae2c0?, {0x100c9af18?, 0x101249700?})
/Users/miguel/gnorigin/tm2/pkg/commands/command.go:251 +0x12c
github.com/gnolang/gno/tm2/pkg/commands.(*Command).ParseAndRun(0x140003ae6e0?, {0x100c9af18, 0x101249700}, {0x1400019c130?, 0x140003ae790?, 0x140003ae840?})
/Users/miguel/gnorigin/tm2/pkg/commands/command.go:132 +0x4c
github.com/gnolang/gno/tm2/pkg/commands.(*Command).Execute(0x100ca3e68?, {0x100c9af18?, 0x101249700?}, {0x1400019c130?, 0x1011a5ef8?, 0x140000021a0?})
/Users/miguel/gnorigin/tm2/pkg/commands/command.go:114 +0x28
main.main()
/Users/miguel/gnorigin/gnovm/cmd/gno/main.go:13 +0x6c
```
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
gnovm/cmd/gno/lint.go | 72 ++++++++++---------
gnovm/cmd/gno/lint_test.go | 3 +
gnovm/cmd/gno/run.go | 17 +++--
gnovm/cmd/gno/test.go | 12 +++-
gnovm/pkg/gnolang/nodes.go | 2 +-
gnovm/tests/integ/several-lint-errors/gno.mod | 1 +
.../tests/integ/several-lint-errors/main.gno | 6 ++
7 files changed, 73 insertions(+), 40 deletions(-)
create mode 100644 gnovm/tests/integ/several-lint-errors/gno.mod
create mode 100644 gnovm/tests/integ/several-lint-errors/main.gno
diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go
index e2a7e53fb69..c1974094da0 100644
--- a/gnovm/cmd/gno/lint.go
+++ b/gnovm/cmd/gno/lint.go
@@ -5,6 +5,8 @@ import (
"errors"
"flag"
"fmt"
+ "go/scanner"
+ "io"
"os"
"path/filepath"
"regexp"
@@ -68,10 +70,6 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
}
hasError := false
- addIssue := func(issue lintIssue) {
- hasError = true
- fmt.Fprint(io.Err(), issue.String()+"\n")
- }
for _, pkgPath := range pkgPaths {
if verbose {
@@ -81,16 +79,18 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
// Check if 'gno.mod' exists
gnoModPath := filepath.Join(pkgPath, "gno.mod")
if !osm.FileExists(gnoModPath) {
- addIssue(lintIssue{
+ hasError = true
+ issue := lintIssue{
Code: lintNoGnoMod,
Confidence: 1,
Location: pkgPath,
Msg: "missing 'gno.mod' file",
- })
+ }
+ fmt.Fprint(io.Err(), issue.String()+"\n")
}
// Handle runtime errors
- catchRuntimeError(pkgPath, addIssue, func() {
+ hasError = catchRuntimeError(pkgPath, io.Err(), func() {
stdout, stdin, stderr := io.Out(), io.In(), io.Err()
testStore := tests.TestStore(
rootDir, "",
@@ -130,7 +130,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
}
tm.RunFiles(testfiles.Files...)
- })
+ }) || hasError
// TODO: Add more checkers
}
@@ -164,47 +164,33 @@ func guessSourcePath(pkg, source string) string {
// XXX: Ideally, error handling should encapsulate location details within a dedicated error type.
var reParseRecover = regexp.MustCompile(`^([^:]+):(\d+)(?::\d+)?:? *(.*)$`)
-func catchRuntimeError(pkgPath string, addIssue func(issue lintIssue), action func()) {
+func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (hasError bool) {
defer func() {
// Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go
r := recover()
if r == nil {
return
}
-
- var err error
+ hasError = true
switch verr := r.(type) {
case *gno.PreprocessError:
- err = verr.Unwrap()
+ err := verr.Unwrap()
+ fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
+ case scanner.ErrorList:
+ for _, err := range verr {
+ fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
+ }
case error:
- err = verr
+ fmt.Fprint(stderr, issueFromError(pkgPath, verr).String()+"\n")
case string:
- err = errors.New(verr)
+ fmt.Fprint(stderr, issueFromError(pkgPath, errors.New(verr)).String()+"\n")
default:
panic(r)
}
-
- var issue lintIssue
- issue.Confidence = 1
- issue.Code = lintGnoError
-
- parsedError := strings.TrimSpace(err.Error())
- parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")
-
- matches := reParseRecover.FindStringSubmatch(parsedError)
- if len(matches) == 4 {
- sourcepath := guessSourcePath(pkgPath, matches[1])
- issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
- issue.Msg = strings.TrimSpace(matches[3])
- } else {
- issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
- issue.Msg = err.Error()
- }
-
- addIssue(issue)
}()
action()
+ return
}
type lintCode int
@@ -229,3 +215,23 @@ func (i lintIssue) String() string {
// TODO: consider crafting a doc URL based on Code.
return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code)
}
+
+func issueFromError(pkgPath string, err error) lintIssue {
+ var issue lintIssue
+ issue.Confidence = 1
+ issue.Code = lintGnoError
+
+ parsedError := strings.TrimSpace(err.Error())
+ parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")
+
+ matches := reParseRecover.FindStringSubmatch(parsedError)
+ if len(matches) == 4 {
+ sourcepath := guessSourcePath(pkgPath, matches[1])
+ issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
+ issue.Msg = strings.TrimSpace(matches[3])
+ } else {
+ issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
+ issue.Msg = err.Error()
+ }
+ return issue
+}
diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go
index 5773ae0a06f..fff05726e53 100644
--- a/gnovm/cmd/gno/lint_test.go
+++ b/gnovm/cmd/gno/lint_test.go
@@ -16,6 +16,9 @@ func TestLintApp(t *testing.T) {
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/package_not_declared/main.gno"},
stderrShouldContain: "main.gno:4: name fmt not declared (code=2).",
+ }, {
+ args: []string{"lint", "--set-exit-status=0", "../../tests/integ/several-lint-errors/main.gno"},
+ stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6",
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go
index 0c5218613a9..65315909f72 100644
--- a/gnovm/cmd/gno/run.go
+++ b/gnovm/cmd/gno/run.go
@@ -5,6 +5,7 @@ import (
"errors"
"flag"
"fmt"
+ "io"
"os"
"path/filepath"
"strings"
@@ -102,7 +103,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error {
}
// read files
- files, err := parseFiles(args)
+ files, err := parseFiles(args, stderr)
if err != nil {
return err
}
@@ -135,15 +136,16 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error {
return nil
}
-func parseFiles(fnames []string) ([]*gno.FileNode, error) {
+func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) {
files := make([]*gno.FileNode, 0, len(fnames))
+ var hasError bool
for _, fname := range fnames {
if s, err := os.Stat(fname); err == nil && s.IsDir() {
subFns, err := listNonTestFiles(fname)
if err != nil {
return nil, err
}
- subFiles, err := parseFiles(subFns)
+ subFiles, err := parseFiles(subFns, stderr)
if err != nil {
return nil, err
}
@@ -154,7 +156,14 @@ func parseFiles(fnames []string) ([]*gno.FileNode, error) {
// in either case not a file we can parse.
return nil, err
}
- files = append(files, gno.MustReadFile(fname))
+
+ hasError = catchRuntimeError(fname, stderr, func() {
+ files = append(files, gno.MustReadFile(fname))
+ })
+ }
+
+ if hasError {
+ os.Exit(1)
}
return files, nil
}
diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go
index 8b48e10d422..e09e06c0418 100644
--- a/gnovm/cmd/gno/test.go
+++ b/gnovm/cmd/gno/test.go
@@ -325,7 +325,15 @@ func gnoTestPkg(
memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath)
// tfiles, ifiles := gno.ParseMemPackageTests(memPkg)
- tfiles, ifiles := parseMemPackageTests(memPkg)
+ var tfiles, ifiles *gno.FileSet
+
+ hasError := catchRuntimeError(gnoPkgPath, stderr, func() {
+ tfiles, ifiles = parseMemPackageTests(memPkg)
+ })
+
+ if hasError {
+ os.Exit(1)
+ }
testPkgName := getPkgNameFromFileset(ifiles)
// run test files in pkg
@@ -639,7 +647,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) {
}
n, err := gno.ParseFile(mfile.Name, mfile.Body)
if err != nil {
- panic(errors.Wrap(err, "parsing file "+mfile.Name))
+ panic(err)
}
if n == nil {
panic("should not happen")
diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go
index 8f2c5054a8a..6f1584dcc28 100644
--- a/gnovm/pkg/gnolang/nodes.go
+++ b/gnovm/pkg/gnolang/nodes.go
@@ -1179,7 +1179,7 @@ func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) {
}
n, err := ParseFile(mfile.Name, mfile.Body)
if err != nil {
- panic(errors.Wrap(err, "parsing file "+mfile.Name))
+ panic(err)
}
if memPkg.Name != string(n.PkgName) {
panic(fmt.Sprintf(
diff --git a/gnovm/tests/integ/several-lint-errors/gno.mod b/gnovm/tests/integ/several-lint-errors/gno.mod
new file mode 100644
index 00000000000..88485411822
--- /dev/null
+++ b/gnovm/tests/integ/several-lint-errors/gno.mod
@@ -0,0 +1 @@
+module gno.land/tests/severalerrors
\ No newline at end of file
diff --git a/gnovm/tests/integ/several-lint-errors/main.gno b/gnovm/tests/integ/several-lint-errors/main.gno
new file mode 100644
index 00000000000..f29aa7ecd33
--- /dev/null
+++ b/gnovm/tests/integ/several-lint-errors/main.gno
@@ -0,0 +1,6 @@
+package main
+
+func main() {
+ for {
+ _ example
+}
\ No newline at end of file
From 8a728ff55d6c336ee8fe113032f1f7bd97777be7 Mon Sep 17 00:00:00 2001
From: grepsuzette <350354+grepsuzette@users.noreply.github.com>
Date: Fri, 24 May 2024 17:07:10 +0800
Subject: [PATCH 12/49] docs: add LongHelp to `gno bug`, remove \n from
`gnodev` LongHelp (#2180)
Added a LongDesc to `gno bug help`.
For gnodev -h think the help body should be on one long line to let it
flow naturally depending on the terminal's width.
Co-authored-by: grepsuzette
---
contribs/gnodev/cmd/gnodev/main.go | 4 +---
gnovm/cmd/gno/bug.go | 13 ++++++++++++-
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go
index 9b769321c83..1b0500dafdc 100644
--- a/contribs/gnodev/cmd/gnodev/main.go
+++ b/contribs/gnodev/cmd/gnodev/main.go
@@ -84,9 +84,7 @@ func main() {
Name: "gnodev",
ShortUsage: "gnodev [flags] [path ...]",
ShortHelp: "runs an in-memory node and gno.land web server for development purposes.",
- LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface
-primarily for realm package development. It automatically loads the 'examples' directory and any
-additional specified paths.`,
+ LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface primarily for realm package development. It automatically loads the 'examples' directory and any additional specified paths.`,
},
cfg,
func(_ context.Context, args []string) error {
diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go
index 98e6fb07319..8d4661e3de8 100644
--- a/gnovm/cmd/gno/bug.go
+++ b/gnovm/cmd/gno/bug.go
@@ -61,6 +61,17 @@ func newBugCmd(io commands.IO) *commands.Command {
Name: "bug",
ShortUsage: "bug",
ShortHelp: "Start a bug report",
+ LongHelp: `opens https://github.com/gnolang/gno/issues in a browser.
+
+The new issue body is prefilled for you with the following information:
+
+- Go version (example: go1.22.2)
+- OS and CPU architecture (example: linux/amd64)
+- Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c)
+
+The rest of the report consists of markdown sections such as ### Steps to reproduce
+that you can edit.
+`,
},
cfg,
func(_ context.Context, args []string) error {
@@ -74,7 +85,7 @@ func (c *bugCfg) RegisterFlags(fs *flag.FlagSet) {
&c.skipBrowser,
"skip-browser",
false,
- "do not open the browser",
+ "output a prefilled issue template on the cli instead",
)
}
From c6cde63403cb56e67622615ecc0feb9254e9f104 Mon Sep 17 00:00:00 2001
From: grepsuzette <350354+grepsuzette@users.noreply.github.com>
Date: Fri, 24 May 2024 17:24:42 +0800
Subject: [PATCH 13/49] fix: make install colors not always shown (#2179)
In my terminals (konsole, kmscon, kmscon+tmux), colors won't show
without -e in
`echo -e "\033[0;32m blabla \033[0m"`.
The -e option enables interpretation of backslash escapes in the string,
which
allows the color code `\033[0;32m` to work. With -e it should be more
widely
supported across different platforms and shell environments.
```
$ help echo
echo: echo [-neE] [arg ...]
Write arguments to the standard output.
Display the ARGs, separated by a single space character and followed by a
newline, on the standard output.
Options:
-n do not append a newline
-e enable interpretation of the following backslash escapes
-E explicitly suppress interpretation of backslash escapes
`echo' interprets the following backslash-escaped characters:
\a alert (bell)
\b backspace
\c suppress further output
\e escape character
\E escape character
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\0nnn the character whose ASCII code is NNN (octal). NNN can be
0 to 3 octal digits
```
An alternative is to directly input the escape character, or to use
printf: `printf "\033[0;32mHello\033[0m\n"`.
@leohhhh does it still work on your machine with `echo -e`? (what
terminal are you using btw?)
---------
Co-authored-by: grepsuzette
Co-authored-by: Morgan Bazalgette
---
Makefile | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Makefile b/Makefile
index c2f5156e054..2dcff0d57a8 100644
--- a/Makefile
+++ b/Makefile
@@ -36,15 +36,15 @@ install: install.gnokey install.gno install.gnodev
install.gnokey:
$(MAKE) --no-print-directory -C ./gno.land install.gnokey
# \033[0;32m ... \033[0m is ansi for green text.
- @echo "\033[0;32m[+] 'gnokey' has been installed. Read more in ./gno.land/\033[0m"
+ @printf "\033[0;32m[+] 'gnokey' has been installed. Read more in ./gno.land/\033[0m\n"
.PHONY: install.gno
install.gno:
$(MAKE) --no-print-directory -C ./gnovm install
- @echo "\033[0;32m[+] 'gno' has been installed. Read more in ./gnovm/\033[0m"
+ @printf "\033[0;32m[+] 'gno' has been installed. Read more in ./gnovm/\033[0m\n"
.PHONY: install.gnodev
install.gnodev:
$(MAKE) --no-print-directory -C ./contribs install.gnodev
- @echo "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m"
+ @printf "\033[0;32m[+] 'gnodev' has been installed. Read more in ./contribs/gnodev/\033[0m\n"
# old aliases
.PHONY: install_gnokey
From 90aa89c28d3c8ad3b7d7b67d0256426bde6cfbc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?=
Date: Fri, 24 May 2024 14:34:07 +0200
Subject: [PATCH 14/49] feat: add more telemetry (#2059)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Description
Closes #1829
This PR introduces new metrics for different TM2 modules, as outlined in
#1829.
Thank you @gfanton and @ajnavarro for helping out with the Docker issues
🙏
## How do I test this out?
Head over to `misc/telemetry` and follow the README -- you can run
everything locally in Docker 😎
![grafana-2](https://github.com/gnolang/gno/assets/16712663/8177d338-6743-480d-b4b3-b447243043d9)
cc @mazzy89
## Metrics added
### Consensus
- [x] block interval (time between current and prev block in seconds)
- [x] number of transactions in the latest block
- [x] block size (in bytes)
- [x] number of validators
- [x] total voting power of the validator set
### Networking
- [x] number of inbound peers
- [x] number of outbound peers
- [x] number of pending peers (dialing)
### JSON-RPC
- [x] response time for requests (http / ws)
### Mempool
- [x] number of valid txs in the mempool
- [x] number of txs in the mempool cache
### VM
- [x] gas used per execution
- [x] CPU cycles
- [x] different VM query message call frequency
- [x] different VM execution frequency (run, call, addpkg)
- [x] VM query error frequency
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Co-authored-by: gfanton <8671905+gfanton@users.noreply.github.com>
---
gno.land/cmd/gnoland/start.go | 6 +-
gno.land/pkg/sdk/vm/handler.go | 66 +-
gno.land/pkg/sdk/vm/keeper.go | 71 +
misc/telemetry/Makefile | 11 +
misc/telemetry/README.md | 56 +
misc/telemetry/assets/grafana-1.jpeg | Bin 0 -> 483246 bytes
misc/telemetry/assets/grafana-2.jpeg | Bin 0 -> 609302 bytes
misc/telemetry/collector/collector.yaml | 22 +
misc/telemetry/docker-compose.yml | 65 +
misc/telemetry/gnoland/Dockerfile | 11 +
misc/telemetry/gnoland/setup.sh | 19 +
misc/telemetry/grafana/dashboards.yaml | 8 +
misc/telemetry/grafana/datasources.yaml | 13 +
misc/telemetry/grafana/gno-dashboards.json | 1360 ++++++++++++++++++++
misc/telemetry/prometheus/prometheus.yml | 7 +
misc/telemetry/supernova.Dockerfile | 12 +
tm2/pkg/bft/config/config.go | 2 +-
tm2/pkg/bft/consensus/state.go | 27 +
tm2/pkg/bft/mempool/clist_mempool.go | 33 +-
tm2/pkg/bft/rpc/lib/server/handlers.go | 55 +-
tm2/pkg/p2p/fuzz.go | 22 -
tm2/pkg/p2p/switch.go | 24 +
tm2/pkg/p2p/test_util.go | 18 -
tm2/pkg/telemetry/README.md | 23 +-
tm2/pkg/telemetry/config/config.go | 24 +-
tm2/pkg/telemetry/config/config_test.go | 29 +
tm2/pkg/telemetry/exporter/error.go | 5 -
tm2/pkg/telemetry/init.go | 35 +-
tm2/pkg/telemetry/metrics/init.go | 69 -
tm2/pkg/telemetry/metrics/metrics.go | 286 ++++
30 files changed, 2213 insertions(+), 166 deletions(-)
create mode 100644 misc/telemetry/Makefile
create mode 100644 misc/telemetry/README.md
create mode 100644 misc/telemetry/assets/grafana-1.jpeg
create mode 100644 misc/telemetry/assets/grafana-2.jpeg
create mode 100644 misc/telemetry/collector/collector.yaml
create mode 100644 misc/telemetry/docker-compose.yml
create mode 100644 misc/telemetry/gnoland/Dockerfile
create mode 100644 misc/telemetry/gnoland/setup.sh
create mode 100644 misc/telemetry/grafana/dashboards.yaml
create mode 100644 misc/telemetry/grafana/datasources.yaml
create mode 100644 misc/telemetry/grafana/gno-dashboards.json
create mode 100644 misc/telemetry/prometheus/prometheus.yml
create mode 100644 misc/telemetry/supernova.Dockerfile
create mode 100644 tm2/pkg/telemetry/config/config_test.go
delete mode 100644 tm2/pkg/telemetry/exporter/error.go
delete mode 100644 tm2/pkg/telemetry/metrics/init.go
create mode 100644 tm2/pkg/telemetry/metrics/metrics.go
diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go
index 11006d69246..5ccf4b3a01b 100644
--- a/gno.land/cmd/gnoland/start.go
+++ b/gno.land/cmd/gnoland/start.go
@@ -247,8 +247,10 @@ func execStart(c *startCfg, io commands.IO) error {
// Wrap the zap logger
logger := log.ZapLoggerToSlog(zapLogger)
- // Initialize telemetry
- telemetry.Init(*cfg.Telemetry)
+ // Initialize the telemetry
+ if err := telemetry.Init(*cfg.Telemetry); err != nil {
+ return fmt.Errorf("unable to initialize telemetry, %w", err)
+ }
// Write genesis file if missing.
// NOTE: this will be dropped in a PR that resolves issue #1886:
diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go
index e1dd31846e7..ae77021aa06 100644
--- a/gno.land/pkg/sdk/vm/handler.go
+++ b/gno.land/pkg/sdk/vm/handler.go
@@ -1,12 +1,17 @@
package vm
import (
+ "context"
"fmt"
"strings"
abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
"github.com/gnolang/gno/tm2/pkg/sdk"
"github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/gnolang/gno/tm2/pkg/telemetry"
+ "github.com/gnolang/gno/tm2/pkg/telemetry/metrics"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
)
type vmHandler struct {
@@ -51,14 +56,6 @@ func (vh vmHandler) handleMsgCall(ctx sdk.Context, msg MsgCall) (res sdk.Result)
}
res.Data = []byte(resstr)
return
- /* TODO handle events.
- ctx.EventManager().EmitEvent(
- sdk.NewEvent(
- sdk.EventTypeMessage,
- sdk.NewAttribute(sdk.AttributeKeyXXX, types.AttributeValueXXX),
- ),
- )
- */
}
// Handle MsgRun.
@@ -71,7 +68,7 @@ func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) {
return
}
-//----------------------------------------
+// ----------------------------------------
// Query
// query paths
@@ -84,27 +81,58 @@ const (
QueryFile = "qfile"
)
-func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) {
- switch secondPart(req.Path) {
+func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQuery {
+ var (
+ res abci.ResponseQuery
+ path = secondPart(req.Path)
+ )
+
+ switch path {
case QueryPackage:
- return vh.queryPackage(ctx, req)
+ res = vh.queryPackage(ctx, req)
case QueryStore:
- return vh.queryStore(ctx, req)
+ res = vh.queryStore(ctx, req)
case QueryRender:
- return vh.queryRender(ctx, req)
+ res = vh.queryRender(ctx, req)
case QueryFuncs:
- return vh.queryFuncs(ctx, req)
+ res = vh.queryFuncs(ctx, req)
case QueryEval:
- return vh.queryEval(ctx, req)
+ res = vh.queryEval(ctx, req)
case QueryFile:
- return vh.queryFile(ctx, req)
+ res = vh.queryFile(ctx, req)
default:
- res = sdk.ABCIResponseQueryFromError(
+ return sdk.ABCIResponseQueryFromError(
std.ErrUnknownRequest(fmt.Sprintf(
"unknown vm query endpoint %s in %s",
secondPart(req.Path), req.Path)))
+ }
+
+ // Log the telemetry
+ logQueryTelemetry(path, res.IsErr())
+
+ return res
+}
+
+// logQueryTelemetry logs the relevant VM query telemetry
+func logQueryTelemetry(path string, isErr bool) {
+ if !telemetry.MetricsEnabled() {
return
}
+
+ metrics.VMQueryCalls.Add(
+ context.Background(),
+ 1,
+ metric.WithAttributes(
+ attribute.KeyValue{
+ Key: "path",
+ Value: attribute.StringValue(path),
+ },
+ ),
+ )
+
+ if isErr {
+ metrics.VMQueryErrors.Add(context.Background(), 1)
+ }
}
// queryPackage fetch a package's files.
@@ -187,7 +215,7 @@ func (vh vmHandler) queryFile(ctx sdk.Context, req abci.RequestQuery) (res abci.
return
}
-//----------------------------------------
+// ----------------------------------------
// misc
func abciResult(err error) sdk.Result {
diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go
index ab7681bc3e1..a77ddac3e28 100644
--- a/gno.land/pkg/sdk/vm/keeper.go
+++ b/gno.land/pkg/sdk/vm/keeper.go
@@ -4,6 +4,7 @@ package vm
import (
"bytes"
+ "context"
"fmt"
"os"
"strings"
@@ -16,6 +17,10 @@ import (
"github.com/gnolang/gno/tm2/pkg/sdk/bank"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/gnolang/gno/tm2/pkg/store"
+ "github.com/gnolang/gno/tm2/pkg/telemetry"
+ "github.com/gnolang/gno/tm2/pkg/telemetry/metrics"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
)
const (
@@ -215,6 +220,17 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) {
}
}()
m2.RunMemPackage(memPkg, true)
+
+ // Log the telemetry
+ logTelemetry(
+ m2.GasMeter.GasConsumed(),
+ m2.Cycles,
+ attribute.KeyValue{
+ Key: "operation",
+ Value: attribute.StringValue("m_addpkg"),
+ },
+ )
+
return nil
}
@@ -312,7 +328,19 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) {
res += "\n"
}
}
+
+ // Log the telemetry
+ logTelemetry(
+ m.GasMeter.GasConsumed(),
+ m.Cycles,
+ attribute.KeyValue{
+ Key: "operation",
+ Value: attribute.StringValue("m_call"),
+ },
+ )
+
res += "\n\n" // use `\n\n` as separator to separate results for single tx with multi msgs
+
return res, nil
// TODO pay for gas? TODO see context?
}
@@ -418,6 +446,17 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) {
}()
m2.RunMain()
res = buf.String()
+
+ // Log the telemetry
+ logTelemetry(
+ m2.GasMeter.GasConsumed(),
+ m2.Cycles,
+ attribute.KeyValue{
+ Key: "operation",
+ Value: attribute.StringValue("m_run"),
+ },
+ )
+
return res, nil
}
@@ -636,3 +675,35 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err
return res, nil
}
}
+
+// logTelemetry logs the VM processing telemetry
+func logTelemetry(
+ gasUsed int64,
+ cpuCycles int64,
+ attributes ...attribute.KeyValue,
+) {
+ if !telemetry.MetricsEnabled() {
+ return
+ }
+
+ // Record the operation frequency
+ metrics.VMExecMsgFrequency.Add(
+ context.Background(),
+ 1,
+ metric.WithAttributes(attributes...),
+ )
+
+ // Record the CPU cycles
+ metrics.VMCPUCycles.Record(
+ context.Background(),
+ cpuCycles,
+ metric.WithAttributes(attributes...),
+ )
+
+ // Record the gas used
+ metrics.VMGasUsed.Record(
+ context.Background(),
+ gasUsed,
+ metric.WithAttributes(attributes...),
+ )
+}
diff --git a/misc/telemetry/Makefile b/misc/telemetry/Makefile
new file mode 100644
index 00000000000..956c4f11190
--- /dev/null
+++ b/misc/telemetry/Makefile
@@ -0,0 +1,11 @@
+.PHONY: up
+up:
+ docker compose up -d --build
+
+.PHONY: down
+down:
+ docker compose down
+
+.PHONY: clean
+clean:
+ docker compose down -v
\ No newline at end of file
diff --git a/misc/telemetry/README.md b/misc/telemetry/README.md
new file mode 100644
index 00000000000..41628cc5f51
--- /dev/null
+++ b/misc/telemetry/README.md
@@ -0,0 +1,56 @@
+## Overview
+
+The purpose of this Telemetry documentation is to showcase the different node metrics exposed by the Gno node through
+OpenTelemetry, without having to do extraneous setup.
+
+The containerized setup is the following:
+
+- Grafana dashboard
+- Prometheus
+- OpenTelemetry collector (separate service that needs to run)
+- Single Gnoland node, with 1s block times and configured telemetry (enabled)
+- Supernova process that simulates load periodically (generates network traffic)
+
+## Starting the containers
+
+### Step 1: Spinning up Docker
+
+Make sure you have Docker installed and running on your system. After that, within the `misc/telemetry` folder run the
+following command:
+
+```shell
+make up
+```
+
+This will build out the required Docker images for this simulation, and start the services
+
+### Step 2: Open Grafana
+
+When you've verified that the `telemetry` containers are up and running, head on over to http://localhost:3000 to open
+the Grafana dashboard.
+
+Default login details:
+
+```
+username: admin
+password: admin
+```
+
+After you've logged in (you can skip setting a new password), on the left hand side, click on
+`Dashboards -> Gno -> Gno Node Metrics`:
+![Grafana](assets/grafana-1.jpeg)
+
+This will open up the predefined Gno Metrics dashboards (added for ease of use) :
+![Metrics Dashboard](assets/grafana-2.jpeg)
+
+Periodically, these metrics will be updated as the `supernova` process is simulating network traffic.
+
+### Step 3: Stopping the cluster
+
+To stop the cluster, you can run:
+
+```shell
+make down
+```
+
+which will stop the Docker containers. Additionally, you can delete the Docker volumes with `make clean`.
\ No newline at end of file
diff --git a/misc/telemetry/assets/grafana-1.jpeg b/misc/telemetry/assets/grafana-1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..f29ca02609e847f7327e38036dc366b1ab9211cb
GIT binary patch
literal 483246
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