diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bcf4251abec..f13ce49ef45 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,7 +31,6 @@ /examples/gno.land/p/demo/seqid/ @thehowl /examples/gno.land/p/demo/ownable/ @leohhhn /examples/gno.land/p/demo/pausable/ @leohhhn -/examples/gno.land/p/demo/stack/ @harry-hov /examples/gno.land/p/demo/svg/ @moul /examples/gno.land/p/demo/tamagotchi/ @moul /examples/gno.land/p/demo/ui/ @moul @@ -64,11 +63,11 @@ /gnovm/ @jaekwon @moul @piux2 @thehowl /gnovm/stdlibs/ @thehowl /gnovm/tests/ @jaekwon @deelawn @thehowl @mvertes -/gnovm/cmd/gno/ @moul @thehowl @harry-hov +/gnovm/cmd/gno/ @moul @thehowl /gnovm/pkg/gnolang/ @jaekwon @moul @piux2 @deelawn /gnovm/pkg/doc/ @thehowl /gnovm/pkg/repl/ @mvertes @ajnavarro -/gnovm/pkg/gnomod/ @harry-hov +/gnovm/pkg/gnomod/ @thehowl /gnovm/pkg/gnoenv/ @gfanton /gnovm/pkg/transpiler/ @thehowl /gnovm/pkg/integration/ @gfanton @@ -91,5 +90,6 @@ /CONTRIBUTING.md @jaekwon @moul @gnolang/tech-staff /LICENSE.md @jaekwon /.github/ @moul @gnolang/tech-staff +/.github/workflows @ajnavarro @moul /.github/CODEOWNERS @jaekwon @moul /go.mod @gnolang/tech-staff # no unnecessary dependencies diff --git a/.github/workflows/build_template.yml b/.github/workflows/build_template.yml index a984589caaa..430aa393a73 100644 --- a/.github/workflows/build_template.yml +++ b/.github/workflows/build_template.yml @@ -16,10 +16,10 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} - + - name: Checkout code uses: actions/checkout@v4 - + - name: Check generated files are up to date working-directory: ${{ inputs.modulepath }} run: | diff --git a/.github/workflows/contribs.yml b/.github/workflows/contribs.yml index 8fdcce9332c..784dc9b71e5 100644 --- a/.github/workflows/contribs.yml +++ b/.github/workflows/contribs.yml @@ -9,6 +9,12 @@ on: paths: - "contribs/**" - ".github/**" + # Contribs directly depend on gno, so we need to test it whenever changes + # are made to one of those + - "go.*" # check on go.mod/sum update + - "gno.land/**" + - "tm2/**.go" + - "gnovm/**.go" jobs: setup: @@ -30,4 +36,4 @@ jobs: with: modulepath: contribs/${{ matrix.program }} secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs-404-checker.yml b/.github/workflows/docs-404-checker.yml new file mode 100644 index 00000000000..0fa8985366c --- /dev/null +++ b/.github/workflows/docs-404-checker.yml @@ -0,0 +1,31 @@ +name: "docs / 404 checker" + +on: + push: + paths: + - master + pull_request: + paths: + - "docs/**" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '1.21' + + - name: Install dependencies + run: go mod download + + - name: Build docs + run: make -C docs/ build + + - name: Run linter + run: make -C docs/ lint \ No newline at end of file diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 97df3f13a2a..202933f8462 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -5,9 +5,12 @@ on: branches: - master workflow_dispatch: - pull_request: + pull_request: paths: - "gno.land/**" + - "tm2/**.go" + - "gnovm/**.go" + - "go.*" # check on go.mod/sum update - ".github/**" jobs: @@ -17,4 +20,4 @@ jobs: with: modulepath: "gno.land" secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index ac304328dbb..8212ec5871d 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -8,6 +8,7 @@ on: pull_request: paths: - "gnovm/**" + - "go.*" # check on go.mod/sum update - ".github/**" jobs: @@ -17,4 +18,4 @@ jobs: with: modulepath: "gnovm" secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + codecov-token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 7471fd74cb7..81b7abf27f9 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -8,6 +8,7 @@ on: pull_request: paths: - "tm2/**" + - "go.*" # check on go.mod/sum update - ".github/**" jobs: diff --git a/.gitignore b/.gitignore index 019c0be3c98..7d3f3f92b41 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ pbbindings.go # Test coverage leftovers cover.out coverage.out + +*.swp +*.swo +*.bak diff --git a/contribs/Makefile b/contribs/Makefile index 04b607cb32d..5a2a00fc2ef 100644 --- a/contribs/Makefile +++ b/contribs/Makefile @@ -31,27 +31,27 @@ install: @echo 'To install a tool, go to the subdirectory, then run `make install`.' @echo 'To do a full installation, run `make install_all`.' -install_all: $(addsuffix .install,$(programs)) -%.install: - @echo "[+] make -C $(basename $@) install" - $(MAKE) --no-print-directory -C $(basename $@) install +install_all: $(addprefix install.,$(programs)) +install.%: + @echo "[+] make -C $(subst install.,,$@) install" + $(MAKE) --no-print-directory -C $(subst install.,,$@) install .PHONY: install_all ######################################## # Test suite -test: $(addsuffix .test,$(programs)) -%.test: - @echo "[+] make -C $(basename $@) install" - $(MAKE) --no-print-directory -C $(basename $@) test -.PHONY: test +test: $(addprefix test.,$(programs)) +test.%: + @echo "[+] make -C $(subst test.,,$@) install" + $(MAKE) --no-print-directory -C $(subst test.,,$@) test +.PHONY: test ######################################## # Lint .PHONY: lint -lint: $(addsuffix .lint,$(programs)) -%.lint: - @echo "[+] make -C $(basename $@) install" - $(MAKE) --no-print-directory -C $(basename $@) lint +lint: $(addprefix lint.,$(programs)) +lint.%: + @echo "[+] make -C $(subst lint.,,$@) install" + $(MAKE) --no-print-directory -C $(subst lint.,,$@) lint ######################################## # Dev tools diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 4741a5d7326..b7a757c36a5 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -13,7 +13,7 @@ require ( github.com/muesli/termenv v0.15.2 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 - golang.org/x/term v0.18.0 + golang.org/x/term v0.20.0 ) require ( @@ -36,7 +36,7 @@ require ( github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/gotuna/gotuna v0.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect @@ -54,25 +54,26 @@ require ( github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.9 // indirect - go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.25.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.19.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index a2c9f154560..81d7a5a103e 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -95,8 +95,8 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -164,20 +164,22 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -189,8 +191,8 @@ go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -200,11 +202,11 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -215,37 +217,35 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index d368402a3c3..4a45cbc83f2 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -25,7 +25,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -36,23 +36,24 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.25.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 4027483eb1c..2e72631bd10 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -80,8 +80,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -134,25 +134,27 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -162,8 +164,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -174,35 +176,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000000..e5bf557ef75 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,12 @@ +all: build lint + +# Build the linter +build: + cd ../misc/docs-linter && go build -o ./build/ + +# Run the linter for the docs/ folder +lint: + ../misc/docs-linter/build/linter -path . + +clean: + rm -rf ../misc/docs-linter/build \ No newline at end of file diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index d08a9089487..8e589f41845 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -679,7 +679,7 @@ For example, if you're creating a coin for cross-chain transfers, Coins are your best bet. They're IBC-ready and their strict rules offer top-notch security. -Read about how to use the Banker module [here](stdlibs/banker). +Read about how to use the Banker module [here](stdlibs/banker.md). #### GRC20 tokens diff --git a/docs/concepts/packages.md b/docs/concepts/packages.md index 79f54d4f59e..cd3e2ace96a 100644 --- a/docs/concepts/packages.md +++ b/docs/concepts/packages.md @@ -12,7 +12,7 @@ The full list of pre-deployed available packages can be found under the [demo pa In Go, the classic key/value data type is represented by the `map` construct. However, while Gno also supports the use of `map`, it is not a viable option as it lacks determinism due to its non-sequential nature. -To address this issue, Gno implements the [AVL Tree](https://en.wikipedia.org/wiki/AVL\_tree) (Adelson-Velsky-Landis Tree) as a solution. The AVL Tree is a self-balancing binary search tree. +To address this issue, Gno implements the [AVL Tree](https://en.wikipedia.org/wiki/AVL_tree) (Adelson-Velsky-Landis Tree) as a solution. The AVL Tree is a self-balancing binary search tree. The `avl` package comprises a set of functions that can manipulate the leaves and nodes of the AVL Tree. @@ -72,7 +72,7 @@ func IsApprovedForAll(owner, operator std.Address) bool * `OwnerOf`: Returns the `owner`'s address of a token specified by its `TokenID`. * `SafeTransferFrom`: Equivalent to the `TransferFrom` function of `grc20`. * The `Safe` prefix indicates that the function runs a check to ensure that the `to` address is a valid address that can receive tokens. - * As you can see from the [code](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic\_nft.gno#L341), the concept of `Safe` has yet to be implemented. + * As you can see from the [code](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L341), the concept of `Safe` has yet to be implemented. * `SetApprovalForAll`: Approves all tokens owned by the `owner` to an `operator`. * You may not set multiple `operator`s. * `GetApproved`: Returns the `address` of the `operator` for a token, specified with its `ID`. diff --git a/docs/concepts/proof-of-contribution.md b/docs/concepts/proof-of-contribution.md index 5446fa83a69..c855b3db8ed 100644 --- a/docs/concepts/proof-of-contribution.md +++ b/docs/concepts/proof-of-contribution.md @@ -24,6 +24,7 @@ Presentation: https://github.com/gnolang/workshops/tree/main/presentations/2023- ## High-level schema +``` ____ ____ ____ __ _ __ __ _ / __ \_________ ____ / __/ ____ / __/ _________ ____ / /______(_) /_ __ __/ /_(_)___ ____ _____ / /_/ / ___/ __ \/ __ \/ /_ / __ \/ /_ / ___/ __ \/ __ \/ __/ ___/ / __ \/ / / / __/ / __ \/ __ \/ ___/ @@ -55,6 +56,7 @@ Presentation: https://github.com/gnolang/workshops/tree/main/presentations/2023- ^ | | | +---------------user TXs can publish and call contracts--------------------+ +``` ## Components diff --git a/docs/concepts/tendermint2.md b/docs/concepts/tendermint2.md index a6004606a78..4dd43b0819e 100644 --- a/docs/concepts/tendermint2.md +++ b/docs/concepts/tendermint2.md @@ -34,9 +34,7 @@ on https://github.com/tendermint/tendermint2.** proto3 for encoding/decoding optimization through protoc. - MISSION: be the basis for improving the encoding standard from proto3, because proto3 length-prefixing is slow, and we need "proto4" or "amino2". - - LOOK at the auto-generated proto files! - https://github.com/gnolang/gno/blob/master/pkgs/bft/consensus/types/cstypes.proto - for example. + - LOOK at the [auto-generated proto files](https://github.com/gnolang/gno/blob/master/tm2/pkg/bft/consensus/consensus.proto)! - There was work to remove this from the CosmosSDK because Amino wasn't ready, but now that it is, it makes sense to incorporate it into Tendermint2. diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md index 7f0734cdc28..dd6d65085cc 100644 --- a/docs/concepts/testnets.md +++ b/docs/concepts/testnets.md @@ -6,7 +6,7 @@ id: testnets This page documents all Gno.land testnets, what their properties are, and how they are meant to be used. For testnet configuration, visit the -[reference section](../reference/network-config). +[reference section](../reference/network-config.md). Gno.land testnets are categorized by 4 main points: - **Persistence of state** diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index fe96998e239..8700ff9a2b2 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -13,7 +13,7 @@ to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.19+** +- **Go 1.21+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: diff --git a/docs/gno-infrastructure/premining-balances.md b/docs/gno-infrastructure/premining-balances.md index b174398c29e..96924dea6a1 100644 --- a/docs/gno-infrastructure/premining-balances.md +++ b/docs/gno-infrastructure/premining-balances.md @@ -15,8 +15,11 @@ have ample funds to interact with the chain and facilitate contract deployments. ## Prerequisites -- **`gnoland` set up. Reference the [Setting up a local chain](setting-up-a-local-chain.md#installation) guide for steps** -- **`gnokey` set up. Reference the [Installation](../getting-started/local-setup/installation.md#2-installing-the-required-tools-) guide +- **`gnoland` set up. Reference + the [Setting up a local chain](validators/validators-setting-up-a-new-chain#installation) + guide for steps** +- **`gnokey` set up. Reference + the [Installation](../getting-started/local-setup/installation.md#2-installing-the-required-tools-) guide for steps** ## 1. Clean chain data @@ -52,36 +55,50 @@ An example of how this looks like in the initial `genesis.json` file after the c "g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa=1000000000000ugnot" ], ``` + The `genesis_balances.txt` file is located at `./gno.land/genesis/genesis_balances.txt`. To add a new entry to the premine table, simply append a line to the end of the file: + ```bash g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot # My address ``` + Replace `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt` with the address you want balance on, and `10000000000ugnot` with the desired `ugnot` balance. + ## 3. Start the local chain + Now that our address and the desired premine balance are located in the `genesis_balances.txt` file, we can start the local Gno node. To run the local Gno node, make sure you are in the `gno.land` sub-folder, and run the appropriate make command: + ```bash cd gno.land gnoland start ``` + This command will initialize the Gno node, generate the `genesis.json` with our newly added premine information, and start the chain. ![gnoland start](../assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif) + ## 3. Check the account balance + To check the balance of any account (or the account we just premined), we can use the following ABCI query: + ```bash gnokey query --remote localhost:26657 bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt ``` + Let's break down this command: + - **`--remote`** - the JSON-RPC URL of the running Gno node. In the case of a local deployment, the default value is `localhost:26657` - **`bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`** - the ABCI query targets the `bank` module to find the `balances` for address `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`. Replace the address with your desired address ![gnokey query](../assets/getting-started/local-setup/setting-up-funds/gnokey-query.gif) + ## Conclusion + That's it 🎉 You have successfully premined a native currency balance on a locally-running Gno chain! Additionally, you have also learned how to query the native currency balance for an address, using built-in ABCI queries diff --git a/docs/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md new file mode 100644 index 00000000000..42c8cdd909b --- /dev/null +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -0,0 +1,110 @@ +--- +id: validators-connect-to-and-existing-gno-chain +--- + +# Connect to an Existing Gno Chain + +## Overview + +In this tutorial, you will learn how to start a local Gno node and connect to an existing Gno chain (like a testnet). + +## Prerequisites + +- **Git** +- **`make` (for running Makefiles)** +- **Go 1.21+** +- **Go Environment Setup**: Ensure you have Go set up as outlined in + the [Go official installation documentation](https://go.dev/doc/install) for your environment + +## 1. Initialize the node directory + +To initialize a new Gno.land node working directory (configuration and secrets), make sure to +follow [Step 1](./validators-setting-up-a-new-chain#1-generate-the-node-directory-secrets--config) from the +chain setup tutorial. + +## 2. Obtain the `genesis.json` of the remote chain + +The genesis file of target chain is required in order to initialize the local node. + +:::info + +The genesis file will +be [easily downloadable from GitHub](https://github.com/gnolang/gno/issues/1836#issuecomment-2049428623) in the future. + +For now, obtain the file by + +1. Sharing via scp or ftp +2. Fetching it from `{chain_rpc:26657}/genesis` (might result in time-out error due to large file sizes) + +::: + +## 3. Confirm the validator information of the first node. + +```bash +gnoland secrets get node_id + +{ + "id": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng", + "p2p_address": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" +} +``` + +### Public IP of the Node + +You need the IP information about the network interface that you wish to connect from external nodes. + +If you wish to only connect from nodes in the same network, using a private IP should suffice. + +However, if you wish to connect from all nodes without any specific limitations, use your public IP. + +```bash +curl ifconfig.me/ip # GET PUBLIC IP + +# 1.2.3.4 # USE YOUR OWN PUBLIC IP +``` + +## 4. Configure the `persistent_peers` list + +We need to configure a list of nodes that your validators will always retain a connection with. +To get the local P2P address of the current node (these values should be obtained from remote peers): + +```bash +gnoland secrets get node_id.p2p_address + +"g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" +``` + +We can use this P2P address value to configure the `persistent_peers` configuration value + +```bash +gnoland config set p2p.persistent_peers "g19d8x6tcr2eyup9e2zwp9ydprm98l76gp66tmd6@1.2.3.4:26656" +``` + +## 5. Configure the seeds + +We should configure the list of seed nodes. Seed nodes provide information about other nodes for the validator to +connect with the chain, enabling a fast and stable initial connection. These seed nodes are also called _bootnodes_. + +:::warning + +The option to activate the Seed Mode from the node is currently missing. + +::: + +```bash +gnoland config set p2p.seeds "g19d8x6tcr2eyup9e2zwp9ydprm98l76gp66tmd6@1.2.3.4:26656" +``` + +## 6. Start the node + +Now that we've set up the local node configuration, and added peering info, we can start the Gno.land node: + +```shell +gnoland start \ +--genesis ./genesis.json \ +--data-dir ./gnoland-data +``` + +That's it! 🎉 + +Your new Gno node should be up and running, and syncing block data from the remote chain. \ No newline at end of file diff --git a/docs/gno-infrastructure/validators/faq.md b/docs/gno-infrastructure/validators/faq.md new file mode 100644 index 00000000000..a84cf9ee635 --- /dev/null +++ b/docs/gno-infrastructure/validators/faq.md @@ -0,0 +1,182 @@ +--- +id: validators-faq +--- + +# Validators FAQ + +## General Concepts + +### What is a Gno.land validator? + +Gno.land is based on [Tendermint2](https://docs.gno.land/concepts/tendermint2) that relies on a set of validators +selected based on [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC) to secure the +network. Validators are tasked with participating in consensus by committing new blocks and broadcasting votes. +Validators are compensated with a portion of transaction fees generated in the network. In Gno.land, the voting power of +all validators are equally weighted to achieve a high nakamoto coefficient and fairness. + +### What is Tendermint2? + +[Tendermint2](https://docs.gno.land/concepts/tendermint2) (TM2) is the consensus protocol that powers Gno.land. TM2 is a +successor of [Tendermint Core](https://github.com/tendermint/tendermint2), a de facto consensus framework for building +Proof of Stake blockchains. The design philosophy of TM2 is to create “complete software” without any vulnerabilities +with development focused on minimalism, dependency removal, and modularity. + +### What is Proof of Contribution? + +[Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC) is a novel consensus mechanism that +secures Gno.land. PoC weighs expertise and alignment with the project to evaluate the contribution of individuals or +teams who govern and operate the chain. Unlike Proof of Stake (PoS), validators are selected via governance of +Contributors based on their reputation and technical proficiency. The voting power of the network is equally distributed +across all validators for higher decentralization. A portion of all transaction fees paid to the network are evenly +shared between all validators to provide a fair incentive structure. + +### How does Gno.land differ from the Cosmos Hub? + +In Cosmos Hub, validators are selected based on the amount of staked `ATOM` tokens delegated. This means that anyone +with enough capital can join as a validator only to seek economic incentives without any alignment or technical +expertise. This system leads to an undesirable incentive structure in which validators are rewarded purely based on the +capital delegated, regardless of the quality of their infrastructure or service. + +On the contrary, validators in Gno.land must be reviewed and verified to have made significant contributions in order to +join the validator set. This property resembles the validator selection mechanism +in [Proof of Authority](https://openethereum.github.io/Proof-of-Authority-Chains). Furthermore, all validators are +evenly rewarded to ensure that the entire validator set is fairly incentivized to ensure the sustainability of the +network. + +### What stage is the Gno.land project in? + +Gno.land is currently in Testnet 3, the single-node testnet stage. The next version, Testnet 4, is scheduled to go live +in Q2 2024, which will include a validator set implementation for a multinode environment. + +## Becoming a Validator + +### How do I join the testnet as a validator? + +Out of many official Gno testnets, Testnet4 (`test4`) is the purpose-built network for testing the multi-node validator +environment prior to mainnet launch. Testnet4 is scheduled to go live in Q2 2024 with genesis validators consisting of +the Gno Core Team, partners, and external contributors. + +For more information about joining testnet4, +visit [the relevant issue](https://github.com/gnolang/hackerspace/issues/69). For more information about different +testnets, visit [Gno Testnets](https://docs.gno.land/concepts/testnets). + +### What are the incentives for running a validator? + +Network transaction fees paid on the Gno.land in `GNOT` are collected, from which a portion is directed to reward +validators for their work. All validators fairly receive an equal amount of rewards. + +### How many validators will there be in mainnet? + +The exact plans for mainnet are still TBD. Based on the latest discussions between contributors, the mainnet will likely +have an inital validator set size of 20~50, which will gradually scale with the development and decentralization of the +Gno.land project. + +### How do I make my first contribution? + +Gno.land is in active development and external contributions are always welcome! If you’re looking for tasks to begin +with, we suggest you visit +the [Bounties &](https://github.com/orgs/gnolang/projects/35/views/3) [Worx](https://github.com/orgs/gnolang/projects/35/views/3) +board and search for open tasks up for grabs. Start from small challenges and work your way up to the bigger ones. Every +contribution is acknowledged and highly regarded in PoC. We look forward to having you onboard as a new Contributor! + +## Technical Guides + +### What are the different types of keys? + +1. **Tendermint ( Tendermint2 ) Key :** A unique key used for voting in consensus during creation of blocks. A + Tendermint Key is also often called a Validator Key. It is automatically created when running + the `gnoland secrets init` command. A validator may check their Tendermint Key by running + the `gnoland secrets get validator_key` command. + +2. **User-owned keys :** A key that is generated when a new account is created using the `gnokey` command. It is used to + sign transactions. + +3. **Node Key :** A key used for communicating with other nodes. It is automatically created when running + the `gnoland secrets init` command. A validator may check their Node Key by running the `gnoland secrets get node_id` + command. + +### What is a full node and a pruned node? + +A full node fully validates transactions and blocks of a blockchain and keeps a full record of all historic activity. A +pruned node is a lighter node that processes only block headers and does not keep all historical data of the blockchain +post-verification. Pruned nodes are less resource intensive in terms of storage costs. Although validators may run +either a full node or a pruned node, it is important to retain enough blocks to be able to validate new blocks. + +## Technical References + +### How do I generate `genesis.json`? + +`genesis.json` is the file that is used to create the initial state of the chain. To generate `genesis.json`, use +the `gnoland genesis generate` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-generate-flags) for various flags that allow you to +manipulate the file. + +:::warning + +Editing generated genesis.json manually is extremely dangerous. It may corrupt chain initial state which leads chain to +not start + +::: + +### How do I add or remove validators from `genesis.json`? + +Validators inside `genesis.json` will be included in the validator set at genesis. To manipulate the genesis validator +set, use the `gnoland genesis validator` command with the `add` or `remove` subcommands. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-validator-flags) for flags that allow you to +configure the name or the voting power of the validator. + +### How do I add the balance information to the `genesis.json`? + +You may premine coins to various addresses. To modify the balances of addresses at genesis, use +the `gnoland genesis balances` command with the `add` or `remove` subcommands. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-genesis-balances-add-flags) for various flags that allow you +to update the entire balance sheet with a file or modify the balance of a single address. + +:::info + +Not only `ugnot`, but other coins are accepted. However, be aware that coins other than `ugnot` may not work(send, and +etc.) properly. + +::: + +### How do I initialize `gno secrets`? + +The `gno secrets init` command allows you to initialize the private information required to run the validator, including +the validator node's private key, the state, and the node ID. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-secrets-init-flags) for various flags that allow you to +define the output directory or to overwrite the existing secrets. + +### How do I get `gno secrets`? + +To retrieve the private information of your validator node, use the `gnoland-secrets-get` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-secrets-get-flags) for a flag that allows you to define the +output directory. + +### How do I initialize the gno node configurations? + +To initialize the configurations required to run a node, use the `gnoland config init` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-init-flags) for various flags that allow you to define +the path or to overwrite the existing configurations. + +### How do I get the current gno node configurations? + +To retrieve the specific values the current gno node configurations, use the `gnoland config get` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-get) for a flag that allows you to define the path to +the configurations file. + +### How do I edit the gno node configurations? + +To edit the specific value of gno node configurations, use the `gnoland-config set` command. Refer +to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-set) for a flag that allows you to define the path to +the configurations file. + +### How do I initialize and start a new gno chain? + +To start an independent gno chain, follow the initialization process available +in [this section](./setting-up-a-new-chain.md). + +### How do I connect to an existing gno chain? + +To join the validator set of a gno chain, you must first establish a connection. Refer +to [this section](./connect-to-existing-chain.md) for a step-by-step guide on how to connect to an existing gno +chain. diff --git a/docs/gno-infrastructure/validators/overview.md b/docs/gno-infrastructure/validators/overview.md new file mode 100644 index 00000000000..f04a20ab5ac --- /dev/null +++ b/docs/gno-infrastructure/validators/overview.md @@ -0,0 +1,98 @@ +--- +id: validators-overview +--- + +# Validator Overview + +## Introduction + +Gno.land is a blockchain powered by the Gno tech stack, which consists of +the [Gno Language](https://docs.gno.land/concepts/gno-language/) ( +Gno), [Tendermint2](https://docs.gno.land/concepts/tendermint2/) (TM2), +and [GnoVM](https://docs.gno.land/concepts/gnovm/). Unlike +existing [Proof of Stake](https://docs.cosmos.network/v0.46/modules/staking/) (PoS) blockchains in the Cosmos ecosystem, +Gno.land runs on [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution/) (PoC), a novel +reputation-based consensus mechanism that values expertise and alignment with the project. In PoC, validators are +selected via governance based on their contribution to the project and technical proficiency. The voting power of the +network is equally distributed across all validators to achieve a high nakamoto coefficient. A portion of all +transaction fees paid to the network are evenly shared between all validators to provide a fair incentive structure. + +| **Blockchain** | Cosmos | Gno.land | +|--------------------------------------|-------------------------|-------------------------------| +| **Consensus Protocol** | Comet BFT | Tendermint2 | +| **Consensus Mechanism** | Proof of Stake | Proof of Contribution | +| **Requirement** | Delegation of Stake | Contribution | +| **Voting Power Reward Distribution** | Capital-based | Evenly-distributed | +| **Number of Validators** | 180 | 20~200 (gradually increasing) | +| **Virtual Machine** | N/A | GnoVM | +| **Tokenomics** | Inflationary (Dilutive) | Deflationary (Non-dilutive) | + +## Hardware Requirements + +The following minimum hardware requirements are recommended for running a validator node. + +- Memory: 16 GB RAM (Recommended: 32 GB) +- CPU: 2 cores (Recommended: 4 cores) +- Disk: 100 GB SSD (Depends on the level of pruning) + +:::warning + +These hardware requirements are currently approximate based on the Cosmos validator specifications. Final requirements +will be determined following thorough testing and optimization experiments in Testnet 4. + +::: + +## Good Validators + +Validators for Gno.land are trusted to demonstrate professionalism and responsibility. Below are best practices that can +be expected from a good, reliable validator. + +#### Ecosystem Contribution + +- Contributing to the core development of the Gno.land project +- Providing useful tools or infrastructure services (wallets, explorers, public RPCs, etc.) +- Creating educational materials to guide new members +- Localizing documentation or content to lower language or cultural barriers + +#### Quality Infrastructure + +- Strong connectivity, CPU, and memory setup +- Exercising technical stability by retaining a high uptime with a robust monitoring system +- Robust contingency plans with failover systems, storage backups, and redundant power supplies +- Geographical distribution of servers + +#### Transparency + +- Providing regular updates +- Engaging actively in community discussions +- Being accountable for any failures + +#### Compliance + +- Exercising legal compliance +- Consulting with legal experts to identify regulatory risks +- Conducting internal audits + +## Community + +Join the official Gno.land community in various channels to receive the latest updates about the project and actively +communicate with other validators and contributors. + +- [Gno.land Blog](https://gno.land/r/gnoland/blog) +- [Gno.land Discord](https://discord.gg/w2MpVEunxr) +- [Gno.land Twitter](https://x.com/_gnoland) + +:::info + +The validator set implementation in Gno.land is abstracted away from the consensus mechanism inside the `r/sys/vals` +realm. The realm is not production ready yet, and is still under active development. Proposals and contributions to +improve and complete the implementation are welcome. + +**Links to related efforts:** + +- Validator set injection through a Realm [[gnolang/gno #1823]](https://github.com/gnolang/gno/issues/1823) +- Add Validator Set Realm / Package [[gnolang/gno #1824](https://github.com/gnolang/gno/issues/1824) +- Add `/r/sys/vals` [[gnolang/gno #2130]](https://github.com/gnolang/gno/pull/2130) +- Add valset injection through `r/sys/vals` [[gnolang/gno #2229]](https://github.com/gnolang/gno/pull/2229) + +::: diff --git a/docs/gno-infrastructure/validators/running-a-validator.md b/docs/gno-infrastructure/validators/running-a-validator.md new file mode 100644 index 00000000000..b9160725121 --- /dev/null +++ b/docs/gno-infrastructure/validators/running-a-validator.md @@ -0,0 +1,27 @@ +--- +id: validators-running-a-validator +--- + +# Running a Validator + +## Becoming a Gno.land validator + +The Gno.land blockchain is powered by the [Tendermint2](https://docs.gno.land/concepts/tendermint2) (TM2) consensus, +which involves committing of new blocks and broadcasting votes by multiple validators selected via governance +in [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC). While traditional Proof of +Stake (PoS) blockchains such as the Cosmos Hub required validators to secure a delegation of staked tokens to join the +validator set, no bonding of capital is involved in Gno.land. Rather, the validators on Gno.land are expected to +demonstrate their technical expertise and alignment with the project by making continuous, meaningful contributions to +the project. Furthermore, the voting power and the transaction fee rewards between validators are distributed evenly to +achieve higher decentralization. From a technical perspective, the validator set implementation in Gno.land as its +abstracted away into the `r/sys/vals` realm ([work in progress](https://github.com/gnolang/gno/issues/1824)), as a form +of smart-contract, for modularity, whereas existing blockchains include the validator management logic within the +consensus layer. + +# Start a New Gno Chain and a Validator + +- [start a new gno chain and a validator](./setting-up-a-new-chain.md) + +# Connect to an Existing Gno Chain + +- [connect to an existing gno chain](./connect-to-existing-chain.md) diff --git a/docs/gno-infrastructure/setting-up-a-local-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md similarity index 82% rename from docs/gno-infrastructure/setting-up-a-local-chain.md rename to docs/gno-infrastructure/validators/setting-up-a-new-chain.md index 088e5a01495..1927679db8f 100644 --- a/docs/gno-infrastructure/setting-up-a-local-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -1,5 +1,5 @@ --- -id: setting-up-a-local-chain +id: validators-setting-up-a-new-chain --- # Setting up a Local Chain @@ -10,10 +10,12 @@ In this tutorial, you will learn how to start a local Gno node (and chain!). Additionally, you will see the different options you can use to make your Gno instance unique. ## Prerequisites + - **Git** - **`make` (for running Makefiles)** - **Go 1.21+** -- **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment +- **Go Environment Setup**: Ensure you have Go set up as outlined in + the [Go official installation documentation](https://go.dev/doc/install) for your environment ## Installation @@ -38,22 +40,6 @@ it, run the `gnoland` command: gnoland --help ``` -If everything was successful, you should get the following output: - -```bash -❯ gnoland -USAGE - [flags] [...] - -starts the gnoland blockchain node. - -SUBCOMMANDS - start run the full node - secrets gno secrets manipulation suite - config gno config manipulation suite - genesis gno genesis manipulation suite -``` - If you do not wish to install the binary globally, you can build and run it with the following command from the `gno.land/` folder: @@ -75,7 +61,7 @@ gnoland start --lazy The command will trigger a chain initialization process (if you haven't run the node before), and start the Gno node, which is ready to accept transactions and interact with other Gno nodes. -![gnoland start](../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif) +![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif) :::info Lazy init @@ -153,7 +139,7 @@ A couple of things to note: - `gnoland config init` initializes a default configuration - `gnoland secrets init` initializes new node secrets (validator key, node p2p key) -Essentially, `gnoland start --lazy` is simply a combination of `gnoland secrets generate` and `gnoland config generate`, +Essentially, `gnoland start --lazy` is simply a combination of `gnoland secrets init` and `gnoland config init`, with the default options enabled. #### Changing the node configuration @@ -167,10 +153,32 @@ gnoland config set rpc.laddr tcp://0.0.0.0:26657 This will update the RPC listen address to `0.0.0.0:26657`. You can verify the configuration was updated by running: -```go +```bash gnoland config get rpc.laddr + +# similar behavior for cosmos validator +# gaiad tx staking create-validator `--node string (default:tcp://localhost:26657)` +``` + +:::tip + +A moniker is a human-readable name of your Gno node. You may customize your moniker with the following +command: + +```bash +gnoland config set moniker node01 ``` +::: + +:::warning Modify existing secrets + +We can modify existing secrets, or utilize our own (if we have them backed up, for example) for the Gno.land node. +Each secret needs to be placed in the appropriate path within `/secrets`, and it can be replaced or +regenerated with `gnoland secrets init --force` + +::: + ### 2. Generate the `genesis.json` :::info Where's the `genesis.json`? @@ -181,6 +189,15 @@ trying to connect to. ::: +The `genesis.json` defines the initial genesis state for the chain. It contains information like: + +- the current validator set +- any predeployed transactions +- any premined balanced + +When the chain starts, the first block will be produced after all the init content inside the `genesis.json` is +executed. + Generating an empty `genesis.json` is relatively straightforward: ```shell @@ -232,7 +249,18 @@ FLAGS -output-path ./genesis.json the output path for the genesis.json ``` -### 3. Add the initial validator set +## 3. Add the `examples` packages into the `genesis.json` (optional) + +This step is not necessarily required, however, using a Gno.land chain without the `examples` packages predeployed can +present challenges with users who expect them to be present. + +The `examples` directory is located in the `$GNOROOT` location, or the local gno repository clone. + +```bash +gnoland genesis txs add packages ./examples +``` + +### 4. Add the initial validator set A new Gno chain cannot advance without an active validator set. Since this example follows starting a completely new Gno chain, you need to add at least one validator to the validator @@ -244,16 +272,16 @@ locally will be the validator node for the new Gno network. To display the generated node key data, run the following command: ```shell -gnoland secrets get ValidatorPrivateKey +gnoland secrets get validator_key ``` -This will display the information we need for updating the `genesis.json`: +This will display the information we need for updating the `genesis.json`, in JSON: ```shell -[Validator Key Info] - -Address: g10e3smsmusjn00n7j75fk9u4zta8djrlglcv6af -Public Key: gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqhjhrqd7xlhda7spfdtx6lrcjxlk67av46w7eng9z4e2ch478fsk4xmq3j +{ + "address": "g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl", + "pub_key": "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk" +} ``` Updating the `genesis.json` is relatively simple, running the following command will add the generated node info to the @@ -261,8 +289,8 @@ validator set: ```shell gnoland genesis validator add \ ---address g10e3smsmusjn00n7j75fk9u4zta8djrlglcv6af \ ---pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqhjhrqd7xlhda7spfdtx6lrcjxlk67av46w7eng9z4e2ch478fsk4xmq3j \ +--address g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl \ +--pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk \ --name Cuttlas ``` @@ -301,11 +329,12 @@ We can verify that the new validator was indeed added to the validator set: } ``` -### 4. Starting the chain +### 5. Starting the chain We have completed the main aspects of setting up a node: - generated the node directory (secrets and configuration) ✅ +- set the adequate configuration params ✅ - generated a `genesis.json` ✅ - added an initial validator set to the `genesis.json` ✅ @@ -321,7 +350,7 @@ That's it! 🎉 Your new Gno node (chain) should be up and running: -![gnoland start](../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif) +![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif) ## Chain runtime options diff --git a/docs/gno-tooling/cli/faucet/faucet.md b/docs/gno-tooling/cli/faucet/faucet.md index e1ec040cac1..4d32f86e9ef 100644 --- a/docs/gno-tooling/cli/faucet/faucet.md +++ b/docs/gno-tooling/cli/faucet/faucet.md @@ -19,14 +19,15 @@ haven't [premined a balance beforehand](../../../gno-infrastructure/premining-ba The Gno faucet works by designating a single address as a faucet address that will distribute funds. -Ensure the faucet account will have enough funds by [premining its balance](../../../gno-infrastructure/premining-balances.md) to a high value. +Ensure the faucet account will have enough funds +by [premining its balance](../../../gno-infrastructure/premining-balances.md) to a high value. In case you do not have an existing address added to `gnokey`, you can consult the [Working with Key Pairs](../../../getting-started/local-setup/working-with-key-pairs.md) guide. ## 2. Start the local chain After ensuring the faucet address will have enough funds in the premine, we -can [run the local blockchain node](../../../gno-infrastructure/setting-up-a-local-chain.md). +can [run the local blockchain node](../../../gno-infrastructure/validators/setting-up-a-new-chain.md). Navigate to the `gno.land` sub-folder and run the appropriate make command: ```bash diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md index 184eb92f721..c641a986a4e 100644 --- a/docs/gno-tooling/cli/gnodev.md +++ b/docs/gno-tooling/cli/gnodev.md @@ -8,13 +8,13 @@ Gnodev allows for quick and efficient development of Gno code. By watching your development directory, gnodev detects changes in your Gno code, reflecting them in the state of the node immediately. Gnodev also runs a -local instance of `gnoweb`, allowing you to see the rendering of your Gno code instantly. +local instance of `gnoweb`, allowing you to see the rendering of your Gno code instantly. ## Features - **In-Memory Node**: Gnodev starts an in-memory node, and automatically loads the **examples** folder and any user-specified paths. - **Web Interface Server**: Gnodev automatically starts a `gnoweb` server on -[`localhost:8888`](https://localhost:8888). + [`localhost:8888`](https://localhost:8888). - **Balances and Keybase Customization**: Users can set account balances, load them from a file, or add new accounts via a flag. - **Hot Reload**: Gnodev monitors the **examples** folder, as well as any folder specified as an argument for @@ -120,20 +120,20 @@ While `gnodev` is running, the following shortcuts are available: ### Options -| Flag | Effect | -|---------------------|---------------------------------------------------------------------| -| --minimal | Start `gnodev` without loading the examples folder. | -| --no-watch | Disable hot reload. | -| --add-account | Pre-add account(s) in the form `[=]` | -| --balances-file | Load a balance for the user(s) from a balance file. | -| --chain-id | Set node ChainID | -| --deploy-key | Default key name or Bech32 address for uploading packages. | -| --home | Set the path to load user's Keybase. | -| --max-gas | Set the maximum gas per block | -| --no-replay | Do not replay previous transactions upon reload | -| --node-rpc-listener | listening address for GnoLand RPC node | -| --root | gno root directory | -| --server-mode | disable interaction, and adjust logging for server use. | -| --verbose | enable verbose output for development | -| --web-listener | web server listening address | -| --web-help-remote | web server help page's remote addr - default to | +| Flag | Effect | +|---------------------|-----------------------------------------------------------------------| +| --minimal | Start `gnodev` without loading the examples folder. | +| --no-watch | Disable hot reload. | +| --add-account | Pre-add account(s) in the form `[=]` | +| --balances-file | Load a balance for the user(s) from a balance file. | +| --chain-id | Set node ChainID | +| --deploy-key | Default key name or Bech32 address for uploading packages. | +| --home | Set the path to load user's Keybase. | +| --max-gas | Set the maximum gas per block | +| --no-replay | Do not replay previous transactions upon reload | +| --node-rpc-listener | listening address for GnoLand RPC node | +| --root | gno root directory | +| --server-mode | disable interaction, and adjust logging for server use. | +| --verbose | enable verbose output for development | +| --web-listener | web server listening address | +| --web-help-remote | web server help page's remote addr - defaults to | diff --git a/docs/gno-tooling/cli/gnokey.md b/docs/gno-tooling/cli/gnokey.md index 9ea51546864..bf110faec5f 100644 --- a/docs/gno-tooling/cli/gnokey.md +++ b/docs/gno-tooling/cli/gnokey.md @@ -124,8 +124,8 @@ gnokey query {QUERY_PATH} | `bank/balances/{ADDRESS}` | Returns balances of an account. | `gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | | `vm/qfuncs` | Returns public facing function signatures as JSON. | `gnokey query vm/qfuncs --data "gno.land/r/demo/boards"` | | `vm/qfile` | Returns the file bytes, or list of files if directory. | `gnokey query vm/qfile --data "gno.land/r/demo/boards"` | -| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards"` | -| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards GetBoardIDFromName("my_board")"` | +| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards:"` | +| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards.GetBoardIDFromName("my_board")"` | | `vm/store` | (not yet supported) Fetches items from the store. | - | | `vm/package` | (not yet supported) Fetches a package's files. | - | @@ -205,16 +205,16 @@ gnokey maketx call \ > unsigned.tx ``` -:::warning `call` is a state-changing message +:::warning `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. +be subtracted from the caller balance. -As opposed to this, an ABCI query, such as `vm/qeval` will not persist state +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. ::: diff --git a/docs/gno-tooling/cli/gnoland.md b/docs/gno-tooling/cli/gnoland.md index 3d4ba9eac00..e4039e6cc0b 100644 --- a/docs/gno-tooling/cli/gnoland.md +++ b/docs/gno-tooling/cli/gnoland.md @@ -4,72 +4,375 @@ id: gno-tooling-gnoland # gnoland -## Overview +`gnoland` is the Gno.land blockchain client binary, which is capable of managing +node working files, as well as starting the blockchain client itself. -`gnoland` is the Gno.land blockchain client binary, which is capable of managing node working files, as well -as starting the blockchain client itself. +### gnoland start [flags] -## `gnoland init` +Starts the Gnoland blockchain node, with accompanying setup. -`gnoland init` is supposed to initialize the node's working directory in the given path. The node's data directory is -comprised initially from the node's secrets and config (default values). +#### FLAGS -It is meant to be an initial step in starting the gno blockchain client, as the client itself cannot run without secrets -data like private keys, and a configuration. When the blockchain client is started, it will initialize on its own -relevant DB working directories inside the node directory. +| Name | Type | Description | +|----------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `chainid` | String | The ID of the chain. (default: `dev`) | +| `data-dir` | String | The path to the node's data directory. This is an important folder. The chain may fail to start if this folder is contaminated. (default: `gnoland-data`) | +| `flag-config-path` | String | The flag config file (optional). | +| `genesis` | String | The path to the `genesis.json` file. (default: `genesis.json`) | +| `genesis-balance-file` | String | Initial distribution file. (default: `~/gno/gno.land/genesis/genesis_balances.txt`) | +| `genesis-max-vm-cycles` | Int | Sets maximum allowed vm cycles per operation. Zero means no limit. When increasing this option, the `block-max-gas` must also be increased to utilize the max cycles. (default: `100000000`) | +| `genesis-remote` | String | A replacement for `$$REMOTES%%` in genesis. (default: `localhost:26657`) | +| `genesis-txs-file` | String | Initial txs to replay. (default: ~/gno/gno.land/genesis/genesis_txs.jsonl) | +| `gnoroot-dir` | String | The root directory of the `gno` repository. (default: `~/gno`) | +| `lazy` | Boolean | Flag indication if lazy init is enabled. Generates the node secrets, configuration, and `genesis.json`. When set to `true`, you may start the chain without any initialization process, which comes in handy when developing. (default: `false`) | +| `log-format` | String | The log format for the gnoland node. (default: `console`) | +| `log-level` | String | The log level for the gnoland node. (default: `debug`) | +| `skip-failing-genesis-txs` | Boolean | Doesn’t panic when replaying invalid genesis txs. When starting a production-level chain, it is recommended to set this value to `true` to monitor and analyze failing transactions. (default: `false`) | -```shell -gnoland init --help +### gnoland genesis \ [flags] [\...] -USAGE - init [flags] +Gno `genesis.json` manipulation suite for managing genesis parameters. -initializes the node directory containing the secrets and configuration files +#### SUBCOMMANDS -FLAGS - -data-dir gnoland-data the path to the node's data directory - -force=false overwrite existing data, if any +| Name | Description | +|-------------|---------------------------------------------| +| `generate` | Generates a fresh `genesis.json`. | +| `validator` | Validator set management in `genesis.json`. | +| `verify` | Verifies a `genesis.json`. | +| `balances` | Manages `genesis.json` account balances. | +| `txs` | Manages the initial genesis transactions. | + +### gnoland genesis generate [flags] + +Generates a node's `genesis.json` based on specified parameters. + +#### FLAGS + +| Name | Type | Description | +|------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `block-max-data-bytes` | Int | The max size of the block data.(default: `2000000`) | +| `block-max-gas` | Int | The max gas limit for the block. (default: `100000000`) | +| `block-max-tx-bytes` | Int | The max size of the block transaction. (default: `1000000`) | +| `block-time-itoa` | Int | The block time itoa (in ms). (default: `100`) | +| `chain-id` | String | The ID of the chain. (default: `dev`) | +| `genesis-time` | Int | The genesis creation time. (default: `utc now timestamp`) | +| `output-path` : | String | The output path for the `genesis.json`. If the genesis-time of the Genesis File is set to a future time, the chain will automatically start at that time if the node is online. (default: `./genesis.json`) | + +### gnoland genesis validator \ [flags] + +Manipulates the `genesis.json` validator set. + +#### SUBCOMANDS + +| Name | Description | +|----------|----------------------------------------------| +| `add` | Adds a new validator to the `genesis.json`. | +| `remove` | Removes a validator from the `genesis.json`. | + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default `./genesis.json`) | + +### gnoland genesis validator add [flags] + +Adds a new validator to the `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-----------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | +| `name` | String | The name of the validator (must be unique). | +| `power` | Uint | The voting power of the validator (must be > 0). (default: `1`) | +| `pub-key` | String | The bech32 string representation of the validator's public key. | + +```bash +gnoland genesis validator add \ +-address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h \ +-name test1 \ +-pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zplmcmggxyxyrch0zcyg684yxmerullv3l6hmau58sk4eyxskmny9h7lsnz + +Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h added to genesis file +``` + +### gnoland genesis validator remove [flags] + +Removes a validator from the `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-------------------------------------------------------------| +| `address` | String | The gno bech32 address of the validator. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json)` | + +```bash +gnoland genesis validator remove \ +-address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h + +Validator with address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed from genesis file +``` + +### gnoland genesis verify \ [flags] [\…] + +Verifies a `genesis.json`. + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|-----------------------------------------------------------| +| `genesis-path` | String | The path to the `genesis.json`. (default: `genesis.json`) | + +### gnoland genesis balances \ [flags] [\…] + +Manages `genesis.json` account balances. + +#### SUBCOMMANDS + +| Name | Description | +|----------|--------------------------------------------------------| +| `add` | Adds the balance information. | +| `remove` | Removes the balance information of a specific account. | + +### gnoland genesis balances add [flags] + +#### FLAGS + +| Name | Type | Description | +|-----------------|--------|--------------------------------------------------------------------------------------------| +| `balance-sheet` | String | The path to the balance file containing addresses in the format `
=ugnot`. | +| `genesis-path` | String | The path to the `genesis.json` (default: `./genesis.json`) | +| `parse-export` | String | The path to the transaction export containing a list of transactions (JSONL). | +| `single` | String | The direct balance addition in the format `
=ugnot`. | + +```bash +gnoland genesis balances add \ +-single g1rzuwh5frve732k4futyw45y78rzuty4626zy6h=100ugnot + +1 pre-mines saved + +g1rzuwh5frve732k4futyw45y78rzuty4626zy6h:{[24 184 235 209 35 102 125 21 90 169 226 200 234 208 158 56 197 197 146 186] [{%!d(string=ugnot) 100}]}ugnot +``` + +### gnoland balances remove [flags] + +#### FLAGS + +| Name | Type | Description | +|----------------|--------|---------------------------------------------------------------------------------------------| +| `address` | String | The address of the account whose balance information should be removed from `genesis.json`. | +| `genesis-path` | String | The path to the `genesis.json`. (default: `./genesis.json`) | + +```bash +gnoland genesis balances remove \ +-address=g1rzuwh5frve732k4futyw45y78rzuty4626zy6h + +Pre-mine information for address g1rzuwh5frve732k4futyw45y78rzuty4626zy6h removed +``` + +### gnoland txs \ [flags] [\…] + +Manages genesis transactions through input files. + +#### SUBCOMMANDS + +| Name | Description | +|----------|---------------------------------------------------| +| `add` | Imports transactions into the `genesis.json`. | +| `remove` | Removes the transactions from the `genesis.json`. | +| `export` | Exports the transactions from the `genesis.json`. | + +### gnoland secrets \ [flags] [\…] + +The gno secrets manipulation suite for managing the validator key, p2p key and +validator state. + +#### SUBCOMMANDS + +| Name | Description | +|----------|---------------------------------------------------------| +| `init` | Initializes required Gno secrets in a common directory. | +| `verify` | Verifies all Gno secrets in a common directory. | +| `get` | Shows all Gno secrets present in a common directory. | + +### gnoland secrets init [flags] [\] + +Initializes the validator private key, the node p2p key and the validator's last +sign state. If a key is provided, it initializes the specified key. + +Available keys: + +- `validator_key` : The private key of the validator, which is different from the private key of the wallet. +- `node_id` : A key used for communicating with other nodes. +- `validator_state` : The current state of the validator such as the last signed block. + +#### FLAGS + +| Name | Type | Description | +|------------|--------|-----------------------------------------------------------------| +| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets`) | +| `force` | String | Overwrites existing secrets, if any. (default: `false`) | + +```bash +# force initialize all key +gnoland secrets init -force + +Validator private key saved at gnoland-data/secrets/priv_validator_key.json +Validator last sign state saved at gnoland-data/secrets/priv_validator_state.json +Node key saved at gnoland-data/secrets/node_key.json + + +# force initialize a specific key type (ex: NodeKey) +gnoland secrets init node_key -force +Node key saved at gnoland-data/secrets/node_key.json ``` -### Example usage +### gnoland secrets verify [flags] [\] + +Verifies the validator private key, the node p2p key and the validator's last +sign state. If a key is provided, it verifies the specified key value. + +Available keys: [`validator_key`, `node_id`, `validator_state`] -#### Generating fresh secrets / config +#### FLAGS -To initialize the node secrets and configuration to `./example-node-data`, run the following command: +| Name | Type | Description | +|------------|--------|-----------------------------------------------------------------| +| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets`) | -```shell -gnoland init --data-dir ./example-node-data +```bash +# verify all keys +gnoland secrets verify +Validator Private Key at gnoland-data/secrets/priv_validator_key.json is valid +Last Validator Sign state at gnoland-data/secrets/priv_validator_state.json is valid +Node P2P key at gnoland-data/secrets/node_key.json is valid + + +# verify a specific key type (ex: NodeKey) +gnoland secrets verify node_key +Node P2P key at gnoland-data/secrets/node_key.json is valid ``` -This will initialize the following directory structure: - -```shell -. -└── example-node-data/ - ├── secrets/ - │ ├── priv_validator_state.json - │ ├── node_key.json - │ └── priv_validator_key.json - └── config/ - └── config.toml +### gnoland secrets get [flags] [\] + +Shows the validator private key, the node p2p key and the validator's last sign +state. If a key is provided, it shows the specified key value. + +Available keys: [`validator_key`, `node_key`, `validator_state`] + +#### FLAGS + +| Name | Type | Description | +|------------|--------|-----------------------------------------------------------------| +| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets)` | + +```bash +gnoland secrets get + +{ + "validator_key": { + "address": "g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl", + "pub_key": "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk" + }, + "validator_state": { + "height": 0, + "round": 0, + "step": 0 + }, + "node_id": { + "id": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng", + "p2p_address": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" + } +} + +# will return node id info +gnoland secrets get node_id + +# to get node id in cosmos +# gaiad tendermint show-node-id + +# will return validator address and pub key +gnoland secrets get validator_key +# to get validator address in cosmos +# gaiad tendermint show-address + +# to get validator pub key in cosmos +# gaiad tendermint show-validator ``` -#### Overwriting the secrets / config +### gnoland config [subcommand] [flags] -In case there is an already existing node directory at the given path, you will need to provide an additional `--force` -flag to enable data overwrite. +The gno config manipulation suite for editing base and module configurations. -:::warning Back up any secrets +#### SUBCOMMANDS -Running `gnoland init` will generate completely new node secrets (validator private key, node p2p key), so make sure -you back up any existing secrets (located at `/secrets`) if you intend to overwrite them, in case you don't -want to lose them. +| Name | Description | +|--------|-----------------------------------------| +| `init` | Initializes the Gno node configuration. | +| `set` | Edits the Gno node configuration. | +| `get` | Shows the Gno node configuration. | +### gnoland config init [flags] + +Initializes the Gno node configuration locally with default values, which +includes the base and module configurations. + +#### FLAGS + +| Name | Type | Description | +|---------------|---------|------------------------------------------------------------------------------| +| `config-path` | String | The path for the `config.toml`. (default: `gnoland-data/config/config.toml`) | +| `force` | Boolean | Overwrites existing config.toml, if any. (default: `false`) | + +```bash +# initialize the configuration file +gnoland config init + +Default configuration initialized at gnoland-data/config/config.toml +``` + +### gnoland config set \ \ + +Edits the Gno node configuration at the given path by setting the option +specified at `\` to the given `\`. + +#### FLAGS + +| Name | Type | Description | +|---------------|--------|------------------------------------------------------------------------------| +| `config-path` | String | The path for the `config.toml`. (default: `gnoland-data/config/config.toml`) | + +:::info +The `config set` command replaces the complexity of manually editing the `config.toml` file. ::: -Following up from the previous example where our desired node directory is `example-node-data` - to -initialize a completely new node data directory, with overwriting any existing data, run the following command: +### gnoland config get \ + +Shows the Gno node configuration at the given path by fetching the option +specified at `\`. + +#### FLAGS + +| Name | Type | Description | +|---------------|--------|---------------------------------------------------------------------------| +| `config-path` | String | the path for the config.toml (default: `gnoland-data/config/config.toml`) | + +```bash +# check the current monkier (the displayed validator name) +gnoland config get -r moniker +n3wbie-MacBook-Pro.local + +# set a new moniker +gnoland config set moniker hello +Updated configuration saved at gnoland-data/config/config.toml + -```shell -gnoland init --data-dir ./example-node-data --force +# confirm the moniker change +gnoland config get -r moniker +hello ``` diff --git a/docs/gno-tooling/gno-tooling.md b/docs/gno-tooling/gno-tooling.md index a03bf84a5bf..290d56820d0 100644 --- a/docs/gno-tooling/gno-tooling.md +++ b/docs/gno-tooling/gno-tooling.md @@ -4,5 +4,28 @@ id: gno-tooling # Gno Tooling -Welcome to the **Gno Tooling** section for Gno. This section outlines programs & tools -that are commonly used when developing applications with Gno. \ No newline at end of file +Welcome to the **Gno Tooling** section for Gno. This section outlines programs +& tools that are commonly used when developing applications with Gno. + +## Gno Command Syntax Guide + +### gno [subcommand] [flags] [arg...] + +#### Subcommand + +The gno command consists of various purpose-built subcommands. + +- `gno {mod}` : manage gno.mod +- `gno {mod} {download} : download modules to local cache + +#### Flags + +Options of the subcommand. + +- `gno mod download [-remote]` : remote for fetching gno modules + +#### Arg + +The actual value of the flag . + +- `gno mod download -remote {rpc.gno.land:26657}` diff --git a/docs/how-to-guides/connecting-from-go.md b/docs/how-to-guides/connecting-from-go.md index cb6ed0547b6..2d8ebbc7f19 100644 --- a/docs/how-to-guides/connecting-from-go.md +++ b/docs/how-to-guides/connecting-from-go.md @@ -101,19 +101,23 @@ func main() { A few things to note: - You can view keys in your local keybase by running `gnokey list`. - You can get the password from a user input using the IO package. -- `Signer` can also be initialized in-memory from a BIP39 mnemonic, using the -[`SignerFromBip39`](../reference/gnoclient/signer.md#func-signerfrombip39) function. +- `Signer` can also be initialized in-memory from a BIP39 mnemonic, using the +[`SignerFromBip39`](https://gnolang.github.io/gno/github.com/gnolang/gno@v0.0.0/gno.land/pkg/gnoclient.html#SignerFromBip39) +function. ## Initialize the RPC connection & Client You can initialize the RPC Client used to connect to the Gno.land network with the following line: ```go -rpc := rpcclient.NewHTTPClient("") +rpc, err := rpcclient.NewHTTPClient("") +if err != nil { + panic(err) +} ``` -A list of Gno.land network endpoints & chain IDs can be found in the [Gno RPC -endpoints](../reference/rpc-endpoints.md#network-configurations) page. +A list of Gno.land network endpoints & chain IDs can be found in the +[Gno RPC endpoints](../reference/network-config.md) page. With this, we can initialize the `gnoclient.Client` struct: @@ -139,7 +143,10 @@ func main() { } // Initialize the RPC client - rpc := rpcclient.NewHTTPClient("") + rpc, err := rpcclient.NewHTTPClient("") + if err != nil { + panic(err) + } // Initialize the gnoclient client := gnoclient.Client{ @@ -158,6 +165,13 @@ To send transactions to the chain, we need to know the account number (ID) and sequence (nonce). We can get this information by querying the chain with the `QueryAccount` function: +```go +import ( + ... + "github.com/gnolang/gno/tm2/pkg/crypto" +) +``` + ```go // Convert Gno address string to `crypto.Address` addr, err := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // your Gno address diff --git a/docs/how-to-guides/simple-library.md b/docs/how-to-guides/simple-library.md index 1ae231251d0..923fc98922e 100644 --- a/docs/how-to-guides/simple-library.md +++ b/docs/how-to-guides/simple-library.md @@ -34,7 +34,7 @@ and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=har ::: We discussed Gno folder structures more in detail in -the [simple Smart Contract guide](simple-contract.md#1-setting-up-the-work-directory). +the [simple Smart Contract guide](simple-contract.md#local-setup). For now, we will just follow some rules outlined there. Create the main working directory for our Package: diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md index a5248349d35..df808106cc3 100644 --- a/docs/reference/gno-js-client/gno-provider.md +++ b/docs/reference/gno-js-client/gno-provider.md @@ -39,7 +39,7 @@ Fetches public facing function signatures * `height` **number** the height for querying. If omitted, the latest height is used (optional, default `0`) -Returns **Promise** +Returns **Promise** #### Usage diff --git a/docs/reference/gno-js-client/gno-wallet.md b/docs/reference/gno-js-client/gno-wallet.md index 7f7c44cd9b0..247c3d52878 100644 --- a/docs/reference/gno-js-client/gno-wallet.md +++ b/docs/reference/gno-js-client/gno-wallet.md @@ -63,7 +63,7 @@ Deploys the specified package / realm #### Parameters * `gnoPackage` **MemPackage** the package / realm to be deployed -* `funds` **Map** the denomination -> value map for funds +* `funds` **Map** the denomination -> value map for funds * `fee` **TxFee** the custom transaction fee, if any Returns **Promise** diff --git a/docs/reference/gnoclient/client.md b/docs/reference/gnoclient/client.md deleted file mode 100644 index 0fbef3f5f93..00000000000 --- a/docs/reference/gnoclient/client.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -id: client ---- - -# Client - -## type [Client]() - -`Client` provides an interface for interacting with the blockchain. It is the main -struct of the `gnoclient` package, exposing all APIs used to communicate with a -Gno.land chain. - -```go -type Client struct { - Signer Signer // Signer for transaction authentication - RPCClient rpcclient.Client // RPC client for blockchain communication -} -``` - -### func \(\*Client\) [AddPackage]() - -```go -func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`AddPackage` executes one or more [AddPackage](#type-msgaddpackage) calls on the blockchain. - -### func \(\*Client\) [Block]() - -```go -func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) -``` - -`Block` gets the latest block at height, if any. Height must be larger than 0. - -### func \(\*Client\) [BlockResult]() - -```go -func (c *Client) BlockResult(height int64) (*ctypes.ResultBlockResults, error) -``` - -`BlockResult` gets the block results at height, if any. Height must be larger than 0. - -### func \(\*Client\) [LatestBlockHeight]() - -```go -func (c *Client) LatestBlockHeight() (int64, error) -``` - -`LatestBlockHeight` gets the latest block height on the chain. - -### func \(\*Client\) [Call]() - -```go -func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Call` executes a one or more [MsgCall](#type-msgcall) calls on the blockchain. - -### func \(\*Client\) [Send]() - -```go -func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Send` executes one or more [MsgSend](#type-msgsend) calls on the blockchain. - -### func \(\*Client\) [Run]() - -```go -func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCommit, error) -``` - -`Run` executes a one or more MsgRun calls on the blockchain. - -### func \(\*Client\) [QEval]() - -```go -func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.ResultABCIQuery, error) -``` - -`QEval` evaluates the given expression with the realm code at `pkgPath`. -The `pkgPath` should include the prefix like `gno.land/`. The expression is -usually a function call like `Render("")`. - -### func \(*Client\) [Query]() - -```go -func (c *Client) Query(cfg QueryCfg) (*ctypes.ResultABCIQuery, error) -``` - -`Query` performs a generic query on the blockchain. - -### func \(*Client\) [QueryAccount]() - -```go -func (c *Client) QueryAccount(addr crypto.Address) (*std.BaseAccount, *ctypes.ResultABCIQuery, error) -``` - -`QueryAccount` retrieves account information for a given address. - -### func \(*Client\) [QueryAppVersion]() - -```go -func (c *Client) QueryAppVersion() (string, *ctypes.ResultABCIQuery, error) -``` - -`QueryAppVersion` retrieves information about the Gno.land app version. - -### func \(*Client\) [Render]() - -```go -func (c *Client) Render(pkgPath string, args string) (string, *ctypes.ResultABCIQuery, error) -``` - -`Render` calls the Render function for pkgPath with optional args. The `pkgPath` -should include the prefix like `gno.land/`. This is similar to using a browser -URL `/:` where `` doesn't have the prefix like -`gno.land/`. - -## type [BaseTxCfg]() - -`BaseTxCfg` defines the base transaction configuration, shared by all message -types. - -```go -type BaseTxCfg struct { - GasFee string // Gas fee - GasWanted int64 // Gas wanted - AccountNumber uint64 // Account number - SequenceNumber uint64 // Sequence number - Memo string // Memo -} -``` - -## type [MsgAddPackage]() - -`MsgAddPackage` \- syntax sugar for `vm.MsgAddPackage`. - -```go -type MsgAddPackage struct { - Package *std.MemPackage // Package to add - Deposit string // Coin deposit -} -``` - -## type [MsgCall]() - -`MsgCall` \- syntax sugar for `vm.MsgCall`. - -```go -type MsgCall struct { - PkgPath string // Package path - FuncName string // Function name - Args []string // Function arguments - Send string // Send amount -} -``` - -## type [MsgRun]() - -`MsgRun` \- syntax sugar for `vm.MsgRun`. - -```go -type MsgRun struct { - Package *std.MemPackage // Package to run - Send string // Send amount -} -``` - -## type [MsgSend]() - -`MsgSend` \- syntax sugar for `bank.MsgSend`. - -```go -type MsgSend struct { - ToAddress crypto.Address // Send to address - Send string // Send amount -} -``` - -## type [QueryCfg]() - -`QueryCfg` contains configuration options for performing ABCI queries. - -```go -type QueryCfg struct { - Path string // Query path - Data []byte // Query data - rpcclient.ABCIQueryOptions // ABCI query options -} -``` \ No newline at end of file diff --git a/docs/reference/gnoclient/gnoclient.md b/docs/reference/gnoclient/gnoclient.md index f5baa9fc03a..588cc528dae 100644 --- a/docs/reference/gnoclient/gnoclient.md +++ b/docs/reference/gnoclient/gnoclient.md @@ -22,4 +22,13 @@ your Go code To add Gnoclient to your Go project, run the following command: ```bash go get github.com/gnolang/gno/gno.land/pkg/gnoclient -``` \ No newline at end of file +``` + +## Reference documentation & usage + +To see the full reference documentation for the `gnoclient` package, we recommend +visiting the [`gnoclient godoc page`](https://gnolang.github.io/gno/github.com/gnolang/gno@v0.0.0/gno.land/pkg/gnoclient.html). + +For a tutorial on how to use the `gnoclient` package, check out +["How to connect a Go app to Gno.land"](../../how-to-guides/connecting-from-go.md) + diff --git a/docs/reference/gnoclient/signer.md b/docs/reference/gnoclient/signer.md deleted file mode 100644 index 75c69a9f7c2..00000000000 --- a/docs/reference/gnoclient/signer.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: signer ---- - -# Signer - -`Signer` is an interface that provides functionality for signing transactions. -The signer can be created from a local keybase, or from a bip39 mnemonic phrase. - -Useful types and functions when using the `Signer` can be found below. - -## type [Signer]() - -`Signer` provides an interface for signing transactions. - -```go -type Signer interface { - Sign(SignCfg) (*std.Tx, error) // Signs a transaction and returns a signed tx ready for broadcasting. - Info() keys.Info // Returns key information, including the address. - Validate() error // Checks whether the signer is properly configured. -} -``` - -## type [SignCfg]() - -`SignCfg` provides the signing configuration, containing the unsigned transaction -data, account number, and account sequence. - -```go -type SignCfg struct { - UnsignedTX std.Tx - SequenceNumber uint64 - AccountNumber uint64 -} -``` - -## type [SignerFromKeybase]() - -`SignerFromKeybase` represents a signer created from a Keybase. - -```go -type SignerFromKeybase struct { - Keybase keys.Keybase // Stores keys in memory or on disk - Account string // Account name or bech32 format - Password string // Password for encryption - ChainID string // Chain ID for transaction signing -} -``` - -### func \(SignerFromKeybase\) [Info]() - -```go -func (s SignerFromKeybase) Info() keys.Info -``` - -`Info` gets keypair information. - -### func \(SignerFromKeybase\) [Sign]() - -```go -func (s SignerFromKeybase) Sign(cfg SignCfg) (*std.Tx, error) -``` - -`Sign` implements the Signer interface for SignerFromKeybase. - -### func \(SignerFromKeybase\) [Validate]() - -```go -func (s SignerFromKeybase) Validate() error -``` - -`Validate` checks if the signer is properly configured. - -## func [SignerFromBip39]() - -```go -func SignerFromBip39(mnemonic string, chainID string, passphrase string, account uint32, index uint32) (Signer, error) -``` - -`SignerFromBip39` creates a `Signer` from an in-memory keybase with a single default -account, derived from the given mnemonic. -This can be useful in scenarios where storing private keys in the filesystem -isn't feasible, or for generating a signer for testing. - -> Using `keys.NewKeyBaseFromDir()` to get a keypair from local storage is -recommended where possible, as it is more secure. \ No newline at end of file diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md index c2ec5409fc9..7902ec5e7f6 100644 --- a/docs/reference/network-config.md +++ b/docs/reference/network-config.md @@ -4,12 +4,12 @@ id: network-config # Network configurations -| Network | RPC Endpoint | Chain ID | -|-------------|------------------------------------|---------------| -| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | -| Testnet 4 | upcoming | upcoming | -| Testnet 3 | https://rpc.test3.gno.land:443 | `test3` | -| Staging | https://rpc.staging.gno.land:26657 | `test3` | +| Network | RPC Endpoint | Chain ID | +|-------------|-----------------------------------|---------------| +| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | +| Testnet 4 | upcoming | upcoming | +| Testnet 3 | https://rpc.test3.gno.land:443 | `test3` | +| Staging | http://rpc.staging.gno.land:36657 | `staging` | ### WebSocket endpoints All networks follow the same pattern for websocket connections: diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md index 9efb98e351d..7a383478ef5 100644 --- a/docs/reference/stdlibs/std/testing.md +++ b/docs/reference/stdlibs/std/testing.md @@ -10,11 +10,15 @@ func TestSetOrigCaller(addr Address) func TestSetOrigPkgAddr(addr Address) func TestSetOrigSend(sent, spent Coins) func TestIssueCoins(addr Address, coins Coins) +func TestSetRealm(realm Realm) +func NewUserRealm(address Address) +func NewCodeRealm(pkgPath string) ``` --- ## TestSkipHeights + ```go func TestSkipHeights(count int64) ``` @@ -29,6 +33,7 @@ std.TestSkipHeights(100) --- ## TestSetOrigCaller + ```go func TestSetOrigCaller(addr Address) ``` @@ -36,23 +41,26 @@ Sets the current caller of the transaction to **addr**. #### Usage ```go -std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") +std.TestSetOrigCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) ``` --- ## TestSetOrigPkgAddr + ```go func TestSetOrigPkgAddr(addr Address) ``` -Sets the current realm/package address to **addr**. +Sets the call entry realm address to **addr**. #### Usage ```go -std.TestSetOrigPkgAddr("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +std.TestSetOrigPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) ``` + --- ## TestSetOrigSend + ```go func TestSetOrigSend(sent, spent Coins) ``` @@ -65,17 +73,78 @@ std.TestSetOrigSend(sent, spent Coins) --- ## TestIssueCoins + ```go func TestIssueCoins(addr Address, coins Coins) ``` + Issues testing context **coins** to **addr**. + #### Usage + ```go issue := std.Coins{{"coin1", 100}, {"coin2", 200}} -addr := "g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec" +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") std.TestIssueCoins(addr, issue) ``` +--- + +## TestSetRealm + +```go +func TestSetRealm(rlm Realm) +``` + +Sets the realm for the current frame. After calling `TestSetRealm()`, calling +[`CurrentRealm()`](chain.md#currentrealm) in the same test function will yield the value of `rlm`, and +any `PrevRealm()` called from a function used after TestSetRealm will yield `rlm`. + +Should be used in combination with [`NewUserRealm`](#newuserrealm) & +[`NewCodeRealm`](#newcoderealm). + +#### Usage +```go +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +std.TestSetRealm(std.NewUserRealm("")) +// or +std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) +``` + +--- + +## NewUserRealm + +```go +func NewUserRealm(address Address) Realm +``` + +Creates a new user realm for testing purposes. + +#### Usage +```go +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +userRealm := std.NewUserRealm(addr) +``` + +--- + +## NewCodeRealm + +```go +func NewCodeRealm(pkgPath string) +``` + +Creates a new code realm for testing purposes. + +#### Usage +```go +path := "gno.land/r/demo/boards" +codeRealm := std.NewCodeRealm(path) +``` + + + diff --git a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md index 50d816e89f9..b7700e1d97c 100644 --- a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md +++ b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md @@ -17,6 +17,6 @@ Creates a new instance of the JSON-RPC Provider #### Usage ```ts -new JSONRPCProvider('http://staging.gno.land:26657'); +new JSONRPCProvider('http://staging.gno.land:36657'); // provider is created ``` diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index e91788ac8eb..e3f1363e205 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -25,67 +25,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -140,13 +98,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -183,19 +160,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "6da365f0d6cacbcdf53cd5a4b125803cddce08c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "091729e38bda8724bce4c314f9624b91af679459", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // }, // { @@ -208,27 +179,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "f216afe7b5a17f4ebdbb98dceccedbc22e237596", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ff1a50d8489090af37a2c7766d659f0d717939b5", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -236,7 +220,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -333,19 +317,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6c9948281d4c60b2d95233b76388d54d8b1a2fad", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ] diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index cdd56a5ad89..a6d2205e240 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { // "T": { @@ -79,13 +79,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "143aebc820da33550f7338723fb1e2eec575b196", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ // "Fields": [ // { // "T": { @@ -122,19 +141,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -147,30 +160,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -207,19 +233,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -232,27 +252,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -260,7 +293,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -357,21 +390,16 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/examples/gno.land/p/demo/avl/z_2_filetest.gno b/examples/gno.land/p/demo/avl/z_2_filetest.gno index 65181bffcac..1d3f20651ed 100644 --- a/examples/gno.land/p/demo/avl/z_2_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_2_filetest.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={ // "Fields": [ // { // "T": { @@ -78,13 +78,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:16", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "db333c89cd6773709e031f1f4e4ed4d3fed66c11", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:16" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ // "Fields": [ // { // "T": { @@ -121,19 +140,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "849a50d6c78d65742752e3c89ad8dd556e2e63cb", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "213baed7e3326f2403b5f30e5d4397510ba4f37d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // }, // { @@ -146,30 +159,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b4fc2fdd2d0fe936c87ed2ace97136cffeed207f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "be751422ef4c2bc068a456f9467d2daca27db8ca", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a1160b0060ad752dbfe5fe436f7734bb19136150", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ // "Fields": [ // { // "T": { @@ -206,19 +232,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "fd95e08763159ac529e26986d652e752e78b6325", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "af4d0b158681d85eb2a7f6888b39a05ca7b790ee", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -231,27 +251,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ef853d70e334fd2c807d6c2c751da1fcd1e5ad58", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "63126557dba88f8556f7a0ccbbfc1d218ae7a302", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ @@ -266,27 +299,22 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "425bc3bff1f4c36b175d055ed8b2c289123fcca1", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "3a5af0895c2c45b8a5e894644bcd689f1fdc4785", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "7", +// "ModTime": "10", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" // } // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:6] diff --git a/examples/gno.land/p/demo/flow/LICENSE.md b/examples/gno.land/p/demo/flow/LICENSE similarity index 100% rename from examples/gno.land/p/demo/flow/LICENSE.md rename to examples/gno.land/p/demo/flow/LICENSE diff --git a/examples/gno.land/p/demo/memeland/memeland_test.gno b/examples/gno.land/p/demo/memeland/memeland_test.gno index 76c44327993..fbfcfec8ae3 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -6,6 +6,7 @@ import ( "testing" "time" + "gno.land/p/demo/seqid" "gno.land/p/demo/testutils" "gno.land/p/demo/ufmt" ) @@ -97,7 +98,7 @@ func TestGetPostsInRangeByTimestamp(t *testing.T) { // Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering postCount := strings.Count(jsonStr, `"id":"`) - if postCount != m.MemeCounter { + if seqid.ID(postCount) != m.MemeCounter { t.Errorf("Expected %d posts in the JSON string, but found %d", m.MemeCounter, postCount) } @@ -157,7 +158,7 @@ func TestGetPostsInRangeByUpvote(t *testing.T) { // Count the number of posts returned in the JSON string as a rudimentary check for correct pagination/filtering postCount := strings.Count(jsonStr, `"id":"`) - if postCount != m.MemeCounter { + if seqid.ID(postCount) != m.MemeCounter { t.Errorf("Expected %d posts in the JSON string, but found %d", m.MemeCounter, postCount) } diff --git a/examples/gno.land/p/gov/proposal/proposal.gno b/examples/gno.land/p/gov/proposal/proposal.gno index f01d3661a5e..d4f151f935a 100644 --- a/examples/gno.land/p/gov/proposal/proposal.gno +++ b/examples/gno.land/p/gov/proposal/proposal.gno @@ -1,6 +1,10 @@ // Package proposal provides a structure for executing proposals. package proposal +import "std" + +const daoPkgPath = "gno.land/r/gov/dao" // XXX: make it configurable with r/sys/vars? + // NewExecutor creates a new executor with the provided callback function. func NewExecutor(callback func() error) Executor { return &executorImpl{ @@ -21,7 +25,7 @@ func (exec *executorImpl) Execute() error { if exec.done { return ErrAlreadyDone } - // XXX: assertCalledByGovdao + assertCalledByGovdao() err := exec.callback() exec.done = true exec.success = err == nil @@ -37,3 +41,21 @@ func (exec *executorImpl) Done() bool { func (exec *executorImpl) Success() bool { return exec.success } + +func (exec executorImpl) Status() Status { + switch { + case exec.success: + return Success + case exec.done: + return Failed + default: + return NotExecuted + } +} + +func assertCalledByGovdao() { + caller := std.CurrentRealm().PkgPath() + if caller != daoPkgPath { + panic("only gov/dao can execute proposals") + } +} diff --git a/examples/gno.land/p/gov/proposal/types.gno b/examples/gno.land/p/gov/proposal/types.gno index a2ebad6f585..d84d1bfd21f 100644 --- a/examples/gno.land/p/gov/proposal/types.gno +++ b/examples/gno.land/p/gov/proposal/types.gno @@ -8,9 +8,19 @@ import "errors" type Executor interface { Execute() error Done() bool - Success() bool // Done() && !err + Success() bool // Done() && !err + Status() Status // human-readable execution status } // ErrAlreadyDone is the error returned when trying to execute an already // executed proposal. var ErrAlreadyDone = errors.New("already executed") + +// Status enum. +type Status string + +var ( + NotExecuted Status = "not_executed" + Success Status = "success" + Failed Status = "failed" +) diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index bc17ee9df3b..6276629cba2 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,5 @@ +// Draft + module gno.land/r/demo/art/gnoface require ( diff --git a/examples/gno.land/r/demo/bar20/bar20.gno b/examples/gno.land/r/demo/bar20/bar20.gno new file mode 100644 index 00000000000..7388d87d24d --- /dev/null +++ b/examples/gno.land/r/demo/bar20/bar20.gno @@ -0,0 +1,46 @@ +// Package bar20 is similar to foo20 but exposes a safe-object that can be used +// by `maketx run`, another contract importing foo20, and in the future when +// we'll support `maketx call Token.XXX`. +package bar20 + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" +) + +var ( + banker *grc20.AdminToken // private banker. + Token grc20.IGRC20 // public safe-object. +) + +func init() { + banker = grc20.NewAdminToken("Bar", "BAR", 4) + Token = banker.GRC20() +} + +func Faucet() string { + caller := std.PrevRealm().Addr() + if err := banker.Mint(caller, 1_000_000); err != nil { + return "error: " + err.Error() + } + return "OK" +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return banker.RenderHome() // XXX: should be Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := std.Address(parts[1]) + balance, _ := Token.BalanceOf(owner) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} diff --git a/examples/gno.land/r/demo/bar20/bar20_test.gno b/examples/gno.land/r/demo/bar20/bar20_test.gno new file mode 100644 index 00000000000..b2a49ebd864 --- /dev/null +++ b/examples/gno.land/r/demo/bar20/bar20_test.gno @@ -0,0 +1,31 @@ +package bar20 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestPackage(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOrigCaller(alice) // XXX: should not need this + + balance, _ := Token.BalanceOf(alice) + expected := uint64(0) + if balance != expected { + t.Errorf("balance should be %d, got %d", expected, balance) + } + + ret := Faucet() + if ret != "OK" { + t.Errorf("faucet should be OK, got %s", ret) + } + + balance, _ = Token.BalanceOf(alice) + expected = uint64(1_000_000) + if balance != expected { + t.Errorf("balance should be %d, got %d", expected, balance) + } +} diff --git a/examples/gno.land/r/demo/bar20/gno.mod b/examples/gno.land/r/demo/bar20/gno.mod new file mode 100644 index 00000000000..fd804beb4c4 --- /dev/null +++ b/examples/gno.land/r/demo/bar20/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/demo/bar20 + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/boards/README.md b/examples/gno.land/r/demo/boards/README.md index 6e3dac115b1..a9b68ec9c92 100644 --- a/examples/gno.land/r/demo/boards/README.md +++ b/examples/gno.land/r/demo/boards/README.md @@ -58,7 +58,7 @@ your `ACCOUNT_ADDR` and `KEYNAME` Instead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps) is to run a local "faucet" and use the web browser to add $GNOT. (This can be done at any time.) -See this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md +See this page: https://github.com/gnolang/gno/blob/master/gno.land/cmd/gnofaucet/README.md ### Start the `gnoland` node. @@ -97,8 +97,7 @@ Interactive documentation: https://test3.gno.land/r/demo/boards?help&__func=Crea Next, query for the permanent board ID by querying (you need this to create a new post): ```bash -./build/gnokey query "vm/qeval" -data "gno.land/r/demo/boards -GetBoardIDFromName(\"BOARDNAME\")" -remote localhost:26657 +./build/gnokey query "vm/qeval" -data 'gno.land/r/demo/boards.GetBoardIDFromName("BOARDNAME")' -remote localhost:26657 ``` ### Create a post of a board with a smart contract call. @@ -120,8 +119,7 @@ Interactive documentation: https://test3.gno.land/r/demo/boards?help&__func=Crea Interactive documentation: https://test3.gno.land/r/demo/boards?help&__func=CreateReply ```bash -./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards -BOARDNAME/1" -remote localhost:26657 +./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:BOARDNAME/1" -remote localhost:26657 ``` ### Render page with optional path expression. @@ -130,8 +128,7 @@ The contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/b the `Render(path string)` function like so: ```bash -./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards -gnolang" +./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:gnolang" ``` ## View the board in the browser. diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 1ef2e72f4c2..1d26126fcb2 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -17,7 +17,9 @@ func GetBoardIDFromName(name string) (BoardID, bool) { } func CreateBoard(name string) BoardID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } bid := incGetBoardID() caller := std.GetOrigCaller() if usernameOf(caller) == "" { @@ -41,7 +43,9 @@ func checkAnonFee() bool { } func CreateThread(bid BoardID, title string, body string) PostID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { @@ -57,7 +61,9 @@ func CreateThread(bid BoardID, title string, body string) PostID { } func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { if !checkAnonFee() { @@ -85,7 +91,9 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { // If dstBoard is private, does not ping back. // If board specified by bid is private, panics. func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() if usernameOf(caller) == "" { // TODO: allow with gDefaultAnonFee payment. @@ -113,7 +121,9 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar } func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() board := getBoard(bid) if board == nil { @@ -143,7 +153,9 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { } func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - std.AssertOriginCall() + if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + panic("invalid non-user call") + } caller := std.GetOrigCaller() board := getBoard(bid) if board == nil { diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index bb98fa289ba..a8b932db892 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -48,83 +48,26 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/boards"] -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000003" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:102" -// } -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101", -// "ModTime": "109", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "ModTime": "123", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "68663c8895d37d479e417c11e21badfe21345c61", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={ // "Fields": [ // { // "T": { @@ -146,19 +89,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" -// } -// } +// "TV": null // } // }, // { @@ -194,13 +131,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3f34ac77289aa1d5f9a2f8b6d083138325816fb0", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={ // "Fields": [ // { // "T": { @@ -237,19 +193,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "94a6665a44bac6ede7f3e3b87173e537b12f9532", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b58581159917d8d7ad0992009d7184fc8ca00fcc", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:101" -// } -// } +// "TV": null // } // }, // { @@ -262,30 +212,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "bc8e5b4e782a0bbc4ac9689681f119beb7b34d59", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fb593e86d35aaf607e0d21e6bd4f84519c44585f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:110" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "9957eadbc91dd32f33b0d815e041a32dbdea0671", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={ // "Fields": [ // { // "T": { @@ -298,13 +261,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={ // "Fields": [ // { // "T": { @@ -317,13 +280,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={ // "Fields": [ // { // "T": { @@ -336,13 +299,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={ // "Fields": [ // { // "N": "AAAAgJSeXbo=", @@ -379,13 +342,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={ // "Fields": [ // { // "T": { @@ -410,13 +373,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", // "RefCount": "1" // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={ // "Fields": [ // { // "T": { @@ -428,19 +391,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Board" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81" -// } -// } +// "TV": null // } // }, // { @@ -487,8 +444,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "130542396d7549d1d516a3ef4a63bb44ef3da06f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" +// "Hash": "f91e355bd19240f0f3350a7fa0e6a82b72225916", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128" // } // }, // { @@ -498,8 +455,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "80acd8746478317194b8546170335c796a4dfb3f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:113" +// "Hash": "9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129" // } // }, // { @@ -509,8 +466,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c1a8f769f3b9d52dd38ac4759116edaca287636f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:114" +// "Hash": "eb768b0140a5fe95f9c58747f0960d647dacfd42", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130" // } // }, // { @@ -540,8 +497,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "1dd77a196db00a4d1a3471539279df79f6ae950e", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115" +// "Hash": "f56a463a97d103e183f1f368bf00a2ce99d4de88", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131" // } // }, // { @@ -551,95 +508,57 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c3a60b602b564d07677a212372f4ac1cae4270fd", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:116" +// "Hash": "bed4afa8ffdbbf775451c947fc68b27a345ce32a", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132" // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", // "IsEscaped": true, // "ModTime": "0", // "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Post" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0651ea376feea18422dbe079f9d7fcbe3dfd32ad", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127" +// } // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000003" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:102" -// } -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={ // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108", -// "ModTime": "117", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120", +// "ModTime": "134", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "dc1f011553dc53e7a846049e08cc77fa35ea6a51", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={ // "Fields": [ // { // "T": { @@ -661,19 +580,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" -// } -// } +// "TV": null // } // }, // { @@ -709,13 +622,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "96b86b4585c7f1075d7794180a5581f72733a7ab", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136" +// } // } // } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={ // "Fields": [ // { // "T": { @@ -752,19 +684,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "32274e1f28fb2b97d67a1262afd362d370de7faa", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6a86bc7763703c8f2b9d286368921159d6db121c", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:108" -// } -// } +// "TV": null // } // }, // { @@ -777,30 +703,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "52faa8a2dfefd4b6b6249eff2f9c123ad455e81d", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:118" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", // "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", // "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81]={ +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5cb875179e86d32c517322af7a323b2a5f3e6cc5", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134" +// } +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={ // "Fields": [ // { // "N": "AQAAAAAAAAA=", @@ -846,8 +785,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "6e8ecb1c773e0e34ba560dcff1b2dee0bc2e5660", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:82" +// "Hash": "1d70015c486ccc9bfb7c425152605fabc78be1dd", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86" // } // }, // { @@ -864,8 +803,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "796da27e11e6d75db3e0e8f9d633f4559f1b1a82", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:83" +// "Hash": "b8ee1687a4f45886912ba624c8513a2a1526b94c", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87" // } // }, // { @@ -875,19 +814,19 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "edb1857302fa916c562cd077cdf2a3626e29ae2b", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" +// "Hash": "af6ed0268f99b7f369329094eb6dfaea7812708b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88" // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:81", -// "IsEscaped": true, -// "ModTime": "108", -// "RefCount": "6" +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", +// "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96]={ +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={ // "Fields": [ // { // "T": { @@ -899,30 +838,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "609e7f519c65f94503427a14f973b4b83989cdc8", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:109" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:96", -// "ModTime": "108", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:95", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", // "RefCount": "1" // } // } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97]={ +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={ // "Fields": [ // { // "T": { @@ -934,26 +867,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6760340f5b40e05221dc530940683b0b9a422503", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:117" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:97", -// "ModTime": "108", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:95", +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", // "RefCount": "1" // } // } diff --git a/examples/gno.land/r/demo/nft/z_0_filetest.gno b/examples/gno.land/r/demo/nft/z_0_filetest.gno index 9e8010abaf5..0595e76a0ed 100644 --- a/examples/gno.land/r/demo/nft/z_0_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_0_filetest.gno @@ -22,7 +22,7 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/nft"] // switchrealm["gno.land/r/demo/nft"] -// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={ +// c[67c479d3d51d4056b2f4111d5352912a00be311e:11]={ // "Fields": [ // { // "T": { @@ -62,13 +62,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:11", // "ModTime": "0", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:10", // "RefCount": "1" // } // } -// c[67c479d3d51d4056b2f4111d5352912a00be311e:7]={ +// c[67c479d3d51d4056b2f4111d5352912a00be311e:10]={ +// "ObjectInfo": { +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:10", +// "ModTime": "0", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:9", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/nft.NFToken" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "564a9e78be869bd258fc3c9ad56f5a75ed68818f", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:11" +// } +// } +// } +// c[67c479d3d51d4056b2f4111d5352912a00be311e:9]={ // "Fields": [ // { // "T": { @@ -90,19 +109,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b53ffc464e1b5655d19b9d5277f3491717c24aca", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/nft.NFToken" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c06f58d0ff2bc26ad3e65e953b127a0d03353e97", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:8" -// } -// } +// "TV": null // } // }, // { @@ -138,13 +151,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:9", // "ModTime": "0", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", // "RefCount": "1" // } // } -// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={ +// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={ +// "ObjectInfo": { +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", +// "ModTime": "0", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b1d928b3716b147c92730e8d234162bec2f0f2fc", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:9" +// } +// } +// } +// u[67c479d3d51d4056b2f4111d5352912a00be311e:6]={ // "Fields": [ // { // "T": { @@ -156,30 +188,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "75850e56fa2c3c8b6d6814b1f150919b75355752", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "45a64533aa57b49b6b4a1d3f6de79db8bea3a710", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", -// "ModTime": "6", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:6", +// "ModTime": "7", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", // "RefCount": "1" // } // } -// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={ +// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={ // "Fields": [ // {}, // { @@ -196,8 +222,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "dad3106a54e1facb92bce473898b8aec0eb930ff", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:5" +// "Hash": "700d932c087f30499941de2b589867dc17aaea5a", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:6" // } // }, // { @@ -207,15 +233,15 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "05ab6746ea84b55ca133806af215d99a1c4b045e", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:6" +// "Hash": "e06585aff551113920c929453ea40250f3cc01bc", +// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:7" // } // } // ], // "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", -// "ModTime": "6", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:2", +// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", +// "ModTime": "7", +// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", // "RefCount": "1" // } // } diff --git a/examples/gno.land/r/gov/dao/dao.gno b/examples/gno.land/r/gov/dao/dao.gno index 37caca9fa19..68f188eb0a4 100644 --- a/examples/gno.land/r/gov/dao/dao.gno +++ b/examples/gno.land/r/gov/dao/dao.gno @@ -2,60 +2,88 @@ package govdao import ( "std" + "strconv" - "gno.land/p/gov/proposal" + "gno.land/p/demo/ufmt" + pproposal "gno.land/p/gov/proposal" ) -var proposals = make([]Proposal, 0) +var proposals = make([]*proposal, 0) // XXX var members ... -// Proposal represents a proposal in the governance system. -type Proposal struct { - author std.Address +type proposal struct { idx int - Comment string - Executor proposal.Executor + author std.Address + comment string + executor pproposal.Executor + // XXX: make "accepted" and "finished" managed by an interface that can have various voting implementations + accepted bool + finished bool +} + +func (p proposal) Status() Status { + if p.executor.Done() { + return Status(p.executor.Status()) + } + if p.accepted { + return Accepted + } + // XXX: timeout + // XXX: not_accepted + return Active } // Propose is designed to be called by another contract or with // `maketx run`, not by a `maketx call`. -func Propose(proposal Proposal) int { +func Propose(comment string, executor pproposal.Executor) int { // XXX: require payment? - // XXX: sanitize proposal + if executor == nil { + panic("missing proposal executor") + } caller := std.PrevRealm().Addr() AssertIsMember(caller) - proposal.author = caller - proposal.idx = len(proposals) - proposals = append(proposals, proposal) - return proposal.idx + + prop := &proposal{ + comment: comment, + executor: executor, + author: caller, + idx: len(proposals), + } + + proposals = append(proposals, prop) + return prop.idx } func VoteOnProposal(idx int, option string) { + assertProposalExists(idx) caller := std.PrevRealm().Addr() AssertIsMember(caller) - panic("not implemented") - // XXX: implement the voting (woudl be cool to have a generic p/) + + prop := getProposal(idx) + if prop.finished { + panic("prop is not active anymore, cannot vote.") + } + // XXX: implement the real voting (would be cool to have a generic p/) + prop.accepted = option == "YES" + prop.finished = true } func ExecuteProposal(idx int) { assertProposalExists(idx) - // XXX: assert voting is finished - // XXX: assert voting result is YES - // XXX: proposal was not already executed - proposal := proposals[idx] - proposal.Executor.Execute() -} - -func assertProposalExists(idx int) { - if idx < 0 || idx >= len(proposals) { - panic("invalid proposal id") + prop := getProposal(idx) + if !prop.finished { + panic("prop is still active, cannot execute.") } + if !prop.accepted { + panic("prop is not accepted, cannot execute.") + } + prop.executor.Execute() } func IsMember(addr std.Address) bool { // XXX: implement - return true + return true // in the meantime, everyone is a DAO member } func AssertIsMember(addr std.Address) { @@ -63,3 +91,47 @@ func AssertIsMember(addr std.Address) { panic("caller is not member of govdao") } } + +func Render(path string) string { + if path == "" { + output := "" + for idx, prop := range proposals { + output += ufmt.Sprintf("- [/r/gov/dao:%d](%d) - %s (by %s)", idx, idx, prop.comment, prop.author) + } + return output + } + + // else display the proposal + idx, err := strconv.Atoi(path) + if err != nil { + return "404" + } + + if !proposalExists(idx) { + return "404" + } + prop := getProposal(idx) + output := "" + output += ufmt.Sprintf("# Prop#%d", prop.idx) + "\n" + output += "\n" + output += prop.comment + output += "\n" + output += ufmt.Sprintf("Status: %s", string(prop.Status())) + output += "\n" + output += ufmt.Sprintf("Author: %s", string(prop.author)) + return output +} + +func getProposal(idx int) *proposal { + return proposals[idx-1] +} + +func proposalExists(idx int) bool { + return idx > 0 && idx <= len(proposals) +} + +func assertProposalExists(idx int) { + if !proposalExists(idx) { + panic("invalid proposal id") + } +} diff --git a/examples/gno.land/r/gov/dao/gno.mod b/examples/gno.land/r/gov/dao/gno.mod index ae296cf121c..0127e84a8e2 100644 --- a/examples/gno.land/r/gov/dao/gno.mod +++ b/examples/gno.land/r/gov/dao/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/gov/dao -require gno.land/p/gov/proposal v0.0.0-latest +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/gov/proposal v0.0.0-latest +) diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno new file mode 100644 index 00000000000..7072618a4a7 --- /dev/null +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -0,0 +1,85 @@ +// Please note that this package is intended for demonstration purposes only. +// You could execute this code (the init part) by running a `maketx run` command +// or by uploading a similar package to a personal namespace. +// +// For the specific case of validators, a `r/gnoland/valopers` will be used to +// organize the lifecycle of validators (register, etc), and this more complex +// contract will be responsible to generate proposals. +package main + +import ( + "std" + + govdao "gno.land/r/gov/dao" + "gno.land/r/sys/validators" +) + +func init() { + // Create the validators change proposal. + changesFn := func() []validators.Change { + return []validators.Change{ + // add a new validator. + {Address: std.Address("g12345678"), Power: 1}, + // remove an existing validator. + {Address: std.Address("g000000000"), Power: 0}, + } + } + + // Wraps changesFn to emit a certified event only if executed from a + // complete governance proposal process. + executor := validators.NewPropExecutor(changesFn) + + // Create a proposal. + // XXX: payment + comment := "manual valset changes proposal example" + govdao.Propose(comment, executor) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("1")) + println("--") + govdao.VoteOnProposal(1, "YES") + println("--") + println(govdao.Render("1")) + println("--") + println(validators.Render("")) + println("--") + govdao.ExecuteProposal(1) + println("--") + println(govdao.Render("1")) + println("--") + println(validators.Render("")) +} + +// Output: +// -- +// - [/r/gov/dao:0](0) - manual valset changes proposal example (by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) +// -- +// # Prop#0 +// +// manual valset changes proposal example +// Status: active +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// -- +// -- +// # Prop#0 +// +// manual valset changes proposal example +// Status: accepted +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// -- +// No valset changes to apply. +// -- +// -- +// # Prop#0 +// +// manual valset changes proposal example +// Status: success +// Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// -- +// Valset changes to apply: +// - g12345678 (1) +// - g000000000 (0) diff --git a/examples/gno.land/r/gov/dao/types.gno b/examples/gno.land/r/gov/dao/types.gno new file mode 100644 index 00000000000..9e4ca0718c9 --- /dev/null +++ b/examples/gno.land/r/gov/dao/types.gno @@ -0,0 +1,10 @@ +package govdao + +// Status enum. +type Status string + +var ( + Accepted Status = "accepted" + Active Status = "active" + // Timeout, NotAccepted +) diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod deleted file mode 100644 index 97faf2aff27..00000000000 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/gov/proposals/prop1 - -require ( - gno.land/r/gov/dao v0.0.0-latest - gno.land/r/sys/validators v0.0.0-latest -) diff --git a/examples/gno.land/r/gov/proposals/prop1/prop1.gno b/examples/gno.land/r/gov/proposals/prop1/prop1.gno deleted file mode 100644 index 6e5edb0c97b..00000000000 --- a/examples/gno.land/r/gov/proposals/prop1/prop1.gno +++ /dev/null @@ -1,39 +0,0 @@ -// Package prop1 is an example of proposal creation using a contract. -// -// Please note that this package is intended for demonstration purposes only. -// You could execute this code by running a `maketx run` command or by uploading -// a similar package to a personal namespace. -// -// For the specific case of validators, a `r/gnoland/valopers` will be used to -// organize the lifecycle of validators (register, etc), and this more complex -// contract will be responsible to generate proposals. -package prop1 - -import ( - "std" - - govdao "gno.land/r/gov/dao" - "gno.land/r/sys/validators" -) - -func init() { - // Create the validators change proposal. - changesFn := func() []validators.Change { - return []validators.Change{ - {Address: std.Address("g12345678"), Power: 1}, // add a new validator - {Address: std.Address("g000000000"), Power: 0}, // remove an existing validator - } - } - - // Wraps changesFn to emit a certified event only if executed from a - // complete governance proposal process. - executor := validators.NewProposalExecutor(changesFn) - - // Create a proposal. - // XXX: payment - proposal := govdao.Proposal{ - Comment: "manual valset changes proposal example", - Executor: executor, - } - govdao.Propose(proposal) -} diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index 0e6e132f6e9..669b688727a 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -3,6 +3,7 @@ package validators import ( "std" + "strconv" "gno.land/p/gov/proposal" ) @@ -15,10 +16,10 @@ type Change struct { Power int } -// NewProposalExecutor creates a new executor that wraps a changes closure +// NewPropExecutor creates a new executor that wraps a changes closure // proposal. It emits a typed object (subscribed by tm2) only if it passes // through a complete p/gov/proposal process. -func NewProposalExecutor(changesFn func() []Change) proposal.Executor { +func NewPropExecutor(changesFn func() []Change) proposal.Executor { if changesFn == nil { panic("changesFn should not be nil") } @@ -45,3 +46,15 @@ func getAndResetChanges() []Change { unappliedChanges = []Change{} return cpy } + +func Render(_ string) string { + if len(unappliedChanges) == 0 { + return "No valset changes to apply." + } + + output := "Valset changes to apply:\n" + for _, change := range unappliedChanges { + output += "- " + string(change.Address) + " (" + strconv.Itoa(change.Power) + ")\n" + } + return output +} diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index e6f705c46b9..9804aecc7f1 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -1,3 +1,5 @@ +// Draft + module gno.land/r/x/manfred_outfmt require ( diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno index 7d07337a17d..03ffe876519 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2/v2.gno @@ -1,4 +1,4 @@ -package v1 +package v2 import "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root" diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno index a31219cd4d0..22e43cf7bdd 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/v2.gno @@ -1,4 +1,4 @@ -package v1 +package v2 import ( "gno.land/p/demo/avl" diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno index 961c7a13208..f884b5278ab 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2/z_filetest.gno @@ -1,17 +1,15 @@ package main import ( - "fmt" - "gno.land/r/x/manfred_upgrade_patterns/upgrade_d/v2" ) func main() { - println("a", v1.Get("a")) - println("b", v1.Get("b")) - println("c", v1.Get("c")) - println("d", v1.Get("d")) - println("e", v1.Get("e")) + println("a", v2.Get("a")) + println("b", v2.Get("b")) + println("c", v2.Get("c")) + println("d", v2.Get("d")) + println("e", v2.Get("e")) } // Output: diff --git a/gno.land/cmd/gnoland/config.go b/gno.land/cmd/gnoland/config.go index eed553901f4..fca72add21e 100644 --- a/gno.land/cmd/gnoland/config.go +++ b/gno.land/cmd/gnoland/config.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "fmt" "path/filepath" @@ -58,6 +59,62 @@ func constructConfigPath(nodeDir string) string { ) } +// printKeyValue searches and prints the given key value in JSON +func printKeyValue[T *secrets | *config.Config]( + input T, + raw bool, + io commands.IO, + key ...string, +) error { + // prepareOutput prepares the JSON output, taking into account raw mode + prepareOutput := func(input any) (string, error) { + encoded, err := json.MarshalIndent(input, "", " ") + if err != nil { + return "", fmt.Errorf("unable to marshal JSON, %w", err) + } + + output := string(encoded) + + if raw { + if err := json.Unmarshal(encoded, &output); err != nil { + return "", fmt.Errorf("unable to unmarshal raw JSON, %w", err) + } + } + + return output, nil + } + + if len(key) == 0 { + // Print the entire input + output, err := prepareOutput(input) + if err != nil { + return err + } + + io.Println(output) + + return nil + } + + // Get the value using reflect + secretValue := reflect.ValueOf(input).Elem() + + // Get the value path, with sections separated out by a period + field, err := getFieldAtPath(secretValue, strings.Split(key[0], ".")) + if err != nil { + return err + } + + output, err := prepareOutput(field.Interface()) + if err != nil { + return err + } + + io.Println(output) + + return nil +} + // getFieldAtPath fetches the given field from the given path func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value, error) { // Look at the current section, and figure out if diff --git a/gno.land/cmd/gnoland/config_get.go b/gno.land/cmd/gnoland/config_get.go index 33a98608b85..1fd4027ec60 100644 --- a/gno.land/cmd/gnoland/config_get.go +++ b/gno.land/cmd/gnoland/config_get.go @@ -3,9 +3,8 @@ package main import ( "context" "errors" + "flag" "fmt" - "reflect" - "strings" "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" @@ -13,14 +12,20 @@ import ( var errInvalidConfigGetArgs = errors.New("invalid number of config get arguments provided") +type configGetCfg struct { + configCfg + + raw bool +} + // newConfigGetCmd creates the config get command func newConfigGetCmd(io commands.IO) *commands.Command { - cfg := &configCfg{} + cfg := &configGetCfg{} cmd := commands.NewCommand( commands.Metadata{ Name: "get", - ShortUsage: "config get ", + ShortUsage: "config get [flags] []", ShortHelp: "shows the Gno node configuration", LongHelp: "Shows the Gno node configuration at the given path " + "by fetching the option specified at ", @@ -34,40 +39,33 @@ func newConfigGetCmd(io commands.IO) *commands.Command { return cmd } -func execConfigGet(cfg *configCfg, io commands.IO, args []string) error { +func (c *configGetCfg) RegisterFlags(fs *flag.FlagSet) { + c.configCfg.RegisterFlags(fs) + + fs.BoolVar( + &c.raw, + "raw", + false, + "output raw string values, rather than as JSON strings", + ) +} + +func execConfigGet(cfg *configGetCfg, io commands.IO, args []string) error { // Load the config loadedCfg, err := config.LoadConfigFile(cfg.configPath) if err != nil { return fmt.Errorf("%s, %w", tryConfigInit, err) } - // Make sure the edit arguments are valid - if len(args) != 1 { + // Make sure the get arguments are valid + if len(args) > 1 { return errInvalidConfigGetArgs } // Find and print the config field, if any - if err := printConfigField(loadedCfg, args[0], io); err != nil { - return fmt.Errorf("unable to update config field, %w", err) - } - - return nil -} - -// printConfigField prints the value of the field at the given path -func printConfigField(config *config.Config, key string, io commands.IO) error { - // Get the config value using reflect - configValue := reflect.ValueOf(config).Elem() - - // Get the value path, with sections separated out by a period - path := strings.Split(key, ".") - - field, err := getFieldAtPath(configValue, path) - if err != nil { - return err + if err := printKeyValue(loadedCfg, cfg.raw, io, args...); err != nil { + return fmt.Errorf("unable to get config field, %w", err) } - io.Printf("%v", field.Interface()) - return nil } diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index 0639e3be60f..f2ddc5ca6d0 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -3,11 +3,13 @@ package main import ( "bytes" "context" - "fmt" - "strconv" + "encoding/json" + "strings" "testing" + "time" "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,7 +36,8 @@ func TestConfig_Get_Invalid(t *testing.T) { type testGetCase struct { name string field string - verifyFn func(*config.Config, string) + verifyFn func(*config.Config, []byte) + isRaw bool } // verifyGetTestTableCommon is the common test table @@ -57,6 +60,10 @@ func verifyGetTestTableCommon(t *testing.T, testTable []testGetCase) { path, } + if testCase.isRaw { + args = append(args, "--raw") + } + // Create the command IO mockOut := new(bytes.Buffer) @@ -75,11 +82,25 @@ func verifyGetTestTableCommon(t *testing.T, testTable []testGetCase) { loadedCfg, err := config.LoadConfigFile(path) require.NoError(t, err) - testCase.verifyFn(loadedCfg, mockOut.String()) + testCase.verifyFn(loadedCfg, mockOut.Bytes()) }) } } +func unmarshalJSONCommon[T any](t *testing.T, input []byte) T { + t.Helper() + + var output T + + require.NoError(t, json.Unmarshal(input, &output)) + + return output +} + +func escapeNewline(value []byte) string { + return strings.ReplaceAll(string(value), "\n", "") +} + func TestConfig_Get_Base(t *testing.T) { t.Parallel() @@ -87,99 +108,194 @@ func TestConfig_Get_Base(t *testing.T) { { "root dir fetched", "home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.RootDir, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir fetched, raw", + "home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RootDir, escapeNewline(value)) }, + true, }, { "proxy app fetched", "proxy_app", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ProxyApp, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProxyApp, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "proxy app fetched, raw", + "proxy_app", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProxyApp, escapeNewline(value)) + }, + true, }, { "moniker fetched", "moniker", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.Moniker, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Moniker, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "moniker fetched, raw", + "moniker", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Moniker, escapeNewline(value)) }, + true, }, { "fast sync mode fetched", "fast_sync", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, loadedCfg.FastSyncMode, boolVal) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.FastSyncMode, unmarshalJSONCommon[bool](t, value)) }, + false, }, { "db backend fetched", "db_backend", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.DBBackend, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBBackend, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "db backend fetched, raw", + "db_backend", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBBackend, escapeNewline(value)) }, + true, }, { "db path fetched", "db_dir", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.DBPath, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "db path fetched, raw", + "db_dir", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.DBPath, escapeNewline(value)) }, + true, }, { "validator key fetched", "priv_validator_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorKey, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorKey, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "validator key fetched, raw", + "priv_validator_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorKey, escapeNewline(value)) }, + true, }, { "validator state file fetched", "priv_validator_state_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorState, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorState, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "validator state file fetched, raw", + "priv_validator_state_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorState, escapeNewline(value)) + }, + true, }, { "validator listen addr fetched", "priv_validator_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.PrivValidatorListenAddr, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorListenAddr, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "validator listen addr fetched, raw", + "priv_validator_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.PrivValidatorListenAddr, escapeNewline(value)) + }, + true, }, { "node key path fetched", "node_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.NodeKey, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.NodeKey, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "node key path fetched, raw", + "node_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.NodeKey, escapeNewline(value)) }, + true, }, { "abci fetched", "abci", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ABCI, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ABCI, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "abci fetched, raw", + "abci", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ABCI, escapeNewline(value)) + }, + true, }, { "profiling listen address fetched", "prof_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, loadedCfg.ProfListenAddress, value) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProfListenAddress, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "profiling listen address fetched, raw", + "prof_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.ProfListenAddress, escapeNewline(value)) }, + true, }, { "filter peers flag fetched", "filter_peers", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, loadedCfg.FilterPeers, boolVal) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.FilterPeers, unmarshalJSONCommon[bool](t, value)) }, + false, }, } @@ -193,105 +309,178 @@ func TestConfig_Get_Consensus(t *testing.T) { { "root dir updated", "consensus.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir updated, raw", + "consensus.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.RootDir, escapeNewline(value)) }, + true, }, { "WAL path updated", "consensus.wal_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.WALPath) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.WALPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "WAL path updated, raw", + "consensus.wal_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Consensus.WALPath, escapeNewline(value)) }, + true, }, { "propose timeout updated", "consensus.timeout_propose", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPropose.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPropose, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "propose timeout delta updated", "consensus.timeout_propose_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutProposeDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutProposeDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "prevote timeout updated", "consensus.timeout_prevote", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrevote.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrevote, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "prevote timeout delta updated", "consensus.timeout_prevote_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrevoteDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrevoteDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "precommit timeout updated", "consensus.timeout_precommit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrecommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrecommit, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "precommit timeout delta updated", "consensus.timeout_precommit_delta", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutPrecommitDelta.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutPrecommitDelta, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "commit timeout updated", "consensus.timeout_commit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.TimeoutCommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.TimeoutCommit, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "skip commit timeout toggle updated", "consensus.skip_timeout_commit", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.Consensus.SkipTimeoutCommit) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.SkipTimeoutCommit, + unmarshalJSONCommon[bool](t, value), + ) }, + false, }, { "create empty blocks toggle updated", "consensus.create_empty_blocks", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - assert.Equal(t, boolVal, loadedCfg.Consensus.CreateEmptyBlocks) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.CreateEmptyBlocks, + unmarshalJSONCommon[bool](t, value), + ) }, + false, }, { "create empty blocks interval updated", "consensus.create_empty_blocks_interval", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.CreateEmptyBlocksInterval.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.CreateEmptyBlocksInterval, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "peer gossip sleep duration updated", "consensus.peer_gossip_sleep_duration", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.PeerGossipSleepDuration.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.PeerGossipSleepDuration, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, { "peer query majority sleep duration updated", "consensus.peer_query_maj23_sleep_duration", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Consensus.PeerQueryMaj23SleepDuration.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.Consensus.PeerQueryMaj23SleepDuration, + unmarshalJSONCommon[time.Duration](t, value), + ) }, + false, }, } @@ -303,18 +492,40 @@ func TestConfig_Get_Events(t *testing.T) { testTable := []testGetCase{ { - "event store type updated", + "event store type", + "tx_event_store.event_store_type", + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.EventStoreType, + unmarshalJSONCommon[string](t, value), + ) + }, + false, + }, + { + "event store type, raw", "tx_event_store.event_store_type", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.TxEventStore.EventStoreType) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.EventStoreType, + escapeNewline(value), + ) }, + true, }, { - "event store params updated", + "event store params", "tx_event_store.event_store_params", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.TxEventStore.Params)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal( + t, + loadedCfg.TxEventStore.Params, + unmarshalJSONCommon[types.EventStoreParams](t, value), + ) }, + false, }, } @@ -326,142 +537,196 @@ func TestConfig_Get_P2P(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", "p2p.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RootDir, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "listen address updated", + "root dir, raw", + "p2p.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RootDir, escapeNewline(value)) + }, + true, + }, + { + "listen address", "p2p.laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.ListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "external address updated", + "listen address, raw", + "p2p.laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ListenAddress, escapeNewline(value)) + }, + true, + }, + { + "external address", + "p2p.external_address", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ExternalAddress, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "external address, raw", "p2p.external_address", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.ExternalAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.ExternalAddress, escapeNewline(value)) }, + true, }, { - "seeds updated", + "seeds", "p2p.seeds", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.Seeds) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.Seeds, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "persistent peers updated", + "seeds, raw", + "p2p.seeds", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.Seeds, escapeNewline(value)) + }, + true, + }, + { + "persistent peers", + "p2p.persistent_peers", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PersistentPeers, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "persistent peers, raw", "p2p.persistent_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.PersistentPeers) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PersistentPeers, escapeNewline(value)) }, + true, }, { - "upnp toggle updated", + "upnp toggle", "p2p.upnp", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.UPNP) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.UPNP, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "max inbound peers updated", + "max inbound peers", "p2p.max_num_inbound_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxNumInboundPeers)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "max outbound peers updated", + "max outbound peers", "p2p.max_num_outbound_peers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxNumOutboundPeers)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "flush throttle timeout updated", + "flush throttle timeout", "p2p.flush_throttle_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.FlushThrottleTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.FlushThrottleTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "max package payload size updated", + "max package payload size", "p2p.max_packet_msg_payload_size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.MaxPacketMsgPayloadSize)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.MaxPacketMsgPayloadSize, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "send rate updated", + "send rate", "p2p.send_rate", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.SendRate)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.SendRate, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "receive rate updated", + "receive rate", "p2p.recv_rate", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.P2P.RecvRate)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.RecvRate, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "pex reactor toggle updated", + "pex reactor toggle", "p2p.pex", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.PexReactor) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PexReactor, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "seed mode updated", + "seed mode", "p2p.seed_mode", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.SeedMode) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.SeedMode, unmarshalJSONCommon[bool](t, value)) + }, + false, + }, + { + "private peer IDs", + "p2p.private_peer_ids", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PrivatePeerIDs, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "private peer IDs updated", + "private peer IDs, raw", "p2p.private_peer_ids", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.PrivatePeerIDs) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.PrivatePeerIDs, escapeNewline(value)) }, + true, }, { - "allow duplicate IP updated", + "allow duplicate IP", "p2p.allow_duplicate_ip", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.AllowDuplicateIP) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.AllowDuplicateIP, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "handshake timeout updated", + "handshake timeout", "p2p.handshake_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.HandshakeTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.HandshakeTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "dial timeout updated", + "dial timeout", "p2p.dial_timeout", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.DialTimeout.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.P2P.DialTimeout, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, } @@ -473,105 +738,156 @@ func TestConfig_Get_RPC(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", "rpc.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.RootDir, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "listen address updated", + "root dir, raw", + "rpc.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.RootDir, escapeNewline(value)) + }, + true, + }, + { + "listen address", "rpc.laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.ListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.ListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "CORS Allowed Origins updated", + "listen address, raw", + "rpc.laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.ListenAddress, escapeNewline(value)) + }, + true, + }, + { + "CORS Allowed Origins", "rpc.cors_allowed_origins", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedOrigins)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedOrigins, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "CORS Allowed Methods updated", + "CORS Allowed Methods", "rpc.cors_allowed_methods", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedMethods)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedMethods, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "CORS Allowed Headers updated", + "CORS Allowed Headers", "rpc.cors_allowed_headers", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%v", loadedCfg.RPC.CORSAllowedHeaders)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.CORSAllowedHeaders, unmarshalJSONCommon[[]string](t, value)) }, + false, }, { - "GRPC listen address updated", + "GRPC listen address", "rpc.grpc_laddr", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.GRPCListenAddress) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCListenAddress, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "GRPC max open connections updated", + "GRPC listen address, raw", + "rpc.grpc_laddr", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCListenAddress, escapeNewline(value)) + }, + true, + }, + { + "GRPC max open connections", "rpc.grpc_max_open_connections", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.GRPCMaxOpenConnections)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.GRPCMaxOpenConnections, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "unsafe value updated", + "unsafe value", "rpc.unsafe", - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.RPC.Unsafe) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.Unsafe, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "rpc max open connections updated", + "rpc max open connections", "rpc.max_open_connections", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxOpenConnections)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxOpenConnections, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "tx commit broadcast timeout updated", + "tx commit broadcast timeout", "rpc.timeout_broadcast_tx_commit", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TimeoutBroadcastTxCommit.String()) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TimeoutBroadcastTxCommit, unmarshalJSONCommon[time.Duration](t, value)) }, + false, }, { - "max body bytes updated", + "max body bytes", "rpc.max_body_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxBodyBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxBodyBytes, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "max header bytes updated", + "max header bytes", "rpc.max_header_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.RPC.MaxHeaderBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.MaxHeaderBytes, unmarshalJSONCommon[int](t, value)) + }, + false, + }, + { + "TLS cert file", + "rpc.tls_cert_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSCertFile, unmarshalJSONCommon[string](t, value)) }, + false, }, { - "TLS cert file updated", + "TLS cert file, raw", "rpc.tls_cert_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TLSCertFile) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSCertFile, escapeNewline(value)) }, + true, }, { - "TLS key file updated", + "TLS key file", "rpc.tls_key_file", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.RPC.TLSKeyFile) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSKeyFile, unmarshalJSONCommon[string](t, value)) }, + false, + }, + { + "TLS key file, raw", + "rpc.tls_key_file", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.RPC.TLSKeyFile, escapeNewline(value)) + }, + true, }, } @@ -583,59 +899,76 @@ func TestConfig_Get_Mempool(t *testing.T) { testTable := []testGetCase{ { - "root dir updated", + "root dir", + "mempool.home", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.RootDir, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "root dir, raw", "mempool.home", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Mempool.RootDir) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.RootDir, escapeNewline(value)) }, + true, }, { - "recheck flag updated", + "recheck flag", "mempool.recheck", - func(loadedCfg *config.Config, value string) { - boolVar, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVar, loadedCfg.Mempool.Recheck) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Recheck, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "broadcast flag updated", + "broadcast flag", "mempool.broadcast", - func(loadedCfg *config.Config, value string) { - boolVar, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVar, loadedCfg.Mempool.Broadcast) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Broadcast, unmarshalJSONCommon[bool](t, value)) }, + false, }, { - "WAL path updated", + "WAL path", + "mempool.wal_dir", + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.WalPath, unmarshalJSONCommon[string](t, value)) + }, + false, + }, + { + "WAL path, raw", "mempool.wal_dir", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.Mempool.WalPath) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.WalPath, escapeNewline(value)) }, + true, }, { - "size updated", + "size", "mempool.size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.Size)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.Size, unmarshalJSONCommon[int](t, value)) }, + false, }, { - "max pending txs bytes updated", + "max pending txs bytes", "mempool.max_pending_txs_bytes", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.MaxPendingTxsBytes)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.MaxPendingTxsBytes, unmarshalJSONCommon[int64](t, value)) }, + false, }, { - "cache size updated", + "cache size", "mempool.cache_size", - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, fmt.Sprintf("%d", loadedCfg.Mempool.CacheSize)) + func(loadedCfg *config.Config, value []byte) { + assert.Equal(t, loadedCfg.Mempool.CacheSize, unmarshalJSONCommon[int](t, value)) }, + false, }, } diff --git a/gno.land/cmd/gnoland/secrets.go b/gno.land/cmd/gnoland/secrets.go index fda2a664c28..6fbce52c638 100644 --- a/gno.land/cmd/gnoland/secrets.go +++ b/gno.land/cmd/gnoland/secrets.go @@ -21,9 +21,9 @@ const ( ) const ( - nodeKeyKey = "NodeKey" - validatorPrivateKeyKey = "ValidatorPrivateKey" - validatorStateKey = "ValidatorState" + nodeIDKey = "node_id" + validatorPrivateKeyKey = "validator_key" + validatorStateKey = "validator_state" ) // newSecretsCmd creates the secrets root command @@ -72,3 +72,33 @@ func constructSecretsPath(nodeDir string) string { config.DefaultSecretsDir, ) } + +type ( + secrets struct { + ValidatorKeyInfo *validatorKeyInfo `json:"validator_key,omitempty" toml:"validator_key" comment:"the validator private key info"` + ValidatorStateInfo *validatorStateInfo `json:"validator_state,omitempty" toml:"validator_state" comment:"the last signed validator state info"` + NodeIDInfo *nodeIDInfo `json:"node_id,omitempty" toml:"node_id" comment:"the derived node ID info"` + } + + // NOTE: keep in sync with tm2/pkg/bft/privval/file.go + validatorKeyInfo struct { + Address string `json:"address" toml:"address" comment:"the validator address"` + PubKey string `json:"pub_key" toml:"pub_key" comment:"the validator public key"` + } + + // NOTE: keep in sync with tm2/pkg/bft/privval/file.go + validatorStateInfo struct { + Height int64 `json:"height" toml:"height" comment:"the height of the last sign"` + Round int `json:"round" toml:"round" comment:"the round of the last sign"` + Step int8 `json:"step" toml:"step" comment:"the step of the last sign"` + + Signature []byte `json:"signature,omitempty" toml:"signature,omitempty" comment:"the signature of the last sign"` + SignBytes []byte `json:"sign_bytes,omitempty" toml:"sign_bytes,omitempty" comment:"the raw signature bytes of the last sign"` + } + + // NOTE: keep in sync with tm2/pkg/p2p/key.go + nodeIDInfo struct { + ID string `json:"id" toml:"id" comment:"the node ID derived from the private key"` + P2PAddress string `json:"p2p_address" toml:"p2p_address" comment:"the node's constructed P2P address'"` + } +) diff --git a/gno.land/cmd/gnoland/secrets_common.go b/gno.land/cmd/gnoland/secrets_common.go index 588307b9b8e..d40e90f6b48 100644 --- a/gno.land/cmd/gnoland/secrets_common.go +++ b/gno.land/cmd/gnoland/secrets_common.go @@ -168,14 +168,14 @@ func verifySecretsKey(args []string) error { // Verify the set key key := args[0] - if key != nodeKeyKey && + if key != nodeIDKey && key != validatorPrivateKeyKey && key != validatorStateKey { return fmt.Errorf( "invalid secrets key value [%s, %s, %s]", validatorPrivateKeyKey, validatorStateKey, - nodeKeyKey, + nodeIDKey, ) } @@ -187,7 +187,7 @@ func getAvailableSecretsKeys() string { return fmt.Sprintf( "[%s, %s, %s]", validatorPrivateKeyKey, - nodeKeyKey, + nodeIDKey, validatorStateKey, ) } diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 699450702b4..47de7a46283 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -2,18 +2,25 @@ package main import ( "context" + "errors" "flag" "fmt" "path/filepath" - "text/tabwriter" + "strings" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" + osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/p2p" ) +var errInvalidSecretsGetArgs = errors.New("invalid number of secrets get arguments provided") + type secretsGetCfg struct { commonAllCfg + + raw bool } // newSecretsGetCmd creates the secrets get command @@ -24,12 +31,9 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "get", ShortUsage: "secrets get [flags] []", - ShortHelp: "shows all Gno secrets present in a common directory", - LongHelp: fmt.Sprintf( - "shows the validator private key, the node p2p key and the validator's last sign state. "+ - "If a key is provided, it shows the specified key value. Available keys: %s", - getAvailableSecretsKeys(), - ), + ShortHelp: "shows the Gno secrets present in a common directory", + LongHelp: "shows the validator private key, the node p2p key and the validator's last sign state at the given path " + + "by fetching the option specified at ", }, cfg, func(_ context.Context, args []string) error { @@ -42,6 +46,13 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { func (c *secretsGetCfg) RegisterFlags(fs *flag.FlagSet) { c.commonAllCfg.RegisterFlags(fs) + + fs.BoolVar( + &c.raw, + "raw", + false, + "output raw string values, rather than as JSON strings", + ) } func execSecretsGet(cfg *secretsGetCfg, args []string, io commands.IO) error { @@ -50,154 +61,144 @@ func execSecretsGet(cfg *secretsGetCfg, args []string, io commands.IO) error { return errInvalidDataDir } - // Verify the secrets key - if err := verifySecretsKey(args); err != nil { - return err + // Make sure the get arguments are valid + if len(args) > 1 { + return errInvalidSecretsGetArgs } - var key string + // Load the secrets from the dir + loadedSecrets, err := loadSecrets(cfg.dataDir) + if err != nil { + return err + } - if len(args) > 0 { - key = args[0] + // Find and print the secrets value, if any + if err := printKeyValue(loadedSecrets, cfg.raw, io, args...); err != nil { + return fmt.Errorf("unable to get secrets value, %w", err) } - // Construct the paths + return nil +} + +// loadSecrets loads the secrets from the specified data directory +func loadSecrets(dirPath string) (*secrets, error) { + // Construct the file paths var ( - validatorKeyPath = filepath.Join(cfg.dataDir, defaultValidatorKeyName) - validatorStatePath = filepath.Join(cfg.dataDir, defaultValidatorStateName) - nodeKeyPath = filepath.Join(cfg.dataDir, defaultNodeKeyName) + validatorKeyPath = filepath.Join(dirPath, defaultValidatorKeyName) + validatorStatePath = filepath.Join(dirPath, defaultValidatorStateName) + nodeKeyPath = filepath.Join(dirPath, defaultNodeKeyName) ) - switch key { - case validatorPrivateKeyKey: - // Show the validator's key info - return readAndShowValidatorKey(validatorKeyPath, io) - case validatorStateKey: - // Show the validator's last sign state - return readAndShowValidatorState(validatorStatePath, io) - case nodeKeyKey: - // Show the node's p2p info - return readAndShowNodeKey(nodeKeyPath, io) - default: - // Show the node's p2p info - if err := readAndShowNodeKey(nodeKeyPath, io); err != nil { - return err - } + var ( + vkInfo *validatorKeyInfo + vsInfo *validatorStateInfo + niInfo *nodeIDInfo - // Show the validator's key info - if err := readAndShowValidatorKey(validatorKeyPath, io); err != nil { - return err - } + err error + ) - // Show the validator's last sign state - return readAndShowValidatorState(validatorStatePath, io) + // Load the secrets + if osm.FileExists(validatorKeyPath) { + vkInfo, err = readValidatorKey(validatorKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } -} -// readAndShowValidatorKey reads and shows the validator key from the given path -func readAndShowValidatorKey(path string, io commands.IO) error { - validatorKey, err := readSecretData[privval.FilePVKey](path) - if err != nil { - return fmt.Errorf("unable to read validator key, %w", err) + if osm.FileExists(validatorStatePath) { + vsInfo, err = readValidatorState(validatorStatePath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Validator Key Info]\n\n"); err != nil { - return err + if osm.FileExists(nodeKeyPath) { + niInfo, err = readNodeID(nodeKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to load secrets, %w", err) + } } - if _, err := fmt.Fprintf(w, "Address:\t%s\n", validatorKey.Address.String()); err != nil { - return err - } + return &secrets{ + ValidatorKeyInfo: vkInfo, + ValidatorStateInfo: vsInfo, + NodeIDInfo: niInfo, + }, nil +} - if _, err := fmt.Fprintf(w, "Public Key:\t%s\n", validatorKey.PubKey.String()); err != nil { - return err +// readValidatorKey reads the validator key from the given path +func readValidatorKey(path string) (*validatorKeyInfo, error) { + validatorKey, err := readSecretData[privval.FilePVKey](path) + if err != nil { + return nil, fmt.Errorf("unable to read validator key, %w", err) } - return w.Flush() + return &validatorKeyInfo{ + Address: validatorKey.Address.String(), + PubKey: validatorKey.PubKey.String(), + }, nil } -// readAndShowValidatorState reads and shows the validator state from the given path -func readAndShowValidatorState(path string, io commands.IO) error { +// readValidatorState reads the validator state from the given path +func readValidatorState(path string) (*validatorStateInfo, error) { validatorState, err := readSecretData[privval.FilePVLastSignState](path) if err != nil { - return fmt.Errorf("unable to read validator state, %w", err) + return nil, fmt.Errorf("unable to read validator state, %w", err) } - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Last Validator Sign State Info]\n\n"); err != nil { - return err - } - - if _, err := fmt.Fprintf( - w, - "Height:\t%d\n", - validatorState.Height, - ); err != nil { - return err - } + return &validatorStateInfo{ + Height: validatorState.Height, + Round: validatorState.Round, + Step: validatorState.Step, + Signature: validatorState.Signature, + SignBytes: validatorState.SignBytes, + }, nil +} - if _, err := fmt.Fprintf( - w, - "Round:\t%d\n", - validatorState.Round, - ); err != nil { - return err +// readNodeID reads the node p2p info from the given path +func readNodeID(path string) (*nodeIDInfo, error) { + nodeKey, err := readSecretData[p2p.NodeKey](path) + if err != nil { + return nil, fmt.Errorf("unable to read node key, %w", err) } - if _, err := fmt.Fprintf( - w, - "Step:\t%d\n", - validatorState.Step, - ); err != nil { - return err - } + // Construct the config path + var ( + nodeDir = filepath.Join(filepath.Dir(path), "..") + configPath = constructConfigPath(nodeDir) - if validatorState.Signature != nil { - if _, err := fmt.Fprintf( - w, - "Signature:\t%X\n", - validatorState.Signature, - ); err != nil { - return err - } - } + cfg = config.DefaultConfig() + ) - if validatorState.SignBytes != nil { - if _, err := fmt.Fprintf( - w, - "Sign Bytes:\t%X\n", - validatorState.SignBytes, - ); err != nil { - return err + // Check if there is an existing config file + if osm.FileExists(configPath) { + // Attempt to grab the config from disk + cfg, err = config.LoadConfig(nodeDir) + if err != nil { + return nil, fmt.Errorf("unable to load config file, %w", err) } } - return w.Flush() + return &nodeIDInfo{ + ID: nodeKey.ID().String(), + P2PAddress: constructP2PAddress(nodeKey.ID(), cfg.P2P.ListenAddress), + }, nil } -// readAndShowNodeKey reads and shows the node p2p key from the given path -func readAndShowNodeKey(path string, io commands.IO) error { - nodeKey, err := readSecretData[p2p.NodeKey](path) - if err != nil { - return fmt.Errorf("unable to read node key, %w", err) - } - - w := tabwriter.NewWriter(io.Out(), 0, 0, 2, ' ', 0) - - if _, err := fmt.Fprintf(w, "[Node P2P Info]\n\n"); err != nil { - return err - } +// constructP2PAddress constructs the P2P address other nodes can use +// to connect directly +func constructP2PAddress(nodeID p2p.ID, listenAddress string) string { + var ( + address string + parts = strings.SplitN(listenAddress, "://", 2) + ) - if _, err := fmt.Fprintf( - w, - "Node ID:\t%s\n", - nodeKey.ID(), - ); err != nil { - return err + switch len(parts) { + case 2: + address = parts[1] + default: + address = listenAddress } - return w.Flush() + return fmt.Sprintf("%s@%s", nodeID, address) } diff --git a/gno.land/cmd/gnoland/secrets_get_test.go b/gno.land/cmd/gnoland/secrets_get_test.go index 20f1eb2ef35..66e6e3509fc 100644 --- a/gno.land/cmd/gnoland/secrets_get_test.go +++ b/gno.land/cmd/gnoland/secrets_get_test.go @@ -3,11 +3,14 @@ package main import ( "bytes" "context" + "encoding/json" "fmt" + "os" "path/filepath" "strconv" "testing" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/p2p" @@ -95,6 +98,13 @@ func TestSecrets_Get_All(t *testing.T) { nodeKey.ID().String(), ) + // Make sure the node p2p address is displayed + assert.Contains( + t, + output, + constructP2PAddress(nodeKey.ID(), config.DefaultConfig().P2P.ListenAddress), + ) + // Make sure the private key info is displayed assert.Contains( t, @@ -142,10 +152,10 @@ func TestSecrets_Get_All(t *testing.T) { }) } -func TestSecrets_Get_Single(t *testing.T) { +func TestSecrets_Get_ValidatorKeyInfo(t *testing.T) { t.Parallel() - t.Run("validator key shown", func(t *testing.T) { + t.Run("validator key info", func(t *testing.T) { t.Parallel() dirPath := t.TempDir() @@ -172,23 +182,175 @@ func TestSecrets_Get_Single(t *testing.T) { // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var vk validatorKeyInfo + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &vk)) // Make sure the private key info is displayed - assert.Contains( + assert.Equal( t, - output, validKey.Address.String(), + vk.Address, ) - assert.Contains( + assert.Equal( t, - output, validKey.PubKey.String(), + vk.PubKey, ) }) - t.Run("validator state shown", func(t *testing.T) { + t.Run("validator key address", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "address"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var address string + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &address)) + + assert.Equal( + t, + validKey.Address.String(), + address, + ) + }) + + t.Run("validator key address, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "address"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + validKey.Address.String(), + escapeNewline(mockOutput.Bytes()), + ) + }) + + t.Run("validator key pubkey", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "pub_key"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var address string + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &address)) + + assert.Equal( + t, + validKey.PubKey.String(), + address, + ) + }) + + t.Run("validator key pubkey, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + keyPath := filepath.Join(dirPath, defaultValidatorKeyName) + + validKey := generateValidatorPrivateKey() + + require.NoError(t, saveSecretData(validKey, keyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorPrivateKeyKey, "pub_key"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + validKey.PubKey.String(), + escapeNewline(mockOutput.Bytes()), + ) + }) +} + +func TestSecrets_Get_ValidatorStateInfo(t *testing.T) { + t.Parallel() + + t.Run("validator state info", func(t *testing.T) { t.Parallel() dirPath := t.TempDir() @@ -215,31 +377,141 @@ func TestSecrets_Get_Single(t *testing.T) { // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var vs validatorStateInfo + + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &vs)) // Make sure the state info is displayed - assert.Contains( + assert.Equal( t, - output, - fmt.Sprintf("%d", validState.Step), + validState.Step, + vs.Step, ) - assert.Contains( + assert.Equal( t, - output, - fmt.Sprintf("%d", validState.Height), + validState.Height, + vs.Height, ) - assert.Contains( + assert.Equal( t, - output, - strconv.Itoa(validState.Round), + validState.Round, + vs.Round, + ) + }) + + t.Run("validator state info height", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "height"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Height), + mockOutput.String(), + ) + }) + + t.Run("validator state info round", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "round"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Round), + mockOutput.String(), ) }) - t.Run("node key shown", func(t *testing.T) { + t.Run("validator state info step", func(t *testing.T) { t.Parallel() + dirPath := t.TempDir() + statePath := filepath.Join(dirPath, defaultValidatorStateName) + + validState := generateLastSignValidatorState() + + require.NoError(t, saveSecretData(validState, statePath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", validatorStateKey, "step"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + assert.Equal( + t, + fmt.Sprintf("%d\n", validState.Step), + mockOutput.String(), + ) + }) +} + +func TestSecrets_Get_NodeIDInfo(t *testing.T) { + t.Parallel() + + t.Run("node ID info, default config", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + dirPath := t.TempDir() nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) @@ -258,19 +530,237 @@ func TestSecrets_Get_Single(t *testing.T) { "get", "--data-dir", dirPath, - nodeKeyKey, + nodeIDKey, } // Run the command require.NoError(t, cmd.ParseAndRun(context.Background(), args)) - output := mockOutput.String() + var ni nodeIDInfo + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &ni)) // Make sure the node p2p key is displayed - assert.Contains( + assert.Equal( t, + validNodeKey.ID().String(), + ni.ID, + ) + + // Make sure the default node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + ni.P2PAddress, + ) + }) + + t.Run("node ID info, existing config", func(t *testing.T) { + t.Parallel() + + var ( + dirPath = t.TempDir() + configPath = constructConfigPath(dirPath) + secretsPath = constructSecretsPath(dirPath) + nodeKeyPath = filepath.Join(secretsPath, defaultNodeKeyName) + ) + + // Ensure the sub-dirs exist + require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755)) + require.NoError(t, os.MkdirAll(secretsPath, 0o755)) + + // Set up the config + cfg := config.DefaultConfig() + cfg.P2P.ListenAddress = "tcp://127.0.0.1:2525" + + require.NoError(t, config.WriteConfigFile(configPath, cfg)) + + validNodeKey := generateNodeKey() + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + secretsPath, + nodeIDKey, + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var ni nodeIDInfo + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &ni)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, + validNodeKey.ID().String(), + ni.ID, + ) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + ni.P2PAddress, + ) + }) + + t.Run("ID", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "id"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var output string + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &output)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, + validNodeKey.ID().String(), output, + ) + }) + + t.Run("ID, raw", func(t *testing.T) { + t.Parallel() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "id"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + // Make sure the node p2p key is displayed + assert.Equal( + t, validNodeKey.ID().String(), + escapeNewline(mockOutput.Bytes()), + ) + }) + + t.Run("P2P Address", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "p2p_address"), + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + var output string + require.NoError(t, json.Unmarshal(mockOutput.Bytes(), &output)) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + output, + ) + }) + + t.Run("P2P Address, raw", func(t *testing.T) { + t.Parallel() + + cfg := config.DefaultConfig() + + dirPath := t.TempDir() + nodeKeyPath := filepath.Join(dirPath, defaultNodeKeyName) + + validNodeKey := generateNodeKey() + + require.NoError(t, saveSecretData(validNodeKey, nodeKeyPath)) + + mockOutput := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOutput)) + + // Create the command + cmd := newRootCmd(io) + args := []string{ + "secrets", + "get", + "--data-dir", + dirPath, + fmt.Sprintf("%s.%s", nodeIDKey, "p2p_address"), + "--raw", + } + + // Run the command + require.NoError(t, cmd.ParseAndRun(context.Background(), args)) + + // Make sure the custom node p2p address is displayed + assert.Equal( + t, + constructP2PAddress(validNodeKey.ID(), cfg.P2P.ListenAddress), + escapeNewline(mockOutput.Bytes()), ) }) } diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 85181d4735c..58dd0783f66 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -93,7 +93,7 @@ func execSecretsInit(cfg *secretsInitCfg, args []string, io commands.IO) error { // Initialize and save the validator's private key return initAndSaveValidatorKey(validatorKeyPath, io) - case nodeKeyKey: + case nodeIDKey: if osm.FileExists(nodeKeyPath) && !cfg.forceOverwrite { return fmt.Errorf("unable to overwrite the node' p2p key, %w", errOverwriteNotEnabled) } diff --git a/gno.land/cmd/gnoland/secrets_init_test.go b/gno.land/cmd/gnoland/secrets_init_test.go index 4a707778cc6..20e061447f5 100644 --- a/gno.land/cmd/gnoland/secrets_init_test.go +++ b/gno.land/cmd/gnoland/secrets_init_test.go @@ -149,7 +149,7 @@ func TestSecrets_Init_Single(t *testing.T) { }, { "node p2p key initialized", - nodeKeyKey, + nodeIDKey, defaultNodeKeyName, verifyNodeKey, }, @@ -207,7 +207,7 @@ func TestSecrets_Init_Single_Overwrite(t *testing.T) { }, { "node p2p key not overwritten", - nodeKeyKey, + nodeIDKey, defaultNodeKeyName, }, } diff --git a/gno.land/cmd/gnoland/secrets_verify.go b/gno.land/cmd/gnoland/secrets_verify.go index 7e6c154d1ac..32e563c1c6f 100644 --- a/gno.land/cmd/gnoland/secrets_verify.go +++ b/gno.land/cmd/gnoland/secrets_verify.go @@ -87,7 +87,7 @@ func execSecretsVerify(cfg *secretsVerifyCfg, args []string, io commands.IO) err } return nil - case nodeKeyKey: + case nodeIDKey: return readAndVerifyNodeKey(nodeKeyPath, io) default: // Validate the validator's private key diff --git a/gno.land/cmd/gnoland/secrets_verify_test.go b/gno.land/cmd/gnoland/secrets_verify_test.go index 77ab8523d40..513d7c8b503 100644 --- a/gno.land/cmd/gnoland/secrets_verify_test.go +++ b/gno.land/cmd/gnoland/secrets_verify_test.go @@ -360,7 +360,7 @@ func TestSecrets_Verify_Single(t *testing.T) { "verify", "--data-dir", dirPath, - nodeKeyKey, + nodeIDKey, } // Run the command diff --git a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar index dab238a6122..8db2c7302fc 100644 --- a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar +++ b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar @@ -30,19 +30,19 @@ stdout '"sequence": "1"' gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "2"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "2"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # attempt calling hello.Grumpy (always panics). @@ -52,19 +52,19 @@ stdout 'Hello, George!' ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "3"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 stdout '"sequence": "4"' -gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' -- test/test.gno -- diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 04ec61b4e5d..3e9db62df46 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -8,10 +8,10 @@ {"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"2000000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards\nGetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards\ntestboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards\ntestboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in Gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards\nGetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards\nBOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards\ngnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards?help\u0026__func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} {"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} diff --git a/gno.land/pkg/gnoclient/client_queries.go b/gno.land/pkg/gnoclient/client_queries.go index a6c8ea60475..9d9d7305116 100644 --- a/gno.land/pkg/gnoclient/client_queries.go +++ b/gno.land/pkg/gnoclient/client_queries.go @@ -90,7 +90,7 @@ func (c *Client) Render(pkgPath string, args string) (string, *ctypes.ResultABCI } path := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", pkgPath, args)) + data := []byte(fmt.Sprintf("%s:%s", pkgPath, args)) qres, err := c.RPCClient.ABCIQuery(path, data) if err != nil { @@ -113,7 +113,7 @@ func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.Resul } path := "vm/qeval" - data := []byte(fmt.Sprintf("%s\n%s", pkgPath, expression)) + data := []byte(fmt.Sprintf("%s.%s", pkgPath, expression)) qres, err := c.RPCClient.ABCIQuery(path, data) if err != nil { diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 04ebb8e27b8..8aef07451d6 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -24,13 +24,13 @@ func TestRender(t *testing.T) { sign: func(cfg SignCfg) (*std.Tx, error) { return &std.Tx{}, nil }, - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{ @@ -63,13 +63,13 @@ func TestCallSingle(t *testing.T) { sign: func(cfg SignCfg) (*std.Tx, error) { return &std.Tx{}, nil }, - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{ @@ -117,13 +117,13 @@ func TestCallMultiple(t *testing.T) { sign: func(cfg SignCfg) (*std.Tx, error) { return &std.Tx{}, nil }, - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{ @@ -482,13 +482,13 @@ func TestClient_Send_Errors(t *testing.T) { name: "Invalid To Address", client: Client{ Signer: &mockSigner{ - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{}, @@ -512,13 +512,13 @@ func TestClient_Send_Errors(t *testing.T) { name: "Invalid Send Coins", client: Client{ Signer: &mockSigner{ - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{}, @@ -561,13 +561,13 @@ func TestRunSingle(t *testing.T) { sign: func(cfg SignCfg) (*std.Tx, error) { return &std.Tx{}, nil }, - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{ @@ -628,13 +628,13 @@ func TestRunMultiple(t *testing.T) { sign: func(cfg SignCfg) (*std.Tx, error) { return &std.Tx{}, nil }, - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{ @@ -849,13 +849,13 @@ func TestRunErrors(t *testing.T) { name: "Invalid Empty Package", client: Client{ Signer: &mockSigner{ - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{}, @@ -1040,13 +1040,13 @@ func TestAddPackageErrors(t *testing.T) { name: "Invalid Empty Package", client: Client{ Signer: &mockSigner{ - info: func() keys.Info { + info: func() (keys.Info, error) { return &mockKeysInfo{ getAddress: func() crypto.Address { adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return adr }, - } + }, nil }, }, RPCClient: &mockRPCClient{}, diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index e306d737ede..a32a6899abe 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -86,9 +86,14 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx return nil, err } + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgCall{ - Caller: c.Signer.Info().GetAddress(), + Caller: caller.GetAddress(), PkgPath: msg.PkgPath, Func: msg.FuncName, Args: msg.Args, @@ -142,14 +147,17 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo return nil, err } - caller := c.Signer.Info().GetAddress() + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } msg.Package.Name = "main" msg.Package.Path = "" // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgRun{ - Caller: caller, + Caller: caller.GetAddress(), Package: msg.Package, Send: send, })) @@ -201,9 +209,14 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx return nil, err } + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } + // Unwrap syntax sugar to vm.MsgSend slice vmMsgs = append(vmMsgs, std.Msg(bank.MsgSend{ - FromAddress: c.Signer.Info().GetAddress(), + FromAddress: caller.GetAddress(), ToAddress: msg.ToAddress, Amount: send, })) @@ -255,11 +268,14 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul return nil, err } - caller := c.Signer.Info().GetAddress() + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgAddPackage{ - Creator: caller, + Creator: caller.GetAddress(), Package: msg.Package, Deposit: deposit, })) @@ -284,10 +300,13 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul // signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { - caller := c.Signer.Info().GetAddress() + caller, err := c.Signer.Info() + if err != nil { + return nil, err + } if sequenceNumber == 0 || accountNumber == 0 { - account, _, err := c.QueryAccount(caller) + account, _, err := c.QueryAccount(caller.GetAddress()) if err != nil { return nil, errors.Wrap(err, "query account") } diff --git a/gno.land/pkg/gnoclient/mock_test.go b/gno.land/pkg/gnoclient/mock_test.go index 385eeb0916e..64da62936c2 100644 --- a/gno.land/pkg/gnoclient/mock_test.go +++ b/gno.land/pkg/gnoclient/mock_test.go @@ -13,7 +13,7 @@ import ( // Signer mock type ( mockSign func(cfg SignCfg) (*std.Tx, error) - mockInfo func() keys.Info + mockInfo func() (keys.Info, error) mockValidate func() error ) @@ -30,11 +30,11 @@ func (m *mockSigner) Sign(cfg SignCfg) (*std.Tx, error) { return nil, nil } -func (m *mockSigner) Info() keys.Info { +func (m *mockSigner) Info() (keys.Info, error) { if m.info != nil { return m.info() } - return nil + return nil, nil } func (m *mockSigner) Validate() error { diff --git a/gno.land/pkg/gnoclient/signer.go b/gno.land/pkg/gnoclient/signer.go index f8e1e6b8522..0462865f2be 100644 --- a/gno.land/pkg/gnoclient/signer.go +++ b/gno.land/pkg/gnoclient/signer.go @@ -12,7 +12,7 @@ import ( // Signer provides an interface for signing transactions. type Signer interface { Sign(SignCfg) (*std.Tx, error) // Signs a transaction and returns a signed tx ready for broadcasting. - Info() keys.Info // Returns key information, including the address. + Info() (keys.Info, error) // Returns key information, including the address. Validate() error // Checks whether the signer is properly configured. } @@ -35,9 +35,14 @@ func (s SignerFromKeybase) Validate() error { return err } + caller, err := s.Info() + if err != nil { + return err + } + // To verify if the password unlocks the account, sign a blank transaction. msg := vm.MsgCall{ - Caller: s.Info().GetAddress(), + Caller: caller.GetAddress(), } signCfg := SignCfg{ UnsignedTX: std.Tx{ @@ -53,12 +58,12 @@ func (s SignerFromKeybase) Validate() error { } // Info gets keypair information. -func (s SignerFromKeybase) Info() keys.Info { +func (s SignerFromKeybase) Info() (keys.Info, error) { info, err := s.Keybase.GetByNameOrAddress(s.Account) if err != nil { - panic("should not happen") + return nil, err } - return info + return info, nil } // SignCfg provides the signing configuration, containing: diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 7669d5cce95..8f5f33d0d9a 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -120,7 +120,9 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // Initialize the VMKeeper. - vmKpr.Initialize(baseApp.GetCacheMultiStore()) + ms := baseApp.GetCacheMultiStore() + vmKpr.Initialize(ms) + ms.MultiWrite() // XXX why was't this needed? return baseApp, nil } diff --git a/gno.land/pkg/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go index 0ccdc0b5d53..b997de7840d 100644 --- a/gno.land/pkg/gnoweb/gnoweb.go +++ b/gno.land/pkg/gnoweb/gnoweb.go @@ -111,7 +111,7 @@ func MakeApp(logger *slog.Logger, cfg Config) gotuna.App { } // realm routes // NOTE: see rePathPart. - app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt|mod)$)?}", handlerRealmFile(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:(?:.*\\.(?:gno|md|txt|mod)$)|(?:LICENSE$))?}", handlerRealmFile(logger, app, &cfg)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(logger, app, &cfg)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(logger, app, &cfg)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(logger, app, &cfg)) @@ -150,7 +150,7 @@ func handlerRealmAlias(logger *slog.Logger, app gotuna.App, cfg *Config, rlmpath } rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/") qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr)) + data := []byte(fmt.Sprintf("%s:%s", rlmfullpath, querystr)) res, err := makeRequest(logger, cfg, qpath, data) if err != nil { writeError(logger, w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) @@ -323,7 +323,7 @@ func handleRealmRender(logger *slog.Logger, app gotuna.App, cfg *Config, w http. return } qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr)) + data := []byte(fmt.Sprintf("%s:%s", rlmpath, querystr)) res, err := makeRequest(logger, cfg, qpath, data) if err != nil { // XXX hack diff --git a/gno.land/pkg/gnoweb/gnoweb_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go index 55434e3b9fc..d6b93b37d69 100644 --- a/gno.land/pkg/gnoweb/gnoweb_test.go +++ b/gno.land/pkg/gnoweb/gnoweb_test.go @@ -46,6 +46,7 @@ func TestRoutes(t *testing.T) { {"/%ED%85%8C%EC%8A%A4%ED%8A%B8", notFound, "/테스트"}, {"/グノー", notFound, "/グノー"}, {"/⚛️", notFound, "/⚛️"}, + {"/p/demo/flow/LICENSE", ok, "BSD 3-Clause"}, } config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) @@ -67,7 +68,6 @@ func TestRoutes(t *testing.T) { response := httptest.NewRecorder() app.Router.ServeHTTP(response, request) assert.Equal(t, r.status, response.Code) - assert.Contains(t, response.Body.String(), r.substring) }) } diff --git a/gno.land/pkg/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html index 83692765d7d..b9c8e119e7a 100644 --- a/gno.land/pkg/gnoweb/views/realm_help.html +++ b/gno.land/pkg/gnoweb/views/realm_help.html @@ -17,7 +17,7 @@
These are the realm's exposed functions ("public smart contracts").

- My address: (see `gnokey list`)
+ My address: (see `gnokey list`)


{{ template "func_specs" . }} diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 368ada6ff82..cbf6df02e93 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -5,42 +5,41 @@ import ( "path/filepath" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) -func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { - // NOTE: native functions/methods added here must be quick operations, - // or account for gas before operation. - // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { - // otherwise, built-in package value. - // first, load from filepath. - stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) - if !osm.DirExists(stdlibPath) { - // does not exist. - return nil, nil - } - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - if memPkg.IsEmpty() { - // no gno files are present, skip this package - return nil, nil - } - - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "gno.land/r/stdlibs/" + pkgPath, - // PkgPath: pkgPath, - Output: os.Stdout, - Store: newStore, - }) - defer m2.Release() - return m2.RunMemPackage(memPkg, true) +// NOTE: this function may add loaded dependencies to store if they don't +// already exist, including mem packages. If this happens during a transaction +// with the tx context store, the transaction caller will pay for operations. +// NOTE: native functions/methods added here must be quick operations, or +// account for gas before operation. +// TODO: define criteria for inclusion, and solve gas calculations(???). +func (vm *VMKeeper) getPackage(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + // otherwise, built-in package value. + // first, load from filepath. + stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) + if !osm.DirExists(stdlibPath) { + // does not exist. + return nil, nil + } + memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) + if memPkg.IsEmpty() { + // no gno files are present, skip this package + return nil, nil } - store.SetPackageGetter(getPackage) - store.SetNativeStore(stdlibs.NativeStore) + + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "gno.land/r/stdlibs/" + pkgPath, + // PkgPath: pkgPath, + Output: os.Stdout, + Store: store, + }) + defer m2.Release() + pn, pv = m2.RunMemPackage(memPkg, true) + return } // ---------------------------------------- diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index 132d98b7ecd..0020e989eb6 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -20,7 +20,7 @@ type ( InvalidExprError struct{ abciError } TypeCheckError struct { abciError - Errors []string + Errors []string `json:"errors"` } ) diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index ae77021aa06..7b26265f35d 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -150,12 +150,12 @@ func (vh vmHandler) queryStore(ctx sdk.Context, req abci.RequestQuery) (res abci // queryRender calls .Render() in readonly mode. func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 2 { - panic("expected two lines in query input data") + dot := strings.IndexByte(reqData, ':') + if dot < 0 { + panic("expected : syntax in query input data") } - pkgPath := reqParts[0] - path := reqParts[1] + + pkgPath, path := reqData[:dot], reqData[dot+1:] expr := fmt.Sprintf("Render(%q)", path) result, err := vh.vm.QueryEvalString(ctx, pkgPath, expr) if err != nil { @@ -168,12 +168,7 @@ func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abc // queryFuncs returns public facing function signatures as JSON. func (vh vmHandler) queryFuncs(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 1 { - panic("expected one line in query input data") - } - pkgPath := reqParts[0] + pkgPath := string(req.Data) fsigs, err := vh.vm.QueryFuncs(ctx, pkgPath) if err != nil { res = sdk.ABCIResponseQueryFromError(err) @@ -185,13 +180,7 @@ func (vh vmHandler) queryFuncs(ctx sdk.Context, req abci.RequestQuery) (res abci // queryEval evaluates any expression in readonly mode and returns the results. func (vh vmHandler) queryEval(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - reqData := string(req.Data) - reqParts := strings.Split(reqData, "\n") - if len(reqParts) != 2 { - panic("expected two lines in query input data") - } - pkgPath := reqParts[0] - expr := reqParts[1] + pkgPath, expr := parseQueryEvalData(string(req.Data)) result, err := vh.vm.QueryEval(ctx, pkgPath, expr) if err != nil { res = sdk.ABCIResponseQueryFromError(err) @@ -201,6 +190,29 @@ func (vh vmHandler) queryEval(ctx sdk.Context, req abci.RequestQuery) (res abci. return } +// parseQueryEval parses the input string of vm/qeval. It takes the first dot +// after the first slash (if any) to separe the pkgPath and the expr. +// For instance, in gno.land/r/realm.MyFunction(), gno.land/r/realm is the +// pkgPath,and MyFunction() is the expr. +func parseQueryEvalData(data string) (pkgPath, expr string) { + slash := strings.IndexByte(data, '/') + if slash >= 0 { + pkgPath += data[:slash] + data = data[slash:] + } + dot := strings.IndexByte(data, '.') + if dot < 0 { + panic(panicInvalidQueryEvalData) + } + pkgPath += data[:dot] + expr = data[dot+1:] + return +} + +const ( + panicInvalidQueryEvalData = "expected . syntax in query input data" +) + // queryFile returns the file bytes, or list of files if directory. // if file, res.Value is []byte("file"). // if dir, res.Value is []byte("dir"). diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 2110681d99d..38ac8fa61b9 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -1,58 +1,50 @@ package vm -/* - import ( - "fmt" - "strings" "testing" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/amino" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/sdk" - tu "github.com/gnolang/gno/tm2/pkg/sdk/testutils" - "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" ) -func TestInvalidMsg(t *testing.T) { - h := NewHandler(BankKeeper{}) - res := h.Process(sdk.NewContext(nil, &bft.Header{}, false, nil), tu.NewTestMsg()) - require.False(t, res.IsOK()) - require.True(t, strings.Contains(res.Log, "unrecognized bank message type")) -} - -func TestBalances(t *testing.T) { - env := setupTestEnv() - h := NewHandler(env.bank) - req := abci.RequestQuery{ - Path: fmt.Sprintf("bank/%s", QueryBalance), - Data: []byte{}, +func Test_parseQueryEvalData(t *testing.T) { + t.Parallel() + tt := []struct { + input string + pkgpath string + expr string + }{ + { + "gno.land/r/realm.Expression()", + "gno.land/r/realm", + "Expression()", + }, + { + "a.b/c/d.e", + "a.b/c/d", + "e", + }, + { + "a.b.c.d.e/c/d.e", + "a.b.c.d.e/c/d", + "e", + }, + { + "abcde/c/d.e", + "abcde/c/d", + "e", + }, } + for _, tc := range tt { + path, expr := parseQueryEvalData(tc.input) + assert.Equal(t, tc.pkgpath, path) + assert.Equal(t, tc.expr, expr) + } +} - res := h.Query(env.ctx, req) - require.NotNil(t, res.Error) - - _, _, addr := tu.KeyTestPubAddr() - req.Data = amino.MustMarshalJSON(NewQueryBalanceParams(addr)) - res = h.Query(env.ctx, req) - require.Nil(t, res.Error) // the account does not exist, no error returned anyway - require.NotNil(t, res) - - var coins std.Coins - require.NoError(t, amino.UnmarshalJSON(res.Data, &coins)) - require.True(t, coins.IsZero()) +func Test_parseQueryEval_panic(t *testing.T) { + t.Parallel() - acc := env.acck.NewAccountWithAddress(env.ctx, addr) - acc.SetCoins(std.NewCoins(std.NewCoin("foo", 10))) - env.acck.SetAccount(env.ctx, acc) - res = h.Query(env.ctx, req) - require.Nil(t, res.Error) - require.NotNil(t, res) - require.NoError(t, amino.UnmarshalJSON(res.Data, &coins)) - require.True(t, coins.AmountOf("foo") == 10) + assert.PanicsWithValue(t, panicInvalidQueryEvalData, func() { + parseQueryEvalData("gno.land/r/demo/users") + }) } - -*/ diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index a77ddac3e28..95f17ce09f0 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -81,7 +81,8 @@ func (vm *VMKeeper) Initialize(ms store.MultiStore) { baseSDKStore := ms.GetStore(vm.baseKey) iavlSDKStore := ms.GetStore(vm.iavlKey) vm.gnoStore = gno.NewStore(alloc, baseSDKStore, iavlSDKStore) - vm.initBuiltinPackagesAndTypes(vm.gnoStore) + vm.gnoStore.SetPackageGetter(vm.getPackage) + vm.gnoStore.SetNativeStore(stdlibs.NativeStore) if vm.gnoStore.NumMemPackages() > 0 { // for now, all mem packages must be re-run after reboot. // TODO remove this, and generally solve for in-mem garbage collection @@ -157,7 +158,6 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { if pv := gnostore.GetPackage(pkgPath, false); pv != nil { return ErrInvalidPkgPath("package already exists: " + pkgPath) } - if gno.ReGnoRunPath.MatchString(pkgPath) { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } diff --git a/gno.land/pkg/sdk/vm/package_test.go b/gno.land/pkg/sdk/vm/package_test.go new file mode 100644 index 00000000000..f19cc55aa20 --- /dev/null +++ b/gno.land/pkg/sdk/vm/package_test.go @@ -0,0 +1,49 @@ +package vm + +import ( + "reflect" + "strings" + "testing" + "unicode" + + "github.com/stretchr/testify/assert" +) + +func TestJSONSnakeCase(t *testing.T) { + t.Parallel() + for _, typ := range Package.Types { + assertJSONSnakeCase(t, typ.Type) + } +} + +func assertJSONSnakeCase(t *testing.T, typ reflect.Type) { + t.Helper() + + switch typ.Kind() { + case reflect.Array, reflect.Slice, reflect.Pointer: + assertJSONSnakeCase(t, typ.Elem()) + case reflect.Map: + assertJSONSnakeCase(t, typ.Key()) + assertJSONSnakeCase(t, typ.Elem()) + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + fld := typ.Field(i) + if !fld.IsExported() { + continue + } + jt := fld.Tag.Get("json") + if jt == "" { + if fld.Anonymous { + assertJSONSnakeCase(t, fld.Type) + continue + } + t.Errorf("field %s.%s does not have a json tag but is exported", typ.Name(), fld.Name) + continue + } + has := strings.ContainsFunc(jt, unicode.IsUpper) + assert.False(t, has, + "field %s.%s contains uppercase symbols in json tag", typ.Name(), fld.Name) + assertJSONSnakeCase(t, fld.Type) + } + } +} diff --git a/gno.land/pkg/sdk/vm/vm.proto b/gno.land/pkg/sdk/vm/vm.proto index b99be0a85ff..aa0be4f6e14 100644 --- a/gno.land/pkg/sdk/vm/vm.proto +++ b/gno.land/pkg/sdk/vm/vm.proto @@ -15,6 +15,12 @@ message m_call { repeated string args = 5; } +message m_run { + string caller = 1; + string send = 2; + std.MemPackage package = 3; +} + message m_addpkg { string creator = 1; std.MemPackage package = 2; @@ -28,4 +34,8 @@ message InvalidStmtError { } message InvalidExprError { +} + +message TypeCheckError { + repeated string errors = 1 [json_name = "Errors"]; } \ No newline at end of file diff --git a/gnovm/Makefile b/gnovm/Makefile index ff3f07158c5..aa80c61ac7d 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -27,6 +27,9 @@ GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../) # We can't use '-trimpath' yet as amino use absolute path from call stack # to find some directory: see #1236 GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" +# file where to place cover profile; used for coverage commands which are +# more complex than adding -coverprofile, like test.cmd.coverage. +GOTEST_COVER_PROFILE ?= cmd-profile.out ######################################## # Dev tools @@ -66,6 +69,24 @@ test: _test.cmd _test.pkg _test.gnolang _test.cmd: go test ./cmd/... $(GOTEST_FLAGS) +# Run tests on ./cmd/, saving the result of the coverage in +# GOTEST_COVER_PROFILE. +.PHONY: test.cmd.coverage +test.cmd.coverage: + $(eval export TXTARCOVERDIR := $(shell mktemp -d --tmpdir gnovm-make.XXXXXXX)) + go test ./cmd/... -covermode atomic -test.gocoverdir='$(TXTARCOVERDIR)' $(GOTEST_FLAGS) + @echo "coverage results:" + go tool covdata percent -i="$(TXTARCOVERDIR)" + go tool covdata textfmt -v 1 -i="$(TXTARCOVERDIR)" -o '$(GOTEST_COVER_PROFILE)' + rm -rf "$(TXTARCOVERDIR)" + +# Run test.cmd.coverage, then view the result in the HTML browser render +# and delete the original file. +.PHONY: test.cmd.coverage_view +test.cmd.coverage_view: test.cmd.coverage + go tool cover -html='$(GOTEST_COVER_PROFILE)' + rm '$(GOTEST_COVER_PROFILE)' + .PHONY: _test.pkg _test.pkg: go test ./pkg/... $(GOTEST_FLAGS) @@ -74,13 +95,16 @@ _test.pkg: _test.gnolang: _test.gnolang.native _test.gnolang.stdlibs _test.gnolang.realm _test.gnolang.pkg0 _test.gnolang.pkg1 _test.gnolang.pkg2 _test.gnolang.other _test.gnolang.other:; go test tests/*.go -run "(TestFileStr|TestSelectors)" $(GOTEST_FLAGS) _test.gnolang.realm:; go test tests/*.go -run "TestFiles/^zrealm" $(GOTEST_FLAGS) -_test.gnolang.pkg0:; go test tests/*.go -run "TestStdlibs/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) +_test.gnolang.pkg0:; go test tests/*.go -run "TestStdlibs/(bufio|crypto|encoding|errors|internal|io|math|sort|std|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) _test.gnolang.pkg1:; go test tests/*.go -run "TestStdlibs/regexp" $(GOTEST_FLAGS) _test.gnolang.pkg2:; go test tests/*.go -run "TestStdlibs/bytes" $(GOTEST_FLAGS) _test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNative/" $(GOTEST_FLAGS) _test.gnolang.stdlibs:; go test tests/*.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) _test.gnolang.native.sync:; go test tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests $(GOTEST_FLAGS) _test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests $(GOTEST_FLAGS) +# NOTE: challenges are current GnoVM bugs which are supposed to fail. +# If any of these tests pass, it should be moved to a normal test. +_test.gnolang.challenges:; go test tests/*.go -test.short -run 'TestChallenges$$/' $(GOTEST_FLAGS) ######################################## # Code gen diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index f78c15edb34..06f9a8dc3a5 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -75,6 +75,10 @@ func TestRunApp(t *testing.T) { args: []string{"run", "-debug-addr", "invalidhost:17538", "../../tests/integ/debugger/sample.gno"}, errShouldContain: "listen tcp: lookup invalidhost", }, + { + args: []string{"run", "../../tests/integ/invalid_assign/main.gno"}, + recoverShouldContain: "cannot use bool as main.C without explicit conversion", + }, // TODO: a test file // TODO: args // TODO: nativeLibs VS stdlibs diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 2e966bd32a9..5884463a552 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -20,7 +20,6 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" @@ -259,7 +258,7 @@ func gnoTestPkg( if gnoPkgPath == "" { // unable to read pkgPath from gno.mod, generate a random realm path io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file") - gnoPkgPath = transpiler.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = gno.RealmPathPrefix + random.RandStr(8) } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) diff --git a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar deleted file mode 100644 index c07c670f721..00000000000 --- a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar +++ /dev/null @@ -1,36 +0,0 @@ -# Run gno transpile with -skip-fmt flag -# NOTE(tb): this flag doesn't actually prevent the code format, because -# `gnolang.Transpile()` calls `format.Node()`. - -gno transpile -skip-fmt . - -! stdout .+ -! stderr .+ - -cmp main.gno.gen.go main.gno.gen.go.golden -cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden - --- main.gno -- -package main - -func main(){} - --- sub/sub.gno -- -package sub - --- main.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line main.gno:1:1 -package main - -func main() {} --- sub/sub.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line sub.gno:1:1 -package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar deleted file mode 100644 index 110d04959c0..00000000000 --- a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar +++ /dev/null @@ -1,38 +0,0 @@ -# Run gno transpile with -gobuild flag - -gno transpile -gobuild . - -! stdout .+ -! stderr .+ - -cmp main.gno.gen.go main.gno.gen.go.golden -cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden - --- main.gno -- -package main - -func main() { - var x = 1 - _=x -} --- sub/sub.gno -- -package sub --- main.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line main.gno:1:1 -package main - -func main() { - var x = 1 - _ = x -} --- sub/sub.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line sub.gno:1:1 -package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar similarity index 60% rename from gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar index 79d4d6a4a2c..0c51012feb7 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar @@ -1,10 +1,10 @@ -# Run gno transpile with gno files with whitelist errors +# Run gno transpile with gno files with an invalid import path ! gno transpile . ! stdout .+ -stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' -stderr '^sub/sub.gno:3:8: import "xxx" is not in the whitelist$' +stderr '^main.gno:5:2: import "xxx" does not exist$' +stderr '^sub/sub.gno:3:8: import "xxx" does not exist$' stderr '^2 transpile error\(s\)$' # no *.gen.go files are created diff --git a/gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar new file mode 100644 index 00000000000..40bb1ecb98a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar @@ -0,0 +1,31 @@ +# Run gno transpile with -gobuild flag on an individual file + +gno transpile -gobuild -v main.gno + +! stdout .+ +cmp stderr stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden + +-- stderr.golden -- +main.gno +main.gno [build] +-- main.gno -- +package main + +func main() { + var x = 1 + _=x +} +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + var x = 1 + _ = x +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar new file mode 100644 index 00000000000..2eacfb9de60 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar @@ -0,0 +1,72 @@ +# Run gno transpile with -gobuild flag + +gno transpile -gobuild -v . + +! stdout .+ +cmp stderr stderr.golden + +# The test file will be excluded from transpilation unless we pass it explicitly. +cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go +cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden +rm mai.gno.gen.gosub/sub.gno.gen.go + +# Re-try, but use an absolute path. +gno transpile -gobuild -v $WORK + +! stdout .+ +cmpenv stderr stderr2.golden + +cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go +cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden + +-- stderr.golden -- +. +sub +. [build] +sub [build] +-- stderr2.golden -- +$WORK +$WORK/sub +$WORK [build] +$WORK/sub [build] +-- main.gno -- +package main + +func main() { + var x = 1 + _=x +} +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + var x = 1 + _ = x +} +-- main_test.gno -- +package main + +import ( + "testing" + "badimport" +) + +func TestMain(t *testing.T) { + badimport.DoesNotExist() +} +-- sub/sub.gno -- +package sub +-- sub/sub.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line sub.gno:1:1 +package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar new file mode 100644 index 00000000000..b1a63890f46 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar @@ -0,0 +1,41 @@ +# Run gno transpile with valid gno files, using the -output flag. + +gno transpile -v -output directory/hello/ . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +-- stderr2.golden -- +$WORK +-- stderr3.golden -- +.. +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar new file mode 100644 index 00000000000..3540e865f3e --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar @@ -0,0 +1,44 @@ +# Run gno transpile with valid gno files, using the -output and -gobuild flags together. + +gno transpile -v -output directory/hello/ -gobuild . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello -gobuild $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello -gobuild .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +directory/hello [build] +-- stderr2.golden -- +$WORK +directory/hello$WORK [build] +-- stderr3.golden -- +.. +hello$WORK [build] +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar new file mode 100644 index 00000000000..86cc6f12f7a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar @@ -0,0 +1,65 @@ +# Run gno transpile with an individual file. + +# Running transpile on the current directory should only precompile +# main.gno. +gno transpile -v . + +! stdout .+ +stderr ^\.$ + +exists main.gno.gen.go +! exists .hello_test.gno.gen_test.go +rm main.gno.gen.go + +# Running it using individual filenames should precompile hello_test.gno, as well. +gno transpile -v main.gno hello_test.gno + +! stdout .+ +cmp stderr transpile-files-stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden +cmp .hello_test.gno.gen_test.go .hello_test.gno.gen_test.go.golden + +-- transpile-files-stderr.golden -- +main.gno +hello_test.gno +-- main.gno -- +package main + +func main() { + println("hello") +} + +-- hello_test.gno -- +package main + +import "std" + +func hello() { + std.AssertOriginCall() +} + +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + println("hello") +} +-- .hello_test.gno.gen_test.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno && test + +//line hello_test.gno:1:1 +package main + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func hello() { + std.AssertOriginCall(nil) +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar new file mode 100644 index 00000000000..a765ab5093b --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar @@ -0,0 +1,94 @@ +# Run gno transpile with dependencies +env GNOROOT=$WORK + +gno transpile -v ./examples + +! stdout .+ +cmpenv stderr stderr.golden + +! exists examples/gno.land/r/question/question.gno.gen.go +cmp examples/gno.land/r/answer/answer.gno.gen.go examples/gno.land/r/answer/answer.gno.gen.go.golden +cmp examples/gno.land/r/answer/anti_answer.gno.gen.go examples/gno.land/r/answer/anti_answer.gno.gen.go.golden +cmp gnovm/stdlibs/math/math.gno.gen.go gnovm/stdlibs/math/math.gno.gen.go.golden + +-- stderr.golden -- +examples/gno.land/r/answer +$WORK/gnovm/stdlibs/math +examples/gno.land/r/question (skipped, gno.mod marks module as draft) +-- examples/gno.land/r/question/gno.mod -- +// Draft + +module gno.land/r/question + +-- examples/gno.land/r/question/question.gno -- +package question + +func Question() string { + return "What is the answer to Life, The Universe and Everything?" + invalid syntax +} + +-- examples/gno.land/r/answer/answer.gno -- +package answer + +import "math" + +func Answer() int { + return math.Sqrt(1764) +} + +-- examples/gno.land/r/answer/answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func Answer() int { + return math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno -- +package answer + +import "math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line anti_answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/gno.mod -- +module gno.land/r/answer + +-- gnovm/stdlibs/math/math.gno -- +package math + +func Sqrt(i int) int { + return 42 +} + +-- gnovm/stdlibs/math/math.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line math.gno:1:1 +package math + +func Sqrt(i int) int { + return 42 +} diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 84744451f9d..2e12ee6f4b3 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -5,48 +5,61 @@ import ( "errors" "flag" "fmt" + "go/ast" "go/scanner" - "log" + "go/token" "os" + "os/exec" "path/filepath" + "regexp" + "slices" + "strconv" + "strings" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) -type importPath string - type transpileCfg struct { verbose bool - skipFmt bool + rootDir string skipImports bool gobuild bool goBinary string - gofmtBinary string output string } type transpileOptions struct { cfg *transpileCfg + // CLI output + io commands.IO // transpiled is the set of packages already // transpiled from .gno to .go. - transpiled map[importPath]struct{} + transpiled map[string]struct{} + // skipped packages (gno mod marks them as draft) + skipped []string } -func newTranspileOptions(cfg *transpileCfg) *transpileOptions { - return &transpileOptions{cfg, map[importPath]struct{}{}} +func newTranspileOptions(cfg *transpileCfg, io commands.IO) *transpileOptions { + return &transpileOptions{ + cfg: cfg, + io: io, + transpiled: map[string]struct{}{}, + } } func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *transpileOptions) isTranspiled(pkg importPath) bool { +func (p *transpileOptions) isTranspiled(pkg string) bool { _, transpiled := p.transpiled[pkg] return transpiled } -func (p *transpileOptions) markAsTranspiled(pkg importPath) { +func (p *transpileOptions) markAsTranspiled(pkg string) { p.transpiled[pkg] = struct{}{} } @@ -74,11 +87,11 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "verbose output when running", ) - fs.BoolVar( - &c.skipFmt, - "skip-fmt", - false, - "do not check syntax of generated .go files", + fs.StringVar( + &c.rootDir, + "root-dir", + "", + "clone location of github.com/gnolang/gno (gno tries to guess it)", ) fs.BoolVar( @@ -102,13 +115,6 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "go binary to use for building", ) - fs.StringVar( - &c.gofmtBinary, - "go-fmt-binary", - "gofmt", - "gofmt binary to use for syntax checking", - ) - fs.StringVar( &c.output, "output", @@ -122,33 +128,54 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return flag.ErrHelp } - // transpile .gno files. - paths, err := gnoFilesFromArgs(args) + // guess cfg.RootDir + if cfg.rootDir == "" { + cfg.rootDir = gnoenv.RootDir() + } + + // transpile .gno packages and files. + paths, err := gnoPackagesFromArgs(args) if err != nil { return fmt.Errorf("list paths: %w", err) } - opts := newTranspileOptions(cfg) + opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList - for _, filepath := range paths { - if err := transpileFile(filepath, opts); err != nil { + for _, path := range paths { + st, err := os.Stat(path) + if err != nil { + return err + } + if st.IsDir() { + err = transpilePkg(path, opts) + } else { + if opts.cfg.verbose { + io.ErrPrintln(filepath.Clean(path)) + } + + err = transpileFile(path, opts) + } + if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: transpile: %w", filepath, err) + return fmt.Errorf("%s: transpile: %w", path, err) } errlist = append(errlist, fileErrlist...) } } if errlist.Len() == 0 && cfg.gobuild { - paths, err := gnoPackagesFromArgs(args) - if err != nil { - return fmt.Errorf("list packages: %w", err) - } - for _, pkgPath := range paths { - err := goBuildFileOrPkg(pkgPath, cfg) + if slices.Contains(opts.skipped, pkgPath) { + continue + } + if cfg.output != "." { + if pkgPath, err = ResolvePath(cfg.output, pkgPath); err != nil { + return fmt.Errorf("resolve output path: %w", err) + } + } + err := goBuildFileOrPkg(io, pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { @@ -169,19 +196,41 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return nil } -func transpilePkg(pkgPath importPath, opts *transpileOptions) error { - if opts.isTranspiled(pkgPath) { +// transpilePkg transpiles all non-test files at the given location. +// Additionally, it checks the gno.mod in said location, and skips it if it is +// a draft module +func transpilePkg(dirPath string, opts *transpileOptions) error { + if opts.isTranspiled(dirPath) { + return nil + } + opts.markAsTranspiled(dirPath) + + gmod, err := gnomod.ParseAt(dirPath) + if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) { + return err + } + if err == nil && gmod.Draft { + if opts.cfg.verbose { + opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) + } + opts.skipped = append(opts.skipped, dirPath) return nil } - opts.markAsTranspiled(pkgPath) - files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) + // XXX(morgan): Currently avoiding test files as they contain imports like "fmt". + // The transpiler doesn't currently support "test stdlibs", and even if it + // did all packages like "fmt" would have to exist as standard libraries to work. + // Easier to skip for now. + files, err := listNonTestFiles(dirPath) if err != nil { - log.Fatal(err) + return err } + if opts.cfg.verbose { + opts.io.ErrPrintln(filepath.Clean(dirPath)) + } for _, file := range files { - if err = transpileFile(file, opts); err != nil { + if err := transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -191,14 +240,6 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error { func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() - gofmt := flags.gofmtBinary - if gofmt == "" { - gofmt = "gofmt" - } - - if flags.verbose { - fmt.Fprintf(os.Stderr, "%s\n", srcPath) - } // parse .gno. source, err := os.ReadFile(srcPath) @@ -207,7 +248,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { } // compute attributes based on filename. - targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) + targetFilename, tags := transpiler.TranspiledFilenameAndTags(srcPath) // preprocess. transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) @@ -218,7 +259,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // resolve target path var targetPath string if flags.output != "." { - path, err := ResolvePath(flags.output, importPath(filepath.Dir(srcPath))) + path, err := ResolvePath(flags.output, filepath.Dir(srcPath)) if err != nil { return fmt.Errorf("resolve output path: %w", err) } @@ -233,18 +274,14 @@ func transpileFile(srcPath string, opts *transpileOptions) error { return fmt.Errorf("write .go file: %w", err) } - // check .go fmt, if `SkipFmt` sets to false. - if !flags.skipFmt { - err = transpiler.TranspileVerifyFile(targetPath, gofmt) + // transpile imported packages, if `SkipImports` sets to false + if !flags.skipImports && + !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { + dirPaths, err := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) if err != nil { - return fmt.Errorf("check .go file: %w", err) + return err } - } - - // transpile imported packages, if `SkipImports` sets to false - if !flags.skipImports { - importPaths := getPathsFromImportSpec(transpileRes.Imports) - for _, path := range importPaths { + for _, path := range dirPaths { if err := transpilePkg(path, opts); err != nil { return err } @@ -254,13 +291,122 @@ func transpileFile(srcPath string, opts *transpileOptions) error { return nil } -func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { +func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error { verbose := cfg.verbose goBinary := cfg.goBinary if verbose { - fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) + io.ErrPrintfln("%s [build]", filepath.Clean(fileOrPkg)) + } + + return buildTranspiledPackage(fileOrPkg, goBinary) +} + +// getPathsFromImportSpec returns the directory paths where the code for each +// importSpec is stored (assuming they start with [transpiler.ImportPrefix]). +func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string, err error) { + for _, i := range importSpec { + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + return nil, err + } + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) + + dirs = append(dirs, rootDir+filepath.FromSlash(res)) + } + } + return +} + +// buildTranspiledPackage tries to run `go build` against the transpiled .go files. +// +// This method is the most efficient to detect errors but requires that +// all the import are valid and available. +func buildTranspiledPackage(fileOrPkg, goBinary string) error { + // TODO: use cmd/compile instead of exec? + // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? + // TODO: temporarily create an in-memory go.mod or disable go modules for gno? + // TODO: ignore .go files that were not generated from gno? + + info, err := os.Stat(fileOrPkg) + if err != nil { + return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) + } + var ( + target string + chdir string + ) + if !info.IsDir() { + dstFilename, _ := transpiler.TranspiledFilenameAndTags(fileOrPkg) + // Makes clear to go compiler that this is a relative path, + // rather than a path to a package/module. + // can't use filepath.Join as it cleans its results. + target = filepath.Dir(fileOrPkg) + string(filepath.Separator) + dstFilename + } else { + // Go does not allow building packages using absolute paths, and requires + // relative paths to always be prefixed with `./` (because the argument + // go expects are import paths, not directories). + // To circumvent this, we use the -C flag to chdir into the right + // directory, then run `go build .` + chdir = fileOrPkg + target = "." + } + + // pre-alloc max 5 args + args := append(make([]string, 0, 5), "build") + if chdir != "" { + args = append(args, "-C", chdir) + } + args = append(args, "-tags=gno", target) + cmd := exec.Command(goBinary, args...) + out, err := cmd.CombinedOutput() + if errors.As(err, new(*exec.ExitError)) { + // there was a non-zero exit code; parse the go build errors + return parseGoBuildErrors(string(out)) + } + // other kinds of errors; return + return err +} + +var ( + reGoBuildError = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) + reGoBuildComment = regexp.MustCompile(`(?m)^#.*$`) +) + +// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found +// in out, which is supposed to be the output of the `go build` command. +// +// TODO(tb): update when `go build -json` is released to replace regexp usage. +// See https://github.com/golang/go/issues/62067 +func parseGoBuildErrors(out string) error { + var errList scanner.ErrorList + matches := reGoBuildError.FindAllStringSubmatch(out, -1) + for _, match := range matches { + filename := match[1] + line, err := strconv.Atoi(match[2]) + if err != nil { + return fmt.Errorf("parse line go build error %s: %w", match, err) + } + + column, err := strconv.Atoi(match[3]) + if err != nil { + return fmt.Errorf("parse column go build error %s: %w", match, err) + } + msg := match[4] + errList.Add(token.Position{ + Filename: filename, + Line: line, + Column: column, + }, msg) + } + + replaced := reGoBuildError.ReplaceAllLiteralString(out, "") + replaced = reGoBuildComment.ReplaceAllString(replaced, "") + replaced = strings.TrimSpace(replaced) + if replaced != "" { + errList.Add(token.Position{}, "Additional go build errors:\n"+replaced) } - return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) + return errList.Err() } diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 2770026a01a..827c09e23f1 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -1,9 +1,13 @@ package main import ( + "go/scanner" + "go/token" + "strconv" "testing" "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/gnovm/pkg/integration" @@ -24,3 +28,79 @@ func Test_ScriptsTranspile(t *testing.T) { testscript.Run(t, p) } + +func Test_parseGoBuildErrors(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + output string + expectedError error + expectedErrorIs error + }{ + { + name: "empty output", + output: "", + expectedError: nil, + }, + { + name: "random output", + output: "xxx", + expectedError: scanner.ErrorList{ + &scanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + { + name: "some errors", + output: `xxx +main.gno:6:2: nasty error +pkg/file.gno:60:20: ugly error`, + expectedError: scanner.ErrorList{ + &scanner.Error{ + Pos: token.Position{ + Filename: "main.gno", + Line: 6, + Column: 2, + }, + Msg: "nasty error", + }, + &scanner.Error{ + Pos: token.Position{ + Filename: "pkg/file.gno", + Line: 60, + Column: 20, + }, + Msg: "ugly error", + }, + &scanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + { + name: "line parse error", + output: `main.gno:9000000000000000000000000000000000000000000000000000:11: error`, + expectedErrorIs: strconv.ErrRange, + }, + { + name: "column parse error", + output: `main.gno:1:9000000000000000000000000000000000000000000000000000: error`, + expectedErrorIs: strconv.ErrRange, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := parseGoBuildErrors(tt.output) + if eis := tt.expectedErrorIs; eis != nil { + assert.ErrorIs(t, err, eis) + } else { + assert.Equal(t, tt.expectedError, err) + } + }) + } +} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index d9ec775dfca..480161c2b7e 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "go/ast" "io" "io/fs" "os" @@ -10,9 +9,6 @@ import ( "regexp" "strings" "time" - - "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func isGnoFile(f fs.DirEntry) bool { @@ -25,84 +21,69 @@ func isFileExist(path string) bool { return err == nil } -func gnoFilesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) +func gnoPackagesFromArgs(args []string) ([]string, error) { + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) if err != nil { return nil, fmt.Errorf("invalid file or package path: %w", err) } + if !info.IsDir() { - curpath := arg - paths = append(paths, curpath) - } else { - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - - if !isGnoFile(f) { - return nil // skip - } - paths = append(paths, curpath) - return nil - }) - if err != nil { - return nil, err - } + paths = append(paths, ensurePathPrefix(argPath)) + + continue + } + + // Gather package paths from the directory + err = walkDirForGnoFiles(argPath, func(path string) { + paths = append(paths, ensurePathPrefix(path)) + }) + if err != nil { + return nil, fmt.Errorf("unable to walk dir: %w", err) } } + return paths, nil } -func gnoPackagesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) +func ensurePathPrefix(path string) string { + if filepath.IsAbs(path) { + return path + } + + // cannot use path.Join or filepath.Join, because we need + // to ensure that ./ is the prefix to pass to go build. + // if not absolute. + return "." + string(filepath.Separator) + path +} + +func walkDirForGnoFiles(root string, addPath func(path string)) error { + visited := make(map[string]struct{}) + + walkFn := func(currPath string, f fs.DirEntry, err error) error { if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) + return fmt.Errorf("%s: walk dir: %w", root, err) } - if !info.IsDir() { - paths = append(paths, arg) - } else { - // if the passed arg is a dir, then we'll recursively walk the dir - // and look for directories containing at least one .gno file. - - visited := map[string]bool{} // used to run the builder only once per folder. - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - if f.IsDir() { - return nil // skip - } - if !isGnoFile(f) { - return nil // skip - } - - parentDir := filepath.Dir(curpath) - if _, found := visited[parentDir]; found { - return nil - } - visited[parentDir] = true - - pkg := parentDir - if !filepath.IsAbs(parentDir) { - // cannot use path.Join or filepath.Join, because we need - // to ensure that ./ is the prefix to pass to go build. - // if not absolute. - pkg = "./" + parentDir - } - - paths = append(paths, pkg) - return nil - }) - if err != nil { - return nil, err - } + + if f.IsDir() || !isGnoFile(f) { + return nil } + + parentDir := filepath.Dir(currPath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = struct{}{} + + addPath(parentDir) + + return nil } - return paths, nil + + return filepath.WalkDir(root, walkFn) } // targetsFromPatterns returns a list of target paths that match the patterns. @@ -192,36 +173,27 @@ func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } -// getPathsFromImportSpec derive and returns ImportPaths -// without ImportPrefix from *ast.ImportSpec -func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) { - for _, i := range importSpec { - path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"` - if strings.HasPrefix(path, transpiler.ImportPrefix) { - res := strings.TrimPrefix(path, transpiler.ImportPrefix) - importPaths = append(importPaths, importPath("."+res)) - } +// ResolvePath determines the path where to place output files. +// output is the output directory provided by the user. +// dstPath is the desired output path by the gno program. +// +// If dstPath is relative non-local path (ie. contains ../), the dstPath will +// be made absolute and joined with output. +// +// Otherwise, the result is simply filepath.Join(output, dstPath). +// +// See related test for examples. +func ResolvePath(output, dstPath string) (string, error) { + if filepath.IsAbs(dstPath) || + filepath.IsLocal(dstPath) { + return filepath.Join(output, dstPath), nil } - return -} - -// ResolvePath joins the output dir with relative pkg path -// e.g -// Output Dir: Temp/gno-transpile -// Pkg Path: ../example/gno.land/p/pkg -// Returns -> Temp/gno-transpile/example/gno.land/p/pkg -func ResolvePath(output string, path importPath) (string, error) { - absOutput, err := filepath.Abs(output) + // Make dstPath absolute and join it with output. + absDst, err := filepath.Abs(dstPath) if err != nil { return "", err } - absPkgPath, err := filepath.Abs(string(path)) - if err != nil { - return "", err - } - pkgPath := strings.TrimPrefix(absPkgPath, gnoenv.RootDir()) - - return filepath.Join(absOutput, pkgPath), nil + return filepath.Join(output, absDst), nil } // WriteDirFile write file to the path and also create diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go index 9e9659bfe4f..a92c924e272 100644 --- a/gnovm/cmd/gno/util_test.go +++ b/gnovm/cmd/gno/util_test.go @@ -295,3 +295,50 @@ func createGnoPackages(t *testing.T, tmpDir string) { } } } + +func TestResolvePath(t *testing.T) { + t.Parallel() + + if os.PathSeparator != '/' { + t.Skip("ResolvePath test is only written of UNIX-like filesystems") + } + wd, err := os.Getwd() + require.NoError(t, err) + tt := []struct { + output string + dstPath string + result string + }{ + { + "transpile-result", + "./examples/test/test1.gno.gen.go", + "transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "./examples/test/test1.gno.gen.go", + "/transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "/home/gno/examples/test/test1.gno.gen.go", + "/transpile-result/home/gno/examples/test/test1.gno.gen.go", + }, + { + "result", + "../hello", + filepath.Join("result", filepath.Join(wd, "../hello")), + }, + } + + for _, tc := range tt { + res, err := ResolvePath(tc.output, tc.dstPath) + // ResolvePath should error only in case we can't get the abs path; + // so never in normal conditions. + require.NoError(t, err) + assert.Equal(t, + tc.result, res, + "unexpected result of ResolvePath(%q, %q)", tc.output, tc.dstPath, + ) + } +} diff --git a/gnovm/pkg/gnolang/alloc.go b/gnovm/pkg/gnolang/alloc.go index 495be0d2dc2..6fef5eda834 100644 --- a/gnovm/pkg/gnolang/alloc.go +++ b/gnovm/pkg/gnolang/alloc.go @@ -62,6 +62,7 @@ const ( // allocPackge = 1 allocAmino = _allocBase + _allocPointer + _allocAny allocAminoByte = 10 // XXX + allocHeapItem = _allocBase + _allocPointer + _allocTypedValue ) func NewAllocator(maxBytes int64) *Allocator { @@ -180,6 +181,10 @@ func (alloc *Allocator) AllocateAmino(l int64) { alloc.Allocate(allocAmino + allocAminoByte*l) } +func (alloc *Allocator) AllocateHeapItem() { + alloc.Allocate(allocHeapItem) +} + //---------------------------------------- // constructor utilities. @@ -291,3 +296,8 @@ func (alloc *Allocator) NewType(t Type) Type { alloc.AllocateType() return t } + +func (alloc *Allocator) NewHeapItem(tv TypedValue) *HeapItemValue { + alloc.AllocateHeapItem() + return &HeapItemValue{Value: tv} +} diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 1b5a72a183a..3f2f58b9709 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -31,9 +31,9 @@ func evalTest(debugAddr, in, file string) (out, err string) { stdout := writeNopCloser{bout} stderr := writeNopCloser{berr} debug := in != "" || debugAddr != "" - mode := tests.ImportModeNativePreferred - if strings.HasSuffix(file, "_stdlibs.gno") { - mode = tests.ImportModeStdlibsPreferred + mode := tests.ImportModeStdlibsPreferred + if strings.HasSuffix(file, "_native.gno") { + mode = tests.ImportModeNativePreferred } defer func() { @@ -196,7 +196,8 @@ func TestRemoteDebug(t *testing.T) { func TestRemoteError(t *testing.T) { _, err := evalTest(":xxx", "", debugTarget) t.Log("err:", err) - if !strings.Contains(err, "tcp/xxx: unknown port") { + if !strings.Contains(err, "tcp/xxx: unknown port") && + !strings.Contains(err, "tcp/xxx: nodename nor servname provided, or not known") { t.Error(err) } } diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 24a92a81312..54d808faefc 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -159,6 +159,38 @@ func main() { m.RunMain() } +func BenchmarkPreprocessForLoop(b *testing.B) { + m := NewMachine("test", nil) + c := `package test +func main() { + for i:=0; i<10000; i++ {} +}` + n := MustParseFile("main.go", c) + m.RunFiles(n) + + for i := 0; i < b.N; i++ { + m.RunMain() + } +} + +func BenchmarkIfStatement(b *testing.B) { + m := NewMachine("test", nil) + c := `package test +func main() { + for i:=0; i<10000; i++ { + if i > 10 { + + } + } +}` + n := MustParseFile("main.go", c) + m.RunFiles(n) + + for i := 0; i < b.N; i++ { + m.RunMain() + } +} + func TestDoOpEvalBaseConversion(t *testing.T) { m := NewMachine("test", nil) diff --git a/gnovm/pkg/gnolang/gnolang.proto b/gnovm/pkg/gnolang/gnolang.proto index f7eaa907ec5..9904e26078f 100644 --- a/gnovm/pkg/gnolang/gnolang.proto +++ b/gnovm/pkg/gnolang/gnolang.proto @@ -58,6 +58,8 @@ message FuncValue { google.protobuf.Any closure = 5 [json_name = "Closure"]; string file_name = 6 [json_name = "FileName"]; string pkg_path = 7 [json_name = "PkgPath"]; + string native_pkg = 8 [json_name = "NativePkg"]; + string native_name = 9 [json_name = "NativeName"]; } message MapValue { diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 4ae65211231..fa958798653 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -478,6 +478,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { //---------------------------------------- // type checking (using go/types) +// XXX move to gotypecheck.go. // MemPackageGetter implements the GetMemPackage() method. It is a subset of // [Store], separated for ease of testing. diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index f73a5c58962..6127fa42b07 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -890,7 +890,7 @@ func gno2GoType(t Type) reflect.Type { // If gno2GoTypeMatches(t, rt) is true, a t value can // be converted to an rt native value using gno2GoValue(v, rv). -// This is called when autoNative is true in checkType(). +// This is called when autoNative is true in assertAssignableTo(). // This is used for all native function calls, and also // for testing whether a native value implements a gno interface. func gno2GoTypeMatches(t Type, rt reflect.Type) (result bool) { diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index 564ac0622c2..c6f7e696ea4 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -7,6 +7,34 @@ import ( "strings" ) +// ---------------------------------------- +// Functions centralizing definitions + +// RealmPathPrefix is the prefix used to identify pkgpaths which are meant to +// be realms and as such to have their state persisted. This is used by [IsRealmPath]. +const RealmPathPrefix = "gno.land/r/" + +// ReGnoRunPath is the path used for realms executed in maketx run. +// These are not considered realms, as an exception to the RealmPathPrefix rule. +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) && + // MsgRun pkgPath aren't realms + !ReGnoRunPath.MatchString(pkgPath) +} + +// IsStdlib determines whether s is a pkgpath for a standard library. +func IsStdlib(s string) bool { + // NOTE(morgan): this is likely to change in the future as we add support for + // IBC/ICS and we allow import paths to other chains. It might be good to + // (eventually) follow the same rule as Go, which is: does the first + // element of the import path contain a dot? + return !strings.HasPrefix(s, "gno.land/") +} + // ---------------------------------------- // AST Construction (Expr) // These are copied over from go-amino-x, but produces Gno ASTs. diff --git a/gnovm/pkg/gnolang/helpers_test.go b/gnovm/pkg/gnolang/helpers_test.go new file mode 100644 index 00000000000..af8fa64ac79 --- /dev/null +++ b/gnovm/pkg/gnolang/helpers_test.go @@ -0,0 +1,55 @@ +package gnolang + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsRealmPath(t *testing.T) { + t.Parallel() + tt := []struct { + input string + result bool + }{ + {"gno.land/r/demo/users", true}, + {"gno.land/r/hello", true}, + {"gno.land/p/demo/users", false}, + {"gno.land/p/hello", false}, + {"gno.land/x", false}, + {"std", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsRealmPath(tc.input), + "unexpected IsRealmPath(%q) result", tc.input, + ) + } +} + +func TestIsStdlib(t *testing.T) { + t.Parallel() + + tt := []struct { + s string + result bool + }{ + {"std", true}, + {"math", true}, + {"very/long/path/with_underscores", true}, + {"gno.land/r/demo/users", false}, + {"gno.land/hello", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsStdlib(tc.s), + "IsStdlib(%q)", tc.s, + ) + } +} diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 48a2145af3a..864384ea122 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1956,9 +1956,11 @@ func (m *Machine) PopAsPointer(lx Expr) PointerValue { return ptr case *CompositeLitExpr: // for *RefExpr tv := *m.PopValue() + hv := m.Alloc.NewHeapItem(tv) return PointerValue{ - TV: &tv, // heap alloc - Base: nil, + TV: &hv.Value, + Base: hv, + Index: 0, } default: panic("should not happen") diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 482f4850b6e..2897fdd5306 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1115,11 +1115,21 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { allowedFileExtensions := []string{ ".gno", } + // exceptions to allowedFileExtensions + var rejectedFileExtensions []string + + if IsStdlib(pkgPath) { + // Allows transpilation to work on stdlibs with native fns. + allowedFileExtensions = append(allowedFileExtensions, ".go") + rejectedFileExtensions = []string{".gen.go"} + } + list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || - (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) { + (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) || + endsWith(file.Name(), rejectedFileExtensions) { continue } list = append(list, filepath.Join(dir, file.Name())) diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 0cc30861355..eb67ffcc351 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -50,7 +50,7 @@ func (m *Machine) doOpAddAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -73,7 +73,7 @@ func (m *Machine) doOpSubAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -96,7 +96,7 @@ func (m *Machine) doOpMulAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -119,7 +119,7 @@ func (m *Machine) doOpQuoAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -142,7 +142,7 @@ func (m *Machine) doOpRemAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -165,7 +165,7 @@ func (m *Machine) doOpBandAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -188,7 +188,7 @@ func (m *Machine) doOpBandnAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -211,7 +211,7 @@ func (m *Machine) doOpBorAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) @@ -234,7 +234,7 @@ func (m *Machine) doOpXorAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) if debug { - assertSameTypes(lv.TV.T, rv.T) + debugAssertSameTypes(lv.TV.T, rv.T) } // XXX HACK (until value persistence impl'd) diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 99b56c18a06..a1861ed3aaa 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -45,7 +45,7 @@ func (m *Machine) doOpLor() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set result in lv. @@ -60,7 +60,7 @@ func (m *Machine) doOpLand() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set result in lv. @@ -77,7 +77,7 @@ func (m *Machine) doOpEql() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertEqualityTypes(lv.T, rv.T) + debugAssertEqualityTypes(lv.T, rv.T) } // set result in lv. @@ -94,7 +94,7 @@ func (m *Machine) doOpNeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertEqualityTypes(lv.T, rv.T) + debugAssertEqualityTypes(lv.T, rv.T) } // set result in lv. @@ -111,7 +111,7 @@ func (m *Machine) doOpLss() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -128,7 +128,7 @@ func (m *Machine) doOpLeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -145,7 +145,7 @@ func (m *Machine) doOpGtr() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -162,7 +162,7 @@ func (m *Machine) doOpGeq() { rv := m.PopValue() lv := m.PeekValue(1) // also the result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // set the result in lv. @@ -179,7 +179,7 @@ func (m *Machine) doOpAdd() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // add rv to lv. @@ -193,7 +193,7 @@ func (m *Machine) doOpSub() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // sub rv from lv. @@ -207,7 +207,7 @@ func (m *Machine) doOpBor() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv | rv @@ -221,7 +221,7 @@ func (m *Machine) doOpXor() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv ^ rv @@ -235,7 +235,7 @@ func (m *Machine) doOpMul() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv * rv @@ -249,7 +249,7 @@ func (m *Machine) doOpQuo() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv / rv @@ -263,7 +263,7 @@ func (m *Machine) doOpRem() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv % rv @@ -309,7 +309,7 @@ func (m *Machine) doOpBand() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv & rv @@ -323,7 +323,7 @@ func (m *Machine) doOpBandn() { rv := m.PopValue() lv := m.PeekValue(1) // also result if debug { - assertSameTypes(lv.T, rv.T) + debugAssertSameTypes(lv.T, rv.T) } // lv &^ rv diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 12e0f9e26e3..c7e8ffd600c 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -957,7 +957,7 @@ func (m *Machine) doOpSwitchClauseCase() { // eval whether cv == tv. if debug { - assertEqualityTypes(cv.T, tv.T) + debugAssertEqualityTypes(cv.T, tv.T) } match := isEql(m.Store, cv, tv) if match { diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 84c39716eec..7a8a885bcf0 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -26,6 +26,10 @@ func (m *Machine) doOpInc() { panic("expected lv.V to be nil for primitive type for OpInc") } } + + // here we can't just switch on the value type + // because it could be a type alias + // type num int switch baseOf(lv.T) { case IntType: lv.SetInt(lv.GetInt() + 1) diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go index f2afc393d05..24d70b5dd84 100644 --- a/gnovm/pkg/gnolang/ownership.go +++ b/gnovm/pkg/gnolang/ownership.go @@ -129,6 +129,7 @@ var ( _ Object = &BoundMethodValue{} _ Object = &MapValue{} _ Object = &Block{} + _ Object = &HeapItemValue{} ) type ObjectInfo struct { @@ -332,11 +333,7 @@ func (tv *TypedValue) GetFirstObject(store Store) Object { // something in it; in that case, ignore the base. That will // likely require maybe a preparation step in persistence // ( or unlikely, a second type of ref-counting). - if cv.Base != nil { - return cv.Base.(Object) - } else { - return cv.TV.GetFirstObject(store) - } + return cv.Base.(Object) case *ArrayValue: return cv case *SliceValue: @@ -359,6 +356,9 @@ func (tv *TypedValue) GetFirstObject(store Store) Object { oo := store.GetObject(cv.ObjectID) tv.V = oo return oo + case *HeapItemValue: + // should only appear in PointerValue.Base + panic("heap item value should only appear as a pointer's base") default: return nil } diff --git a/gnovm/pkg/gnolang/package.go b/gnovm/pkg/gnolang/package.go index 4d4cb5aaf45..e2fdb2580ca 100644 --- a/gnovm/pkg/gnolang/package.go +++ b/gnovm/pkg/gnolang/package.go @@ -31,6 +31,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( // &NativeValue{}, &Block{}, RefValue{}, + &HeapItemValue{}, //---------------------------------------- // Realm/Object diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index d566b071860..177b99f2b94 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -754,66 +754,75 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *BinaryExpr: lt := evalStaticTypeOf(store, last, n.Left) rt := evalStaticTypeOf(store, last, n.Right) - // Special (recursive) case if shift and right isn't uint. - isShift := n.Op == SHL || n.Op == SHR - if isShift && baseOf(rt) != UintType { - // convert n.Right to (gno) uint type, - rn := Expr(Call("uint", n.Right)) - // reset/create n2 to preprocess right child. - n2 := &BinaryExpr{ - Left: n.Left, - Op: n.Op, - Right: rn, - } - resn := Preprocess(store, last, n2) - return resn, TRANS_CONTINUE + + lcx, lic := n.Left.(*ConstExpr) + rcx, ric := n.Right.(*ConstExpr) + + if debug { + debug.Printf("Trans_leave, BinaryExpr, OP: %v, lx: %v, rx: %v, lt: %v, rt: %v, isLeftConstExpr: %v, isRightConstExpr %v, isLeftUntyped: %v, isRightUntyped: %v \n", n.Op, n.Left, n.Right, lt, rt, lic, ric, isUntyped(lt), isUntyped(rt)) } - // Left and right hand expressions must evaluate to a boolean typed value if - // the operation is a logical AND or OR. - if (n.Op == LAND || n.Op == LOR) && (lt.Kind() != BoolKind || rt.Kind() != BoolKind) { - panic("operands of boolean operators must evaluate to boolean typed values") + // Special (recursive) case if shift and right isn't uint. + isShift := n.Op == SHL || n.Op == SHR + if isShift { + // check LHS type compatibility + n.checkShiftLhs(lt) + // checkOrConvert RHS + if baseOf(rt) != UintType { + // convert n.Right to (gno) uint type, + rn := Expr(Call("uint", n.Right)) + // reset/create n2 to preprocess right child. + n2 := &BinaryExpr{ + Left: n.Left, + Op: n.Op, + Right: rn, + } + resn := Preprocess(store, last, n2) + return resn, TRANS_CONTINUE + } + // Then, evaluate the expression. + if lic && ric { + cx := evalConst(store, last, n) + return cx, TRANS_CONTINUE + } + return n, TRANS_CONTINUE } + // general cases + n.AssertCompatible(lt, rt) // check compatibility against binaryExprs other than shift expr // General case. - lcx, lic := n.Left.(*ConstExpr) - rcx, ric := n.Right.(*ConstExpr) if lic { if ric { // Left const, Right const ---------------------- // Replace with *ConstExpr if const operands. // First, convert untyped as necessary. - if !isShift { - cmp := cmpSpecificity(lcx.T, rcx.T) - if cmp < 0 { - // convert n.Left to right type. - checkOrConvertType(store, last, &n.Left, rcx.T, false) - } else if cmp == 0 { - // NOTE: the following doesn't work. - // TODO: make it work. - // convert n.Left to right type, - // or check for compatibility. - // (the other way around would work too) - // checkOrConvertType(store, last, n.Left, rcx.T, false) - } else { - // convert n.Right to left type. - checkOrConvertType(store, last, &n.Right, lcx.T, false) - } + if !shouldSwapOnSpecificity(lcx.T, rcx.T) { + // convert n.Left to right type. + checkOrConvertType(store, last, &n.Left, rcx.T, false) + } else { + // convert n.Right to left type. + checkOrConvertType(store, last, &n.Right, lcx.T, false) } // Then, evaluate the expression. cx := evalConst(store, last, n) return cx, TRANS_CONTINUE } else if isUntyped(lcx.T) { // Left untyped const, Right not ---------------- - if rnt, ok := rt.(*NativeType); ok { - if isShift { + if rnt, ok := rt.(*NativeType); ok { // untyped -> gno(native), e.g. 1*time.Second + if isShift { // RHS of shift should not be native panic("should not happen") } // get concrete native base type. - pt := go2GnoBaseType(rnt.Type).(PrimitiveType) + pt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) + } // convert n.Left to pt type, checkOrConvertType(store, last, &n.Left, pt, false) - // convert n.Right to (gno) pt type, + // if check pass, convert n.Right to (gno) pt type, rn := Expr(Call(pt.String(), n.Right)) // and convert result back. tx := constType(n, rnt) @@ -823,100 +832,127 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { Op: n.Op, Right: rn, } - resn := Node(Call(tx, n2)) + resn := Node(Call(tx, n2)) // this make current node to gonative{xxx} resn = Preprocess(store, last, resn) return resn, TRANS_CONTINUE // NOTE: binary operations are always computed in // gno, never with reflect. } else { - if isShift { - // nothing to do, right type is (already) uint type. - // we don't yet know what this type should be, - // but another checkOrConvertType() later does. - // (e.g. from AssignStmt or other). - } else { - // convert n.Left to right type. - checkOrConvertType(store, last, &n.Left, rt, false) - } + // convert n.Left to right type. + checkOrConvertType(store, last, &n.Left, rt, false) } - } else if lcx.T == nil { + } else if lcx.T == nil { // LHS is nil. // convert n.Left to typed-nil type. checkOrConvertType(store, last, &n.Left, rt, false) } - } else if ric { + } else if ric { // right is const, left is not if isUntyped(rcx.T) { // Left not, Right untyped const ---------------- - if isShift { - if baseOf(rt) != UintType { - // convert n.Right to (gno) uint type. - checkOrConvertType(store, last, &n.Right, UintType, false) - } else { - // leave n.Left as is and baseOf(n.Right) as UintType. + if lnt, ok := lt.(*NativeType); ok { + // get concrete native base type. + pt, ok := go2GnoBaseType(lnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + rt.String(), + lnt.String())) } - } else { - if lnt, ok := lt.(*NativeType); ok { - // get concrete native base type. - pt := go2GnoBaseType(lnt.Type).(PrimitiveType) - // convert n.Left to (gno) pt type, - ln := Expr(Call(pt.String(), n.Left)) - // convert n.Right to pt type, - checkOrConvertType(store, last, &n.Right, pt, false) - // and convert result back. - tx := constType(n, lnt) - // reset/create n2 to preprocess left child. - n2 := &BinaryExpr{ - Left: ln, - Op: n.Op, - Right: n.Right, - } - resn := Node(Call(tx, n2)) - resn = Preprocess(store, last, resn) - return resn, TRANS_CONTINUE - // NOTE: binary operations are always computed in - // gno, never with reflect. - } else { - // convert n.Right to left type. - checkOrConvertType(store, last, &n.Right, lt, false) + // convert n.Left to (gno) pt type, + ln := Expr(Call(pt.String(), n.Left)) + // convert n.Right to pt type, + checkOrConvertType(store, last, &n.Right, pt, false) + // and convert result back. + tx := constType(n, lnt) + // reset/create n2 to preprocess left child. + n2 := &BinaryExpr{ + Left: ln, + Op: n.Op, + Right: n.Right, } + resn := Node(Call(tx, n2)) + resn = Preprocess(store, last, resn) + return resn, TRANS_CONTINUE + // NOTE: binary operations are always computed in + // gno, never with reflect. + } else { + // convert n.Right to left type. + checkOrConvertType(store, last, &n.Right, lt, false) } - } else if rcx.T == nil { - // convert n.Right to typed-nil type. + } else if rcx.T == nil { // RHS is nil + // refer to tests/files/types/eql_0f20.gno checkOrConvertType(store, last, &n.Right, lt, false) } } else { // Left not const, Right not const ------------------ - if n.Op == EQL || n.Op == NEQ { - // If == or !=, no conversions. - } else if lnt, ok := lt.(*NativeType); ok && isNative(rt) { - if debug { - if !isShift { - assertSameTypes(lt, rt) - } - } - // If left and right are native type, and same type + if lnt, ok := lt.(*NativeType); ok { + // If left and right are native type, // convert left and right to gno, then // convert result back to native. - // - // get concrete native base type. - if lt.TypeID() != rt.TypeID() { + // get native base type. + lpt, ok := go2GnoBaseType(lnt.Type).(PrimitiveType) + if !ok { panic(fmt.Sprintf( - "incompatible types in binary expression: %v %v %v", + "unexpected type pair: cannot use %s as %s", + rt.String(), + lnt.String())) + } + // convert n.Left to (gno) pt type, + ln := Expr(Call(lpt.String(), n.Left)) + + rn := n.Right + // e.g. native: time.Second + time.Second, convert both(or it will be converted recursively) + // see tests/files/types/time_native.gno + if rnt, ok := rt.(*NativeType); ok { + rpt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) + } + // check assignable, if pass, convert right to gno first + assertAssignableTo(lpt, rpt, false) // both primitive types + rn = Expr(Call(rpt.String(), n.Right)) + } else { // rt not native + panic(fmt.Sprintf( + "incompatible operands in binary expression: %s %s %s", lt.TypeID(), n.Op, rt.TypeID())) } - pt := go2GnoBaseType(lnt.Type).(PrimitiveType) + + // and convert result back. + tx := constType(n, lnt) + // reset/create n2 to preprocess + // children. + n2 := &BinaryExpr{ + Left: ln, + Op: n.Op, + Right: rn, + } + resn := Node(Call(tx, n2)) + resn = Preprocess(store, last, resn) + return resn, TRANS_CONTINUE + // NOTE: binary operations are always + // computed in gno, never with + // reflect. + } else if rnt, ok := rt.(*NativeType); ok { // e.g. a * time.Second + pt, ok := go2GnoBaseType(rnt.Type).(PrimitiveType) + if !ok { + panic(fmt.Sprintf( + "unexpected type pair: cannot use %s as %s", + lt.String(), + rnt.String())) + } // convert n.Left to (gno) pt type, - ln := Expr(Call(pt.String(), n.Left)) + rn := Expr(Call(pt.String(), n.Right)) // convert n.Right to pt or uint type, - rn := n.Right + ln := n.Left if isShift { - if baseOf(rt) != UintType { - rn = Expr(Call("uint", n.Right)) - } + panic("should not happen") } else { - rn = Expr(Call(pt.String(), n.Right)) + checkOrConvertType(store, last, &n.Left, pt, false) } // and convert result back. - tx := constType(n, lnt) + tx := constType(n, rnt) // reset/create n2 to preprocess // children. n2 := &BinaryExpr{ @@ -930,8 +966,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: binary operations are always // computed in gno, never with // reflect. - } else if n.Op == SHL || n.Op == SHR { - // shift operator, nothing yet to do. } else { // non-shift non-const binary operator. liu, riu := isUntyped(lt), isUntyped(rt) @@ -942,24 +976,20 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { "incompatible types in binary expression: %v %v %v", lt.TypeID(), n.Op, rt.TypeID())) } - } else { + } else { // left untyped, right typed checkOrConvertType(store, last, &n.Left, rt, false) } - } else { - if riu { - checkOrConvertType(store, last, &n.Right, lt, false) + } else if riu { // left typed, right untyped + checkOrConvertType(store, last, &n.Right, lt, false) + } else { // both typed, refer to 0a1g.gno + if !shouldSwapOnSpecificity(lt, rt) { + checkOrConvertType(store, last, &n.Left, rt, false) } else { - // left is untyped, right is not. - if lt.TypeID() != rt.TypeID() { - panic(fmt.Sprintf( - "incompatible types in binary expression: %v %v %v", - lt.TypeID(), n.Op, rt.TypeID())) - } + checkOrConvertType(store, last, &n.Right, lt, false) } } } } - // TRANS_LEAVE ----------------------- case *CallExpr: // Func type evaluation. @@ -1178,12 +1208,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { for i, tv := range argTVs { if hasVarg { if (len(spts) - 1) <= i { - checkType(tv.T, spts[len(spts)-1].Type.Elem(), true) + assertAssignableTo(tv.T, spts[len(spts)-1].Type.Elem(), true) } else { - checkType(tv.T, spts[i].Type, true) + assertAssignableTo(tv.T, spts[i].Type, true) } } else { - checkType(tv.T, spts[i].Type, true) + assertAssignableTo(tv.T, spts[i].Type, true) } } } else { @@ -1224,7 +1254,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case StringKind, ArrayKind, SliceKind: // Replace const index with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerType(store, last, n.Index) + checkOrConvertIntegerKind(store, last, n.Index) case MapKind: mt := baseOf(gnoTypeOf(store, dt)).(*MapType) checkOrConvertType(store, last, &n.Index, mt.Key, false) @@ -1238,9 +1268,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *SliceExpr: // Replace const L/H/M with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerType(store, last, n.Low) - checkOrConvertIntegerType(store, last, n.High) - checkOrConvertIntegerType(store, last, n.Max) + checkOrConvertIntegerKind(store, last, n.Low) + checkOrConvertIntegerKind(store, last, n.High) + checkOrConvertIntegerKind(store, last, n.Max) // TRANS_LEAVE ----------------------- case *TypeAssertExpr: @@ -1279,6 +1309,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *UnaryExpr: xt := evalStaticTypeOf(store, last, n.X) + n.AssertCompatible(xt) if xnt, ok := xt.(*NativeType); ok { // get concrete native base type. pt := go2GnoBaseType(xnt.Type).(PrimitiveType) @@ -1326,12 +1357,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } case *ArrayType: for i := 0; i < len(n.Elts); i++ { - checkOrConvertType(store, last, &n.Elts[i].Key, IntType, false) + convertType(store, last, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) } case *SliceType: for i := 0; i < len(n.Elts); i++ { - checkOrConvertType(store, last, &n.Elts[i].Key, IntType, false) + convertType(store, last, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) } case *MapType: @@ -1572,6 +1603,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *AssignStmt: + n.AssertCompatible(store, last) // NOTE: keep DEFINE and ASSIGN in sync. if n.Op == DEFINE { // Rhs consts become default *ConstExprs. @@ -1579,22 +1611,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: does nothing if rx is "nil". convertIfConst(store, last, rx) } + if len(n.Lhs) > len(n.Rhs) { - // Unpack n.Rhs[0] to n.Lhs[:] - if len(n.Rhs) != 1 { - panic("should not happen") - } switch cx := n.Rhs[0].(type) { case *CallExpr: // Call case: a, b := x(...) ift := evalStaticTypeOf(store, last, cx.Func) cft := getGnoFuncTypeOf(store, ift) - if len(n.Lhs) != len(cft.Results) { - panic(fmt.Sprintf( - "assignment mismatch: "+ - "%d variables but %s returns %d values", - len(n.Lhs), cx.Func.String(), len(cft.Results))) - } for i, lx := range n.Lhs { ln := lx.(*NameExpr).Name rf := cft.Results[i] @@ -1602,11 +1625,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(ln, anyValue(rf.Type)) } case *TypeAssertExpr: - // Type-assert case: a, ok := x.(type) - if len(n.Lhs) != 2 { - panic("should not happen") - } - cx.HasOK = true lhs0 := n.Lhs[0].(*NameExpr).Name lhs1 := n.Lhs[1].(*NameExpr).Name tt := evalStaticType(store, last, cx.Type) @@ -1614,11 +1632,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(lhs0, anyValue(tt)) last.Define(lhs1, anyValue(BoolType)) case *IndexExpr: - // Index case: v, ok := x[k], x is map. - if len(n.Lhs) != 2 { - panic("should not happen") - } - cx.HasOK = true lhs0 := n.Lhs[0].(*NameExpr).Name lhs1 := n.Lhs[1].(*NameExpr).Name @@ -1650,56 +1663,27 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) - // If this is an assignment operation, ensure there's only 1 - // expr on lhs/rhs. - if n.Op != ASSIGN && - (len(n.Lhs) != 1 || len(n.Rhs) != 1) { - panic("assignment operator " + n.Op.TokenString() + - " requires only one expression on lhs and rhs") - } - // NOTE: Keep in sync with DEFINE above. - if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { - // Special case if shift assign <<= or >>=. - checkOrConvertType(store, last, &n.Rhs[0], UintType, false) - } else if len(n.Lhs) > len(n.Rhs) { - // TODO dry code w/ above. - // Unpack n.Rhs[0] to n.Lhs[:] - if len(n.Rhs) != 1 { - panic("should not happen") - } - switch cx := n.Rhs[0].(type) { - case *CallExpr: - // Call case: a, b = x(...) - ift := evalStaticTypeOf(store, last, cx.Func) - cft := getGnoFuncTypeOf(store, ift) - if len(n.Lhs) != len(cft.Results) { - panic(fmt.Sprintf( - "assignment mismatch: "+ - "%d variables but %s returns %d values", - len(n.Lhs), cx.Func.String(), len(cft.Results))) - } - case *TypeAssertExpr: - // Type-assert case: a, ok := x.(type) - if len(n.Lhs) != 2 { + if len(n.Lhs) > len(n.Rhs) { + // check is done in assertCompatible + } else { // len(Lhs) == len(Rhs) + if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { + if len(n.Lhs) != 1 || len(n.Rhs) != 1 { panic("should not happen") } - cx.HasOK = true - case *IndexExpr: - // Index case: v, ok := x[k], x is map. - if len(n.Lhs) != 2 { - panic("should not happen") + // Special case if shift assign <<= or >>=. + convertType(store, last, &n.Rhs[0], UintType) + } else if n.Op == ADD_ASSIGN || n.Op == SUB_ASSIGN || n.Op == MUL_ASSIGN || n.Op == QUO_ASSIGN || n.Op == REM_ASSIGN { + // e.g. a += b, single value for lhs and rhs, + lt := evalStaticTypeOf(store, last, n.Lhs[0]) + checkOrConvertType(store, last, &n.Rhs[0], lt, true) + } else { // all else, like BAND_ASSIGN, etc + // General case: a, b = x, y. + for i, lx := range n.Lhs { + lt := evalStaticTypeOf(store, last, lx) + // if lt is interface, nothing will happen + checkOrConvertType(store, last, &n.Rhs[i], lt, true) } - cx.HasOK = true - default: - panic("should not happen") - } - } else { - // General case: a, b = x, y. - for i, lx := range n.Lhs { - lt := evalStaticTypeOf(store, last, lx) - // converts if rx is "nil". - checkOrConvertType(store, last, &n.Rhs[i], lt, false) } } } @@ -1738,19 +1722,24 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { panic("should not happen") } + case *IncDecStmt: + xt := evalStaticTypeOf(store, last, n.X) + n.AssertCompatible(xt) + // TRANS_LEAVE ----------------------- case *ForStmt: // Cond consts become bool *ConstExprs. - checkOrConvertType(store, last, &n.Cond, BoolType, false) + checkOrConvertBoolKind(store, last, n.Cond) // TRANS_LEAVE ----------------------- case *IfStmt: // Cond consts become bool *ConstExprs. - checkOrConvertType(store, last, &n.Cond, BoolType, false) + checkOrConvertBoolKind(store, last, n.Cond) // TRANS_LEAVE ----------------------- case *RangeStmt: // NOTE: k,v already defined @ TRANS_BLOCK. + n.AssertCompatible(store, last) // TRANS_LEAVE ----------------------- case *ReturnStmt: @@ -2439,47 +2428,25 @@ func isConstType(x Expr) bool { return ok } -func cmpSpecificity(t1, t2 Type) int { - t1s, t2s := 0, 0 - if t1p, ok := t1.(PrimitiveType); ok { - t1s = t1p.Specificity() - } - if t2p, ok := t2.(PrimitiveType); ok { - t2s = t2p.Specificity() - } - if t1s < t2s { - // NOTE: higher specificity has lower value, so backwards. - return 1 - } else if t1s == t2s { - return 0 - } else { - return -1 - } -} - -// 1. convert x to t if x is *ConstExpr. -// 2. otherwise, assert that x can be coerced to t. -// autoNative is usually false, but set to true -// for native function calls, where gno values are -// automatically converted to native go types. -// NOTE: also see checkOrConvertIntegerType() +// check before convert type func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative bool) { + if debug { + debug.Printf("checkOrConvertType, *x: %v:, t:%v \n", *x, t) + } if cx, ok := (*x).(*ConstExpr); ok { - convertConst(store, last, cx, t) + if _, ok := t.(*NativeType); !ok { // not native type, refer to time4_native.gno. + // e.g. int(1) == int8(1) + assertAssignableTo(cx.T, t, autoNative) + } } else if bx, ok := (*x).(*BinaryExpr); ok && (bx.Op == SHL || bx.Op == SHR) { - // "push" expected type into shift binary's left operand. + // "push" expected type into shift binary's left operand. recursively. checkOrConvertType(store, last, &bx.Left, t, autoNative) } else if *x != nil { // XXX if x != nil && t != nil { - // check type xt := evalStaticTypeOf(store, last, *x) if t != nil { - checkType(xt, t, autoNative) + assertAssignableTo(xt, t, autoNative) } - // convert type - if isUntyped(xt) { // convert if x is untyped literal - if t == nil { - t = defaultTypeOf(xt, nil) - } + if isUntyped(xt) { // Push type into expr if qualifying binary expr. if bx, ok := (*x).(*BinaryExpr); ok { switch bx.Op { @@ -2497,20 +2464,47 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative // default: } } + } + } + // convert recursively + convertType(store, last, x, t) +} + +// 1. convert x to t if x is *ConstExpr. +// 2. otherwise, assert that x can be coerced to t. +// autoNative is usually false, but set to true +// for native function calls, where gno values are +// automatically converted to native go types. +// NOTE: also see checkOrConvertIntegerKind() +func convertType(store Store, last BlockNode, x *Expr, t Type) { + if debug { + debug.Printf("convertType, *x: %v:, t:%v \n", *x, t) + } + if cx, ok := (*x).(*ConstExpr); ok { + convertConst(store, last, cx, t) + } else if *x != nil { + xt := evalStaticTypeOf(store, last, *x) + if isUntyped(xt) { + if t == nil { + t = defaultTypeOf(xt) + } + if debug { + debug.Printf("default type of t: %v \n", t) + } // convert x to destination type t - convertType(store, last, x, t) + doConvertType(store, last, x, t) } else { // if one side is declared name type and the other side is unnamed type if isNamedConversion(xt, t) { // covert right (xt) to the type of the left (t) - convertType(store, last, x, t) + doConvertType(store, last, x, t) } } } } // convert x to destination type t -func convertType(store Store, last BlockNode, x *Expr, t Type) { +func doConvertType(store Store, last BlockNode, x *Expr, t Type) { cx := Expr(Call(constType(nil, t), *x)) cx = Preprocess(store, last, cx).(Expr) *x = cx @@ -2564,7 +2558,7 @@ func convertIfConst(store Store, last BlockNode, x Expr) { func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { if t != nil && t.Kind() == InterfaceKind { if cx.T != nil { - checkType(cx.T, t, false) + assertAssignableTo(cx.T, t, false) } t = nil // signifies to convert to default type. } @@ -2578,232 +2572,6 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } -// Assert that xt can be assigned as dt (dest type). -// If autoNative is true, a broad range of xt can match against -// a target native dt type, if and only if dt is a native type. -func checkType(xt Type, dt Type, autoNative bool) { - // Special case if dt is interface kind: - if dt.Kind() == InterfaceKind { - if idt, ok := baseOf(dt).(*InterfaceType); ok { - if idt.IsEmptyInterface() { - // if dt is an empty Gno interface, any x ok. - return // ok - } else if idt.IsImplementedBy(xt) { - // if dt implements idt, ok. - return // ok - } else { - panic(fmt.Sprintf( - "%s does not implement %s", - xt.String(), - dt.String())) - } - } else if ndt, ok := baseOf(dt).(*NativeType); ok { - nidt := ndt.Type - if nidt.NumMethod() == 0 { - // if dt is an empty Go native interface, ditto. - return // ok - } else if nxt, ok := baseOf(xt).(*NativeType); ok { - // if xt has native base, do the naive native. - if nxt.Type.AssignableTo(nidt) { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s", - nxt.String(), - nidt.String())) - } - } else if pxt, ok := baseOf(xt).(*PointerType); ok { - nxt, ok := pxt.Elt.(*NativeType) - if !ok { - panic(fmt.Sprintf( - "pointer to non-native type cannot satisfy non-empty native interface; %s doesn't implement %s", - pxt.String(), - nidt.String())) - } - // if xt has native base, do the naive native. - if reflect.PointerTo(nxt.Type).AssignableTo(nidt) { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s", - pxt.String(), - nidt.String())) - } - } else { - panic(fmt.Sprintf( - "unexpected type pair: cannot use %s as %s", - xt.String(), - dt.String())) - } - } else { - panic("should not happen") - } - } - // Special case if xt or dt is *PointerType to *NativeType, - // convert to *NativeType of pointer kind. - if pxt, ok := xt.(*PointerType); ok { - // *gonative{x} is(to) gonative{*x} - //nolint:misspell - if enxt, ok := pxt.Elt.(*NativeType); ok { - xt = &NativeType{ - Type: reflect.PointerTo(enxt.Type), - } - } - } - if pdt, ok := dt.(*PointerType); ok { - // *gonative{x} is gonative{*x} - if endt, ok := pdt.Elt.(*NativeType); ok { - dt = &NativeType{ - Type: reflect.PointerTo(endt.Type), - } - } - } - // Special case of xt or dt is *DeclaredType, - // allow implicit conversion unless both are declared. - // TODO simplify with .IsNamedType(). - if dxt, ok := xt.(*DeclaredType); ok { - if ddt, ok := dt.(*DeclaredType); ok { - // types must match exactly. - if !dxt.sealed && !ddt.sealed && - dxt.PkgPath == ddt.PkgPath && - dxt.Name == ddt.Name { // not yet sealed - return // ok - } else if dxt.TypeID() == ddt.TypeID() { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - dxt.String(), - ddt.String())) - } - } else { - // special case if implicitly named primitive type. - // TODO simplify with .IsNamedType(). - if _, ok := dt.(PrimitiveType); ok { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - dxt.String(), - dt.String())) - } else { - // carry on with baseOf(dxt) - xt = dxt.Base - } - } - } else if ddt, ok := dt.(*DeclaredType); ok { - // special case if implicitly named primitive type. - // TODO simplify with .IsNamedType(). - if _, ok := xt.(PrimitiveType); ok { - panic(fmt.Sprintf( - "cannot use %s as %s without explicit conversion", - xt.String(), - ddt.String())) - } else { - // carry on with baseOf(ddt) - dt = ddt.Base - } - } - // General cases. - switch cdt := dt.(type) { - case PrimitiveType: - // if xt is untyped, ensure dt is compatible. - switch xt { - case UntypedBoolType: - if dt.Kind() == BoolKind { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use untyped bool as %s", - dt.Kind())) - } - case UntypedStringType: - if dt.Kind() == StringKind { - return // ok - } else { - panic(fmt.Sprintf( - "cannot use untyped string as %s", - dt.Kind())) - } - case UntypedRuneType, UntypedBigintType: - switch dt.Kind() { - case IntKind, Int8Kind, Int16Kind, Int32Kind, - Int64Kind, UintKind, Uint8Kind, Uint16Kind, - Uint32Kind, Uint64Kind: - return // ok - default: - panic(fmt.Sprintf( - "cannot use untyped rune as %s", - dt.Kind())) - } - default: - if isUntyped(xt) { - panic("unexpected untyped type") - } - if xt.TypeID() == cdt.TypeID() { - return // ok - } - } - case *PointerType: - if pt, ok := xt.(*PointerType); ok { - checkType(pt.Elt, cdt.Elt, false) - return // ok - } - case *ArrayType: - if at, ok := xt.(*ArrayType); ok { - checkType(at.Elt, cdt.Elt, false) - return // ok - } - case *SliceType: - if st, ok := xt.(*SliceType); ok { - checkType(st.Elt, cdt.Elt, false) - return // ok - } - case *MapType: - if mt, ok := xt.(*MapType); ok { - checkType(mt.Key, cdt.Key, false) - checkType(mt.Value, cdt.Value, false) - return // ok - } - case *FuncType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *InterfaceType: - panic("should not happen") - case *DeclaredType: - panic("should not happen") - case *StructType, *PackageType, *ChanType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *TypeType: - if xt.TypeID() == cdt.TypeID() { - return // ok - } - case *NativeType: - if !autoNative { - if xt.TypeID() == cdt.TypeID() { - return // ok - } - } else { - // autoNative, so check whether matches. - // xt: any type but a *DeclaredType; could be native. - // cdt: actual concrete native target type. - // ie, if cdt can match against xt. - if gno2GoTypeMatches(xt, cdt.Type) { - return // ok - } - } - default: - panic(fmt.Sprintf( - "unexpected type %s", - dt.String())) - } - panic(fmt.Sprintf( - "cannot use %s as %s", - xt.String(), - dt.String())) -} - // Returns any names not yet defined nor predefined in expr. These happen // upon transcribe:enter from the top, so value paths cannot be used. If no // names are un and x is TypeExpr, evalStaticType(store,last, x) must not @@ -3016,18 +2784,40 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { return } -// like checkOrConvertType() but for any integer type. -func checkOrConvertIntegerType(store Store, last BlockNode, x Expr) { +// like checkOrConvertType() but for any typed bool kind. +func checkOrConvertBoolKind(store Store, last BlockNode, x Expr) { + if cx, ok := x.(*ConstExpr); ok { + convertConst(store, last, cx, BoolType) + } else if x != nil { + xt := evalStaticTypeOf(store, last, x) + checkBoolKind(xt) + } +} + +// assert that xt is a typed bool kind. +func checkBoolKind(xt Type) { + switch xt.Kind() { + case BoolKind: + return // ok + default: + panic(fmt.Sprintf( + "expected typed bool kind, but got %v", + xt.Kind())) + } +} + +// like checkOrConvertType() but for any typed integer kind. +func checkOrConvertIntegerKind(store Store, last BlockNode, x Expr) { if cx, ok := x.(*ConstExpr); ok { convertConst(store, last, cx, IntType) } else if x != nil { xt := evalStaticTypeOf(store, last, x) - checkIntegerType(xt) + checkIntegerKind(xt) } } -// assert that xt can be assigned as an integer type. -func checkIntegerType(xt Type) { +// assert that xt is a typed integer kind. +func checkIntegerKind(xt Type) { switch xt.Kind() { case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind, UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind, @@ -3035,7 +2825,7 @@ func checkIntegerType(xt Type) { return // ok default: panic(fmt.Sprintf( - "expected integer type, but got %v", + "expected typed integer kind, but got %v", xt.Kind())) } } diff --git a/gnovm/pkg/gnolang/preprocess_test.go b/gnovm/pkg/gnolang/preprocess_test.go index 2419a385e14..49e6d53fd3d 100644 --- a/gnovm/pkg/gnolang/preprocess_test.go +++ b/gnovm/pkg/gnolang/preprocess_test.go @@ -29,7 +29,7 @@ func main() { defer func() { err := recover() - assert.Contains(t, fmt.Sprint(err), "incompatible types in binary expression") + assert.Contains(t, fmt.Sprint(err), "incompatible operands in binary expression") }() Preprocess(store, pn, n) } @@ -54,7 +54,7 @@ func main() { defer func() { err := recover() - assert.Contains(t, fmt.Sprint(err), "incompatible types in binary expression") + assert.Contains(t, fmt.Sprint(err), "incompatible operands in binary expression") }() Preprocess(store, pn, n) } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 85f94d4fcbe..3a55b2e14b4 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "reflect" - "regexp" "strings" ) @@ -148,6 +147,12 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if po.GetObjectID().PkgID != rlm.ID { panic("cannot modify external-realm or non-realm object") } + + // XXX check if this boosts performance + // XXX with broad integration benchmarking. + // XXX if co == xo { + // XXX } + // From here on, po is real (not new-real). // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) // More appends happen during FinalizeRealmTransactions(). (second+ gen) @@ -724,18 +729,6 @@ func (rlm *Realm) saveObject(store Store, oo Object) { if oid.IsZero() { panic("unexpected zero object id") } - /* XXX DELETE - // ensure all types were already saved (@ preprocessor). - if debug { - types := getUnsavedTypes(oo, nil) - for _, typ := range types { - tid := typ.TypeID() - if store.GetType(tid) == nil { - panic("missing type") - } - } - } - */ // set hash to escape index. if oo.GetIsNewEscaped() { oo.SetIsNewEscaped(false) @@ -823,11 +816,10 @@ func getChildObjects(val Value, more []Value) []Value { case DataByteValue: panic("cannot get children from data byte objects") case PointerValue: - if cv.Base != nil { - more = getSelfOrChildObjects(cv.Base, more) - } else { - more = getSelfOrChildObjects(cv.TV.V, more) + if cv.Base == nil { + panic("should not happen") } + more = getSelfOrChildObjects(cv.Base, more) return more case *ArrayValue: for _, ctv := range cv.List { @@ -869,8 +861,14 @@ func getChildObjects(val Value, more []Value) []Value { for _, ctv := range cv.Values { more = getSelfOrChildObjects(ctv.V, more) } + // Generally the parent block must also be persisted. + // Otherwise NamePath may not resolve when referencing + // a parent block. more = getSelfOrChildObjects(cv.Parent, more) return more + case *HeapItemValue: + more = getSelfOrChildObjects(cv.Value.V, more) + return more case *NativeValue: panic("native values not supported") default: @@ -936,7 +934,7 @@ func copyMethods(methods []TypedValue) []TypedValue { // gets saved (e.g. from *Machine.savePackage()). res[i] = TypedValue{ T: copyTypeWithRefs(mtv.T), - V: copyValueWithRefs(nil, mtv.V), + V: copyValueWithRefs(mtv.V), } } return res @@ -1055,7 +1053,7 @@ func copyTypeWithRefs(typ Type) Type { // persistence bytes serialization. // Also checks for integrity of immediate children -- they must already be // persistent (real), and not dirty, or else this function panics. -func copyValueWithRefs(parent Object, val Value) Value { +func copyValueWithRefs(val Value) Value { switch cv := val.(type) { case nil: return nil @@ -1068,33 +1066,25 @@ func copyValueWithRefs(parent Object, val Value) Value { case DataByteValue: panic("cannot copy data byte value with references") case PointerValue: - if cv.Base != nil { - return PointerValue{ - /* - already represented in .Base[Index]: - TypedValue: &TypedValue{ - T: cv.TypedValue.T, - V: copyValueWithRefs(cv.TypedValue.V), - }, - */ - Base: toRefValue(parent, cv.Base), - Index: cv.Index, - } - } else { - etv := refOrCopyValue(parent, *cv.TV) - return PointerValue{ - TV: &etv, - /* - Base: nil, - Index: 0, - */ - } + if cv.Base == nil { + panic("should not happen") + } + return PointerValue{ + /* + already represented in .Base[Index]: + TypedValue: &TypedValue{ + T: cv.TypedValue.T, + V: copyValueWithRefs(cv.TypedValue.V), + }, + */ + Base: toRefValue(cv.Base), + Index: cv.Index, } case *ArrayValue: if cv.Data == nil { list := make([]TypedValue, len(cv.List)) for i, etv := range cv.List { - list[i] = refOrCopyValue(cv, etv) + list[i] = refOrCopyValue(etv) } return &ArrayValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1108,7 +1098,7 @@ func copyValueWithRefs(parent Object, val Value) Value { } case *SliceValue: return &SliceValue{ - Base: toRefValue(parent, cv.Base), + Base: toRefValue(cv.Base), Offset: cv.Offset, Length: cv.Length, Maxcap: cv.Maxcap, @@ -1116,7 +1106,7 @@ func copyValueWithRefs(parent Object, val Value) Value { case *StructValue: fields := make([]TypedValue, len(cv.Fields)) for i, ftv := range cv.Fields { - fields[i] = refOrCopyValue(cv, ftv) + fields[i] = refOrCopyValue(ftv) } return &StructValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1130,7 +1120,7 @@ func copyValueWithRefs(parent Object, val Value) Value { } var closure Value if cv.Closure != nil { - closure = toRefValue(parent, cv.Closure) + closure = toRefValue(cv.Closure) } // nativeBody funcs which don't come from NativeStore (and thus don't // have NativePkg/Name) can't be persisted, and should not be able @@ -1151,8 +1141,8 @@ func copyValueWithRefs(parent Object, val Value) Value { NativeName: cv.NativeName, } case *BoundMethodValue: - fnc := copyValueWithRefs(cv, cv.Func).(*FuncValue) - rtv := refOrCopyValue(cv, cv.Receiver) + fnc := copyValueWithRefs(cv.Func).(*FuncValue) + rtv := refOrCopyValue(cv.Receiver) return &BoundMethodValue{ ObjectInfo: cv.ObjectInfo.Copy(), // XXX ??? Func: fnc, @@ -1161,8 +1151,8 @@ func copyValueWithRefs(parent Object, val Value) Value { case *MapValue: list := &MapList{} for cur := cv.List.Head; cur != nil; cur = cur.Next { - key2 := refOrCopyValue(cv, cur.Key) - val2 := refOrCopyValue(cv, cur.Value) + key2 := refOrCopyValue(cur.Key) + val2 := refOrCopyValue(cur.Value) list.Append(nilAllocator, key2).Value = val2 } return &MapValue{ @@ -1172,10 +1162,10 @@ func copyValueWithRefs(parent Object, val Value) Value { case TypeValue: return toTypeValue(copyTypeWithRefs(cv.Type)) case *PackageValue: - block := toRefValue(cv, cv.Block) + block := toRefValue(cv.Block) fblocks := make([]Value, len(cv.FBlocks)) for i, fb := range cv.FBlocks { - fblocks[i] = toRefValue(cv, fb) + fblocks[i] = toRefValue(fb) } return &PackageValue{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1190,11 +1180,11 @@ func copyValueWithRefs(parent Object, val Value) Value { source := toRefNode(cv.Source) vals := make([]TypedValue, len(cv.Values)) for i, tv := range cv.Values { - vals[i] = refOrCopyValue(cv, tv) + vals[i] = refOrCopyValue(tv) } var bparent Value if cv.Parent != nil { - bparent = toRefValue(parent, cv.Parent) + bparent = toRefValue(cv.Parent) } bl := &Block{ ObjectInfo: cv.ObjectInfo.Copy(), @@ -1206,6 +1196,24 @@ func copyValueWithRefs(parent Object, val Value) Value { return bl case RefValue: return cv + case *HeapItemValue: + // NOTE: While this could be eliminated sometimes with some + // intelligence prior to persistence, to unwrap the + // HeapItemValue in case where the HeapItemValue only has + // refcount of 1, + // + // 1. The HeapItemValue is necessary when the .Value is a + // primitive non-object anyways, and + // 2. This would mean PointerValue.Base is nil, and we'd need + // additional logic to re-wrap when necessary, and + // 3. And with the above point, it's not clear the result + // would be any faster. But this is something we could + // explore after launch. + hiv := &HeapItemValue{ + ObjectInfo: cv.ObjectInfo.Copy(), + Value: refOrCopyValue(cv.Value), + } + return hiv case *NativeValue: panic("native values not supported") default: @@ -1377,6 +1385,9 @@ func fillTypesOfValue(store Store, val Value) Value { panic("native values not supported") case RefValue: // do nothing return cv + case *HeapItemValue: + fillTypesTV(store, &cv.Value) + return cv default: panic(fmt.Sprintf( "unexpected type %v", @@ -1424,7 +1435,7 @@ func toRefNode(bn BlockNode) RefNode { } } -func toRefValue(parent Object, val Value) RefValue { +func toRefValue(val Value) RefValue { // TODO use type switch stmt. if ref, ok := val.(RefValue); ok { return ref @@ -1497,15 +1508,15 @@ func ensureUniq(oozz ...[]Object) { } } -func refOrCopyValue(parent Object, tv TypedValue) TypedValue { +func refOrCopyValue(tv TypedValue) TypedValue { if tv.T != nil { tv.T = refOrCopyType(tv.T) } if obj, ok := tv.V.(Object); ok { - tv.V = toRefValue(parent, obj) + tv.V = toRefValue(obj) return tv } else { - tv.V = copyValueWithRefs(parent, tv.V) + tv.V = copyValueWithRefs(tv.V) return tv } } @@ -1514,20 +1525,6 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } -// realmPathPrefix is the prefix used to identify pkgpaths which are meant to -// 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) && - // MsgRun pkgPath aren't realms - !ReGnoRunPath.MatchString(pkgPath) -} - func prettyJSON(jstr []byte) []byte { var c interface{} err := json.Unmarshal(jstr, &c) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 3db53213f8b..12666c3d7ad 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -9,8 +9,12 @@ import ( "strings" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/colors" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" + "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" + stringz "github.com/gnolang/gno/tm2/pkg/strings" ) // PackageGetter specifies how the store may retrieve packages which are not @@ -18,7 +22,8 @@ import ( // package does not exist. store should be used to run the machine, or otherwise // call any methods which may call store.GetPackage; avoid using any "global" // store as the one passed to the PackageGetter may be a fork of that (ie. -// the original is not meant to be written to). +// the original is not meant to be written to). Loading dependencies may +// cause writes to happen to the store, such as MemPackages to iavlstore. type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) @@ -68,6 +73,8 @@ type Store interface { LogSwitchRealm(rlmpath string) // to mark change of realm boundaries ClearCache() Print() + Write() + Flush() } // Used to keep track of in-mem objects during tx. @@ -86,7 +93,7 @@ type defaultStore struct { // transient opslog []StoreOp // for debugging and testing. - current []string + current []string // for detecting import cycles. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -115,6 +122,7 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + // helper to detect circular imports if isImport { if slices.Contains(ds.current, pkgPath) { panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) @@ -305,7 +313,7 @@ func (ds *defaultStore) loadObjectSafe(oid ObjectID) Object { func (ds *defaultStore) SetObject(oo Object) { oid := oo.GetObjectID() // replace children/fields with Ref. - o2 := copyValueWithRefs(nil, oo) + o2 := copyValueWithRefs(oo) // marshal to binary. bz := amino.MustMarshalAny(o2) // set hash. @@ -672,8 +680,16 @@ func (ds *defaultStore) GetNative(pkgPath string, name Name) func(m *Machine) { return nil } +// Writes one level of cache to store. +func (ds *defaultStore) Write() { + ds.baseStore.(types.Writer).Write() + ds.iavlStore.(types.Writer).Write() +} + +// Flush cached writes to disk. func (ds *defaultStore) Flush() { - // XXX + ds.baseStore.(types.Flusher).Flush() + ds.iavlStore.(types.Flusher).Flush() } // ---------------------------------------- @@ -755,22 +771,25 @@ func (ds *defaultStore) ClearCache() { // for debugging func (ds *defaultStore) Print() { - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:baseStore...") - store.Print(ds.baseStore) - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:iavlStore...") - store.Print(ds.iavlStore) - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:cacheTypes...") + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:baseStore...")) + utils.Print(ds.baseStore) + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:iavlStore...")) + utils.Print(ds.iavlStore) + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:cacheTypes...")) for tid, typ := range ds.cacheTypes { - fmt.Printf("- %v: %v\n", tid, typ) + fmt.Printf("- %v: %v\n", tid, + stringz.TrimN(fmt.Sprintf("%v", typ), 50)) } - fmt.Println("//----------------------------------------") - fmt.Println("defaultStore:cacheNodes...") + fmt.Println(colors.Yellow("//----------------------------------------")) + fmt.Println(colors.Green("defaultStore:cacheNodes...")) for loc, bn := range ds.cacheNodes { - fmt.Printf("- %v: %v\n", loc, bn) + fmt.Printf("- %v: %v\n", loc, + stringz.TrimN(fmt.Sprintf("%v", bn), 50)) } + fmt.Println(colors.Red("//----------------------------------------")) } // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go new file mode 100644 index 00000000000..870eb10b690 --- /dev/null +++ b/gnovm/pkg/gnolang/type_check.go @@ -0,0 +1,926 @@ +package gnolang + +import ( + "fmt" + "reflect" + + "github.com/gnolang/gno/tm2/pkg/errors" +) + +// here are a range of rules predefined for preprocessor to check the compatibility between operands and operators +// e,g. for binary expr x + y, x, y can only be numeric or string, 1+2, "a" + "b" +// this is used in assertCompatible()s. +var ( + binaryChecker = map[Word]func(t Type) bool{ + ADD: isNumericOrString, + SUB: isNumeric, + MUL: isNumeric, + QUO: isNumeric, + REM: isIntNum, + SHL: isIntNum, + SHR: isIntNum, + BAND: isIntNum, // bit ops + XOR: isIntNum, + BOR: isIntNum, + BAND_NOT: isIntNum, + LAND: isBoolean, // logic + LOR: isBoolean, + LSS: isOrdered, // compare + LEQ: isOrdered, + GTR: isOrdered, + GEQ: isOrdered, + } + // TODO: star, addressable + unaryChecker = map[Word]func(t Type) bool{ + ADD: isNumeric, + SUB: isNumeric, + XOR: isIntNum, + NOT: isBoolean, + } + IncDecStmtChecker = map[Word]func(t Type) bool{ + INC: isNumeric, + DEC: isNumeric, + } + AssignStmtChecker = map[Word]func(t Type) bool{ + ADD_ASSIGN: isNumericOrString, + SUB_ASSIGN: isNumeric, + MUL_ASSIGN: isNumeric, + QUO_ASSIGN: isNumeric, + REM_ASSIGN: isIntNum, + SHL_ASSIGN: isNumeric, + SHR_ASSIGN: isNumeric, + BAND_ASSIGN: isIntNum, + XOR_ASSIGN: isIntNum, + BOR_ASSIGN: isIntNum, + BAND_NOT_ASSIGN: isIntNum, + } +) + +type category int + +const ( + IsBoolean category = 1 << iota + IsInteger + IsFloat + IsString + IsBigInt + IsBigDec + + IsNumeric = IsInteger | IsFloat | IsBigInt | IsBigDec + IsOrdered = IsNumeric | IsString +) + +func (pt PrimitiveType) category() category { + switch pt.Kind() { + case BoolKind: + return IsBoolean + case StringKind: + return IsString + case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind, UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind: + return IsInteger // UntypedRuneType is int32kind, DataByteType is uint8 kind + case Float32Kind, Float64Kind: + return IsFloat + case BigintKind: + return IsBigInt + case BigdecKind: + return IsBigDec + default: + panic(fmt.Sprintf("unexpected primitive type %v", pt)) + } +} + +func isOrdered(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsOrdered != 0 + default: + return false + } +} + +func isBoolean(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsBoolean != 0 + default: + return false + } +} + +// rune can be numeric and string +func isNumeric(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsNumeric != 0 + default: + return false + } +} + +func isIntNum(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsInteger != 0 || t.category()&IsBigInt != 0 + default: + return false + } +} + +func isNumericOrString(t Type) bool { + switch t := baseOf(t).(type) { + case PrimitiveType: + return t.category()&IsNumeric != 0 || t.category()&IsString != 0 + default: + return false + } +} + +// =========================================================== +func assertComparable(xt, dt Type) { + switch baseOf(dt).(type) { + case *SliceType, *FuncType, *MapType: + if xt != nil { + panic(fmt.Sprintf("%v can only be compared to nil", dt)) + } + } + assertComparable2(dt) +} + +// assert value with dt is comparable +func assertComparable2(dt Type) { + if debug { + debug.Printf("assertComparable2 dt: %v \n", dt) + } + switch cdt := baseOf(dt).(type) { + case PrimitiveType: + case *ArrayType: + switch baseOf(cdt.Elem()).(type) { + case PrimitiveType, *PointerType, *InterfaceType, *NativeType, *ArrayType, *StructType, *ChanType: + assertComparable2(cdt.Elem()) + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } + case *StructType: + for _, f := range cdt.Fields { + switch cft := baseOf(f.Type).(type) { + case PrimitiveType, *PointerType, *InterfaceType, *NativeType, *ArrayType, *StructType: + assertComparable2(cft) + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } + } + case *PointerType: // &a == &b + case *InterfaceType: + case *SliceType, *FuncType, *MapType: + case *NativeType: + if !cdt.Type.Comparable() { + panic(fmt.Sprintf("%v is not comparable", dt)) + } + default: + panic(fmt.Sprintf("%v is not comparable", dt)) + } +} + +func maybeNil(t Type) bool { + switch cxt := baseOf(t).(type) { + case *SliceType, *FuncType, *MapType, *InterfaceType, *PointerType, *ChanType: // we don't have unsafePointer + return true + case *NativeType: + switch nk := cxt.Type.Kind(); nk { + case reflect.Slice, reflect.Func, reflect.Map, reflect.Interface, reflect.Pointer: + return true + default: + return false + } + default: + return false + } +} + +func checkSame(at, bt Type, msg string) error { + if debug { + debug.Printf("checkSame, at: %v bt: %v \n", at, bt) + } + if at.TypeID() != bt.TypeID() { + return errors.New("incompatible types %v and %v %s", + at.TypeID(), bt.TypeID(), msg) + } + return nil +} + +func assertAssignableTo(xt, dt Type, autoNative bool) { + err := checkAssignableTo(xt, dt, autoNative) + if err != nil { + panic(err.Error()) + } +} + +// Assert that xt can be assigned as dt (dest type). +// If autoNative is true, a broad range of xt can match against +// a target native dt type, if and only if dt is a native type. +func checkAssignableTo(xt, dt Type, autoNative bool) error { + if debug { + debug.Printf("checkAssignableTo, xt: %v dt: %v \n", xt, dt) + } + // case0 + if xt == nil { // see test/files/types/eql_0f18 + if !maybeNil(dt) { + panic(fmt.Sprintf("invalid operation, nil can not be compared to %v", dt)) + } + return nil + } else if dt == nil { // _ = xxx, assign8.gno, 0f31. else cases? + return nil + } + // case3 + if dt.Kind() == InterfaceKind { // note native interface + if idt, ok := baseOf(dt).(*InterfaceType); ok { + if idt.IsEmptyInterface() { // XXX, can this be merged with IsImplementedBy? + // if dt is an empty Gno interface, any x ok. + return nil // ok + } else if idt.IsImplementedBy(xt) { + // if dt implements idt, ok. + return nil // ok + } else { + return errors.New( + "%s does not implement %s", + xt.String(), + dt.String()) + } + } else if ndt, ok := baseOf(dt).(*NativeType); ok { + nidt := ndt.Type + if nidt.NumMethod() == 0 { + // if dt is an empty Go native interface, ditto. + return nil // ok + } else if nxt, ok := baseOf(xt).(*NativeType); ok { + // if xt has native base, do the naive native. + if nxt.Type.AssignableTo(nidt) { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s", + nxt.String(), + nidt.String()) + } + } else if pxt, ok := baseOf(xt).(*PointerType); ok { + nxt, ok := pxt.Elt.(*NativeType) + if !ok { + return errors.New( + "pointer to non-native type cannot satisfy non-empty native interface; %s doesn't implement %s", + pxt.String(), + nidt.String()) + } + // if xt has native base, do the naive native. + if reflect.PtrTo(nxt.Type).AssignableTo(nidt) { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s", + pxt.String(), + nidt.String()) + } + } else if xdt, ok := xt.(*DeclaredType); ok { + if gno2GoTypeMatches(baseOf(xdt), ndt.Type) { + return nil + } // not check against native interface + } else { + return errors.New( + "unexpected type pair: cannot use %s as %s", + xt.String(), + dt.String()) + } + } else { + return errors.New("should not happen") + } + } + + // case2 + // Special case if xt or dt is *PointerType to *NativeType, + // convert to *NativeType of pointer kind. + if pxt, ok := xt.(*PointerType); ok { + // *gonative{x} is gonative{*x} + //nolint:misspell + if enxt, ok := pxt.Elt.(*NativeType); ok { + xt = &NativeType{ + Type: reflect.PtrTo(enxt.Type), + } + } + } + if pdt, ok := dt.(*PointerType); ok { + // *gonative{x} is gonative{*x} + if endt, ok := pdt.Elt.(*NativeType); ok { + dt = &NativeType{ + Type: reflect.PtrTo(endt.Type), + } + } + } + + // Special case of xt or dt is *DeclaredType, + // allow implicit conversion unless both are declared. + // TODO simplify with .IsNamedType(). + if dxt, ok := xt.(*DeclaredType); ok { + if ddt, ok := dt.(*DeclaredType); ok { + // types must match exactly. + if !dxt.sealed && !ddt.sealed && + dxt.PkgPath == ddt.PkgPath && + dxt.Name == ddt.Name { // not yet sealed + return nil // ok + } else if dxt.TypeID() == ddt.TypeID() { + return nil // ok + } else { + return errors.New( + "cannot use %s as %s without explicit conversion", + dxt.String(), + ddt.String()) + } + } else { + // special case if implicitly named primitive type. + // TODO simplify with .IsNamedType(). + if _, ok := dt.(PrimitiveType); ok { + return errors.New( + "cannot use %s as %s without explicit conversion", + dxt.String(), + dt.String()) + } else { + // carry on with baseOf(dxt) + xt = dxt.Base // set as base to do the rest check + } + } + } else if ddt, ok := dt.(*DeclaredType); ok { + // special case if implicitly named primitive type. + // TODO simplify with .IsNamedType(). + if _, ok := xt.(PrimitiveType); ok { // e.g. 1 == Int(1) + if debug { + debug.Printf("xt is primitiveType: %v, ddt: %v \n", xt, ddt) + } + // this is special when dt is the declared type of x + if !isUntyped(xt) { + return errors.New( + "cannot use %s as %s without explicit conversion", + xt.String(), + ddt.String()) + } else { // xt untyped, carry on with check below + dt = ddt.Base + } + } else { + dt = ddt.Base + } + } + + // General cases. + switch cdt := dt.(type) { + case PrimitiveType: // case 1 + // if xt is untyped, ensure dt is compatible. + switch xt { + case UntypedBoolType: + if dt.Kind() == BoolKind { + return nil // ok + } else { + return errors.New( + "cannot use untyped bool as %s", + dt.Kind()) + } + case UntypedStringType: + if dt.Kind() == StringKind { + return nil // ok + } else { + return errors.New( + "cannot use untyped string as %s", + dt.Kind()) + } + // XXX, this is a loose check, we don't have the context + // to check if it is an exact integer, e.g. 1.2 or 1.0(1.0 can be converted to int). + // this ensure expr like (a % 1.0) pass check, while + // expr like (a % 1.2) panic at ConvertUntypedTo, which is a delayed assertion after const evaluated. + // assignable does not guarantee convertible. + case UntypedBigdecType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigdecKind, Float32Kind, Float64Kind: + return nil // ok + default: + panic(fmt.Sprintf( + "cannot use untyped Bigdec as %s", + dt.Kind())) + } + case UntypedBigintType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigintKind, BigdecKind, Float32Kind, Float64Kind: // see 0d0 + return nil // ok + default: + return errors.New( + "cannot use untyped Bigint as %s", + dt.Kind()) + } + case UntypedRuneType: + switch dt.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, + Int64Kind, UintKind, Uint8Kind, Uint16Kind, + Uint32Kind, Uint64Kind, BigintKind, BigdecKind, Float32Kind, Float64Kind: + return nil // ok + default: + return errors.New( + "cannot use untyped rune as %s", + dt.Kind()) + } + + default: + if isUntyped(xt) { + panic("unexpected untyped type") + } + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + } + case *PointerType: // case 4 from here on + if pt, ok := xt.(*PointerType); ok { + return checkAssignableTo(pt.Elt, cdt.Elt, false) + } + case *ArrayType: + if at, ok := xt.(*ArrayType); ok { + if at.Len != cdt.Len { + return errors.New( + "cannot use %s as %s", + at.String(), + cdt.String()) + } + err := checkSame(at.Elt, cdt.Elt, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + at.String(), + cdt.String()) + } + return nil + } + case *SliceType: + if st, ok := xt.(*SliceType); ok { + if cdt.Vrd { + return checkAssignableTo(st.Elt, cdt.Elt, false) + } else { + err := checkSame(st.Elt, cdt.Elt, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + st.String(), + cdt.String()) + } + return nil + } + } + case *MapType: + if mt, ok := xt.(*MapType); ok { + err := checkSame(mt.Key, cdt.Key, "") + if err != nil { + return errors.New( + "cannot use %s as %s", + mt.String(), + cdt.String()).Stacktrace() + } + return nil + } + case *InterfaceType: + return errors.New("should not happen") + case *DeclaredType: + panic("should not happen") + case *FuncType, *StructType, *PackageType, *ChanType, *TypeType: + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + case *NativeType: + if !autoNative { + if debug { + debug.Printf("native type, xt.TypeID: %v, cdt.TypeID: %v \n", xt.TypeID(), cdt.TypeID()) + } + if xt.TypeID() == cdt.TypeID() { + return nil // ok + } + } else { + // autoNative, so check whether matches. + // xt: any type but a *DeclaredType; could be native. + // cdt: actual concrete native target type. + // ie, if cdt can match against xt. + if gno2GoTypeMatches(xt, cdt.Type) { + return nil // ok + } + } + default: + return errors.New( + "unexpected type %s", + dt.String()) + } + return errors.New( + "cannot use %s as %s", + xt.String(), + dt.String()).Stacktrace() +} + +// =========================================================== +func (x *BinaryExpr) checkShiftLhs(dt Type) { + if checker, ok := binaryChecker[x.Op]; ok { + if !checker(dt) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(dt))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +// AssertCompatible works as a pre-check prior to checkOrConvertType. +// It checks against expressions to ensure the compatibility between operands and operators. +// e.g. "a" << 1, the left hand operand is not compatible with <<, it will fail the check. +// Overall,it efficiently filters out incompatible expressions, stopping before the next +// checkOrConvertType() operation to optimize performance. +func (x *BinaryExpr) AssertCompatible(lt, rt Type) { + // native type will be converted to gno in latter logic, + // this check logic will be conduct again from trans_leave *BinaryExpr. + lnt, lin := lt.(*NativeType) + rnt, rin := rt.(*NativeType) + if lin && rin { + if lt.TypeID() != rt.TypeID() { + panic(fmt.Sprintf( + "incompatible operands in binary expression: %s %s %s", + lt.TypeID(), x.Op, rt.TypeID())) + } + } + if lin { + if _, ok := go2GnoBaseType(lnt.Type).(PrimitiveType); ok { + return + } + } + if rin { + if _, ok := go2GnoBaseType(rnt.Type).(PrimitiveType); ok { + return + } + } + + xt, dt := lt, rt + if shouldSwapOnSpecificity(lt, rt) { + xt, dt = dt, xt + } + + if isComparison(x.Op) { + switch x.Op { + case EQL, NEQ: + assertComparable(xt, dt) + if !isUntyped(xt) && !isUntyped(dt) { + assertAssignableTo(xt, dt, false) + } + case LSS, LEQ, GTR, GEQ: + if checker, ok := binaryChecker[x.Op]; ok { + x.checkCompatibility(xt, dt, checker, x.Op.TokenString()) + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + default: + panic("invalid comparison operator") + } + } else { + if checker, ok := binaryChecker[x.Op]; ok { + x.checkCompatibility(xt, dt, checker, x.Op.TokenString()) + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + + switch x.Op { + case QUO, REM: + // special case of zero divisor + if isQuoOrRem(x.Op) { + if rcx, ok := x.Right.(*ConstExpr); ok { + if rcx.TypedValue.isZero() { + panic("invalid operation: division by zero") + } + } + } + default: + // do nothing + } + } +} + +// Check compatibility of the destination type (dt) with the operator. +// If both source type (xt) and destination type (dt) are typed: +// Verify that xt is assignable to dt. +// If xt is untyped: +// The function checkOrConvertType will be invoked after this check. +// NOTE: dt is established based on a specificity check between xt and dt, +// confirming dt as the appropriate destination type for this context. +func (x *BinaryExpr) checkCompatibility(xt, dt Type, checker func(t Type) bool, OpStr string) { + if !checker(dt) { + panic(fmt.Sprintf("operator %s not defined on: %v", OpStr, kindString(dt))) + } + + // if both typed + if !isUntyped(xt) && !isUntyped(dt) { + err := checkAssignableTo(xt, dt, false) + if err != nil { + panic(fmt.Sprintf("invalid operation: mismatched types %v and %v", xt, dt)) + } + } +} + +func (x *UnaryExpr) AssertCompatible(t Type) { + if nt, ok := t.(*NativeType); ok { + if _, ok := go2GnoBaseType(nt.Type).(PrimitiveType); ok { + return + } + } + // check compatible + if checker, ok := unaryChecker[x.Op]; ok { + if !checker(t) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(t))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +func (x *IncDecStmt) AssertCompatible(t Type) { + if nt, ok := t.(*NativeType); ok { + if _, ok := go2GnoBaseType(nt.Type).(PrimitiveType); ok { + return + } + } + // check compatible + if checker, ok := IncDecStmtChecker[x.Op]; ok { + if !checker(t) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(t))) + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } +} + +func assertIndexTypeIsInt(kt Type) { + if kt.Kind() != IntKind { + panic(fmt.Sprintf("index type should be int, but got %v", kt)) + } +} + +func (x *RangeStmt) AssertCompatible(store Store, last BlockNode) { + if x.Op != ASSIGN { + return + } + if isBlankIdentifier(x.Key) && isBlankIdentifier(x.Value) { + // both "_" + return + } + assertValidAssignLhs(store, last, x.Key) + // if is valid left value + + kt := evalStaticTypeOf(store, last, x.Key) + var vt Type + if x.Value != nil { + vt = evalStaticTypeOf(store, last, x.Value) + } + + xt := evalStaticTypeOf(store, last, x.X) + switch cxt := xt.(type) { + case *MapType: + assertAssignableTo(cxt.Key, kt, false) + if vt != nil { + assertAssignableTo(cxt.Value, vt, false) + } + case *SliceType: + assertIndexTypeIsInt(kt) + if vt != nil { + assertAssignableTo(cxt.Elt, vt, false) + } + case *ArrayType: + assertIndexTypeIsInt(kt) + if vt != nil { + assertAssignableTo(cxt.Elt, vt, false) + } + case PrimitiveType: + if cxt.Kind() == StringKind { + if kt != nil && kt.Kind() != IntKind { + panic(fmt.Sprintf("index type should be int, but got %v", kt)) + } + if vt != nil { + if vt.Kind() != Int32Kind { // rune + panic(fmt.Sprintf("value type should be int32, but got %v", kt)) + } + } + } + } +} + +func (x *AssignStmt) AssertCompatible(store Store, last BlockNode) { + if x.Op == ASSIGN || x.Op == DEFINE { + if len(x.Lhs) > len(x.Rhs) { + if len(x.Rhs) != 1 { + panic(fmt.Sprintf("assignment mismatch: %d variables but %d values", len(x.Lhs), len(x.Rhs))) + } + switch cx := x.Rhs[0].(type) { + case *CallExpr: + // Call case: a, b = x(...) + ift := evalStaticTypeOf(store, last, cx.Func) + cft := getGnoFuncTypeOf(store, ift) + if len(x.Lhs) != len(cft.Results) { + panic(fmt.Sprintf( + "assignment mismatch: "+ + "%d variables but %s returns %d values", + len(x.Lhs), cx.Func.String(), len(cft.Results))) + } + if x.Op == ASSIGN { + // check assignable + for i, lx := range x.Lhs { + if !isBlankIdentifier(lx) { + assertValidAssignLhs(store, last, lx) + lxt := evalStaticTypeOf(store, last, lx) + assertAssignableTo(cft.Results[i].Type, lxt, false) + } + } + } + case *TypeAssertExpr: + // Type-assert case: a, ok := x.(type) + if len(x.Lhs) != 2 { + panic("should not happen") + } + if x.Op == ASSIGN { + // check assignable to first value + if !isBlankIdentifier(x.Lhs[0]) { // see composite3.gno + assertValidAssignLhs(store, last, x.Lhs[0]) + dt := evalStaticTypeOf(store, last, x.Lhs[0]) + ift := evalStaticTypeOf(store, last, cx) + assertAssignableTo(ift, dt, false) + } + if !isBlankIdentifier(x.Lhs[1]) { // see composite3.gno + assertValidAssignLhs(store, last, x.Lhs[1]) + dt := evalStaticTypeOf(store, last, x.Lhs[1]) + if dt.Kind() != BoolKind { // typed, not bool + panic(fmt.Sprintf("want bool type got %v", dt)) + } + } + } + cx.HasOK = true + case *IndexExpr: // must be with map type when len(Lhs) > len(Rhs) + if len(x.Lhs) != 2 { + panic("should not happen") + } + if x.Op == ASSIGN { + if !isBlankIdentifier(x.Lhs[0]) { + assertValidAssignLhs(store, last, x.Lhs[0]) + lt := evalStaticTypeOf(store, last, x.Lhs[0]) + if _, ok := cx.X.(*NameExpr); ok { + rt := evalStaticTypeOf(store, last, cx.X) + if mt, ok := rt.(*MapType); ok { + assertAssignableTo(mt.Value, lt, false) + } + } else if _, ok := cx.X.(*CompositeLitExpr); ok { + cpt := evalStaticTypeOf(store, last, cx.X) + if mt, ok := cpt.(*MapType); ok { + assertAssignableTo(mt.Value, lt, false) + } else { + panic("should not happen") + } + } + } + if !isBlankIdentifier(x.Lhs[1]) { + assertValidAssignLhs(store, last, x.Lhs[1]) + dt := evalStaticTypeOf(store, last, x.Lhs[1]) + if dt != nil && dt.Kind() != BoolKind { // typed, not bool + panic(fmt.Sprintf("want bool type got %v", dt)) + } + } + } + cx.HasOK = true + default: + panic(fmt.Sprintf("RHS should not be %v when len(Lhs) > len(Rhs)", cx)) + } + } else { // len(Lhs) == len(Rhs) + if x.Op == ASSIGN { + // assert valid left value + for _, lx := range x.Lhs { + assertValidAssignLhs(store, last, lx) + } + } + } + } else { // Ops other than assign and define + // If this is an assignment operation, ensure there's only 1 + // expr on lhs/rhs. + if len(x.Lhs) != 1 || len(x.Rhs) != 1 { + panic("assignment operator " + x.Op.TokenString() + + " requires only one expression on lhs and rhs") + } + for i, lx := range x.Lhs { + lt := evalStaticTypeOf(store, last, lx) + rt := evalStaticTypeOf(store, last, x.Rhs[i]) + + if checker, ok := AssignStmtChecker[x.Op]; ok { + if !checker(lt) { + panic(fmt.Sprintf("operator %s not defined on: %v", x.Op.TokenString(), kindString(lt))) + } + switch x.Op { + case ADD_ASSIGN, SUB_ASSIGN, MUL_ASSIGN, QUO_ASSIGN, REM_ASSIGN, BAND_ASSIGN, BOR_ASSIGN, BAND_NOT_ASSIGN, XOR_ASSIGN: + // check when both typed + if !isUntyped(lt) && !isUntyped(rt) { // in this stage, lt or rt maybe untyped, not converted yet + if lt != nil && rt != nil { + if lt.TypeID() != rt.TypeID() { + panic(fmt.Sprintf("invalid operation: mismatched types %v and %v", lt, rt)) + } + } + } + default: + // do nothing + } + } else { + panic(fmt.Sprintf("checker for %s does not exist", x.Op)) + } + } + } +} + +// misc +func assertValidAssignLhs(store Store, last BlockNode, lx Expr) { + shouldPanic := true + switch clx := lx.(type) { + case *NameExpr, *StarExpr, *SelectorExpr: + shouldPanic = false + case *IndexExpr: + xt := evalStaticTypeOf(store, last, clx.X) + shouldPanic = xt != nil && xt.Kind() == StringKind + default: + } + if shouldPanic { + panic(fmt.Sprintf("cannot assign to %v", lx)) + } +} + +func kindString(xt Type) string { + if xt != nil { + return xt.Kind().String() + } + return "nil" +} + +func isQuoOrRem(op Word) bool { + switch op { + case QUO, QUO_ASSIGN, REM, REM_ASSIGN: + return true + default: + return false + } +} + +func isComparison(op Word) bool { + switch op { + case EQL, NEQ, LSS, LEQ, GTR, GEQ: + return true + default: + return false + } +} + +// shouldSwapOnSpecificity determines the potential direction for +// checkOrConvertType. it checks whether a swap is needed between two types +// based on their specificity. If t2 has a lower specificity than t1, it returns +// false, indicating no swap is needed. If t1 has a lower specificity than t2, +// it returns true, indicating a swap is needed. +func shouldSwapOnSpecificity(t1, t2 Type) bool { + // check nil + if t1 == nil { // see test file 0f46 + return false // also with both nil + } else if t2 == nil { + return true + } + + // check interface + if it1, ok := baseOf(t1).(*InterfaceType); ok { + if it1.IsEmptyInterface() { + return true // left empty interface + } else { + if _, ok := baseOf(t2).(*InterfaceType); ok { + return false + } else { + return true // right not interface + } + } + } else if _, ok := t2.(*InterfaceType); ok { + return false // left not interface, right is interface + } + + // primitive types + t1s, t2s := 0, 0 + if t1p, ok := t1.(PrimitiveType); ok { + t1s = t1p.Specificity() + } + if t2p, ok := t2.(PrimitiveType); ok { + t2s = t2p.Specificity() + } + if t1s < t2s { + // NOTE: higher specificity has lower value, so backwards. + return true + } else { + return false + } +} + +func isBlankIdentifier(x Expr) bool { + if nx, ok := x.(*NameExpr); ok { + return nx.Name == "_" + } + return false +} diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 86b3d588eda..ab8e9effdc8 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1203,8 +1203,8 @@ func (ft *FuncType) Specify(store Store, argTVs []TypedValue, isVarg bool) *Func continue } else if vargt == nil { vargt = varg.T - } else if isUntyped(varg.T) && vargt.TypeID() == defaultTypeOf(varg.T, varg.V).TypeID() { - vargt = defaultTypeOf(varg.T, varg.V) + } else if isUntyped(varg.T) && vargt.TypeID() == defaultTypeOf(varg.T).TypeID() { + vargt = defaultTypeOf(varg.T) } else if vargt.TypeID() != varg.T.TypeID() { panic(fmt.Sprintf( "incompatible varg types: expected %v, got %s", @@ -2203,11 +2203,11 @@ func KindOf(t Type) Kind { // ---------------------------------------- // main type-assertion functions. -// TODO: document what class of problems its for. +// Only for runtime debugging. // One of them can be nil, and this lets uninitialized primitives // and others serve as empty values. See doOpAdd() -// usage: if debug { assertSameTypes() } -func assertSameTypes(lt, rt Type) { +// usage: if debug { debugAssertSameTypes() } +func debugAssertSameTypes(lt, rt Type) { if lt == nil && rt == nil { // both are nil. } else if lt == nil || rt == nil { @@ -2232,8 +2232,10 @@ func assertSameTypes(lt, rt Type) { } } -// Like assertSameTypes(), but more relaxed, for == and !=. -func assertEqualityTypes(lt, rt Type) { +// Only for runtime debugging. +// Like debugAssertSameTypes(), but more relaxed, for == and !=. +// usage: if debug { debugAssertEqualityTypes() } +func debugAssertEqualityTypes(lt, rt Type) { if lt == nil && rt == nil { // both are nil. } else if lt == nil || rt == nil { @@ -2281,23 +2283,14 @@ func isDataByte(t Type) bool { // TODO move untyped const stuff to preprocess.go. // TODO associate with ConvertTo() in documentation. -func defaultTypeOf(t Type, v Value) Type { +func defaultTypeOf(t Type) Type { switch t { case UntypedBoolType: return BoolType case UntypedRuneType: return Int32Type case UntypedBigintType: - typeVal := IntType - if bigintValue, ok := v.(BigintValue); ok { - if bigintValue.V != nil && bigintValue.V.Sign() == 1 && !bigintValue.V.IsInt64() { - // Use an unsigned type if the value is positive and we know - // it won't fit in an int64. - typeVal = Uint64Type - } - } - - return typeVal + return IntType case UntypedBigdecType: return Float64Type case UntypedStringType: @@ -2371,6 +2364,7 @@ func fillEmbeddedName(ft *FieldType) { ft.Embedded = true } +// TODO: empty interface? refer to assertAssignableTo func IsImplementedBy(it Type, ot Type) bool { switch cbt := baseOf(it).(type) { case *InterfaceType: @@ -2505,7 +2499,7 @@ func specifyType(store Store, lookup map[Name]Type, tmpl Type, spec Type, specTy generic := ct.Generic[:len(ct.Generic)-len(".Elem()")] match, ok := lookup[generic] if ok { - checkType(spec, match.Elem(), false) + assertAssignableTo(spec, match.Elem(), false) return // ok } else { // Panic here, because we don't know whether T @@ -2519,11 +2513,11 @@ func specifyType(store Store, lookup map[Name]Type, tmpl Type, spec Type, specTy } else { match, ok := lookup[ct.Generic] if ok { - checkType(spec, match, false) + assertAssignableTo(spec, match, false) return // ok } else { if isUntyped(spec) { - spec = defaultTypeOf(spec, nil) + spec = defaultTypeOf(spec) } lookup[ct.Generic] = spec return // ok diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index df16ecf0ad9..880a75396ca 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -911,16 +911,18 @@ func UverseNode() *PackageNode { tt := arg0.TV.GetType() vv := defaultValue(m.Alloc, tt) m.Alloc.AllocatePointer() + hi := m.Alloc.NewHeapItem(TypedValue{ + T: tt, + V: vv, + }) m.PushValue(TypedValue{ T: m.Alloc.NewType(&PointerType{ Elt: tt, }), V: PointerValue{ - TV: &TypedValue{ - T: tt, - V: vv, - }, - Base: nil, + TV: &hi.Value, + Base: hi, + Index: 0, }, }) return diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index d8d4bc58bc3..d38c083428c 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -41,6 +41,7 @@ func (*PackageValue) assertValue() {} func (*NativeValue) assertValue() {} func (*Block) assertValue() {} func (RefValue) assertValue() {} +func (*HeapItemValue) assertValue() {} const ( nilStr = "nil" @@ -64,6 +65,7 @@ var ( _ Value = &NativeValue{} _ Value = &Block{} _ Value = RefValue{} + _ Value = &HeapItemValue{} ) // ---------------------------------------- @@ -166,14 +168,20 @@ func (dbv DataByteValue) SetByte(b byte) { // Index is -1 for the shared "_" block var, // and -2 for (gno and native) map items. // +// A pointer constructed via a &x{} composite lit expression or constructed via +// new() or make() will have a virtual HeapItemValue as base. +// // Allocation for PointerValue is not immediate, // as usually PointerValues are temporary for assignment // or binary operations. When a pointer is to be // allocated, *Allocator.AllocatePointer() is called separately, // as in OpRef. +// +// Since PointerValue is used internally for assignment etc, +// it MUST stay minimal for computational efficiency. type PointerValue struct { TV *TypedValue // escape val if pointer to var. - Base Value // array/struct/block. + Base Value // array/struct/block, or heapitem. Index int // list/fields/values index, or -1 or -2 (see below). Key *TypedValue `json:",omitempty"` // for maps. } @@ -1494,6 +1502,88 @@ func (tv *TypedValue) GetBigDec() *apd.Decimal { return tv.V.(BigdecValue).V } +// returns true if tv is zero +func (tv *TypedValue) isZero() bool { + if tv.T == nil { + panic("type should not be nil") + } + switch tv.T.Kind() { + case IntKind: + v := tv.GetInt() + if v == 0 { + return true + } + case Int8Kind: + v := tv.GetInt8() + if v == 0 { + return true + } + case Int16Kind: + v := tv.GetInt16() + if v == 0 { + return true + } + case Int32Kind: + v := tv.GetInt32() + if v == 0 { + return true + } + case Int64Kind: + v := tv.GetInt64() + if v == 0 { + return true + } + case UintKind: + v := tv.GetUint() + if v == 0 { + return true + } + case Uint8Kind: + v := tv.GetUint8() + if v == 0 { + return true + } + case Uint16Kind: + v := tv.GetUint16() + if v == 0 { + return true + } + case Uint32Kind: + v := tv.GetUint32() + if v == 0 { + return true + } + case Uint64Kind: + v := tv.GetUint64() + if v == 0 { + return true + } + case Float32Kind: + v := tv.GetFloat32() + if v == 0 { + return true + } + case Float64Kind: + v := tv.GetFloat64() + if v == 0 { + return true + } + case BigintKind: + v := tv.GetBigInt() + if v.Sign() == 0 { + return true + } + case BigdecKind: + v := tv.GetBigDec() + if v.Sign() == 0 { + return true + } + default: + panic("not numeric") + } + return false +} + func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey { // Special case when nil: has no separator. if tv.T == nil { @@ -1594,7 +1684,7 @@ func (tv *TypedValue) Assign(alloc *Allocator, tv2 TypedValue, cu bool) { } *tv = tv2.Copy(alloc) if cu && isUntyped(tv.T) { - ConvertUntypedTo(tv, defaultTypeOf(tv.T, tv.V)) + ConvertUntypedTo(tv, defaultTypeOf(tv.T)) } } @@ -2254,12 +2344,12 @@ func (tv *TypedValue) GetSlice2(alloc *Allocator, low, high, max int) TypedValue // TODO rename to BlockValue. type Block struct { - ObjectInfo // for closures - Source BlockNode - Values []TypedValue - Parent Value - Blank TypedValue // captures "_" // XXX remove and replace with global instance. - bodyStmt bodyStmt // XXX expose for persistence, not needed for MVP. + ObjectInfo + Source BlockNode + Values []TypedValue + Parent Value + Blank TypedValue // captures "_" // XXX remove and replace with global instance. + bodyStmt bodyStmt // XXX expose for persistence, not needed for MVP. } // NOTE: for allocation, use *Allocator.NewBlock. @@ -2419,6 +2509,15 @@ type RefValue struct { Hash ValueHash `json:",omitempty"` } +// Base for a detached singleton (e.g. new(int) or &struct{}) +// Conceptually like a Block that holds one value. +// NOTE: could be renamed to HeapItemBaseValue. +// See also note in realm.go about auto-unwrapping. +type HeapItemValue struct { + ObjectInfo + Value TypedValue +} + // ---------------------------------------- func defaultStructFields(alloc *Allocator, st *StructType) []TypedValue { @@ -2547,6 +2646,8 @@ func fillValueTV(store Store, tv *TypedValue) *TypedValue { case *Block: vpv := cb.GetPointerToInt(store, cv.Index) cv.TV = vpv.TV // TODO optimize? + case *HeapItemValue: + cv.TV = &cb.Value default: panic("should not happen") } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index edaa8883819..c5ddc232fcb 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -881,6 +881,11 @@ GNO_CASE: // Panics if conversion is illegal. // TODO: method on TypedValue? func ConvertUntypedTo(tv *TypedValue, t Type) { + if debug { + defer func() { + debug.Printf("ConvertUntypedTo done, tv: %v \n", tv) + }() + } if debug { if !isUntyped(tv.T) { panic(fmt.Sprintf( @@ -926,7 +931,7 @@ func ConvertUntypedTo(tv *TypedValue, t Type) { } // general case if t == nil { - t = defaultTypeOf(tv.T, tv.V) + t = defaultTypeOf(tv.T) } switch tv.T { case UntypedBoolType: diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index 4ae05c8a97d..204fab62c86 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -258,6 +258,11 @@ func (v RefValue) String() string { v.PkgPath) } +func (v *HeapItemValue) String() string { + return fmt.Sprintf("heapitem(%v)", + v.Value) +} + // ---------------------------------------- // *TypedValue.Sprint diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index fda9263914e..b6ee95acac8 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/transpiler" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -183,13 +183,8 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { if strings.HasSuffix(path, modFile.Module.Mod.Path) { continue } - // skip if `std`, special case. - if path == transpiler.GnoStdPkgAfter { - continue - } - if strings.HasPrefix(path, transpiler.ImportPrefix) { - path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") + if !gno.IsStdlib(path) { modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 014873b8faa..0effa532107 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -3,6 +3,8 @@ package gnomod import ( "errors" "fmt" + "go/parser" + gotoken "go/token" "os" "path/filepath" "strings" @@ -63,23 +65,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err } else { // Is File // Transpile and write generated go file - if strings.HasSuffix(fileName, ".gno") { - filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) - transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) - if err != nil { - return nil, fmt.Errorf("transpile: %w", err) - } - - for _, i := range transpileRes.Imports { - requirements = append(requirements, i.Path.Value) - } - - targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) - err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) - if err != nil { - return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) - } + file, err := parser.ParseFile(gotoken.NewFileSet(), fileName, res.Data, parser.ImportsOnly) + if err != nil { + return nil, fmt.Errorf("parse gno file: %w", err) + } + for _, i := range file.Imports { + requirements = append(requirements, i.Path.Value) } // Write file @@ -96,11 +87,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err // GnoToGoMod make necessary modifications in the gno.mod // and return go.mod file. func GnoToGoMod(f File) (*File, error) { + // TODO(morgan): good candidate to move to pkg/transpiler. + gnoModPath := GetGnoModPath() - if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPurePkgsPrefixBefore) { - f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) + if !gnolang.IsStdlib(f.Module.Mod.Path) { + f.AddModuleStmt(transpiler.TranspileImportPath(f.Module.Mod.Path)) } for i := range f.Require { @@ -111,14 +103,13 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPurePkgsPrefixBefore) { + if !gnolang.IsStdlib(path) { // Add dependency with a modified import path - f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) + f.AddRequire(transpiler.TranspileImportPath(path), f.Require[i].Mod.Version) } - f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") + f.AddReplace(path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") // Remove the old require since the new dependency was added above - f.DropRequire(f.Require[i].Mod.Path) + f.DropRequire(path) } // Remove replacements that are not replaced by directories. diff --git a/gnovm/pkg/integration/gno.go b/gnovm/pkg/integration/gno.go index ee0216fa9e8..a389b6a9b24 100644 --- a/gnovm/pkg/integration/gno.go +++ b/gnovm/pkg/integration/gno.go @@ -68,6 +68,8 @@ func SetupGno(p *testscript.Params, buildDir string) error { return fmt.Errorf("unable to create temporary home directory: %w", err) } env.Setenv("HOME", home) + // Avoids go command printing errors relating to lack of go.mod. + env.Setenv("GO111MODULE", "off") // Cleanup home folder env.Defer(func() { os.RemoveAll(home) }) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 8a91ae4a486..bd4bb1b1bc9 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -1,3 +1,5 @@ +// Package transpiler implements a source-to-source compiler for translating Gno +// code into Go code. package transpiler import ( @@ -9,102 +11,52 @@ import ( goscanner "go/scanner" "go/token" "os" - "os/exec" + "path" "path/filepath" - "regexp" - "sort" "strconv" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" "golang.org/x/tools/go/ast/astutil" ) -const ( - GnoRealmPkgsPrefixBefore = "gno.land/r/" - GnoRealmPkgsPrefixAfter = "github.com/gnolang/gno/examples/gno.land/r/" - GnoPurePkgsPrefixBefore = "gno.land/p/" - GnoPurePkgsPrefixAfter = "github.com/gnolang/gno/examples/gno.land/p/" - GnoStdPkgBefore = "std" - GnoStdPkgAfter = "github.com/gnolang/gno/gnovm/stdlibs/stdshim" -) - -var stdlibWhitelist = []string{ - // go - "bufio", - "bytes", - "compress/gzip", - "context", - "crypto/md5", - "crypto/sha1", - "crypto/chacha20", - "crypto/cipher", - "crypto/sha256", - "encoding/base64", - "encoding/binary", - "encoding/hex", - "encoding/json", - "encoding/xml", - "errors", - "hash", - "hash/adler32", - "internal/bytealg", - "internal/os", - "flag", - "fmt", - "io", - "io/util", - "math", - "math/big", - "math/bits", - "math/rand", - "net/url", - "path", - "regexp", - "sort", - "strconv", - "strings", - "text/template", - "time", - "unicode", - "unicode/utf8", - "unicode/utf16", +// ImportPrefix is the import path to the root of the gno repository, which should +// be used to create go import paths. +const ImportPrefix = "github.com/gnolang/gno" - // gno - "std", +// TranspileImportPath takes an import path s, and converts it into the full +// import path relative to the Gno repository. +func TranspileImportPath(s string) string { + return ImportPrefix + "/" + PackageDirLocation(s) } -var importPrefixWhitelist = []string{ - "github.com/gnolang/gno/_test", +// PackageDirLocation provides the supposed directory of the package, relative to the root dir. +// +// TODO(morgan): move out, this should go in a "resolver" package. +func PackageDirLocation(s string) string { + switch { + case !gno.IsStdlib(s): + return "examples/" + s + default: + return "gnovm/stdlibs/" + s + } } -const ImportPrefix = "github.com/gnolang/gno" - -type transpileResult struct { +// Result is returned by Transpile, returning the file's imports and output +// out the transpilation. +type Result struct { Imports []*ast.ImportSpec Translated string + File *ast.File } // TODO: func TranspileFile: supports caching. // TODO: func TranspilePkg: supports directories. -func guessRootDir(fileOrPkg string, goBinary string) (string, error) { - abs, err := filepath.Abs(fileOrPkg) - if err != nil { - return "", err - } - args := []string{"list", "-m", "-mod=mod", "-f", "{{.Dir}}", ImportPrefix} - cmd := exec.Command(goBinary, args...) - cmd.Dir = abs - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("can't guess --root-dir") - } - rootDir := strings.TrimSpace(string(out)) - return rootDir, nil -} - -// GetTranspileFilenameAndTags returns the filename and tags for transpiled files. -func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { +// TranspiledFilenameAndTags returns the filename and tags for transpiled files. +func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { nameNoExtension := strings.TrimSuffix(filepath.Base(gnoFilePath), ".gno") switch { case strings.HasSuffix(gnoFilePath, "_filetest.gno"): @@ -120,17 +72,43 @@ func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags strin return } -func Transpile(source string, tags string, filename string) (*transpileResult, error) { +// Transpile performs transpilation on the given source code. tags can be used +// to specify build tags; and filename helps generate useful error messages and +// discriminate between test and normal source files. +func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) + f, err := parser.ParseFile(fset, filename, source, + // SkipObjectResolution -- unused here. + // ParseComments -- so that they show up when re-building the AST. + parser.SkipObjectResolution|parser.ParseComments) if err != nil { return nil, fmt.Errorf("parse: %w", err) } isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") - shouldCheckWhitelist := !isTestFile + ctx := &transpileCtx{ + rootDir: gnoenv.RootDir(), + } + stdlibPrefix := filepath.Join(ctx.rootDir, "gnovm", "stdlibs") + if isTestFile { + // XXX(morgan): this disables checking that a package exists (in examples or stdlibs) + // when transpiling a test file. After all Gno functions, including those in + // tests/imports.go are converted to native bindings, support should + // be added for transpiling stdlibs only available in tests/stdlibs, and + // enable as such "package checking" also on test files. + ctx.rootDir = "" + } + if strings.HasPrefix(filename, stdlibPrefix) { + // this is a standard library. Mark it in the options so the native + // bindings resolve correctly. + path := strings.TrimPrefix(filename, stdlibPrefix) + path = filepath.ToSlash(filepath.Dir(path)) + path = strings.TrimLeft(path, "/") + + ctx.stdlibPath = path + } - transformed, err := transpileAST(fset, f, shouldCheckWhitelist) + transformed, err := ctx.transformFile(fset, f) if err != nil { return nil, fmt.Errorf("transpileAST: %w", err) } @@ -152,190 +130,64 @@ func Transpile(source string, tags string, filename string) (*transpileResult, e return nil, fmt.Errorf("format.Node: %w", err) } - res := &transpileResult{ + res := &Result{ Imports: f.Imports, Translated: out.String(), + File: transformed, } return res, nil } -// TranspileVerifyFile tries to run `go fmt` against a transpiled .go file. -// -// This is fast and won't look the imports. -func TranspileVerifyFile(path string, gofmtBinary string) error { - // TODO: use cmd/parser instead of exec? +type transpileCtx struct { + // If rootDir is given, we will check that the directory of the import path + // exists (using rootDir/packageDirLocation()). + rootDir string + // This should be set if we're working with a file from a standard library. + // This allows us to easily check if a function has a native binding, and as + // such modify its call expressions appropriately. + stdlibPath string - args := strings.Split(gofmtBinary, " ") - args = append(args, []string{"-l", "-e", path}...) - cmd := exec.Command(args[0], args[1:]...) - out, err := cmd.CombinedOutput() - if err != nil { - fmt.Fprintln(os.Stderr, string(out)) - return fmt.Errorf("%s: %w", gofmtBinary, err) - } - return nil -} - -// TranspileBuildPackage tries to run `go build` against the transpiled .go files. -// -// This method is the most efficient to detect errors but requires that -// all the import are valid and available. -func TranspileBuildPackage(fileOrPkg, goBinary string) error { - // TODO: use cmd/compile instead of exec? - // TODO: find the nearest go.mod file, chdir in the same folder, rim prefix? - // TODO: temporarily create an in-memory go.mod or disable go modules for gno? - // TODO: ignore .go files that were not generated from gno? - // TODO: automatically transpile if not yet done. - - files := []string{} - - info, err := os.Stat(fileOrPkg) - if err != nil { - return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) - } - if !info.IsDir() { - file := fileOrPkg - files = append(files, file) - } else { - pkgDir := fileOrPkg - goGlob := filepath.Join(pkgDir, "*.go") - goMatches, err := filepath.Glob(goGlob) - if err != nil { - return fmt.Errorf("glob %s: %w", goGlob, err) - } - for _, goMatch := range goMatches { - switch { - case strings.HasPrefix(goMatch, "."): // skip - case strings.HasSuffix(goMatch, "_filetest.go"): // skip - case strings.HasSuffix(goMatch, "_filetest.gno.gen.go"): // skip - case strings.HasSuffix(goMatch, "_test.go"): // skip - case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip - default: - files = append(files, goMatch) - } - } - } - - sort.Strings(files) - args := append([]string{"build", "-v", "-tags=gno"}, files...) - cmd := exec.Command(goBinary, args...) - rootDir, err := guessRootDir(fileOrPkg, goBinary) - if err == nil { - cmd.Dir = rootDir - } - out, err := cmd.CombinedOutput() - if _, ok := err.(*exec.ExitError); ok { - // exit error - return parseGoBuildErrors(string(out)) - } - return err -} - -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. -// -// TODO(tb): update when `go build -json` is released to replace regexp usage. -// See https://github.com/golang/go/issues/62067 -func parseGoBuildErrors(out string) error { - var errList goscanner.ErrorList - matches := reGoBuildError.FindAllStringSubmatch(out, -1) - for _, match := range matches { - filename := match[1] - line, err := strconv.Atoi(match[2]) - if err != nil { - return fmt.Errorf("parse line go build error %s: %w", match, err) - } - - column, err := strconv.Atoi(match[3]) - if err != nil { - return fmt.Errorf("parse column go build error %s: %w", match, err) - } - msg := match[4] - errList.Add(token.Position{ - Filename: filename, - Line: line, - Column: column, - }, msg) - } - return errList.Err() + stdlibImports map[string]string // symbol -> import path } -func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { +func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.File, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) + ctx.stdlibImports = make(map[string]string) - // import whitelist - if checkWhitelist { - for _, paragraph := range imports { - for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - continue - } - - if strings.HasPrefix(importPath, GnoPurePkgsPrefixBefore) { - continue - } - - valid := false - for _, whitelisted := range stdlibWhitelist { - if importPath == whitelisted { - valid = true - break - } - } - if valid { - continue - } - - for _, whitelisted := range importPrefixWhitelist { - if strings.HasPrefix(importPath, whitelisted) { - valid = true - break - } - } - if valid { - continue - } - - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q is not in the whitelist", importPath)) - } - } - } - - // rewrite imports + // rewrite imports to point to stdlibs/ or examples/ for _, paragraph := range imports { for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - // std package - if importPath == GnoStdPkgBefore { - if !astutil.RewriteImport(fset, f, GnoStdPkgBefore, GnoStdPkgAfter) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", GnoStdPkgBefore, GnoStdPkgAfter)) - } + importPath, err := strconv.Unquote(importSpec.Path.Value) + if err != nil { + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("can't unquote import path %s: %v", importSpec.Path.Value, err)) + continue } - // p/pkg packages - if strings.HasPrefix(importPath, GnoPurePkgsPrefixBefore) { - target := GnoPurePkgsPrefixAfter + strings.TrimPrefix(importPath, GnoPurePkgsPrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) + if ctx.rootDir != "" { + dirPath := filepath.Join(ctx.rootDir, PackageDirLocation(importPath)) + if _, err := os.Stat(dirPath); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q does not exist", importPath)) + continue } } - // r/realm packages - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - target := GnoRealmPkgsPrefixAfter + strings.TrimPrefix(importPath, GnoRealmPkgsPrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) + // Create mapping + if gno.IsStdlib(importPath) { + if importSpec.Name != nil { + ctx.stdlibImports[importSpec.Name.Name] = importPath + } else { + // XXX: imperfect, see comment on transformCallExpr + ctx.stdlibImports[path.Base(importPath)] = importPath } } + + transp := TranspileImportPath(importPath) + importSpec.Path.Value = strconv.Quote(transp) } } @@ -343,14 +195,72 @@ func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.No node := astutil.Apply(f, // pre func(c *astutil.Cursor) bool { - // do things here + node := c.Node() + // is function declaration without body? + // -> delete (native binding) + if fd, ok := node.(*ast.FuncDecl); ok && fd.Body == nil { + c.Delete() + return false // don't attempt to traverse children + } + + // is function call to a native function? + // -> rename if unexported, apply `nil,` for the first arg if necessary + if ce, ok := node.(*ast.CallExpr); ok { + return ctx.transformCallExpr(c, ce) + } + return true }, + // post func(c *astutil.Cursor) bool { - // and here return true }, ) - return node, errs.Err() + return node.(*ast.File), errs.Err() +} + +func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) bool { + switch fe := ce.Fun.(type) { + case *ast.SelectorExpr: + // XXX: This is not correct in 100% of cases. If I shadow the `std` symbol, and + // its replacement is a type with the method AssertOriginCall, this system + // will incorrectly add a `nil` as the first argument. + // A full fix requires understanding scope; the Go standard library recommends + // using go/types, which for proper functioning requires an importer + // which can work with Gno. This is deferred for a future PR. + id, ok := fe.X.(*ast.Ident) + if !ok { + break + } + ip, ok := ctx.stdlibImports[id.Name] + if !ok { + break + } + nat := stdlibs.FindNative(ip, gno.Name(fe.Sel.Name)) + if nat != nil && nat.HasMachineParam() { + // Because it's an import, the symbol is always exported, so no need for the + // X_ prefix we add below. + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + + case *ast.Ident: + // Is this a native binding? + // Note: this is only useful within packages like `std` and `math`. + // The logic here is not robust to be generic. It does not account for locally + // defined scope. However, because native bindings have a narrowly defined and + // controlled scope (standard libraries) this will work for our usecase. + nat := stdlibs.FindNative(ctx.stdlibPath, gno.Name(fe.Name)) + if ctx.stdlibPath != "" && nat != nil { + if nat.HasMachineParam() { + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + if !fe.IsExported() { + // Prefix unexported names with X_, per native binding convention + // (to export the symbol within Go). + fe.Name = "X_" + fe.Name + } + } + } + return true } diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index b9e9b218675..2a0707f7f79 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -2,21 +2,69 @@ package transpiler import ( "go/ast" - goscanner "go/scanner" - "go/token" + "path/filepath" "strings" "testing" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestTranspiledFilenameAndTags(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + changed string + tags string + }{ + { + "hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "a/b/hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "hey_test.gno", + ".hey_test.gno.gen_test.go", + "gno && test", + }, + { + "hey_filetest.gno", + ".hey_filetest.gno.gen.go", + "gno && filetest", + }, + { + "badname.go", + "badname.go.gno.gen.go", + "gno", + }, + { + "badname_test.go", + "badname_test.go.gno.gen.go", + "gno", + }, + } + + for _, tc := range tt { + newName, tags := TranspiledFilenameAndTags(tc.name) + assert.Equal(t, tc.changed, newName, "name for %q", tc.name) + assert.Equal(t, tc.tags, tags, "tags for %q", tc.name) + } +} + func TestTranspile(t *testing.T) { t.Parallel() cases := []struct { name string tags string + filename string source string expectedOutput string expectedImports []*ast.ImportSpec @@ -75,7 +123,7 @@ func hello() string { //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = std.Foo @@ -87,9 +135,8 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 26, }, }, }, @@ -119,7 +166,6 @@ func foo() { _ = users.Register } Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/r/demo/users"`, }, - EndPos: 44, }, }, }, @@ -130,7 +176,7 @@ package foo import "gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedOutput: ` // Code generated by github.com/gnolang/gno. DO NOT EDIT. @@ -140,7 +186,7 @@ package foo import "github.com/gnolang/gno/examples/gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedImports: []*ast.ImportSpec{ { @@ -149,7 +195,6 @@ func foo() { _ = avl.Tree } Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/avl"`, }, - EndPos: 42, }, }, }, @@ -171,7 +216,7 @@ func hello() string { //line foo.gno:1:1 package foo -import bar "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import bar "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = bar.Foo @@ -187,14 +232,13 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 25, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 30, }, }, }, { - name: "blacklisted-package", + name: "unknown-package", source: ` package foo @@ -202,7 +246,7 @@ import "reflect" func foo() { _ = reflect.ValueOf } `, - expectedError: `transpileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, + expectedError: `transpileAST: foo.gno:3:8: import "reflect" does not exist`, }, { name: "syntax-error", @@ -226,6 +270,27 @@ import "gno.land/p/demo/unknownxyz" //line foo.gno:1:1 package foo +import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" +`, + expectedError: `transpileAST: foo.gno:3:8: import "gno.land/p/demo/unknownxyz" does not exist`, + }, + { + // Test files should allow unknown imports while + // we still have "native" packages. + + name: "unknown-realm-test", + filename: "foo_test.gno", + source: ` +package foo + +import "gno.land/p/demo/unknownxyz" +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line foo_test.gno:1:1 +package foo + import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" `, expectedImports: []*ast.ImportSpec{ @@ -235,12 +300,11 @@ import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz"`, }, - EndPos: 49, }, }, }, { - name: "whitelisted-package", + name: "imported-package", source: ` package foo @@ -254,7 +318,7 @@ func foo() { _ = regexp.MatchString } //line foo.gno:1:1 package foo -import "regexp" +import "github.com/gnolang/gno/gnovm/stdlibs/regexp" func foo() { _ = regexp.MatchString } `, @@ -263,11 +327,87 @@ func foo() { _ = regexp.MatchString } Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"regexp"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/regexp"`, }, }, }, }, + { + name: "natbind-func", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/math/math.gno"), + source: ` +package math + +import "std" + +func Float32bits(i float32) uint32 + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall() +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall() +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line math.gno:1:1 +package math + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall(nil) +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall(nil) +} +`, + expectedImports: []*ast.ImportSpec{ + { + Path: &ast.BasicLit{ + ValuePos: 22, + Kind: 9, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, + }, + }, + }, + }, + { + name: "natbind-std", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/std/std.gno"), + source: ` +package std + +func AssertOriginCall() +func origCaller() string + +func testfunc() { + AssertOriginCall() + println(origCaller()) +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line std.gno:1:1 +package std + +func testfunc() { + AssertOriginCall(nil) + println(X_origCaller(nil)) +} +`, + }, } for _, c := range cases { c := c // scopelint @@ -276,7 +416,11 @@ func foo() { _ = regexp.MatchString } // "\n" is added for better test case readability, now trim it source := strings.TrimPrefix(c.source, "\n") - res, err := Transpile(source, c.tags, "foo.gno") + filename := c.filename + if filename == "" { + filename = "foo.gno" + } + res, err := Transpile(source, c.tags, filename) if c.expectedError != "" { require.EqualError(t, err, c.expectedError) @@ -289,53 +433,3 @@ func foo() { _ = regexp.MatchString } }) } } - -func TestParseGoBuildErrors(t *testing.T) { - tests := []struct { - name string - output string - expectedError error - }{ - { - name: "empty output", - output: "", - expectedError: nil, - }, - { - name: "random output", - output: "xxx", - expectedError: nil, - }, - { - name: "some errors", - output: `xxx -main.gno:6:2: nasty error -pkg/file.gno:60:20: ugly error`, - expectedError: goscanner.ErrorList{ - &goscanner.Error{ - Pos: token.Position{ - Filename: "main.gno", - Line: 6, - Column: 2, - }, - Msg: "nasty error", - }, - &goscanner.Error{ - Pos: token.Position{ - Filename: "pkg/file.gno", - Line: 60, - Column: 20, - }, - Msg: "ugly error", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := parseGoBuildErrors(tt.output) - - assert.Equal(t, tt.expectedError, err) - }) - } -} diff --git a/gnovm/stdlibs/bytes/boundary_test.gno b/gnovm/stdlibs/bytes/boundary_test.gno index 9873b1db987..5d77c576ff8 100644 --- a/gnovm/stdlibs/bytes/boundary_test.gno +++ b/gnovm/stdlibs/bytes/boundary_test.gno @@ -1,8 +1,6 @@ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// -//go:build linux package bytes_test diff --git a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno index 903e2653572..91e5481fc42 100644 --- a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno +++ b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a license that can be // found in the LICENSE file. -//go:build (!amd64 && !386) || gccgo || appengine || nacl - package chacha import "encoding/binary" diff --git a/gnovm/stdlibs/internal/bytealg/compare_generic.gno b/gnovm/stdlibs/internal/bytealg/compare_generic.gno index e1795e47e9a..b56d0a67e02 100644 --- a/gnovm/stdlibs/internal/bytealg/compare_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/compare_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !wasm && !mips64 && !mips64le - package bytealg // import _ "unsafe" // for go:linkname @@ -35,7 +33,6 @@ samebytes: return 0 } -//go:linkname runtime_cmpstring runtime.cmpstring func runtime_cmpstring(a, b string) int { l := len(a) if len(b) < l { diff --git a/gnovm/stdlibs/internal/bytealg/count_generic.gno b/gnovm/stdlibs/internal/bytealg/count_generic.gno index 932a7c584c1..de08418fcaa 100644 --- a/gnovm/stdlibs/internal/bytealg/count_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/count_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm && !arm64 && !ppc64le && !ppc64 && !riscv64 && !s390x - package bytealg func Count(b []byte, c byte) int { diff --git a/gnovm/stdlibs/internal/bytealg/index_generic.gno b/gnovm/stdlibs/internal/bytealg/index_generic.gno index a59e32938e7..d751b1bc940 100644 --- a/gnovm/stdlibs/internal/bytealg/index_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/index_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm64 && !s390x && !ppc64le && !ppc64 - package bytealg const MaxBruteForce = 0 diff --git a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno index 0a45f903843..47aee225df9 100644 --- a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !mips64 && !mips64le && !riscv64 && !wasm - package bytealg func IndexByte(b []byte, c byte) int { diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 7319e393c35..3dd432c90c0 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -16,15 +16,25 @@ import ( libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "crypto/ed25519", "verify", @@ -36,6 +46,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -69,6 +80,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[32]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -96,6 +108,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -123,6 +136,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -150,6 +164,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -177,6 +192,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -206,6 +222,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -245,6 +262,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -281,6 +299,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -314,6 +333,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -347,6 +367,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -378,6 +399,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p1"), Type: gno.X("[]string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -400,6 +422,7 @@ var nativeFuncs = [...]nativeFunc{ "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { libs_std.AssertOriginCall( m, @@ -413,6 +436,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := libs_std.IsOriginCall( m, @@ -432,6 +456,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetChainID( m, @@ -451,6 +476,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetHeight( m, @@ -471,6 +497,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { r0, r1 := libs_std.X_origSend( m, @@ -495,6 +522,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origCaller( m, @@ -514,6 +542,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origPkgAddr( m, @@ -535,6 +564,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -565,6 +595,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("string")}, {Name: gno.N("r1"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -599,6 +630,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -627,6 +659,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -659,6 +692,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("[20]byte")}, {Name: gno.N("r2"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -696,6 +730,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -725,6 +760,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -759,6 +795,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("int")}, {Name: gno.N("r1"), Type: gno.X("error")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -791,6 +828,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -819,6 +857,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -850,6 +889,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -880,6 +920,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -907,6 +948,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -932,6 +974,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := libs_testing.X_unixNano() @@ -951,6 +994,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("int32")}, {Name: gno.N("r2"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0, r1, r2 := libs_time.X_now( m, diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 48e69f78253..c9b16815ab5 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -13,11 +13,24 @@ func GetContext(m *gno.Machine) ExecContext { return libsstd.GetContext(m) } -func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { - for _, nf := range nativeFuncs { +// FindNative returns the NativeFunc associated with the given pkgPath+name +// combination. If there is none, FindNative returns nil. +func FindNative(pkgPath string, name gno.Name) *NativeFunc { + for i, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { - return nf.f + return &nativeFuncs[i] } } return nil } + +// NativeStore is used by the GnoVM to determine if the given function, +// specified by its pkgPath and name, has a native implementation; and if so +// retrieve it. +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + nt := FindNative(pkgPath, name) + if nt == nil { + return nil + } + return nt.f +} diff --git a/gnovm/stdlibs/stdshim/addr_set.gno b/gnovm/stdlibs/stdshim/addr_set.gno deleted file mode 100644 index 3f46f48b8b5..00000000000 --- a/gnovm/stdlibs/stdshim/addr_set.gno +++ /dev/null @@ -1,47 +0,0 @@ -package std - -import "errors" - -//---------------------------------------- -// AddressSet - -type AddressSet interface { - Size() int - AddAddress(Address) error - HasAddress(Address) bool -} - -//---------------------------------------- -// AddressList implements AddressSet. -// TODO implement AddressTree with avl. - -type AddressList []Address - -func NewAddressList() *AddressList { - return &AddressList{} -} - -func (alist *AddressList) Size() int { - return len(*alist) -} - -func (alist *AddressList) AddAddress(newAddr Address) error { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return errors.New("address already exists") - } - } - *alist = append(*alist, newAddr) - return nil -} - -func (alist *AddressList) HasAddress(newAddr Address) bool { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return true - } - } - return false -} diff --git a/gnovm/stdlibs/stdshim/banker.gno b/gnovm/stdlibs/stdshim/banker.gno deleted file mode 100644 index 44e611b780f..00000000000 --- a/gnovm/stdlibs/stdshim/banker.gno +++ /dev/null @@ -1,70 +0,0 @@ -package std - -// Realm functions can call std.GetBanker(options) to get -// a banker instance. Banker objects cannot be persisted, -// but can be passed onto other functions to be transacted -// on. A banker instance can be passed onto other realm -// functions; this allows other realms to spend coins on -// behalf of the first realm. -// -// Banker panics on errors instead of returning errors. -// This also helps simplify the interface and prevent -// hidden bugs (e.g. ignoring errors) -// -// NOTE: this Gno interface is satisfied by a native go -// type, and those can't return non-primitive objects -// (without confusion). -type Banker interface { - GetCoins(addr Address) (dst Coins) - SendCoins(from, to Address, amt Coins) - TotalCoin(denom string) int64 - IssueCoin(addr Address, denom string, amount int64) - RemoveCoin(addr Address, denom string, amount int64) -} - -// Also available natively in stdlibs/context.go -type BankerType uint8 - -// Also available natively in stdlibs/context.go -const ( - // Can only read state. - BankerTypeReadonly BankerType = iota - // Can only send from tx send. - BankerTypeOrigSend - // Can send from all realm coins. - BankerTypeRealmSend - // Can issue and remove realm coins. - BankerTypeRealmIssue -) - -//---------------------------------------- -// adapter for native banker - -type bankAdapter struct { - nativeBanker Banker -} - -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) - } - return dst -} - -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) -} - -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) -} - -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) -} - -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) -} diff --git a/gnovm/stdlibs/stdshim/coins.gno b/gnovm/stdlibs/stdshim/coins.gno deleted file mode 100644 index 4589113bff4..00000000000 --- a/gnovm/stdlibs/stdshim/coins.gno +++ /dev/null @@ -1,174 +0,0 @@ -package std - -import "strconv" - -// NOTE: this is selectively copied over from tm2/pkgs/std/coin.go - -// Coin hold some amount of one currency. -// A negative amount is invalid. -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -// NewCoin returns a new coin with a denomination and amount -func NewCoin(denom string, amount int64) Coin { - return Coin{ - Denom: denom, - Amount: amount, - } -} - -// String provides a human-readable representation of a coin -func (c Coin) String() string { - return strconv.Itoa(int(c.Amount)) + c.Denom -} - -// IsGTE returns true if they are the same type and the receiver is -// an equal or greater value -func (c Coin) IsGTE(other Coin) bool { - mustMatchDenominations(c.Denom, other.Denom) - - return c.Amount >= other.Amount -} - -// IsLT returns true if they are the same type and the receiver is -// a smaller value -func (c Coin) IsLT(other Coin) bool { - mustMatchDenominations(c.Denom, other.Denom) - - return c.Amount < other.Amount -} - -// IsEqual returns true if the two sets of Coins have the same value -func (c Coin) IsEqual(other Coin) bool { - mustMatchDenominations(c.Denom, other.Denom) - - return c.Amount == other.Amount -} - -// Add adds amounts of two coins with same denom. -// If the coins differ in denom then it panics. -// An overflow or underflow panics. -// An invalid result panics. -func (c Coin) Add(coinB Coin) Coin { - mustMatchDenominations(c.Denom, coinB.Denom) - - sum := c.Amount + coinB.Amount - - c.Amount = sum - return c -} - -// Sub subtracts amounts of two coins with same denom. -// If the coins differ in denom then it panics. -// An overflow or underflow panics. -// An invalid result panics. -func (c Coin) Sub(coinB Coin) Coin { - mustMatchDenominations(c.Denom, coinB.Denom) - - dff := c.Amount - coinB.Amount - c.Amount = dff - - return c -} - -// IsPositive returns true if coin amount is positive. -func (c Coin) IsPositive() bool { - return c.Amount > 0 -} - -// IsNegative returns true if the coin amount is negative and false otherwise. -func (c Coin) IsNegative() bool { - return c.Amount < 0 -} - -// IsZero returns if this represents no money -func (c Coin) IsZero() bool { - return c.Amount == 0 -} - -func mustMatchDenominations(denomA, denomB string) { - if denomA != denomB { - panic("incompatible coin denominations: " + denomA + ", " + denomB) - } -} - -// Coins is a set of Coin, one per currency -type Coins []Coin - -// NewCoins returns a new set of Coins given one or more Coins -// Consolidates any denom duplicates into one, keeping the properties of a mathematical set -func NewCoins(coins ...Coin) Coins { - coinMap := make(map[string]int64) - - for _, coin := range coins { - coinMap[coin.Denom] = coin.Amount - } - - var setCoins Coins - for denom, amount := range coinMap { - setCoins = append(setCoins, NewCoin(denom, amount)) - } - - return setCoins -} - -// String returns the string representation of Coins -func (cz Coins) String() string { - if len(cz) == 0 { - return "" - } - - res := "" - for i, c := range cz { - if i > 0 { - res += "," - } - res += c.String() - } - - return res -} - -// AmountOf returns the amount of a specific coin from the Coins set -func (cz Coins) AmountOf(denom string) int64 { - for _, c := range cz { - if c.Denom == denom { - return c.Amount - } - } - - return 0 -} - -// Add adds a Coin to the Coins set -func (cz Coins) Add(b Coins) Coins { - c := Coins{} - for _, ac := range cz { - bc := b.AmountOf(ac.Denom) - ac.Amount += bc - c = append(c, ac) - } - - for _, bc := range b { - cc := c.AmountOf(bc.Denom) - if cc == 0 { - c = append(c, bc) - } - } - - return c -} - -// expandNative expands for usage within natively bound functions. -func (cz Coins) expandNative() (denoms []string, amounts []int64) { - denoms = make([]string, len(cz)) - amounts = make([]int64, len(cz)) - for i, coin := range cz { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - - return denoms, amounts -} diff --git a/gnovm/stdlibs/stdshim/context.gno b/gnovm/stdlibs/stdshim/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/stdshim/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/stdshim/crypto.gno b/gnovm/stdlibs/stdshim/crypto.gno deleted file mode 100644 index 402a6af3e22..00000000000 --- a/gnovm/stdlibs/stdshim/crypto.gno +++ /dev/null @@ -1,17 +0,0 @@ -package std - -type Address string // NOTE: bech32 - -func (a Address) String() string { - return string(a) -} - -// IsValid checks if the address is valid bech32 encoded string -func (a Address) IsValid() bool { - _, _, ok := DecodeBech32(a) - return ok -} - -const RawAddressSize = 20 - -type RawAddress [RawAddressSize]byte diff --git a/gnovm/stdlibs/stdshim/frame.gno b/gnovm/stdlibs/stdshim/frame.gno deleted file mode 100644 index bc3a000f5a0..00000000000 --- a/gnovm/stdlibs/stdshim/frame.gno +++ /dev/null @@ -1,18 +0,0 @@ -package std - -type Realm struct { - addr Address - pkgPath string -} - -func (r Realm) Addr() Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno deleted file mode 100644 index 47a176296be..00000000000 --- a/gnovm/stdlibs/stdshim/stdshim.gno +++ /dev/null @@ -1,85 +0,0 @@ -package std - -const shimWarn = "stdshim cannot be used to run code" - -func AssertOriginCall() { - panic(shimWarn) -} - -func IsOriginCall() (isOrigin bool) { - panic(shimWarn) - return false -} - -func Hash(bz []byte) (hash [20]byte) { - panic(shimWarn) - return -} - -func GetChainID() string { - panic(shimWarn) - return "" -} - -func GetHeight() int64 { - panic(shimWarn) - return -1 -} - -func GetOrigSend() Coins { - panic(shimWarn) - return Coins{} -} - -func CurrentRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func PrevRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func GetOrigCaller() Address { - panic(shimWarn) - return Address("") -} - -func GetOrigPkgAddr() Address { - panic(shimWarn) - return Address("") -} - -func GetCallerAt(n int) Address { - panic(shimWarn) - return Address("") -} - -func GetBanker(bankerType BankerType) Banker { - panic(shimWarn) - return nil -} - -func EncodeBech32(prefix string, bytes [20]byte) (addr Address) { - panic(shimWarn) - return "" -} - -func DecodeBech32(addr Address) (prefix string, bytes [20]byte, ok bool) { - panic(shimWarn) -} - -func DerivePkgAddr(pkgPath string) (addr Address) { - panic(shimWarn) -} - -func Emit(typ string, attrs ...string) { - panic(shimWarn) -} diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 521679e48d5..f3395142d1d 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -80,7 +80,6 @@ package time import ( "errors" - // XXX _ "unsafe" // for go:linkname ) // A Time represents an instant in time with nanosecond precision. @@ -1072,8 +1071,6 @@ func daysSinceEpoch(year int) uint64 { func now() (sec int64, nsec int32, mono int64) // injected // runtimeNano returns the current value of the runtime clock in nanoseconds. -// -//go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 { _, _, mono := now() return mono diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 852bb74d198..8ab60145bd5 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -263,8 +263,18 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) } + parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) + if len(parts) == 2 { + fmt.Println(parts[0]) + errstr = parts[0] + } if errstr != errWanted { - panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted)) + if f.syncWanted { + // write error to file + replaceWantedInPlace(path, "Error", errstr) + } else { + panic(fmt.Sprintf("fail on %s: got %q, want: %q", path, errstr, errWanted)) + } } // NOTE: ignores any gno.GetDebugErrors(). @@ -279,12 +289,16 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { } else { errstr = strings.TrimSpace(fmt.Sprintf("%v", pnc)) } + parts := strings.SplitN(errstr, ":\n--- preprocess stack ---", 2) + if len(parts) == 2 { + fmt.Println(parts[0]) + errstr = parts[0] + } // check tip line, write to file - ctl := fmt.Sprintf( - errstr + - "\n*** CHECK THE ERR MESSAGES ABOVE, MAKE SURE IT'S WHAT YOU EXPECTED, " + - "DELETE THIS LINE AND RUN TEST AGAIN ***", - ) + ctl := errstr + + "\n*** CHECK THE ERR MESSAGES ABOVE, MAKE SURE IT'S WHAT YOU EXPECTED, " + + "DELETE THIS LINE AND RUN TEST AGAIN ***" + // write error to file replaceWantedInPlace(path, "Error", ctl) panic(fmt.Sprintf("fail on %s: err recorded, check the message and run test again", path)) } diff --git a/gnovm/tests/files/access0.gno b/gnovm/tests/files/access0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access0.gno rename to gnovm/tests/files/access0_stdlibs.gno diff --git a/gnovm/tests/files/access1.gno b/gnovm/tests/files/access1_stdlibs.gno similarity index 53% rename from gnovm/tests/files/access1.gno rename to gnovm/tests/files/access1_stdlibs.gno index d9bb9f46203..7a2f2cf3817 100644 --- a/gnovm/tests/files/access1.gno +++ b/gnovm/tests/files/access1_stdlibs.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/access1.gno:8: cannot access gno.land/p/demo/testutils.testVar2 from main +// main/files/access1_stdlibs.gno:8: cannot access gno.land/p/demo/testutils.testVar2 from main diff --git a/gnovm/tests/files/access2.gno b/gnovm/tests/files/access2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access2.gno rename to gnovm/tests/files/access2_stdlibs.gno diff --git a/gnovm/tests/files/access3.gno b/gnovm/tests/files/access3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access3.gno rename to gnovm/tests/files/access3_stdlibs.gno diff --git a/gnovm/tests/files/access4.gno b/gnovm/tests/files/access4_stdlibs.gno similarity index 57% rename from gnovm/tests/files/access4.gno rename to gnovm/tests/files/access4_stdlibs.gno index bb2dcc340f0..6f092efb61c 100644 --- a/gnovm/tests/files/access4.gno +++ b/gnovm/tests/files/access4_stdlibs.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/access4.gno:9: cannot access gno.land/p/demo/testutils.TestAccessStruct.privateField from main +// main/files/access4_stdlibs.gno:9: cannot access gno.land/p/demo/testutils.TestAccessStruct.privateField from main diff --git a/gnovm/tests/files/access5.gno b/gnovm/tests/files/access5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/access5.gno rename to gnovm/tests/files/access5_stdlibs.gno diff --git a/gnovm/tests/files/access6.gno b/gnovm/tests/files/access6_stdlibs.gno similarity index 67% rename from gnovm/tests/files/access6.gno rename to gnovm/tests/files/access6_stdlibs.gno index 991b4a5457a..b9e019525a7 100644 --- a/gnovm/tests/files/access6.gno +++ b/gnovm/tests/files/access6_stdlibs.gno @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/access6.gno:15: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access6_stdlibs.gno:15: main.mystruct does not implement gno.land/p/demo/testutils.PrivateInterface diff --git a/gnovm/tests/files/access7.gno b/gnovm/tests/files/access7_stdlibs.gno similarity index 72% rename from gnovm/tests/files/access7.gno rename to gnovm/tests/files/access7_stdlibs.gno index 2b4c32ab818..ab63b3b9639 100644 --- a/gnovm/tests/files/access7.gno +++ b/gnovm/tests/files/access7_stdlibs.gno @@ -20,4 +20,4 @@ func main() { } // Error: -// main/files/access7.gno:19: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface +// main/files/access7_stdlibs.gno:19: main.PrivateInterface2 does not implement gno.land/p/demo/testutils.PrivateInterface diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno index 5d994352f35..ba40fb8caa3 100644 --- a/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType6_filetest.gno @@ -17,4 +17,4 @@ func main() { } // Error: -// main/files/assign_unnamed_type/method/declaredType6_filetest.gno:15: cannot use main.c as main.word without explicit conversion +// main/files/assign_unnamed_type/method/declaredType6_filetest.gno:15: cannot use []main.c as []main.word diff --git a/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno index 88b3203a805..9e46c884252 100644 --- a/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/method/declaredType6b_filetest.gno @@ -17,4 +17,4 @@ func main() { } // Error: -// main/files/assign_unnamed_type/method/declaredType6b_filetest.gno:15: cannot use uint as main.word without explicit conversion +// main/files/assign_unnamed_type/method/declaredType6b_filetest.gno:15: cannot use []uint as []main.word diff --git a/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest_stdlibs.gno similarity index 100% rename from gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest.gno rename to gnovm/tests/files/assign_unnamed_type/more/cross_realm_compositelit_filetest_stdlibs.gno diff --git a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno index 6549d1824ed..6ef12bd33d4 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno @@ -30,7 +30,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ // "Data": null, // "List": [ // { @@ -41,13 +41,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -58,8 +58,8 @@ func main() { // "@type": "/gno.SliceValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "9263ebf7e55e2d929e9c190bc4490ee58db148ec", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// "Hash": "e256933ba4dfda233a7edb0902880d554118ba6e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Length": "1", // "Maxcap": "1", @@ -68,10 +68,29 @@ func main() { // } // ], // "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Int" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2b8a024c53e94431e6203115feaf86b36413d7b2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -209,19 +228,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "3c89d875f7d6daa94113aa4c7e03432ba56202c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Int" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "91ebdb8ff6b68e0b93179fae022213185a450649", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno index cc1d54cfa2a..8f1eb5399b2 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype0b_filetest.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/assign_unnamed_type/unnamedtype0b_filetest.gno:11: cannot use main.word as int without explicit conversion +// main/files/assign_unnamed_type/unnamedtype0b_filetest.gno:11: cannot use []main.word as []int diff --git a/gnovm/tests/files/bigint1.gno b/gnovm/tests/files/bigint1.gno deleted file mode 100644 index 01032e27b3f..00000000000 --- a/gnovm/tests/files/bigint1.gno +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "math" -) - -func main() { - println(float64(math.MaxUint32)) - println(float64(math.MaxUint64)) -} - -// Output: -// 4.294967295e+09 -// 1.8446744073709552e+19 diff --git a/gnovm/tests/files/bool6.gno b/gnovm/tests/files/bool6.gno index ad4d832036c..3ad789dda32 100644 --- a/gnovm/tests/files/bool6.gno +++ b/gnovm/tests/files/bool6.gno @@ -9,4 +9,4 @@ func X() string { } // Error: -// main/files/bool6.gno:8: operands of boolean operators must evaluate to boolean typed values +// main/files/bool6.gno:8: operator || not defined on: StringKind diff --git a/gnovm/tests/files/comp4.gno b/gnovm/tests/files/comp4.gno new file mode 100644 index 00000000000..501cffeb871 --- /dev/null +++ b/gnovm/tests/files/comp4.gno @@ -0,0 +1,32 @@ +package main + +type S struct { +} + +func main() { + x := (*S)(nil) + println("x == x", x == x) + println("x == nil", x == nil) + println("nil == x", nil == x) + + var y interface{} = (*S)(nil) + println("y == y", y == y) + println("y == nil", y == nil) + println("nil == y", nil == y) + + y = nil + println("y == y", y == y) + println("y == nil", y == nil) + println("nil == y", nil == y) +} + +// Output: +// x == x true +// x == nil true +// nil == x true +// y == y true +// y == nil false +// nil == y false +// y == y true +// y == nil true +// nil == y true diff --git a/gnovm/tests/files/comp5.gno b/gnovm/tests/files/comp5.gno new file mode 100644 index 00000000000..827800f2a67 --- /dev/null +++ b/gnovm/tests/files/comp5.gno @@ -0,0 +1,23 @@ +package main + +type S struct { +} + +func main() { + x := (*S)(nil) + var y interface{} = (*S)(nil) + var znil interface{} = nil + + println("y == znil", y == znil) + println("znil == y", znil == y) + + y = nil + println("y == znil", y == znil) + println("znil == y", znil == y) +} + +// Output: +// y == znil false +// znil == y false +// y == znil true +// znil == y true diff --git a/gnovm/tests/files/for19.gno b/gnovm/tests/files/for19.gno new file mode 100644 index 00000000000..ce02c5fb655 --- /dev/null +++ b/gnovm/tests/files/for19.gno @@ -0,0 +1,25 @@ +package main + +func main() { + v := T + i := 0 + for v { + if i > 2 { + break + } + println(i) + i++ + } +} + +type C bool + +const ( + F C = false + T C = true +) + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/for7.gno b/gnovm/tests/files/for7.gno index 92ad5f3ce34..0f471956314 100644 --- a/gnovm/tests/files/for7.gno +++ b/gnovm/tests/files/for7.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// main/files/for7.gno:4: cannot use int as bool +// main/files/for7.gno:4: expected typed bool kind, but got IntKind diff --git a/gnovm/tests/files/fun24.gno b/gnovm/tests/files/fun24.gno index 42468ff087e..100d3c26ded 100644 --- a/gnovm/tests/files/fun24.gno +++ b/gnovm/tests/files/fun24.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun24.gno:3: cannot convert StringKind to IntKind +// main/files/fun24.gno:3: cannot use untyped string as IntKind diff --git a/gnovm/tests/files/heap_item_value.gno b/gnovm/tests/files/heap_item_value.gno new file mode 100644 index 00000000000..fe2873f9b60 --- /dev/null +++ b/gnovm/tests/files/heap_item_value.gno @@ -0,0 +1,178 @@ +// PKGPATH: gno.land/r/test +package test + +type S struct { + A int +} + +var a, b *S + +func main() { + a = new(S) + a.A = 4 + b = a +} + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Fields": [ +// { +// "N": "BAAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "IsEscaped": true, +// "ModTime": "0", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8c001dde13b1f4dc01fc6d3a5bb4bc0cdfe2a50b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "3", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "A", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// ], +// "PkgPath": "gno.land/r/test" +// }, +// "Methods": [], +// "Name": "S", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ] +// } diff --git a/gnovm/tests/files/heap_item_value_init.gno b/gnovm/tests/files/heap_item_value_init.gno new file mode 100644 index 00000000000..0d1db7bcb76 --- /dev/null +++ b/gnovm/tests/files/heap_item_value_init.gno @@ -0,0 +1,185 @@ +// PKGPATH: gno.land/r/test +package test + +type S struct { + A *int +} + +var a, b *S + +func init() { + a = new(S) + a.A = new(int) + *a.A = 4 +} + +func main() { + b = a +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "6", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.TypeType" +// }, +// "V": { +// "@type": "/gno.TypeValue", +// "Type": { +// "@type": "/gno.DeclaredType", +// "Base": { +// "@type": "/gno.StructType", +// "Fields": [ +// { +// "Embedded": false, +// "Name": "A", +// "Tag": "", +// "Type": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// } +// } +// ], +// "PkgPath": "gno.land/r/test" +// }, +// "Methods": [], +// "Name": "S", +// "PkgPath": "gno.land/r/test" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.1", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "16", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ] +// } diff --git a/gnovm/tests/files/if2.gno b/gnovm/tests/files/if2.gno index 88261c520c9..bc417a92fe7 100644 --- a/gnovm/tests/files/if2.gno +++ b/gnovm/tests/files/if2.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/if2.gno:7: cannot use int as bool +// main/files/if2.gno:7: expected typed bool kind, but got IntKind diff --git a/gnovm/tests/files/if8.gno b/gnovm/tests/files/if8.gno new file mode 100644 index 00000000000..c013896d9d3 --- /dev/null +++ b/gnovm/tests/files/if8.gno @@ -0,0 +1,18 @@ +package main + +func main() { + v := T + if v { + println("true") + } +} + +type C bool + +const ( + F C = false + T C = true +) + +// Output: +// true diff --git a/gnovm/tests/files/issue_1326.gno b/gnovm/tests/files/issue_1326.gno new file mode 100644 index 00000000000..a86058a63e0 --- /dev/null +++ b/gnovm/tests/files/issue_1326.gno @@ -0,0 +1,64 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "strconv" +) + +func init() { + New() + println(Delta()) +} + +func main() { + println(Delta()) +} + +type Move struct { + N1, N2, N3 byte +} + +type S struct { + Moves []Move +} + +func (s S) clone() S { + mv := s.Moves + return S{Moves: mv} +} + +func (olds S) change() S { + s := olds.clone() + + counter++ + s.Moves = append([]Move{}, s.Moves...) + s.Moves = append(s.Moves, Move{counter, counter, counter}) + return s +} + +var ( + el *S + counter byte +) + +func New() { + el = new(S) +} + +func Delta() string { + n := el.change() + *el = n + return Values() +} + +func Values() string { + s := "" + for _, val := range el.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} + +// Output: +// 1,1,1; +// 1,1,1;2,2,2; diff --git a/gnovm/tests/files/issue_558b.gno b/gnovm/tests/files/issue_558b_stdlibs.gno similarity index 100% rename from gnovm/tests/files/issue_558b.gno rename to gnovm/tests/files/issue_558b_stdlibs.gno diff --git a/gnovm/tests/files/math_native.gno b/gnovm/tests/files/math_native.gno new file mode 100644 index 00000000000..54bddcde1f3 --- /dev/null +++ b/gnovm/tests/files/math_native.gno @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "math" +) + +func main() { + var a float64 = 2.0 + fmt.Println(math.Pi * a) +} + +// Output: +// 6.283185307179586 diff --git a/gnovm/tests/files/op7.gno b/gnovm/tests/files/op7.gno index 7167171035b..0c3f540600b 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: .uverse.error GTR main.T +// main/files/op7.gno:11: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/star_assign.gno b/gnovm/tests/files/star_assign.gno new file mode 100644 index 00000000000..e40fe2794ac --- /dev/null +++ b/gnovm/tests/files/star_assign.gno @@ -0,0 +1,54 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "gno.land/p/demo/ufmt" +) + +type A struct { + nums []int +} + +var ( + intPtr *int + strPtr *string + aPtr *A + concretePtr *int + concreteValue int +) + +func init() { + New() +} + +func main() { + Delta() + println(Values()) +} + +func New() { + intPtr = new(int) + strPtr = new(string) + aPtr = &A{} + concretePtr = &concreteValue +} + +func Delta() { + *intPtr++ + *strPtr += "hello" + *aPtr = A{nums: []int{8, 5, 8}} + *concretePtr = 100 +} + +func Values() string { + var results string + results += ufmt.Sprintf("%d, %s, %d, %d", *intPtr, *strPtr, *concretePtr, concreteValue) + for _, n := range aPtr.nums { + results += ufmt.Sprintf(", %d", n) + } + + return results +} + +// Output: +// 1, hello, 100, 100, 8, 5, 8 diff --git a/gnovm/tests/files/std0.gno b/gnovm/tests/files/std0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std0.gno rename to gnovm/tests/files/std0_stdlibs.gno diff --git a/gnovm/tests/files/std10.gno b/gnovm/tests/files/std10_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std10.gno rename to gnovm/tests/files/std10_stdlibs.gno diff --git a/gnovm/tests/files/std11.gno b/gnovm/tests/files/std11_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std11.gno rename to gnovm/tests/files/std11_stdlibs.gno diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std2.gno rename to gnovm/tests/files/std2_stdlibs.gno diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std3.gno rename to gnovm/tests/files/std3_stdlibs.gno diff --git a/gnovm/tests/files/std4.gno b/gnovm/tests/files/std4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std4.gno rename to gnovm/tests/files/std4_stdlibs.gno diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std5.gno rename to gnovm/tests/files/std5_stdlibs.gno diff --git a/gnovm/tests/files/std6.gno b/gnovm/tests/files/std6_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std6.gno rename to gnovm/tests/files/std6_stdlibs.gno diff --git a/gnovm/tests/files/std7.gno b/gnovm/tests/files/std7_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std7.gno rename to gnovm/tests/files/std7_stdlibs.gno diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std8.gno rename to gnovm/tests/files/std8_stdlibs.gno diff --git a/gnovm/tests/files/std9.gno b/gnovm/tests/files/std9_stdlibs.gno similarity index 100% rename from gnovm/tests/files/std9.gno rename to gnovm/tests/files/std9_stdlibs.gno diff --git a/gnovm/tests/files/time16_native.gno b/gnovm/tests/files/time16_native.gno index 1789fcbef4f..03eca47172f 100644 --- a/gnovm/tests/files/time16_native.gno +++ b/gnovm/tests/files/time16_native.gno @@ -11,4 +11,4 @@ func main() { } // Error: -// main/files/time16_native.gno:10: incompatible types in binary expression: go:time.Duration MUL int64 +// main/files/time16_native.gno:10: incompatible operands in binary expression: go:time.Duration MUL int64 diff --git a/gnovm/tests/files/type31.gno b/gnovm/tests/files/type31.gno index a613c67525c..68c92e9a504 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: string ADD main.String +// main/files/type31.gno:8: invalid operation: mismatched types string and main.String diff --git a/gnovm/tests/files/type32.gno b/gnovm/tests/files/type32.gno index bc943d90186..9565cdba245 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: string ADD main.S +// main/files/type32.gno:9#1: invalid operation: mismatched types string and main.S diff --git a/gnovm/tests/files/types/add_a0.gno b/gnovm/tests/files/types/add_a0.gno new file mode 100644 index 00000000000..9b8e08718b7 --- /dev/null +++ b/gnovm/tests/files/types/add_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) + int8(1)) +} + +// Error: +// main/files/types/add_a0.gno:5: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/add_a1.gno b/gnovm/tests/files/types/add_a1.gno new file mode 100644 index 00000000000..f5bcec883b2 --- /dev/null +++ b/gnovm/tests/files/types/add_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) + Error2(0)) +} + +// Error: +// main/files/types/add_a1.gno:21: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_a0.gno b/gnovm/tests/files/types/add_assign_a0.gno new file mode 100644 index 00000000000..181941d1ea9 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + int(0) += int8(1) +} + +// Error: +// main/files/types/add_assign_a0.gno:5: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/add_assign_a1.gno b/gnovm/tests/files/types/add_assign_a1.gno new file mode 100644 index 00000000000..f169e58d5b1 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + Error1(0) += Error2(0) +} + +// Error: +// main/files/types/add_assign_a1.gno:21: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_a_01.gno b/gnovm/tests/files/types/add_assign_a_01.gno new file mode 100644 index 00000000000..f09c7ed7344 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_a_01.gno @@ -0,0 +1,12 @@ +package main + +func main() { + a := 1 + b := 1 + a, b += 1, 1 + println(a) + println(b) +} + +// Error: +// main/files/types/add_assign_a_01.gno:6: assignment operator += requires only one expression on lhs and rhs diff --git a/gnovm/tests/files/types/add_assign_b0.gno b/gnovm/tests/files/types/add_assign_b0.gno new file mode 100644 index 00000000000..28b6af83d3a --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + r := 1 + r += Error(1) + println(r) +} + +// Error: +// main/files/types/add_assign_b0.gno:8: invalid operation: mismatched types int and main.Error diff --git a/gnovm/tests/files/types/add_assign_b1.gno b/gnovm/tests/files/types/add_assign_b1.gno new file mode 100644 index 00000000000..2e702256f29 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b1.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + r := Error(1) + r += 1 + println(r) +} + +// Output: +// (2 main.Error) diff --git a/gnovm/tests/files/types/add_assign_b2.gno b/gnovm/tests/files/types/add_assign_b2.gno new file mode 100644 index 00000000000..87b09754046 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_b2.gno @@ -0,0 +1,10 @@ +package main + +func main() { + r := 1 + r += 'a' + println(r) +} + +// Output: +// 98 diff --git a/gnovm/tests/files/types/add_assign_d0.gno b/gnovm/tests/files/types/add_assign_d0.gno new file mode 100644 index 00000000000..f3da2497a84 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_d0.gno @@ -0,0 +1,12 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + r := 1.0 + r += 1 + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/add_assign_e.gno b/gnovm/tests/files/types/add_assign_e.gno new file mode 100644 index 00000000000..3a72f9711be --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + var e1 Error1 = Error1(0) + var e2 int64 = 1 + e1 += Error1(e2) + println(e1) +} + +// Output: +// error: 1 diff --git a/gnovm/tests/files/types/add_assign_e0.gno b/gnovm/tests/files/types/add_assign_e0.gno new file mode 100644 index 00000000000..d71bedab118 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e0.gno @@ -0,0 +1,28 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + e1 += e2 + println(e1) +} + +// Error: +// main/files/types/add_assign_e0.gno:23: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_assign_e1.gno b/gnovm/tests/files/types/add_assign_e1.gno new file mode 100644 index 00000000000..3657cf2872d --- /dev/null +++ b/gnovm/tests/files/types/add_assign_e1.gno @@ -0,0 +1,20 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + var e1 Error1 = Error1(0) + e1 += 1 + println(e1) +} + +// Output: +// error: 1 diff --git a/gnovm/tests/files/types/add_assign_f0_stdlibs.gno b/gnovm/tests/files/types/add_assign_f0_stdlibs.gno new file mode 100644 index 00000000000..b91fa58c30d --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f0_stdlibs.gno @@ -0,0 +1,25 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + r := 1 + r += errCmp + println(r) +} + +// Error: +// main/files/types/add_assign_f0_stdlibs.gno:20: invalid operation: mismatched types int and .uverse.error diff --git a/gnovm/tests/files/types/add_assign_f1_stdlibs.gno b/gnovm/tests/files/types/add_assign_f1_stdlibs.gno new file mode 100644 index 00000000000..090f8f51f89 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f1_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// 1. base type of left is int64, op is legal; +// 2. while RHS is interface kind, and can be converted to left +func main() { + r := Error(0) + r += errCmp // in case of this, should panic mismatch on operand, except RHS is untyped + //println(r) + // println(Error(0) == errCmp) // Note: this is different with += + +} + +// Error: +// main/files/types/add_assign_f1_stdlibs.gno:21: invalid operation: mismatched types main.Error and .uverse.error diff --git a/gnovm/tests/files/types/add_assign_f2_stdlibs.gno b/gnovm/tests/files/types/add_assign_f2_stdlibs.gno new file mode 100644 index 00000000000..5cfd351f574 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f2_stdlibs.gno @@ -0,0 +1,25 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + r := Error(0) + errCmp += r + println(errCmp) +} + +// Error: +// main/files/types/add_assign_f2_stdlibs.gno:20: operator += not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_assign_f3.gno b/gnovm/tests/files/types/add_assign_f3.gno new file mode 100644 index 00000000000..7a48c584dff --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f3.gno @@ -0,0 +1,32 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + e1 += e2 + println(e1) +} + +// Error: +// main/files/types/add_assign_f3.gno:27: operator += not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_assign_f4.gno b/gnovm/tests/files/types/add_assign_f4.gno new file mode 100644 index 00000000000..bb4083e8935 --- /dev/null +++ b/gnovm/tests/files/types/add_assign_f4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a += int(1) + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/add_b0.gno b/gnovm/tests/files/types/add_b0.gno new file mode 100644 index 00000000000..3790f9d7134 --- /dev/null +++ b/gnovm/tests/files/types/add_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 + Error(1)) + println(Error(1) + 1) +} + +// Output: +// (2 main.Error) +// (2 main.Error) diff --git a/gnovm/tests/files/types/add_b1.gno b/gnovm/tests/files/types/add_b1.gno new file mode 100644 index 00000000000..854ecaa9770 --- /dev/null +++ b/gnovm/tests/files/types/add_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 + Error(1)) + println(Error(1) + 1) +} + +// Output: +// error: 2 +// error: 2 diff --git a/gnovm/tests/files/types/add_b2.gno b/gnovm/tests/files/types/add_b2.gno new file mode 100644 index 00000000000..b6463134da4 --- /dev/null +++ b/gnovm/tests/files/types/add_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 + "a") +} + +// Error: +// main/files/types/add_b2.gno:5: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/add_b3.gno b/gnovm/tests/files/types/add_b3.gno new file mode 100644 index 00000000000..c8a94c5b6ab --- /dev/null +++ b/gnovm/tests/files/types/add_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" + "a") +} + +// Output: +// ba diff --git a/gnovm/tests/files/types/add_d0.gno b/gnovm/tests/files/types/add_d0.gno new file mode 100644 index 00000000000..3f0d9548fff --- /dev/null +++ b/gnovm/tests/files/types/add_d0.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// untyped bigint to untyped bigdec +// TODO: dec value representation +func main() { + println(1.0 + 1) + println(1.0 + 0) +} + +// Output: +// 2 +// 1 diff --git a/gnovm/tests/files/types/add_d1.gno b/gnovm/tests/files/types/add_d1.gno new file mode 100644 index 00000000000..7bf7c26565a --- /dev/null +++ b/gnovm/tests/files/types/add_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' + 'b') +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d2.gno b/gnovm/tests/files/types/add_d2.gno new file mode 100644 index 00000000000..7ea4924a594 --- /dev/null +++ b/gnovm/tests/files/types/add_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r + 'b') +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d3.gno b/gnovm/tests/files/types/add_d3.gno new file mode 100644 index 00000000000..b130fc98b66 --- /dev/null +++ b/gnovm/tests/files/types/add_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 + r2) +} + +// Output: +// 195 diff --git a/gnovm/tests/files/types/add_d4.gno b/gnovm/tests/files/types/add_d4.gno new file mode 100644 index 00000000000..bdfca69ea88 --- /dev/null +++ b/gnovm/tests/files/types/add_d4.gno @@ -0,0 +1,11 @@ +package main + +var a int +var b interface{} + +func main() { + println(b + a) +} + +// Error: +// main/files/types/add_d4.gno:7: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_e0.gno b/gnovm/tests/files/types/add_e0.gno new file mode 100644 index 00000000000..b7fc8e395be --- /dev/null +++ b/gnovm/tests/files/types/add_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 + e2) +} + +// Error: +// main/files/types/add_e0.gno:23: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/add_f0_stdlibs.gno b/gnovm/tests/files/types/add_f0_stdlibs.gno new file mode 100644 index 00000000000..cf16d0ff084 --- /dev/null +++ b/gnovm/tests/files/types/add_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 + errCmp) +} + +// Error: +// main/files/types/add_f0_stdlibs.gno:19: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_f1_stdlibs.gno b/gnovm/tests/files/types/add_f1_stdlibs.gno new file mode 100644 index 00000000000..6bf1f814ac5 --- /dev/null +++ b/gnovm/tests/files/types/add_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) + errCmp) +} + +// Error: +// main/files/types/add_f1_stdlibs.gno:19: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/add_f2.gno b/gnovm/tests/files/types/add_f2.gno new file mode 100644 index 00000000000..7adfcfdaca6 --- /dev/null +++ b/gnovm/tests/files/types/add_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 + e2) +} + +// Error: +// main/files/types/add_f2.gno:27: operator + not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_a0.gno b/gnovm/tests/files/types/and_a0.gno new file mode 100644 index 00000000000..b1d23b23c91 --- /dev/null +++ b/gnovm/tests/files/types/and_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(0) & int8(1)) +} + +// Error: +// main/files/types/and_a0.gno:5: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/and_a1.gno b/gnovm/tests/files/types/and_a1.gno new file mode 100644 index 00000000000..d2fceec80ef --- /dev/null +++ b/gnovm/tests/files/types/and_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) & Error2(0)) +} + +// Error: +// main/files/types/and_a1.gno:21: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/and_b0.gno b/gnovm/tests/files/types/and_b0.gno new file mode 100644 index 00000000000..525796320e6 --- /dev/null +++ b/gnovm/tests/files/types/and_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 & Error(1)) + println(Error(1) & 1) +} + +// Output: +// (1 main.Error) +// (1 main.Error) diff --git a/gnovm/tests/files/types/and_b1.gno b/gnovm/tests/files/types/and_b1.gno new file mode 100644 index 00000000000..dc21fc3956b --- /dev/null +++ b/gnovm/tests/files/types/and_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 & Error(1)) + println(Error(1) & 1) +} + +// Output: +// error: 1 +// error: 1 diff --git a/gnovm/tests/files/types/and_b2.gno b/gnovm/tests/files/types/and_b2.gno new file mode 100644 index 00000000000..8ff36aaa26b --- /dev/null +++ b/gnovm/tests/files/types/and_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 & "a") +} + +// Error: +// main/files/types/and_b2.gno:5: operator & not defined on: StringKind diff --git a/gnovm/tests/files/types/and_b3.gno b/gnovm/tests/files/types/and_b3.gno new file mode 100644 index 00000000000..69be88d2dc9 --- /dev/null +++ b/gnovm/tests/files/types/and_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" & "a") +} + +// Error: +// main/files/types/and_b3.gno:5: operator & not defined on: StringKind diff --git a/gnovm/tests/files/types/and_b4.gno b/gnovm/tests/files/types/and_b4.gno new file mode 100644 index 00000000000..725714eeafd --- /dev/null +++ b/gnovm/tests/files/types/and_b4.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 & 'a') +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/and_d0.gno b/gnovm/tests/files/types/and_d0.gno new file mode 100644 index 00000000000..90646e97bc9 --- /dev/null +++ b/gnovm/tests/files/types/and_d0.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + println(1.0 & 1) +} + +// Error: +// main/files/types/and_d0.gno:6: operator & not defined on: BigdecKind diff --git a/gnovm/tests/files/types/and_d1.gno b/gnovm/tests/files/types/and_d1.gno new file mode 100644 index 00000000000..232fff4d385 --- /dev/null +++ b/gnovm/tests/files/types/and_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' & 'b') +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d2.gno b/gnovm/tests/files/types/and_d2.gno new file mode 100644 index 00000000000..f5c2660bafc --- /dev/null +++ b/gnovm/tests/files/types/and_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r & 'b') +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d3.gno b/gnovm/tests/files/types/and_d3.gno new file mode 100644 index 00000000000..f5cb8075f24 --- /dev/null +++ b/gnovm/tests/files/types/and_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 & r2) +} + +// Output: +// 96 diff --git a/gnovm/tests/files/types/and_d4.gno b/gnovm/tests/files/types/and_d4.gno new file mode 100644 index 00000000000..b4f8a70ad3c --- /dev/null +++ b/gnovm/tests/files/types/and_d4.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println(1.0 & 0) +} + +// Error: +// main/files/types/and_d4.gno:6: operator & not defined on: BigdecKind diff --git a/gnovm/tests/files/types/and_e0.gno b/gnovm/tests/files/types/and_e0.gno new file mode 100644 index 00000000000..28d58acf42b --- /dev/null +++ b/gnovm/tests/files/types/and_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 & e2) +} + +// Error: +// main/files/types/and_e0.gno:23: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/and_f0_stdlibs.gno b/gnovm/tests/files/types/and_f0_stdlibs.gno new file mode 100644 index 00000000000..6b56a8e59c4 --- /dev/null +++ b/gnovm/tests/files/types/and_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 & errCmp) +} + +// Error: +// main/files/types/and_f0_stdlibs.gno:19: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_f1_stdlibs.gno b/gnovm/tests/files/types/and_f1_stdlibs.gno new file mode 100644 index 00000000000..f1d7589e2f6 --- /dev/null +++ b/gnovm/tests/files/types/and_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) & errCmp) +} + +// Error: +// main/files/types/and_f1_stdlibs.gno:19: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/and_f2.gno b/gnovm/tests/files/types/and_f2.gno new file mode 100644 index 00000000000..24368256822 --- /dev/null +++ b/gnovm/tests/files/types/and_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 & e2) +} + +// Error: +// main/files/types/and_f2.gno:27: operator & not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/assign_call.gno b/gnovm/tests/files/types/assign_call.gno new file mode 100644 index 00000000000..a94b4a0b126 --- /dev/null +++ b/gnovm/tests/files/types/assign_call.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, float32) { + return 1, 1.0 +} + +func main() { + var s, s1 string + s, s1 = foo() + println(s) +} + +// Error: +// main/files/types/assign_call.gno:9: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index.gno b/gnovm/tests/files/types/assign_index.gno new file mode 100644 index 00000000000..93f4cf0a3c1 --- /dev/null +++ b/gnovm/tests/files/types/assign_index.gno @@ -0,0 +1,14 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + + var s string + var ok bool + s, ok = m["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index.gno:8: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index_a.gno b/gnovm/tests/files/types/assign_index_a.gno new file mode 100644 index 00000000000..dd92220b1fe --- /dev/null +++ b/gnovm/tests/files/types/assign_index_a.gno @@ -0,0 +1,12 @@ +package main + +func main() { + var s string + var ok bool + s, ok = map[string]int{"a": 1, "b": 2, "c": 3}["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index_a.gno:6: cannot use int as string diff --git a/gnovm/tests/files/types/assign_index_b.gno b/gnovm/tests/files/types/assign_index_b.gno new file mode 100644 index 00000000000..d8900759cb2 --- /dev/null +++ b/gnovm/tests/files/types/assign_index_b.gno @@ -0,0 +1,14 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + + var s int + var ok int + s, ok = m["a"] + println(s) + println(ok) +} + +// Error: +// main/files/types/assign_index_b.gno:8: want bool type got int diff --git a/gnovm/tests/files/types/assign_index_c.gno b/gnovm/tests/files/types/assign_index_c.gno new file mode 100644 index 00000000000..592191189a2 --- /dev/null +++ b/gnovm/tests/files/types/assign_index_c.gno @@ -0,0 +1,12 @@ +package main + +func main() { + s := []int{1, 2, 3} + + var s1 string + s1 = s[0] + println(s) +} + +// Error: +// main/files/types/assign_index_c.gno:7: cannot use int as string diff --git a/gnovm/tests/files/types/assign_literal.gno b/gnovm/tests/files/types/assign_literal.gno new file mode 100644 index 00000000000..61c128fbe1f --- /dev/null +++ b/gnovm/tests/files/types/assign_literal.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 = 6 +} + +// Error: +// main/files/types/assign_literal.gno:4: cannot assign to (const (1 bigint)) diff --git a/gnovm/tests/files/types/assign_literal10.gno b/gnovm/tests/files/types/assign_literal10.gno new file mode 100644 index 00000000000..d7b29f91aec --- /dev/null +++ b/gnovm/tests/files/types/assign_literal10.gno @@ -0,0 +1,21 @@ +package main + +type foo struct { + a int +} + +var n int + +func (f foo) add() *int { return &n } + +func main() { + s := &foo{} + *(s.add()) = 1 + println((*s).a) + + println(n) +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/types/assign_literal10a.gno b/gnovm/tests/files/types/assign_literal10a.gno new file mode 100644 index 00000000000..f6963d926ee --- /dev/null +++ b/gnovm/tests/files/types/assign_literal10a.gno @@ -0,0 +1,20 @@ +package main + +type foo struct { + a int +} + +var n int + +func (f foo) add() *int { return &n } + +func main() { + s := &foo{} + s.add() = 1 + println((*s).a) + + println(n) +} + +// Error: +// main/files/types/assign_literal10a.gno:13: cannot assign to s.add() diff --git a/gnovm/tests/files/types/assign_literal11.gno b/gnovm/tests/files/types/assign_literal11.gno new file mode 100644 index 00000000000..821f95422a0 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal11.gno @@ -0,0 +1,11 @@ +package main + +const Pi = 3.14 + +func main() { + Pi = 3.14159 + println(Pi) +} + +// Error: +// main/files/types/assign_literal11.gno:6: cannot assign to (const (3.14 bigdec)) diff --git a/gnovm/tests/files/types/assign_literal2.gno b/gnovm/tests/files/types/assign_literal2.gno new file mode 100644 index 00000000000..2e495b6fb78 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a int + a, 2 = 6, 6 +} + +// Error: +// main/files/types/assign_literal2.gno:5: cannot assign to (const (2 bigint)) diff --git a/gnovm/tests/files/types/assign_literal2_a.gno b/gnovm/tests/files/types/assign_literal2_a.gno new file mode 100644 index 00000000000..f66868ab842 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal2_a.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a int + a, 2 := 6, 6 +} + +// Error: +// files/types/assign_literal2_a.gno:5:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_literal3.gno b/gnovm/tests/files/types/assign_literal3.gno new file mode 100644 index 00000000000..67d4877cd5a --- /dev/null +++ b/gnovm/tests/files/types/assign_literal3.gno @@ -0,0 +1,8 @@ +package main + +func main() { + true = false +} + +// Error: +// main/files/types/assign_literal3.gno:4: cannot assign to (const (true bool)) diff --git a/gnovm/tests/files/types/assign_literal4.gno b/gnovm/tests/files/types/assign_literal4.gno new file mode 100644 index 00000000000..bdae4fa39a5 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal4.gno @@ -0,0 +1,8 @@ +package main + +func main() { + []int{1, 2} = []int{3, 4} +} + +// Error: +// main/files/types/assign_literal4.gno:4: cannot assign to [](const-type int){(const (1 int)), (const (2 int))} diff --git a/gnovm/tests/files/types/assign_literal4_a.gno b/gnovm/tests/files/types/assign_literal4_a.gno new file mode 100644 index 00000000000..054ae227b32 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal4_a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + []int{1, 2} := []int{3, 4} +} + +// Error: +// files/types/assign_literal4_a.gno:4:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_literal5.gno b/gnovm/tests/files/types/assign_literal5.gno new file mode 100644 index 00000000000..da382b8ac1c --- /dev/null +++ b/gnovm/tests/files/types/assign_literal5.gno @@ -0,0 +1,8 @@ +package main + +func main() { + map[string]int{"a": 1, "b": 2} = map[string]int{"a": 1, "b": 2} +} + +// Error: +// main/files/types/assign_literal5.gno:4: cannot assign to map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))} diff --git a/gnovm/tests/files/types/assign_literal6.gno b/gnovm/tests/files/types/assign_literal6.gno new file mode 100644 index 00000000000..dfcf9cbb80a --- /dev/null +++ b/gnovm/tests/files/types/assign_literal6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 + 2 = 6 +} + +// Error: +// main/files/types/assign_literal6.gno:4: cannot assign to (const (3 bigint)) diff --git a/gnovm/tests/files/types/assign_literal7.gno b/gnovm/tests/files/types/assign_literal7.gno new file mode 100644 index 00000000000..56308cdbbfc --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7.gno @@ -0,0 +1,10 @@ +package main + +func main() { + s := []int{1, 2, 3} + s[0] = 0 + println(s) +} + +// Output: +// slice[(0 int),(2 int),(3 int)] diff --git a/gnovm/tests/files/types/assign_literal7a.gno b/gnovm/tests/files/types/assign_literal7a.gno new file mode 100644 index 00000000000..8be126c49de --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7a.gno @@ -0,0 +1,10 @@ +package main + +func main() { + m := map[string]int{"a": 1, "b": 2, "c": 3} + m["a"] = 0 + println(m) +} + +// Output: +// map{("a" string):(0 int),("b" string):(2 int),("c" string):(3 int)} diff --git a/gnovm/tests/files/types/assign_literal7b.gno b/gnovm/tests/files/types/assign_literal7b.gno new file mode 100644 index 00000000000..dc61467e07c --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7b.gno @@ -0,0 +1,10 @@ +package main + +func main() { + s := [2]int{} + s[0] = 0 + println(s) +} + +// Output: +// array[(0 int),(0 int)] diff --git a/gnovm/tests/files/types/assign_literal7c.gno b/gnovm/tests/files/types/assign_literal7c.gno new file mode 100644 index 00000000000..989dcca6699 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7c.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + str := "hello" + str[0] = 'y' + fmt.Println(str[0]) + fmt.Printf("%c\n", str[0]) +} + +// Error: +// main/files/types/assign_literal7c.gno:7: cannot assign to str[(const (0 int))] diff --git a/gnovm/tests/files/types/assign_literal7d.gno b/gnovm/tests/files/types/assign_literal7d.gno new file mode 100644 index 00000000000..f56c304b3d3 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal7d.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + bs := []byte("hello") + bs[0] = 'y' + fmt.Println(bs[0]) + fmt.Printf("%c\n", bs[0]) +} + +// Output: +// 121 +// y diff --git a/gnovm/tests/files/types/assign_literal8.gno b/gnovm/tests/files/types/assign_literal8.gno new file mode 100644 index 00000000000..d652efc0874 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal8.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var a int + p := &a + *p = 1 + println(*p) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/assign_literal9.gno b/gnovm/tests/files/types/assign_literal9.gno new file mode 100644 index 00000000000..1d672ae46ce --- /dev/null +++ b/gnovm/tests/files/types/assign_literal9.gno @@ -0,0 +1,14 @@ +package main + +type foo struct { + a int +} + +func main() { + s := &foo{} + s.a = 1 + println(s.a) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/assign_literal_a.gno b/gnovm/tests/files/types/assign_literal_a.gno new file mode 100644 index 00000000000..dd17e97c466 --- /dev/null +++ b/gnovm/tests/files/types/assign_literal_a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + 1 := 6 +} + +// Error: +// files/types/assign_literal_a.gno:4:2: no new variables on left side of := \ No newline at end of file diff --git a/gnovm/tests/files/types/assign_nil.gno b/gnovm/tests/files/types/assign_nil.gno new file mode 100644 index 00000000000..e4012745d4f --- /dev/null +++ b/gnovm/tests/files/types/assign_nil.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var i interface{} + i = 4 + var j int + j, nil = i.(int) +} + +// Error: +// main/files/types/assign_nil.gno:7: cannot assign to (const (undefined)) diff --git a/gnovm/tests/files/types/assign_nil2.gno b/gnovm/tests/files/types/assign_nil2.gno new file mode 100644 index 00000000000..8af61a1e496 --- /dev/null +++ b/gnovm/tests/files/types/assign_nil2.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var i interface{} + i = 4 + var ok bool + nil, nil = i.(int) +} + +// Error: +// main/files/types/assign_nil2.gno:7: cannot assign to (const (undefined)) diff --git a/gnovm/tests/files/types/assign_range.gno b/gnovm/tests/files/types/assign_range.gno new file mode 100644 index 00000000000..5ee1c49a2e9 --- /dev/null +++ b/gnovm/tests/files/types/assign_range.gno @@ -0,0 +1,42 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + for k, v := range scores { + fmt.Printf("%s has a score of %d\n", k, v) + } + + // Modifying values during iteration + for k := range scores { + scores[k] += 5 // Adding 5 to each score + } + fmt.Println("Updated scores:", scores) + + // Collecting keys and values into slices + var keys []string + var values []int + for k, v := range scores { + keys = append(keys, k) + values = append(values, v) + } + fmt.Println("Keys:", keys) + fmt.Println("Values:", values) +} + +// Output: +// Alice has a score of 92 +// Bob has a score of 89 +// Charlie has a score of 95 +// Updated scores: map[Alice:97 Bob:94 Charlie:100] +// Keys: [Alice Bob Charlie] +// Values: [97 94 100] diff --git a/gnovm/tests/files/types/assign_range_a.gno b/gnovm/tests/files/types/assign_range_a.gno new file mode 100644 index 00000000000..72115771e52 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_a.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%s has a score of %d\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_a.gno:16: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_a1.gno b/gnovm/tests/files/types/assign_range_a1.gno new file mode 100644 index 00000000000..4e6ede4ffda --- /dev/null +++ b/gnovm/tests/files/types/assign_range_a1.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := map[string]int{ + "Alice": 92, + "Bob": 89, + "Charlie": 95, + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var v int + for 6, v = range scores { + fmt.Printf("%s has a score of %d\n", "a", v) + } +} + +// Error: +// main/files/types/assign_range_a1.gno:16: cannot assign to (const (6 bigint)) diff --git a/gnovm/tests/files/types/assign_range_b.gno b/gnovm/tests/files/types/assign_range_b.gno new file mode 100644 index 00000000000..df7fbeb8158 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := []string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_b.gno:14: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_b1.gno b/gnovm/tests/files/types/assign_range_b1.gno new file mode 100644 index 00000000000..4632b79b6f0 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b1.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := [3]string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v int + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_b1.gno:14: cannot use string as int diff --git a/gnovm/tests/files/types/assign_range_b2.gno b/gnovm/tests/files/types/assign_range_b2.gno new file mode 100644 index 00000000000..a96c3f91ace --- /dev/null +++ b/gnovm/tests/files/types/assign_range_b2.gno @@ -0,0 +1,28 @@ +package main + +func main() { + rangeMap() + rangeSlice() +} + +func rangeMap() { + m := map[int]string{999: "asdf"} + var v int + for v = range m { + println(v) + } +} + +func rangeSlice() { + m := []int{9, 4, 6} + var v int + for v = range m { + println(v) + } +} + +// Output: +// 999 +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/types/assign_range_c.gno b/gnovm/tests/files/types/assign_range_c.gno new file mode 100644 index 00000000000..783534bebb4 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_c.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +func main() { + // Creating a map where keys are strings and values are integers + scores := []string{ + "a", "b", "c", + } + + // Using range to iterate over the map + // k is the key and v is the value for each pair in the map + var k, v float32 + for k, v = range scores { + fmt.Printf("%d has a score of %s\n", k, v) + } +} + +// Error: +// main/files/types/assign_range_c.gno:14: index type should be int, but got float32 diff --git a/gnovm/tests/files/types/assign_range_d.gno b/gnovm/tests/files/types/assign_range_d.gno new file mode 100644 index 00000000000..6903cb0b7bc --- /dev/null +++ b/gnovm/tests/files/types/assign_range_d.gno @@ -0,0 +1,14 @@ +package main + +func main() { + s := "hello" + + var index float32 + var value rune + for index, value = range s { + println(index) + } +} + +// Error: +// main/files/types/assign_range_d.gno:8: index type should be int, but got float32 diff --git a/gnovm/tests/files/types/assign_range_e.gno b/gnovm/tests/files/types/assign_range_e.gno new file mode 100644 index 00000000000..7f7f15c5c23 --- /dev/null +++ b/gnovm/tests/files/types/assign_range_e.gno @@ -0,0 +1,15 @@ +package main + +func main() { + s := "hello" + + var index int + var value int + for index, value = range s { + println(index) + println(value) + } +} + +// Error: +// main/files/types/assign_range_e.gno:8: value type should be int32, but got int diff --git a/gnovm/tests/files/types/assign_rune.gno b/gnovm/tests/files/types/assign_rune.gno new file mode 100644 index 00000000000..7c1e8742fd4 --- /dev/null +++ b/gnovm/tests/files/types/assign_rune.gno @@ -0,0 +1,18 @@ +package main + +var r rune + +// TODO: print right dec, float +func main() { + r = 'a' + println(r) + println(float32(r)) + println(float64(r)) + println(string(r)) +} + +// Output: +// 97 +// 97 +// 97 +// a diff --git a/gnovm/tests/files/types/assign_rune_a.gno b/gnovm/tests/files/types/assign_rune_a.gno new file mode 100644 index 00000000000..13321fd8b08 --- /dev/null +++ b/gnovm/tests/files/types/assign_rune_a.gno @@ -0,0 +1,12 @@ +package main + +var r rune + +// assign +func main() { + r = "a" + println(r) +} + +// Error: +// main/files/types/assign_rune_a.gno:7: cannot use untyped string as Int32Kind diff --git a/gnovm/tests/files/types/assign_type_assertion.gno b/gnovm/tests/files/types/assign_type_assertion.gno new file mode 100644 index 00000000000..6bc2b4b26c8 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion.gno @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" +) + +func main() { + var i interface{} = "Hello, world!" + + // Attempt to assert the type of i to string + var n int + var ok bool + n, ok = i.(string) + if ok { + fmt.Println("i contains a string:", n) + } else { + fmt.Println("i does not contain a string") + } +} + +// Error: +// main/files/types/assign_type_assertion.gno:13: cannot use string as int diff --git a/gnovm/tests/files/types/assign_type_assertion_a.gno b/gnovm/tests/files/types/assign_type_assertion_a.gno new file mode 100644 index 00000000000..1b28408d309 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_a.gno @@ -0,0 +1,33 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr interface{ IsSet() bool } // Define a variable of the interface type + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// Output: +// Assertion succeeded: true +// IsSet: true diff --git a/gnovm/tests/files/types/assign_type_assertion_b.gno b/gnovm/tests/files/types/assign_type_assertion_b.gno new file mode 100644 index 00000000000..4f68751667d --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_b.gno @@ -0,0 +1,32 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr int // Define a variable of the interface type + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// Error: +// main/files/types/assign_type_assertion_b.gno:22: cannot use interface{IsSet func()(bool)} as int diff --git a/gnovm/tests/files/types/assign_type_assertion_c.gno b/gnovm/tests/files/types/assign_type_assertion_c.gno new file mode 100644 index 00000000000..044154dadfd --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_c.gno @@ -0,0 +1,33 @@ +package main + +import "fmt" + +type MyError struct{} + +func (e MyError) Error() string { + return "my error" +} + +func (e MyError) IsSet() bool { + return true +} + +func main() { + var err error = MyError{} + + var assertedErr interface{ IsNotSet() bool } // Define a variable of the interface type + + var ok bool + + // Perform the assertion and assign the result to assertedErr + assertedErr, ok = err.(interface{ IsSet() bool }) // not impl lhs + if ok { + fmt.Println("Assertion succeeded:", ok) + fmt.Println("IsSet:", assertedErr.IsSet()) + } else { + fmt.Println("Assertion failed:", ok) + } +} + +// Error: +// main/files/types/assign_type_assertion_c.gno:23: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} diff --git a/gnovm/tests/files/types/assign_type_assertion_d.gno b/gnovm/tests/files/types/assign_type_assertion_d.gno new file mode 100644 index 00000000000..4ab24f36aac --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_d.gno @@ -0,0 +1,25 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var r Robot + + r = animal.(Dog) + + println(r) +} + +// Error: +// main/files/types/assign_type_assertion_d.gno:19: cannot use main.Dog as main.Robot without explicit conversion diff --git a/gnovm/tests/files/types/assign_type_assertion_e.gno b/gnovm/tests/files/types/assign_type_assertion_e.gno new file mode 100644 index 00000000000..fbe91031667 --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_e.gno @@ -0,0 +1,27 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var r Robot + var ok bool + + r, ok = animal.(Dog) + + println(r) + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_e.gno:20: cannot use main.Dog as main.Robot without explicit conversion diff --git a/gnovm/tests/files/types/assign_type_assertion_f.gno b/gnovm/tests/files/types/assign_type_assertion_f.gno new file mode 100644 index 00000000000..bc2907eb8af --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_f.gno @@ -0,0 +1,25 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var ok bool + + 1, ok = animal.(Dog) + + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_f.gno:19: cannot assign to (const (1 bigint)) diff --git a/gnovm/tests/files/types/assign_type_assertion_g.gno b/gnovm/tests/files/types/assign_type_assertion_g.gno new file mode 100644 index 00000000000..2d3d61c561d --- /dev/null +++ b/gnovm/tests/files/types/assign_type_assertion_g.gno @@ -0,0 +1,27 @@ +package main + +type Animal interface { + eat() +} + +type Robot struct { +} + +type Dog struct{} + +func (Dog) eat() {} + +func main() { + var animal Animal = Dog{} + + var a Animal + var ok int + + a, ok = animal.(Dog) + + println(a) + println(ok) +} + +// Error: +// main/files/types/assign_type_assertion_g.gno:20: want bool type got int diff --git a/gnovm/tests/files/types/bigdec.gno b/gnovm/tests/files/types/bigdec.gno new file mode 100644 index 00000000000..baa3d4d6309 --- /dev/null +++ b/gnovm/tests/files/types/bigdec.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(a % 1e9) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/bigdec2.gno b/gnovm/tests/files/types/bigdec2.gno new file mode 100644 index 00000000000..60048151a31 --- /dev/null +++ b/gnovm/tests/files/types/bigdec2.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(a % 1.2) +} + +// Error: +// main/files/types/bigdec2.gno:7: cannot convert untyped bigdec to integer -- 1.2 not an exact integer diff --git a/gnovm/tests/files/types/bigdec3.gno b/gnovm/tests/files/types/bigdec3.gno new file mode 100644 index 00000000000..0e0effa7cbb --- /dev/null +++ b/gnovm/tests/files/types/bigdec3.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1 + println(1.0 % a) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/types/bigdec4.gno b/gnovm/tests/files/types/bigdec4.gno new file mode 100644 index 00000000000..88cbe894020 --- /dev/null +++ b/gnovm/tests/files/types/bigdec4.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1.0 + println(a) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/bigdec5.gno b/gnovm/tests/files/types/bigdec5.gno new file mode 100644 index 00000000000..e3f64e0a8ce --- /dev/null +++ b/gnovm/tests/files/types/bigdec5.gno @@ -0,0 +1,11 @@ +package main + +var a uint64 + +func main() { + a = 1.2 + println(a) +} + +// Error: +// main/files/types/bigdec5.gno:6: cannot convert untyped bigdec to integer -- 1.2 not an exact integer diff --git a/gnovm/tests/files/types/bigdec_6.gno b/gnovm/tests/files/types/bigdec_6.gno new file mode 100644 index 00000000000..5ef078619b5 --- /dev/null +++ b/gnovm/tests/files/types/bigdec_6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(1 % 1e9) +} + +// Error: +// main/files/types/bigdec_6.gno:4: operator % not defined on: BigdecKind diff --git a/gnovm/tests/files/types/cmp_array.gno b/gnovm/tests/files/types/cmp_array.gno new file mode 100644 index 00000000000..52fea95b034 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func main() { + a := [2][2]int{{1, 2}, {3, 4}} + b := [2][2]int{{1, 2}, {3, 4}} + c := [2][2]int{{5, 6}, {7, 8}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Output: +// a == b: true +// a == c: false diff --git a/gnovm/tests/files/types/cmp_array_a.gno b/gnovm/tests/files/types/cmp_array_a.gno new file mode 100644 index 00000000000..1d30f7c51eb --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_a.gno @@ -0,0 +1,21 @@ +package main + +import "fmt" + +// Define a struct that embeds an array +type Matrix struct { + data [2]int +} + +func main() { + a := [2]Matrix{{data: [2]int{1, 2}}, {data: [2]int{3, 4}}} + b := [2]Matrix{{data: [2]int{1, 2}}, {data: [2]int{3, 4}}} + c := [2]Matrix{{data: [2]int{5, 6}}, {data: [2]int{7, 8}}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Output: +// a == b: true +// a == c: false diff --git a/gnovm/tests/files/types/cmp_array_b.gno b/gnovm/tests/files/types/cmp_array_b.gno new file mode 100644 index 00000000000..490a3424965 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_b.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + a := [2][]int{{1, 2}, {3, 4}} + b := [2][]int{{1, 2}, {3, 4}} + c := [2][]int{{5, 6}, {7, 8}} + + fmt.Println("a == b:", a == b) // True because the elements match exactly + fmt.Println("a == c:", a == c) // False because the elements do not match +} + +// Error: +// main/files/types/cmp_array_b.gno:10: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_array_c.gno b/gnovm/tests/files/types/cmp_array_c.gno new file mode 100644 index 00000000000..f23ef6c9a78 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_c.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + a := [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}} + b := [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}} + c := [2][2][]int{{{5, 6}, {7, 8}}, {{1, 2}, {3, 4}}} + + fmt.Println("a == b:", a == b) + fmt.Println("a == c:", a == c) +} + +// Error: +// main/files/types/cmp_array_c.gno:10: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_array_d.gno b/gnovm/tests/files/types/cmp_array_d.gno new file mode 100644 index 00000000000..78f9527f819 --- /dev/null +++ b/gnovm/tests/files/types/cmp_array_d.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := [2]int{1, 2} + b := [2]int{1, 2} + + fmt.Println("a == b:", a == b) +} + +// Output: +// a == b: true diff --git a/gnovm/tests/files/types/cmp_poiner.gno b/gnovm/tests/files/types/cmp_poiner.gno new file mode 100644 index 00000000000..0d7625a878d --- /dev/null +++ b/gnovm/tests/files/types/cmp_poiner.gno @@ -0,0 +1,23 @@ +package main + +import "fmt" + +// Define a Person struct +type Person struct { + Name string + Age int +} + +type Worker string + +func main() { + // Create two pointers to Person instances + p1 := &Person{Name: "Alice", Age: 30} + p2 := Worker("a") + p2Ptr := &p2 + + fmt.Println("p1 == p2:", p1 == p2Ptr) +} + +// Error: +// main/files/types/cmp_poiner.gno:19: cannot use main.Person as main.Worker without explicit conversion diff --git a/gnovm/tests/files/types/cmp_poiner2.gno b/gnovm/tests/files/types/cmp_poiner2.gno new file mode 100644 index 00000000000..6899803075f --- /dev/null +++ b/gnovm/tests/files/types/cmp_poiner2.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := []int{1, 2} + b := []int{1, 2} + println(&a == &b) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/cmp_struct.gno b/gnovm/tests/files/types/cmp_struct.gno new file mode 100644 index 00000000000..c3ead1510a4 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct.gno @@ -0,0 +1,23 @@ +package main + +import "fmt" + +// Define a struct that contains an array field +type Matrix struct { + data [2]int +} + +func main() { + // Create two instances of the struct + m1 := Matrix{data: [2]int{1, 2}} + m2 := Matrix{data: [2]int{1, 2}} + m3 := Matrix{data: [2]int{3, 4}} + + // Compare the instances + fmt.Println("m1 == m2:", m1 == m2) // True because the data fields are identical + fmt.Println("m1 == m3:", m1 == m3) // False because the data fields are different +} + +// Output: +// m1 == m2: true +// m1 == m3: false diff --git a/gnovm/tests/files/types/cmp_struct_a.gno b/gnovm/tests/files/types/cmp_struct_a.gno new file mode 100644 index 00000000000..5e8add9f1d6 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_a.gno @@ -0,0 +1,20 @@ +package main + +import "fmt" + +// Define a struct that wraps the three-dimensional data structure +type Matrix struct { + data [2][2][]int +} + +func main() { + a := Matrix{data: [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}} + b := Matrix{data: [2][2][]int{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}} + c := Matrix{data: [2][2][]int{{{5, 6}, {7, 8}}, {{1, 2}, {3, 4}}}} + + fmt.Println("a == b:", a == b) + fmt.Println("a == c:", a == c) +} + +// Error: +// main/files/types/cmp_struct_a.gno:15: [2][]int is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_b.gno b/gnovm/tests/files/types/cmp_struct_b.gno new file mode 100644 index 00000000000..f767fec16d5 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_b.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_b.gno:15: cannot use main.foo as main.bar without explicit conversion diff --git a/gnovm/tests/files/types/cmp_struct_c.gno b/gnovm/tests/files/types/cmp_struct_c.gno new file mode 100644 index 00000000000..4894e32d0e5 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_c.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_c.gno:15: main.bar is not comparable \ No newline at end of file diff --git a/gnovm/tests/files/types/cmp_struct_c1.gno b/gnovm/tests/files/types/cmp_struct_c1.gno new file mode 100644 index 00000000000..3cb4b835dcd --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_c1.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(bb == fa) +} + +// Error: +// main/files/types/cmp_struct_c1.gno:15: cannot use main.bar as main.foo without explicit conversion diff --git a/gnovm/tests/files/types/cmp_struct_d.gno b/gnovm/tests/files/types/cmp_struct_d.gno new file mode 100644 index 00000000000..4c2f6eb848f --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_d.gno @@ -0,0 +1,19 @@ +package main + +type foo struct { + a []int +} + +type bar struct { + b []int +} + +func main() { + fa := foo{} + bb := bar{} + + println(fa == bb) +} + +// Error: +// main/files/types/cmp_struct_d.gno:15: main.bar is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_e.gno b/gnovm/tests/files/types/cmp_struct_e.gno new file mode 100644 index 00000000000..4640c0e4252 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_e.gno @@ -0,0 +1,15 @@ +package main + +type foo struct { + a []int +} + +func main() { + fa := foo{} + fb := foo{} + + println(fa == fb) +} + +// Error: +// main/files/types/cmp_struct_e.gno:11: main.foo is not comparable diff --git a/gnovm/tests/files/types/cmp_struct_f.gno b/gnovm/tests/files/types/cmp_struct_f.gno new file mode 100644 index 00000000000..20366b625e9 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_f.gno @@ -0,0 +1,43 @@ +package main + +import "fmt" + +// Define a Person struct +type Person struct { + Name string + Age int +} + +// Define an Employee struct that embeds Person +type Employee struct { + Person + Department string + ID int +} + +func main() { + emp1 := Employee{ + Person: Person{Name: "John Doe", Age: 30}, + Department: "Engineering", + ID: 12345, + } + + emp2 := Employee{ + Person: Person{Name: "John Doe", Age: 30}, + Department: "Engineering", + ID: 12345, + } + + emp3 := Employee{ + Person: Person{Name: "Jane Doe", Age: 29}, + Department: "Marketing", + ID: 67890, + } + + fmt.Println("emp1 == emp2:", emp1 == emp2) // True because all fields match + fmt.Println("emp1 == emp3:", emp1 == emp3) // False because some fields differ +} + +// Output: +// emp1 == emp2: true +// emp1 == emp3: false diff --git a/gnovm/tests/files/types/cmp_struct_g.gno b/gnovm/tests/files/types/cmp_struct_g.gno new file mode 100644 index 00000000000..05224000f05 --- /dev/null +++ b/gnovm/tests/files/types/cmp_struct_g.gno @@ -0,0 +1,21 @@ +package main + +import "fmt" + +type Person struct { + age int +} + +type Dog struct { + age int +} + +func main() { + a := Person{} + b := Dog{} + + fmt.Println("a == b:", a == b) +} + +// Error: +// main/files/types/cmp_struct_g.gno:17: cannot use main.Person as main.Dog without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a0.gno b/gnovm/tests/files/types/eql_0a0.gno new file mode 100644 index 00000000000..32e5473b34f --- /dev/null +++ b/gnovm/tests/files/types/eql_0a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) == int8(1)) +} + +// Error: +// main/files/types/eql_0a0.gno:5: cannot use int as int8 diff --git a/gnovm/tests/files/types/eql_0a01.gno b/gnovm/tests/files/types/eql_0a01.gno new file mode 100644 index 00000000000..e5cdeff4b63 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a01.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(nil == nil) // xt: , dt: +} + +// Error: +// main/files/types/eql_0a01.gno:4: is not comparable diff --git a/gnovm/tests/files/types/eql_0a02.gno b/gnovm/tests/files/types/eql_0a02.gno new file mode 100644 index 00000000000..67116da6cc8 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a02.gno @@ -0,0 +1,11 @@ +package main + +func main() { + intPtr := new(int) + *intPtr = 5 + s := "hello" + println(intPtr == s) +} + +// Error: +// main/files/types/eql_0a02.gno:7: cannot use *int as string diff --git a/gnovm/tests/files/types/eql_0a03.gno b/gnovm/tests/files/types/eql_0a03.gno new file mode 100644 index 00000000000..677493a9af6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a03.gno @@ -0,0 +1,12 @@ +package main + +func main() { + intPtr := new(int8) + *intPtr = 5 + + i := 0 + println(intPtr == &i) +} + +// Error: +// main/files/types/eql_0a03.gno:8: cannot use int8 as int diff --git a/gnovm/tests/files/types/eql_0a1.gno b/gnovm/tests/files/types/eql_0a1.gno new file mode 100644 index 00000000000..9641e67c90a --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) != int8(1)) +} + +// Error: +// main/files/types/eql_0a1.gno:5: cannot use int as int8 diff --git a/gnovm/tests/files/types/eql_0a1a.gno b/gnovm/tests/files/types/eql_0a1a.gno new file mode 100644 index 00000000000..deb369511fd --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a.gno @@ -0,0 +1,11 @@ +package main + +// left typed, right untyped +func main() { + println(int(1) == 1) + println(int(1) != 1) +} + +// Output: +// true +// false diff --git a/gnovm/tests/files/types/eql_0a1a0.gno b/gnovm/tests/files/types/eql_0a1a0.gno new file mode 100644 index 00000000000..e251174adef --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := uint(1) + println(uint64(1) == a) +} + +// Error: +// main/files/types/eql_0a1a0.gno:5: cannot use uint64 as uint diff --git a/gnovm/tests/files/types/eql_0a1a1.gno b/gnovm/tests/files/types/eql_0a1a1.gno new file mode 100644 index 00000000000..0af8aa45943 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1a1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := uint(1) + println(a == uint64(1)) +} + +// Error: +// main/files/types/eql_0a1a1.gno:5: cannot use uint as uint64 diff --git a/gnovm/tests/files/types/eql_0a1b.gno b/gnovm/tests/files/types/eql_0a1b.gno new file mode 100644 index 00000000000..1db537a4d8c --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1b.gno @@ -0,0 +1,20 @@ +package main + +type S struct { + expected string +} + +// special case when RHS is result of slice operation, its type is determined in runtime +func main() { + s := S{ + expected: `hello`[:], // this is not converted + } + + a := "hello" + + println(a == s.expected) + +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1c.gno b/gnovm/tests/files/types/eql_0a1c.gno new file mode 100644 index 00000000000..76f2db8d7d8 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1c.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := "hello" + println(a == expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1d.gno b/gnovm/tests/files/types/eql_0a1d.gno new file mode 100644 index 00000000000..018d53fa81a --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1d.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + expected string +} + +func main() { + println("hello" == S{ + expected: `hello`[:], + }.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1e.gno b/gnovm/tests/files/types/eql_0a1e.gno new file mode 100644 index 00000000000..2795d618e91 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1e.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + expected string +} + +func main() { + var s = S{ + expected: `hello`[:], + } + a := "hello" + println(a == s.expected) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0a1f.gno b/gnovm/tests/files/types/eql_0a1f.gno new file mode 100644 index 00000000000..fda84dcdff8 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1f.gno @@ -0,0 +1,10 @@ +package main + +func main() { + expected := `hello`[:] + a := 1 + println(a == expected) // both typed +} + +// Error: +// main/files/types/eql_0a1f.gno:6: cannot use int as string diff --git a/gnovm/tests/files/types/eql_0a1g.gno b/gnovm/tests/files/types/eql_0a1g.gno new file mode 100644 index 00000000000..d80b6e0c9ac --- /dev/null +++ b/gnovm/tests/files/types/eql_0a1g.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a int = 1 + var b float32 = 1.0 + println(a == b) // both typed +} + +// Error: +// main/files/types/eql_0a1g.gno:6: cannot use int as float32 diff --git a/gnovm/tests/files/types/eql_0a2.gno b/gnovm/tests/files/types/eql_0a2.gno new file mode 100644 index 00000000000..281a61e50eb --- /dev/null +++ b/gnovm/tests/files/types/eql_0a2.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) == Error2(0)) +} + +// Error: +// main/files/types/eql_0a2.gno:21: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a3.gno b/gnovm/tests/files/types/eql_0a3.gno new file mode 100644 index 00000000000..198a65b0827 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a3.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) != Error2(0)) +} + +// Error: +// main/files/types/eql_0a3.gno:21: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0a4.gno b/gnovm/tests/files/types/eql_0a4.gno new file mode 100644 index 00000000000..d2c859edd09 --- /dev/null +++ b/gnovm/tests/files/types/eql_0a4.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) != Error2(0)) +} + +// Error: +// main/files/types/eql_0a4.gno:21: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0b0.gno b/gnovm/tests/files/types/eql_0b0.gno new file mode 100644 index 00000000000..2c968b5158f --- /dev/null +++ b/gnovm/tests/files/types/eql_0b0.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is untyped const, right is typed const +// left is assignable to right +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0b1.gno b/gnovm/tests/files/types/eql_0b1.gno new file mode 100644 index 00000000000..a7bdf5275da --- /dev/null +++ b/gnovm/tests/files/types/eql_0b1.gno @@ -0,0 +1,23 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is untyped const, right is typed const +func main() { + if 1 != Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/eql_0b2.gno b/gnovm/tests/files/types/eql_0b2.gno new file mode 100644 index 00000000000..51981348a6e --- /dev/null +++ b/gnovm/tests/files/types/eql_0b2.gno @@ -0,0 +1,22 @@ +package main + +type Error string + +func (e Error) Error() string { + return "error: " + string(e) +} + +// left is untyped const, right is typed const +// left is not assignable to right +// a) it's (untyped) bigint +// b) base type of right is string +func main() { + if 1 == Error(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0b2.gno:14: cannot use untyped Bigint as StringKind diff --git a/gnovm/tests/files/types/eql_0b3.gno b/gnovm/tests/files/types/eql_0b3.gno new file mode 100644 index 00000000000..34c8a24cba2 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b3.gno @@ -0,0 +1,15 @@ +package main + +var a int8 + +func main() { + a = 1 + if 1 == a { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0b4_native.gno b/gnovm/tests/files/types/eql_0b4_native.gno new file mode 100644 index 00000000000..b78d5f9be73 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b4_native.gno @@ -0,0 +1,13 @@ +package main + +import ( + "errors" +) + +func main() { + errCmp := errors.New("xxx") + println(5 == errCmp) +} + +// Error: +// main/files/types/eql_0b4_native.gno:9: unexpected type pair: cannot use bigint as gonative{error} diff --git a/gnovm/tests/files/types/eql_0b4_stdlibs.gno b/gnovm/tests/files/types/eql_0b4_stdlibs.gno new file mode 100644 index 00000000000..de36f025f34 --- /dev/null +++ b/gnovm/tests/files/types/eql_0b4_stdlibs.gno @@ -0,0 +1,13 @@ +package main + +import ( + "errors" +) + +func main() { + errCmp := errors.New("xxx") + println(5 == errCmp) +} + +// Error: +// main/files/types/eql_0b4_stdlibs.gno:9: bigint does not implement .uverse.error diff --git a/gnovm/tests/files/types/eql_0c2.gno b/gnovm/tests/files/types/eql_0c2.gno new file mode 100644 index 00000000000..6ac4a2799ab --- /dev/null +++ b/gnovm/tests/files/types/eql_0c2.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// left is typed const, right is untyped const +// NOTE: overflow +func main() { + if Error(1) == 128 { // note, this would overflow as expected + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0c2.gno:16: bigint overflows target kind diff --git a/gnovm/tests/files/types/eql_0d0.gno b/gnovm/tests/files/types/eql_0d0.gno new file mode 100644 index 00000000000..6890d450bab --- /dev/null +++ b/gnovm/tests/files/types/eql_0d0.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +func main() { + println(1.0 == 1) + println(1.0 == 0) + println(float32(1.0) == 0) +} + +// Output: +// true +// false +// false diff --git a/gnovm/tests/files/types/eql_0e0.gno b/gnovm/tests/files/types/eql_0e0.gno new file mode 100644 index 00000000000..a1ab28d1932 --- /dev/null +++ b/gnovm/tests/files/types/eql_0e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 == e2) +} + +// Error: +// main/files/types/eql_0e0.gno:23: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0e1.gno b/gnovm/tests/files/types/eql_0e1.gno new file mode 100644 index 00000000000..798d4f04777 --- /dev/null +++ b/gnovm/tests/files/types/eql_0e1.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 != e2) +} + +// Error: +// main/files/types/eql_0e1.gno:23: cannot use main.Error1 as main.Error2 without explicit conversion diff --git a/gnovm/tests/files/types/eql_0f0_native.gno b/gnovm/tests/files/types/eql_0f0_native.gno new file mode 100644 index 00000000000..7db72819774 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f0_native.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if 1 == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f0_native.gno:19: unexpected type pair: cannot use bigint as gonative{error} diff --git a/gnovm/tests/files/types/eql_0f0_stdlibs.gno b/gnovm/tests/files/types/eql_0f0_stdlibs.gno new file mode 100644 index 00000000000..39a7ac68490 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f0_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if 1 == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f0_stdlibs.gno:19: bigint does not implement .uverse.error diff --git a/gnovm/tests/files/types/eql_0f12.gno b/gnovm/tests/files/types/eql_0f12.gno new file mode 100644 index 00000000000..751852d0b70 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f12.gno @@ -0,0 +1,13 @@ +package main + +var a [2]string +var b [2]string + +func main() { + a = [2]string{"hello", "world"} + b = [2]string{"hello", "world"} + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f14.gno b/gnovm/tests/files/types/eql_0f14.gno new file mode 100644 index 00000000000..500157d7303 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f14.gno @@ -0,0 +1,14 @@ +package main + +var a [2]string +var c [2]int + +// TODO: should stop at comparable check +func main() { + a = [2]string{"hello", "world"} + c = [2]int{1, 2} + println(a == c) +} + +// Error: +// main/files/types/eql_0f14.gno:10: cannot use [2]string as [2]int diff --git a/gnovm/tests/files/types/eql_0f15.gno b/gnovm/tests/files/types/eql_0f15.gno new file mode 100644 index 00000000000..36077127332 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f15.gno @@ -0,0 +1,17 @@ +package main + +var a [2]interface{} +var c [2]interface{} + +func gen() interface{} { + return 1 +} + +func main() { + a = [2]interface{}{gen(), gen()} + c = [2]interface{}{gen(), gen()} + println(a == c) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f16.gno b/gnovm/tests/files/types/eql_0f16.gno new file mode 100644 index 00000000000..ae0d8f45ab6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f16.gno @@ -0,0 +1,20 @@ +package main + +type word []int + +var a [2]word +var c [2]word + +func gen() word { + return []int{1} +} + +// TODO: consider log desc +func main() { + a = [2]word{gen(), gen()} + c = [2]word{gen(), gen()} + println(a == c) +} + +// Error: +// main/files/types/eql_0f16.gno:16: [2]main.word is not comparable diff --git a/gnovm/tests/files/types/eql_0f17.gno b/gnovm/tests/files/types/eql_0f17.gno new file mode 100644 index 00000000000..d60d474f668 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f17.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a f = func() bool { return true } +var b f = func() bool { return false } + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f17.gno:9: main.f can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f18.gno b/gnovm/tests/files/types/eql_0f18.gno new file mode 100644 index 00000000000..ad534066fd1 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f18.gno @@ -0,0 +1,12 @@ +package main + +type f func() bool + +var a f = func() bool { return true } + +func main() { + println(a == nil) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f19.gno b/gnovm/tests/files/types/eql_0f19.gno new file mode 100644 index 00000000000..8b08d616a06 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f19.gno @@ -0,0 +1,12 @@ +package main + +type f func() bool + +var a f + +func main() { + println(a == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f1_stdlibs.gno b/gnovm/tests/files/types/eql_0f1_stdlibs.gno new file mode 100644 index 00000000000..e0b8ec5c753 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f1_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + if int64(1) == errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f1_stdlibs.gno:19: int64 does not implement .uverse.error diff --git a/gnovm/tests/files/types/eql_0f20.gno b/gnovm/tests/files/types/eql_0f20.gno new file mode 100644 index 00000000000..f0e2e93ffc9 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f20.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +// slice would be comparable +var a [2]f + +func main() { + println(a == nil) // rcx.T == nil +} + +// Error: +// main/files/types/eql_0f20.gno:9: [2]main.f is not comparable diff --git a/gnovm/tests/files/types/eql_0f21.gno b/gnovm/tests/files/types/eql_0f21.gno new file mode 100644 index 00000000000..20b52988a8d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f21.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a [2]f +var b [2]f + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f21.gno:9: [2]main.f is not comparable diff --git a/gnovm/tests/files/types/eql_0f21a.gno b/gnovm/tests/files/types/eql_0f21a.gno new file mode 100644 index 00000000000..075149d264f --- /dev/null +++ b/gnovm/tests/files/types/eql_0f21a.gno @@ -0,0 +1,13 @@ +package main + +type f func() bool + +var a [2]*f +var b [2]*f + +func main() { + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f22.gno b/gnovm/tests/files/types/eql_0f22.gno new file mode 100644 index 00000000000..75891def307 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f22.gno @@ -0,0 +1,11 @@ +package main + +var a int = 0 +var b int = 1 + +func main() { + println(&a == &b) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f23.gno b/gnovm/tests/files/types/eql_0f23.gno new file mode 100644 index 00000000000..55f2ab5189d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f23.gno @@ -0,0 +1,13 @@ +package main + +var a int = 0 +var b int = 1 + +func main() { + c := &a + d := &b + println(*c == *d) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f24.gno b/gnovm/tests/files/types/eql_0f24.gno new file mode 100644 index 00000000000..d01a9d4a14a --- /dev/null +++ b/gnovm/tests/files/types/eql_0f24.gno @@ -0,0 +1,13 @@ +package main + +var a int = 0 +var b int = 0 + +func main() { + c := &a + d := &b + println(*c == *d) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f25.gno b/gnovm/tests/files/types/eql_0f25.gno new file mode 100644 index 00000000000..8a3343146b0 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f25.gno @@ -0,0 +1,19 @@ +package main + +var a int = 0 +var b int = 0 + +func main() { + c := &a + d := &b + println(*c > *d) + println(*c >= *d) + println(*c < *d) + println(*c <= *d) +} + +// Output: +// false +// true +// false +// true diff --git a/gnovm/tests/files/types/eql_0f27_stdlibs.gno b/gnovm/tests/files/types/eql_0f27_stdlibs.gno new file mode 100644 index 00000000000..2bee791279a --- /dev/null +++ b/gnovm/tests/files/types/eql_0f27_stdlibs.gno @@ -0,0 +1,21 @@ +package main + +import ( + "errors" +) + +var errCmp1 = errors.New("XXXX") +var errCmp2 = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + if errCmp1 > errCmp2 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f27_stdlibs.gno:13: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f28.gno b/gnovm/tests/files/types/eql_0f28.gno new file mode 100644 index 00000000000..64f973cdde0 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f28.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 > e2) +} + +// Error: +// main/files/types/eql_0f28.gno:27: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f29.gno b/gnovm/tests/files/types/eql_0f29.gno new file mode 100644 index 00000000000..b5d1e897b89 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f29.gno @@ -0,0 +1,24 @@ +package main + +import ( + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var l interface{} + if l > Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f29.gno:16: operator > not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2b_native.gno b/gnovm/tests/files/types/eql_0f2b_native.gno new file mode 100644 index 00000000000..fe961441328 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2b_native.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) <= errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2b_native.gno:19: operator <= not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2b_stdlibs.gno b/gnovm/tests/files/types/eql_0f2b_stdlibs.gno new file mode 100644 index 00000000000..20de4b5b104 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2b_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) <= errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2b_stdlibs.gno:19: operator <= not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2c_native.gno b/gnovm/tests/files/types/eql_0f2c_native.gno new file mode 100644 index 00000000000..c17c757b760 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2c_native.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) < errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2c_native.gno:19: operator < not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2c_stdlibs.gno b/gnovm/tests/files/types/eql_0f2c_stdlibs.gno new file mode 100644 index 00000000000..f8da518f4a9 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2c_stdlibs.gno @@ -0,0 +1,28 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) < errCmp { + //if errCmp == 1 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f2c_stdlibs.gno:19: operator < not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/eql_0f2d.gno b/gnovm/tests/files/types/eql_0f2d.gno new file mode 100644 index 00000000000..5ad121f515b --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2d.gno @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strconv" +) + +type E interface { + Error() string +} +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// special case: +// one is interface +func main() { + var e0 E + e0 = Error(0) + fmt.Printf("%T \n", e0) + if e0 == Error(0) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// int64 +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f2e.gno b/gnovm/tests/files/types/eql_0f2e.gno new file mode 100644 index 00000000000..ea03028f5e1 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f2e.gno @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strconv" +) + +type E interface { + Error() string +} +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// special case: +// one is interface +func main() { + var e0 E + e0 = Error(0) + fmt.Printf("%T \n", e0) + if Error(0) == e0 { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// int64 +// what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f30.gno b/gnovm/tests/files/types/eql_0f30.gno new file mode 100644 index 00000000000..0a9a61a96cf --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30.gno @@ -0,0 +1,9 @@ +package main + +// both not const, and both interface +func main() { + println([]byte("a") == []byte("b")) +} + +// Error: +// main/files/types/eql_0f30.gno:5: []uint8 can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30a.gno b/gnovm/tests/files/types/eql_0f30a.gno new file mode 100644 index 00000000000..2dadbd9d6d3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(map[string]int{"a": 1} == map[string]int{"b": 2}) +} + +// Error: +// main/files/types/eql_0f30a.gno:4: map[string]int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30b.gno b/gnovm/tests/files/types/eql_0f30b.gno new file mode 100644 index 00000000000..2e7a463fddd --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30b.gno @@ -0,0 +1,13 @@ +package main + +type f func() + +var f1 f +var f2 f + +func main() { + println(f1 == f2) +} + +// Error: +// main/files/types/eql_0f30b.gno:9: main.f can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30c.gno b/gnovm/tests/files/types/eql_0f30c.gno new file mode 100644 index 00000000000..ffa8c7c96e7 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30c.gno @@ -0,0 +1,10 @@ +package main + +var f1 func() + +func main() { + println(f1 == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f30d.gno b/gnovm/tests/files/types/eql_0f30d.gno new file mode 100644 index 00000000000..8b0612f011b --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30d.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]int{1} == []int{1}) +} + +// Error: +// main/files/types/eql_0f30d.gno:4: []int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f30e.gno b/gnovm/tests/files/types/eql_0f30e.gno new file mode 100644 index 00000000000..002910072c3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30e.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]int{1} == nil) +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f30f.gno b/gnovm/tests/files/types/eql_0f30f.gno new file mode 100644 index 00000000000..880b081b6cd --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30f.gno @@ -0,0 +1,11 @@ +package main + +var a = [2]int{1, 1} +var b = [3]int{1, 1} + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f30f.gno:7: cannot use [2]int as [3]int diff --git a/gnovm/tests/files/types/eql_0f30g.gno b/gnovm/tests/files/types/eql_0f30g.gno new file mode 100644 index 00000000000..66135a83b62 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f30g.gno @@ -0,0 +1,11 @@ +package main + +var a = [2]int{1, 1} +var b = []int{1, 1} + +func main() { + println(a == b) +} + +// Error: +// main/files/types/eql_0f30g.gno:7: []int can only be compared to nil diff --git a/gnovm/tests/files/types/eql_0f31.gno b/gnovm/tests/files/types/eql_0f31.gno new file mode 100644 index 00000000000..bcb62dc76c3 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f31.gno @@ -0,0 +1,13 @@ +package main + +import "bytes" + +// both not const, and both interface +func main() { + // lv.T: * uint8, rv.T: * uint8 + cmp := bytes.Compare([]byte("a"), []byte("b")) + println(cmp) +} + +// Output: +// -1 diff --git a/gnovm/tests/files/types/eql_0f32.gno b/gnovm/tests/files/types/eql_0f32.gno new file mode 100644 index 00000000000..321824566cb --- /dev/null +++ b/gnovm/tests/files/types/eql_0f32.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println([]byte("a") == nil) // lx: (const (slice[0x61] []uint8)), rx: (const (undefined)), cmp = 0 +} + +// Output: +// false diff --git a/gnovm/tests/files/types/eql_0f33.gno b/gnovm/tests/files/types/eql_0f33.gno new file mode 100644 index 00000000000..e7aac0cf069 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f33.gno @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// var f func() +var a *struct{} +var b interface{} +var c map[string]int +var s []int + +func main() { + if a == nil { + fmt.Println("pointer == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if b == nil { + fmt.Println("interface == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if c == nil { + fmt.Println("map == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if s == nil { + fmt.Println("slice == nil") + } else { + println("not nil!") + } + fmt.Println("----") +} + +// Output: +// pointer == nil +// ---- +// interface == nil +// ---- +// map == nil +// ---- +// slice == nil +// ---- diff --git a/gnovm/tests/files/types/eql_0f34.gno b/gnovm/tests/files/types/eql_0f34.gno new file mode 100644 index 00000000000..8b5d3830091 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f34.gno @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// var f func() +var a *struct{} +var b interface{} +var c map[string]int +var s []int + +func main() { + if nil == a { + fmt.Println("pointer == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == b { + fmt.Println("interface == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == c { + fmt.Println("map == nil") + } else { + println("not nil!") + } + fmt.Println("----") + + if nil == s { + fmt.Println("slice == nil") + } else { + println("not nil!") + } + fmt.Println("----") +} + +// Output: +// pointer == nil +// ---- +// interface == nil +// ---- +// map == nil +// ---- +// slice == nil +// ---- diff --git a/gnovm/tests/files/types/eql_0f35.gno b/gnovm/tests/files/types/eql_0f35.gno new file mode 100644 index 00000000000..c57d3e9d375 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f35.gno @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Error0 int64 + +func (e Error0) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func main() { + defer func() { + if r := recover(); r != nil { + if r == Error1(0) { + fmt.Println("Recovered. Error:\n", r) + } + } + }() + + panic(Error1(0)) +} + +// Output: +// Recovered. Error: +// 0 diff --git a/gnovm/tests/files/types/eql_0f37.gno b/gnovm/tests/files/types/eql_0f37.gno new file mode 100644 index 00000000000..7fb051961ee --- /dev/null +++ b/gnovm/tests/files/types/eql_0f37.gno @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Error0 int64 + +func (e Error0) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +func get() interface{} { + return Error1(0) +} + +func main() { + defer func() { + if r := recover(); r != nil { + if r == get() { + fmt.Println("recover Error1") + } else { + fmt.Println("recover Error0") + } + } + }() + + panic(Error1(0)) +} + +// Output: +// recover Error1 diff --git a/gnovm/tests/files/types/eql_0f40_stdlibs.gno b/gnovm/tests/files/types/eql_0f40_stdlibs.gno new file mode 100644 index 00000000000..d826bdbd155 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f40_stdlibs.gno @@ -0,0 +1,41 @@ +package main + +import ( + "errors" +) + +type animal interface { + eat() +} + +type dog struct { +} + +func (d *dog) eat() { + println("dog eating") +} + +func get() animal { + d := &dog{} + return d +} + +var errCmp = errors.New("errCmp") + +// no empty interface, different interface(with different methods) +func main() { + defer func() { + if r := recover(); r != nil { + if r == errCmp { + println("same error") + } else { + println("different error") + } + } + }() + + panic(get()) +} + +// Output: +// different error diff --git a/gnovm/tests/files/types/eql_0f41_stdlibs.gno b/gnovm/tests/files/types/eql_0f41_stdlibs.gno new file mode 100644 index 00000000000..679cb1f9295 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f41_stdlibs.gno @@ -0,0 +1,35 @@ +package main + +import ( + "errors" +) + +type animal interface { + eat() +} + +type dog struct { +} + +func (d *dog) eat() { + println("dog eating") +} + +func get() animal { + d := &dog{} + return d +} + +var errCmp = errors.New("errCmp") + +// no empty interface, different interface(with different methods) +func main() { + if get() == errCmp { + println("same error") + } else { + println("different error") + } +} + +// Error: +// main/files/types/eql_0f41_stdlibs.gno:27: main.animal does not implement .uverse.error diff --git a/gnovm/tests/files/types/eql_0f42.gno b/gnovm/tests/files/types/eql_0f42.gno new file mode 100644 index 00000000000..efb63d40b97 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f42.gno @@ -0,0 +1,10 @@ +package main + +var m map[string]int + +func main() { + println(m == nil) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f43_hasNil.gno b/gnovm/tests/files/types/eql_0f43_hasNil.gno new file mode 100644 index 00000000000..c336aa92ef6 --- /dev/null +++ b/gnovm/tests/files/types/eql_0f43_hasNil.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func main() { + var myPointer *int + if myPointer == nil { // rcx.T == nil, <-, after conversion lv: (nil *int), rv: (nil *int) + fmt.Println("Pointer is nil") + } else { + fmt.Println("Pointer is not nil") + } +} + +// Output: +// Pointer is nil diff --git a/gnovm/tests/files/types/eql_0f44.gno b/gnovm/tests/files/types/eql_0f44.gno new file mode 100644 index 00000000000..e23e6c07aba --- /dev/null +++ b/gnovm/tests/files/types/eql_0f44.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +type f func() + +func main() { + if f(nil) == nil { // rcx.T == nil + fmt.Println("func is nil") + } else { + fmt.Println("func is not nil") + } +} + +// Output: +// func is nil diff --git a/gnovm/tests/files/types/eql_0f45.gno b/gnovm/tests/files/types/eql_0f45.gno new file mode 100644 index 00000000000..7707136b82e --- /dev/null +++ b/gnovm/tests/files/types/eql_0f45.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + if func() {} == nil { + fmt.Println("func is nil") + } else { + fmt.Println("func is not nil") + } +} + +// Output: +// func is not nil diff --git a/gnovm/tests/files/types/eql_0f46.gno b/gnovm/tests/files/types/eql_0f46.gno new file mode 100644 index 00000000000..3a65bd18b3b --- /dev/null +++ b/gnovm/tests/files/types/eql_0f46.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type m map[string]int + +func main() { + // m(nil) is a conversion from nil to m, whose underlying type is map[string]int + // lv: (nil main.m), rv: (undefined) + if m(nil) == nil { + fmt.Println("m is nil") + } else { + fmt.Println("m is not nil") + } +} + +// Output: +// m is nil diff --git a/gnovm/tests/files/types/eql_0f46a.gno b/gnovm/tests/files/types/eql_0f46a.gno new file mode 100644 index 00000000000..9476b1f22ef --- /dev/null +++ b/gnovm/tests/files/types/eql_0f46a.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type s []int + +func main() { + // s(nil) is a conversion from nil to s, whose underlying type is []int + // lv: (nil main.s), rv: (undefined) + if s(nil) == nil { + fmt.Println("s is nil") + } else { + fmt.Println("s is not nil") + } +} + +// Output: +// s is nil diff --git a/gnovm/tests/files/types/eql_0f48.gno b/gnovm/tests/files/types/eql_0f48.gno new file mode 100644 index 00000000000..fbf4f164c9d --- /dev/null +++ b/gnovm/tests/files/types/eql_0f48.gno @@ -0,0 +1,22 @@ +package main + +func testEql(want, got interface{}) { + if want != got { + println(false) + } else { + println(true) + } +} + +// return var of error nil +func gen() error { + return nil +} + +func main() { // about untyped nil to (interface{})typed-nil, no support for native for now. + r := gen() + testEql(r, error(nil)) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/eql_0f8_stdlibs.gno b/gnovm/tests/files/types/eql_0f8_stdlibs.gno new file mode 100644 index 00000000000..400ce3c6d1e --- /dev/null +++ b/gnovm/tests/files/types/eql_0f8_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if errCmp == int64(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/eql_0f8_stdlibs.gno:19: int64 does not implement .uverse.error diff --git a/gnovm/tests/files/types/eql_iface.gno b/gnovm/tests/files/types/eql_iface.gno new file mode 100644 index 00000000000..d3d41348a62 --- /dev/null +++ b/gnovm/tests/files/types/eql_iface.gno @@ -0,0 +1,13 @@ +package main + +type Foo struct { + n int +} + +func main() { + var l interface{} = 1 + println(1 == l) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/incdec_a0.gno b/gnovm/tests/files/types/incdec_a0.gno new file mode 100644 index 00000000000..8f3f97caaee --- /dev/null +++ b/gnovm/tests/files/types/incdec_a0.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a++ + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/incdec_a1.gno b/gnovm/tests/files/types/incdec_a1.gno new file mode 100644 index 00000000000..ee9d2b18c7e --- /dev/null +++ b/gnovm/tests/files/types/incdec_a1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := "hello" + a++ + println(a) +} + +// Error: +// main/files/types/incdec_a1.gno:5: operator ++ not defined on: StringKind diff --git a/gnovm/tests/files/types/incdec_a2.gno b/gnovm/tests/files/types/incdec_a2.gno new file mode 100644 index 00000000000..a91080a0d95 --- /dev/null +++ b/gnovm/tests/files/types/incdec_a2.gno @@ -0,0 +1,29 @@ +package main + +func main() { + a := int(1) + a++ + println(a) + + // TODO: no support for now + //b := 1.0 + //b++ + //println(b) + // + //c := float32(1.0) + //c++ + //println(c) + + d := 'a' + d++ + println(d) + + e := uint(1) + e++ + println(e) +} + +// Output: +// 2 +// 98 +// 2 diff --git a/gnovm/tests/files/types/incdec_a3.gno b/gnovm/tests/files/types/incdec_a3.gno new file mode 100644 index 00000000000..f257b822f8e --- /dev/null +++ b/gnovm/tests/files/types/incdec_a3.gno @@ -0,0 +1,19 @@ +package main + +type Int int8 + +func (i Int) Inc() Int { + i++ + return i +} + +// right is typed const, can not use as its correspondence declared type +func main() { + var a Int + a = Int(int8(0)) + a = a.Inc() + println(a) +} + +// Output: +// (1 main.Int) diff --git a/gnovm/tests/files/types/incdec_a4.gno b/gnovm/tests/files/types/incdec_a4.gno new file mode 100644 index 00000000000..5f87eb5c09c --- /dev/null +++ b/gnovm/tests/files/types/incdec_a4.gno @@ -0,0 +1,18 @@ +package main + +type Int int8 + +func (i Int) Inc() { + i++ +} + +// right is typed const, can not use as its correspondence declared type +func main() { + var a Int + a = int8(0) + a.Inc() + println(a) +} + +// Error: +// main/files/types/incdec_a4.gno:12: cannot use int8 as main.Int without explicit conversion diff --git a/gnovm/tests/files/types/nil.gno b/gnovm/tests/files/types/nil.gno new file mode 100644 index 00000000000..a836d328dfe --- /dev/null +++ b/gnovm/tests/files/types/nil.gno @@ -0,0 +1,9 @@ +package main + +func main() { + + println(nil + nil) +} + +// Error: +// main/files/types/nil.gno:5: operator + not defined on: nil diff --git a/gnovm/tests/files/types/or_a0.gno b/gnovm/tests/files/types/or_a0.gno new file mode 100644 index 00000000000..b2f14c73785 --- /dev/null +++ b/gnovm/tests/files/types/or_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(0) | int8(1)) +} + +// Error: +// main/files/types/or_a0.gno:5: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/or_a1.gno b/gnovm/tests/files/types/or_a1.gno new file mode 100644 index 00000000000..c2e7bff0d5d --- /dev/null +++ b/gnovm/tests/files/types/or_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) | Error2(0)) +} + +// Error: +// main/files/types/or_a1.gno:21: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/or_b0.gno b/gnovm/tests/files/types/or_b0.gno new file mode 100644 index 00000000000..655121ee2f2 --- /dev/null +++ b/gnovm/tests/files/types/or_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 | Error(1)) + println(Error(1) | 1) +} + +// Output: +// (1 main.Error) +// (1 main.Error) diff --git a/gnovm/tests/files/types/or_b1.gno b/gnovm/tests/files/types/or_b1.gno new file mode 100644 index 00000000000..1616c4db687 --- /dev/null +++ b/gnovm/tests/files/types/or_b1.gno @@ -0,0 +1,21 @@ +package main + +import ( + "strconv" +) + +type Error int8 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// one untyped const, one typed const +func main() { + println(1 | Error(1)) + println(Error(1) | 1) +} + +// Output: +// error: 1 +// error: 1 diff --git a/gnovm/tests/files/types/or_b2.gno b/gnovm/tests/files/types/or_b2.gno new file mode 100644 index 00000000000..0bdf3e0a73a --- /dev/null +++ b/gnovm/tests/files/types/or_b2.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 | "a") +} + +// Error: +// main/files/types/or_b2.gno:5: operator | not defined on: StringKind diff --git a/gnovm/tests/files/types/or_b3.gno b/gnovm/tests/files/types/or_b3.gno new file mode 100644 index 00000000000..4b6f20530cd --- /dev/null +++ b/gnovm/tests/files/types/or_b3.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println("b" | "a") +} + +// Error: +// main/files/types/or_b3.gno:5: operator | not defined on: StringKind diff --git a/gnovm/tests/files/types/or_b4.gno b/gnovm/tests/files/types/or_b4.gno new file mode 100644 index 00000000000..54c50033ad1 --- /dev/null +++ b/gnovm/tests/files/types/or_b4.gno @@ -0,0 +1,9 @@ +package main + +// one untyped const, one typed const +func main() { + println(1 | 'a') +} + +// Output: +// 97 diff --git a/gnovm/tests/files/types/or_d0.gno b/gnovm/tests/files/types/or_d0.gno new file mode 100644 index 00000000000..827e29a8fba --- /dev/null +++ b/gnovm/tests/files/types/or_d0.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation, and this should happen in process stage!!! +func main() { + println(1.0 | 1) +} + +// Error: +// main/files/types/or_d0.gno:6: operator | not defined on: BigdecKind diff --git a/gnovm/tests/files/types/or_d1.gno b/gnovm/tests/files/types/or_d1.gno new file mode 100644 index 00000000000..ac2458a96a2 --- /dev/null +++ b/gnovm/tests/files/types/or_d1.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println('a' | 'b') +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d2.gno b/gnovm/tests/files/types/or_d2.gno new file mode 100644 index 00000000000..0812ff926e7 --- /dev/null +++ b/gnovm/tests/files/types/or_d2.gno @@ -0,0 +1,13 @@ +package main + +// both untyped const +// TODO: dec value representation +var r rune + +func main() { + r = 'a' + println(r | 'b') +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d3.gno b/gnovm/tests/files/types/or_d3.gno new file mode 100644 index 00000000000..226c9423da5 --- /dev/null +++ b/gnovm/tests/files/types/or_d3.gno @@ -0,0 +1,15 @@ +package main + +// both untyped const +// TODO: dec value representation +var r1 rune +var r2 rune + +func main() { + r1 = 'a' + r2 = 'b' + println(r1 | r2) +} + +// Output: +// 99 diff --git a/gnovm/tests/files/types/or_d4.gno b/gnovm/tests/files/types/or_d4.gno new file mode 100644 index 00000000000..f1240f32c71 --- /dev/null +++ b/gnovm/tests/files/types/or_d4.gno @@ -0,0 +1,10 @@ +package main + +// both untyped const +// TODO: dec value representation +func main() { + println(1.0 | 0) +} + +// Error: +// main/files/types/or_d4.gno:6: operator | not defined on: BigdecKind diff --git a/gnovm/tests/files/types/or_e0.gno b/gnovm/tests/files/types/or_e0.gno new file mode 100644 index 00000000000..555ee5ad37b --- /dev/null +++ b/gnovm/tests/files/types/or_e0.gno @@ -0,0 +1,27 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, typed +func main() { + var e1 Error1 = Error1(0) + var e2 Error2 = Error2(0) + println(e1 | e2) +} + +// Error: +// main/files/types/or_e0.gno:23: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/or_f0_stdlibs.gno b/gnovm/tests/files/types/or_f0_stdlibs.gno new file mode 100644 index 00000000000..d2a080e0bab --- /dev/null +++ b/gnovm/tests/files/types/or_f0_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(1 | errCmp) +} + +// Error: +// main/files/types/or_f0_stdlibs.gno:19: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/or_f1_stdlibs.gno b/gnovm/tests/files/types/or_f1_stdlibs.gno new file mode 100644 index 00000000000..9cc9eb5157b --- /dev/null +++ b/gnovm/tests/files/types/or_f1_stdlibs.gno @@ -0,0 +1,23 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// specil case: +// one is interface +func main() { + println(Error(0) | errCmp) +} + +// Error: +// main/files/types/or_f1_stdlibs.gno:19: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/or_f2.gno b/gnovm/tests/files/types/or_f2.gno new file mode 100644 index 00000000000..284078f41d4 --- /dev/null +++ b/gnovm/tests/files/types/or_f2.gno @@ -0,0 +1,31 @@ +package main + +import ( + "strconv" +) + +type E interface { + Error() string +} + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both not const, and both interface +func main() { + var e1 E = Error1(0) + var e2 E = Error2(0) + println(e1 | e2) +} + +// Error: +// main/files/types/or_f2.gno:27: operator | not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/overflow_a0.gno b/gnovm/tests/files/types/overflow_a0.gno new file mode 100644 index 00000000000..e3ecefd621f --- /dev/null +++ b/gnovm/tests/files/types/overflow_a0.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const Huge = 1e1000 + fmt.Println(Huge / 1e999) +} + +// Output: +// 10 diff --git a/gnovm/tests/files/types/overflow_a1.gno b/gnovm/tests/files/types/overflow_a1.gno new file mode 100644 index 00000000000..bd893312cf2 --- /dev/null +++ b/gnovm/tests/files/types/overflow_a1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const Huge = 1e1000 + println(Huge) +} + +// Error: +// cannot convert untyped bigdec to float64: strconv.ParseFloat: parsing "1E+1000": value out of range diff --git a/gnovm/tests/files/types/rem_a0.gno b/gnovm/tests/files/types/rem_a0.gno new file mode 100644 index 00000000000..7ba0f66f533 --- /dev/null +++ b/gnovm/tests/files/types/rem_a0.gno @@ -0,0 +1,9 @@ +package main + +// both typed(different) const +func main() { + println(int(1) % int8(1)) +} + +// Error: +// main/files/types/rem_a0.gno:5: invalid operation: mismatched types int and int8 diff --git a/gnovm/tests/files/types/rem_a1.gno b/gnovm/tests/files/types/rem_a1.gno new file mode 100644 index 00000000000..edea73382ce --- /dev/null +++ b/gnovm/tests/files/types/rem_a1.gno @@ -0,0 +1,25 @@ +package main + +import ( + "strconv" +) + +type Error1 int64 + +func (e Error1) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +type Error2 int + +func (e Error2) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +// both typed(different) const +func main() { + println(Error1(0) % Error2(0)) +} + +// Error: +// main/files/types/rem_a1.gno:21: invalid operation: mismatched types main.Error1 and main.Error2 diff --git a/gnovm/tests/files/types/rem_a2.gno b/gnovm/tests/files/types/rem_a2.gno new file mode 100644 index 00000000000..3ebba194c0a --- /dev/null +++ b/gnovm/tests/files/types/rem_a2.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(1 % 0) +} + +// Error: +// main/files/types/rem_a2.gno:4: invalid operation: division by zero diff --git a/gnovm/tests/files/types/rem_b0.gno b/gnovm/tests/files/types/rem_b0.gno new file mode 100644 index 00000000000..9b5f82fb3b7 --- /dev/null +++ b/gnovm/tests/files/types/rem_b0.gno @@ -0,0 +1,13 @@ +package main + +type Error int8 + +// one untyped const, one typed const +func main() { + println(1 % Error(1)) + println(Error(1) % 1) +} + +// Output: +// (0 main.Error) +// (0 main.Error) diff --git a/gnovm/tests/files/types/rem_f3.gno b/gnovm/tests/files/types/rem_f3.gno new file mode 100644 index 00000000000..e34dfab9ae0 --- /dev/null +++ b/gnovm/tests/files/types/rem_f3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := 1 + println(a % 0) +} + +// Error: +// main/files/types/rem_f3.gno:5: invalid operation: division by zero diff --git a/gnovm/tests/files/types/runtime_a0.gno b/gnovm/tests/files/types/runtime_a0.gno new file mode 100644 index 00000000000..7d6312e5b9c --- /dev/null +++ b/gnovm/tests/files/types/runtime_a0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + m := map[string]bool{"foo": true} + m["foo"]++ +} + +// Error: +// main/files/types/runtime_a0.gno:5: operator ++ not defined on: BoolKind diff --git a/gnovm/tests/files/types/runtime_a0a.gno b/gnovm/tests/files/types/runtime_a0a.gno new file mode 100644 index 00000000000..fecce01817e --- /dev/null +++ b/gnovm/tests/files/types/runtime_a0a.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(map[string]bool{"foo": true}["foo"] == true) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/runtime_a2.gno b/gnovm/tests/files/types/runtime_a2.gno new file mode 100644 index 00000000000..d53c9c4d970 --- /dev/null +++ b/gnovm/tests/files/types/runtime_a2.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func gen() interface{} { + return false +} + +func main() { + r := gen() + fmt.Printf("%T \n", r) +} + +// Output: +// bool diff --git a/gnovm/tests/files/types/runtime_a3.gno b/gnovm/tests/files/types/runtime_a3.gno new file mode 100644 index 00000000000..d46a83a1c35 --- /dev/null +++ b/gnovm/tests/files/types/runtime_a3.gno @@ -0,0 +1,12 @@ +package main + +func gen() interface{} { + return false +} + +func main() { + gen()++ +} + +// Error: +// main/files/types/runtime_a3.gno:8: operator ++ not defined on: InterfaceKind diff --git a/gnovm/tests/files/types/shift_a0.gno b/gnovm/tests/files/types/shift_a0.gno new file mode 100644 index 00000000000..a31e7bd8f82 --- /dev/null +++ b/gnovm/tests/files/types/shift_a0.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(int(1) << int(1)) + println(int(1) >> int(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a1.gno b/gnovm/tests/files/types/shift_a1.gno new file mode 100644 index 00000000000..ba92bf0f7d2 --- /dev/null +++ b/gnovm/tests/files/types/shift_a1.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(int(1) << int8(1)) + println(int(1) >> int8(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a10.gno b/gnovm/tests/files/types/shift_a10.gno new file mode 100644 index 00000000000..0eba81f4a9c --- /dev/null +++ b/gnovm/tests/files/types/shift_a10.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << 1.0 // NOTE: go vet would fail, but still process + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/shift_a11.gno b/gnovm/tests/files/types/shift_a11.gno new file mode 100644 index 00000000000..2d3c935e97d --- /dev/null +++ b/gnovm/tests/files/types/shift_a11.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << "hello" // NOTE: go vet would fail, but still process + println(r) +} + +// Error: +// main/files/types/shift_a11.gno:3: cannot convert StringKind to UintKind diff --git a/gnovm/tests/files/types/shift_a12.gno b/gnovm/tests/files/types/shift_a12.gno new file mode 100644 index 00000000000..5735854d684 --- /dev/null +++ b/gnovm/tests/files/types/shift_a12.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func main() { + a := uint(1) + r := uint64(1) << a + println(r) + fmt.Printf("%T \n", r) +} + +// Output: +// 2 +// uint64 diff --git a/gnovm/tests/files/types/shift_a13.gno b/gnovm/tests/files/types/shift_a13.gno new file mode 100644 index 00000000000..7d70cc3589a --- /dev/null +++ b/gnovm/tests/files/types/shift_a13.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + var r uint64 = 1 << int8(1) + fmt.Printf("%T \n", r) + println(r) +} + +// Output: +// uint64 +// 2 diff --git a/gnovm/tests/files/types/shift_a14.gno b/gnovm/tests/files/types/shift_a14.gno new file mode 100644 index 00000000000..f879da5f3f9 --- /dev/null +++ b/gnovm/tests/files/types/shift_a14.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := "hello" + r := a << 1 + println(r) +} + +// Error: +// main/files/types/shift_a14.gno:5: operator << not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a15.gno b/gnovm/tests/files/types/shift_a15.gno new file mode 100644 index 00000000000..b86445f3e75 --- /dev/null +++ b/gnovm/tests/files/types/shift_a15.gno @@ -0,0 +1,10 @@ +package main + +// TODO: fix in assignment +func main() { + a := "hello" + a <<= 1 +} + +// Error: +// main/files/types/shift_a15.gno:6: operator <<= not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a16.gno b/gnovm/tests/files/types/shift_a16.gno new file mode 100644 index 00000000000..ed41f35f20d --- /dev/null +++ b/gnovm/tests/files/types/shift_a16.gno @@ -0,0 +1,9 @@ +package main + +func main() { + r := "hello" << 1 + println(r) +} + +// Error: +// main/files/types/shift_a16.gno:4: operator << not defined on: StringKind diff --git a/gnovm/tests/files/types/shift_a2.gno b/gnovm/tests/files/types/shift_a2.gno new file mode 100644 index 00000000000..91072929306 --- /dev/null +++ b/gnovm/tests/files/types/shift_a2.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(1 << int(1)) + println(1 >> int(1)) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a3.gno b/gnovm/tests/files/types/shift_a3.gno new file mode 100644 index 00000000000..79d5c566f1d --- /dev/null +++ b/gnovm/tests/files/types/shift_a3.gno @@ -0,0 +1,10 @@ +package main + +// both typed(different) const +func main() { + println(1 << 'a') + println(1 >> 'a') +} + +// Error: +// main/files/types/shift_a3.gno:5: bigint overflows target kind diff --git a/gnovm/tests/files/types/shift_a4.gno b/gnovm/tests/files/types/shift_a4.gno new file mode 100644 index 00000000000..3561929b672 --- /dev/null +++ b/gnovm/tests/files/types/shift_a4.gno @@ -0,0 +1,11 @@ +package main + +// both typed(different) const +func main() { + println(1 << 1.0) + println(1 >> 1.0) +} + +// Output: +// 2 +// 0 diff --git a/gnovm/tests/files/types/shift_a5.gno b/gnovm/tests/files/types/shift_a5.gno new file mode 100644 index 00000000000..0394f8a2f3f --- /dev/null +++ b/gnovm/tests/files/types/shift_a5.gno @@ -0,0 +1,10 @@ +package main + +// TODO: support this? +func main() { + println(1.0 << 1) + println(1.0 >> 1) +} + +// Error: +// main/files/types/shift_a5.gno:5: operator << not defined on: BigdecKind diff --git a/gnovm/tests/files/types/shift_a6.gno b/gnovm/tests/files/types/shift_a6.gno new file mode 100644 index 00000000000..03ad4c0bcea --- /dev/null +++ b/gnovm/tests/files/types/shift_a6.gno @@ -0,0 +1,14 @@ +package main + +func main() { + r := int(1) + println(r << 5) + println(r) + r <<= 5 + println(r) +} + +// Output: +// 32 +// 1 +// 32 diff --git a/gnovm/tests/files/types/shift_a7.gno b/gnovm/tests/files/types/shift_a7.gno new file mode 100644 index 00000000000..5cad6897a21 --- /dev/null +++ b/gnovm/tests/files/types/shift_a7.gno @@ -0,0 +1,9 @@ +package main + +func main() { + r := int(1) + println(r << "a") +} + +// Error: +// main/files/types/shift_a7.gno:3: cannot convert StringKind to UintKind diff --git a/gnovm/tests/files/types/shift_a8.gno b/gnovm/tests/files/types/shift_a8.gno new file mode 100644 index 00000000000..a7a3e1f07d9 --- /dev/null +++ b/gnovm/tests/files/types/shift_a8.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + r := a << 1 + println(r) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/shift_a9.gno b/gnovm/tests/files/types/shift_a9.gno new file mode 100644 index 00000000000..f998381ef1c --- /dev/null +++ b/gnovm/tests/files/types/shift_a9.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a <<= 1 + println(a) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/types/time_native.gno b/gnovm/tests/files/types/time_native.gno new file mode 100644 index 00000000000..ef1c3daaf44 --- /dev/null +++ b/gnovm/tests/files/types/time_native.gno @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + fmt.Println(time.Second + time.Second) +} + +// Output: +// 2s diff --git a/gnovm/tests/files/types/unary_a0.gno b/gnovm/tests/files/types/unary_a0.gno new file mode 100644 index 00000000000..2e5a1d289c9 --- /dev/null +++ b/gnovm/tests/files/types/unary_a0.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 5 + y := +x + fmt.Println(y) // Output: 5 +} + +// Output: +// 5 diff --git a/gnovm/tests/files/types/unary_a0a.gno b/gnovm/tests/files/types/unary_a0a.gno new file mode 100644 index 00000000000..3556bcf2bf6 --- /dev/null +++ b/gnovm/tests/files/types/unary_a0a.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1.0 + y := +x + fmt.Println(y) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/unary_a0b.gno b/gnovm/tests/files/types/unary_a0b.gno new file mode 100644 index 00000000000..ff4e863e10e --- /dev/null +++ b/gnovm/tests/files/types/unary_a0b.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := int(1) + y := +x + fmt.Println(y) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/types/unary_a0c.gno b/gnovm/tests/files/types/unary_a0c.gno new file mode 100644 index 00000000000..9f1fa057357 --- /dev/null +++ b/gnovm/tests/files/types/unary_a0c.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := "hello" + y := +x + fmt.Println(y) +} + +// Error: +// main/files/types/unary_a0c.gno:7: operator + not defined on: StringKind diff --git a/gnovm/tests/files/types/unary_a1.gno b/gnovm/tests/files/types/unary_a1.gno new file mode 100644 index 00000000000..c3c96d6724e --- /dev/null +++ b/gnovm/tests/files/types/unary_a1.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + x := 5 + y := -x + fmt.Println(y) // Output: -5 + +} + +// Output: +// -5 diff --git a/gnovm/tests/files/types/unary_a2.gno b/gnovm/tests/files/types/unary_a2.gno new file mode 100644 index 00000000000..1b9edf1b712 --- /dev/null +++ b/gnovm/tests/files/types/unary_a2.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := true + b := !a + fmt.Println(b) // Output: false + +} + +// Output: +// false diff --git a/gnovm/tests/files/types/unary_a2a.gno b/gnovm/tests/files/types/unary_a2a.gno new file mode 100644 index 00000000000..9081ad64ebf --- /dev/null +++ b/gnovm/tests/files/types/unary_a2a.gno @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func main() { + a := 1 + b := !a + fmt.Println(b) // Output: false + +} + +// Error: +// main/files/types/unary_a2a.gno:7: operator ! not defined on: IntKind diff --git a/gnovm/tests/files/types/unary_a3.gno b/gnovm/tests/files/types/unary_a3.gno new file mode 100644 index 00000000000..3f216ccb1c1 --- /dev/null +++ b/gnovm/tests/files/types/unary_a3.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 10 + p := &x + fmt.Println(*p) // Output: 10 +} + +// Output: +// 10 diff --git a/gnovm/tests/files/types/unary_a4.gno b/gnovm/tests/files/types/unary_a4.gno new file mode 100644 index 00000000000..caaf1ed0438 --- /dev/null +++ b/gnovm/tests/files/types/unary_a4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + v := 42 + p := &v + println(v == *p) +} + +// Output: +// true diff --git a/gnovm/tests/files/types/unary_a5.gno b/gnovm/tests/files/types/unary_a5.gno new file mode 100644 index 00000000000..bb956f85673 --- /dev/null +++ b/gnovm/tests/files/types/unary_a5.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1 + y := ^x + fmt.Println(y) // Output will be the bitwise complement of 1 +} + +// Output: +// -2 diff --git a/gnovm/tests/files/types/unary_a6.gno b/gnovm/tests/files/types/unary_a6.gno new file mode 100644 index 00000000000..99e87b66683 --- /dev/null +++ b/gnovm/tests/files/types/unary_a6.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + x := 1.0 + y := ^x + fmt.Println(y) // Output will be the bitwise complement of 1 +} + +// Error: +// main/files/types/unary_a6.gno:7: operator ^ not defined on: Float64Kind diff --git a/gnovm/tests/files/var18.gno b/gnovm/tests/files/var18.gno index 9fc0cba3053..f2f97f62b15 100644 --- a/gnovm/tests/files/var18.gno +++ b/gnovm/tests/files/var18.gno @@ -5,4 +5,4 @@ func main() { } // Error: -// main/files/var18.gno:4: should not happen +// main/files/var18.gno:4: assignment mismatch: 3 variables but 2 values diff --git a/gnovm/tests/files/zrealm12.gno b/gnovm/tests/files/zrealm12_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm12.gno rename to gnovm/tests/files/zrealm12_stdlibs.gno diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index da8a581375c..8e2b9827322 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -25,7 +25,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -57,10 +57,29 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "567a18d9c7594ece7956ce54384b0858888bb834", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -68,7 +87,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -238,21 +257,16 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "8197b7c5b4f2c7bf9c12b1c614f6b4dc6e7ce8dd", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b1d00c9606ffbb00b2aa3d475c5a390514f6dc14", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 4f5254b6951..8a4bac1fe32 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -72,9 +72,9 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "4", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -83,7 +83,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -180,19 +180,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "de0c4b2dd935220f7d37d10fc9feb1448bfb011d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index ebe107290e7..108483a435c 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -72,13 +72,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8a86634afa28ef7d7a1f4272255637f16daae2cd", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -126,26 +145,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "1a4158d473290431f9d4f9c5a85a3b6697640f2a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "4", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -154,7 +167,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -251,19 +264,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "011a2960ff92aedda8acd122b9f4d251902e0cd8", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index 9884ab1909c..ff5e7e758a1 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -73,13 +73,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "34a46349a2bc1b58591d0222a145b585452683be", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -127,30 +146,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "81074f5da453299a913435a2ddd05248ee012f8c", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "b1719c55a9b07d432385f020b0bdbc678ba2b9ac", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -198,26 +211,20 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "8d00c3fa6c15cb0d78dcbaa23df49f96bbc9591b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } @@ -226,7 +233,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "5", +// "ModTime": "7", // "RefCount": "2" // }, // "Parent": null, @@ -323,19 +330,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ade9fce2a987ef1924040a1d75c0172410c66952", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "8f2cb2a771ddc55ab5798b791e16df547c94d862", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index a706ffcad78..e6fd99503be 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -25,7 +25,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -74,13 +74,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "42cd813e173ad23c7873e9605901e8bea1176c96", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -128,30 +147,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "4f88fcdc73a4a94905e8e4044aa50c2ec7bf2227", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "2ac7cc7e6fdb1ff6dc1f340486011f1449757d85", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -160,7 +192,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key0" +// "value": "key1" // } // }, // { @@ -170,11 +202,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value0" +// "value": "value1" // } // }, // { -// "N": "AQAAAAAAAAA=", +// "N": "AwAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -187,6 +219,16 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a4fa9bdf45caf8c6b5be7a3752704423817b3ef2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null // } // }, // { @@ -196,13 +238,23 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, +// "Index": "0", +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } @@ -215,7 +267,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key1" +// "value": "key0" // } // }, // { @@ -225,11 +277,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value1" +// "value": "value0" // } // }, // { -// "N": "AwAAAAAAAAA=", +// "N": "AQAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -242,22 +294,6 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "a0af92becf7bef8d5d71c94e8f8f044e4cfe526d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } // } // }, // { @@ -267,38 +303,41 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "752161efcfe5a3e2ef70c03ff4354097f09ada56", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } // } // } // ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "6", +// "ModTime": "9", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f56fbd9c8db299689cc0cf806fe741b6a6e641e6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ // "Blank": {}, // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -395,19 +434,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "450aef9858564ed4ec1c418f1e8dac828079016b", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c59d05a21bf190551bb15a8b9d41a9e8da717f3d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index e91788ac8eb..e3f1363e205 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -25,67 +25,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value0" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ // "Fields": [ // { // "T": { @@ -140,13 +98,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -183,19 +160,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "6da365f0d6cacbcdf53cd5a4b125803cddce08c2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "091729e38bda8724bce4c314f9624b91af679459", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// } +// "TV": null // } // }, // { @@ -208,27 +179,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "f216afe7b5a17f4ebdbb98dceccedbc22e237596", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ff1a50d8489090af37a2c7766d659f0d717939b5", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -236,7 +220,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "4", +// "ModTime": "5", // "RefCount": "2" // }, // "Parent": null, @@ -333,19 +317,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "6c9948281d4c60b2d95233b76388d54d8b1a2fad", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // } // ] diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index cdd56a5ad89..a6d2205e240 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { // "T": { @@ -79,13 +79,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:14]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "143aebc820da33550f7338723fb1e2eec575b196", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:15" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:13]={ // "Fields": [ // { // "T": { @@ -122,19 +141,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // }, // { @@ -147,30 +160,43 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2e733a8e9e74fe14f0a5d10fb0f6728fa53d052d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", // "RefCount": "1" // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:12]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" +// } +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:11]={ // "Fields": [ // { // "T": { @@ -207,19 +233,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -232,27 +252,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ @@ -260,7 +293,7 @@ func main() { // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "IsEscaped": true, -// "ModTime": "6", +// "ModTime": "9", // "RefCount": "2" // }, // "Parent": null, @@ -357,21 +390,16 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ] // } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm_avl2.gno b/gnovm/tests/files/zrealm_avl2.gno index 8ecf6ba52bc..a89f0540f47 100644 --- a/gnovm/tests/files/zrealm_avl2.gno +++ b/gnovm/tests/files/zrealm_avl2.gno @@ -24,67 +24,25 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "key1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "value1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", -// "ModTime": "6", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "8", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ // "Fields": [ // { // "T": { @@ -139,13 +97,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", // "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "be751422ef4c2bc068a456f9467d2daca27db8ca", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } // } // } -// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ // "Fields": [ // { // "T": { @@ -182,19 +159,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "9fa04d8791e205a6de2eedce81bb4dbd0883cac7", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "23c8d928ce614d559719cb47e71a75a456b49a2a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// } +// "TV": null // } // }, // { @@ -207,27 +178,40 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a55a6a6b2027d6ec5e322aa32d4269b974fe1a4f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "fc92b122743503329a416d02fb4fe84cbca6dc57", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "ModTime": "0", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "03d901636a4e56d5bd32a75a7b923c7700c8859a", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } // } // } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ @@ -242,25 +226,19 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "2c8281b6c5a347a3d3aeedb74e61f081060ac050", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "e6d40c7e6f2c94668ab964b4c356d7cbd537a2b5", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", -// "ModTime": "5", +// "ModTime": "6", // "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "1" // } diff --git a/gnovm/tests/files/zrealm_const.gno b/gnovm/tests/files/zrealm_const_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_const.gno rename to gnovm/tests/files/zrealm_const_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm0.gno b/gnovm/tests/files/zrealm_crossrealm0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm0.gno rename to gnovm/tests/files/zrealm_crossrealm0_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm10.gno b/gnovm/tests/files/zrealm_crossrealm10_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm10.gno rename to gnovm/tests/files/zrealm_crossrealm10_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno similarity index 99% rename from gnovm/tests/files/zrealm_crossrealm11.gno rename to gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno index b250b07bbac..e6f33c50654 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11_stdlibs.gno @@ -2,13 +2,10 @@ package crossrealm_test import ( - "std" - "strings" - ptests "gno.land/p/demo/tests" "gno.land/p/demo/ufmt" rtests "gno.land/r/demo/tests" - testfoo "gno.land/r/demo/tests_foo" + "std" ) func getPrevRealm() std.Realm { diff --git a/gnovm/tests/files/zrealm_crossrealm12.gno b/gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno similarity index 86% rename from gnovm/tests/files/zrealm_crossrealm12.gno rename to gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno index ef8ea141ac5..f2f229cd5de 100644 --- a/gnovm/tests/files/zrealm_crossrealm12.gno +++ b/gnovm/tests/files/zrealm_crossrealm12_stdlibs.gno @@ -2,22 +2,22 @@ package crossrealm_test import ( - "std" "fmt" + "std" psubtests "gno.land/p/demo/tests/subtests" rsubtests "gno.land/r/demo/tests/subtests" ) func main() { - tests := []struct{ + tests := []struct { fn func() std.Realm }{ - { std.CurrentRealm }, - { psubtests.GetCurrentRealm }, - { rsubtests.GetCurrentRealm }, + {std.CurrentRealm}, + {psubtests.GetCurrentRealm}, + {rsubtests.GetCurrentRealm}, } - + for _, test := range tests { r := test.fn() diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm13.gno rename to gnovm/tests/files/zrealm_crossrealm13_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm13a.gno rename to gnovm/tests/files/zrealm_crossrealm13a_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm1.gno b/gnovm/tests/files/zrealm_crossrealm1_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm1.gno rename to gnovm/tests/files/zrealm_crossrealm1_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm2.gno b/gnovm/tests/files/zrealm_crossrealm2_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm2.gno rename to gnovm/tests/files/zrealm_crossrealm2_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm3.gno b/gnovm/tests/files/zrealm_crossrealm3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm3.gno rename to gnovm/tests/files/zrealm_crossrealm3_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm4.gno b/gnovm/tests/files/zrealm_crossrealm4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm4.gno rename to gnovm/tests/files/zrealm_crossrealm4_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm5.gno b/gnovm/tests/files/zrealm_crossrealm5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm5.gno rename to gnovm/tests/files/zrealm_crossrealm5_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm6.gno b/gnovm/tests/files/zrealm_crossrealm6_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm6.gno rename to gnovm/tests/files/zrealm_crossrealm6_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm7.gno b/gnovm/tests/files/zrealm_crossrealm7_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm7.gno rename to gnovm/tests/files/zrealm_crossrealm7_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm8.gno b/gnovm/tests/files/zrealm_crossrealm8_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm8.gno rename to gnovm/tests/files/zrealm_crossrealm8_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_crossrealm9.gno b/gnovm/tests/files/zrealm_crossrealm9_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_crossrealm9.gno rename to gnovm/tests/files/zrealm_crossrealm9_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_example.gno b/gnovm/tests/files/zrealm_example.gno index 1ce70c04390..45aeb7c5ddb 100644 --- a/gnovm/tests/files/zrealm_example.gno +++ b/gnovm/tests/files/zrealm_example.gno @@ -24,7 +24,7 @@ func main() { // Realm: // switchrealm["gno.land/r/example"] -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:8]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:11]={ // "Fields": [ // { // "T": { @@ -37,13 +37,13 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:11", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10", // "RefCount": "1" // } // } -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:7]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:10]={ // "Fields": [ // { // "T": { @@ -72,19 +72,38 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "c3dc30d2f2a57a0eeb4336dae59355aa7bee0ff5", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8" +// "Hash": "f190df54e397e2006cee3fc525bcc1b4d556e4c4", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:11" // } // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9", // "RefCount": "1" // } // } -// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:6]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:9]={ +// "ObjectInfo": { +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9", +// "ModTime": "0", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/dom.Post" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a74fad6da10f1cec74ad3a8751490b4dca957761", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:10" +// } +// } +// } +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:8]={ // "Fields": [ // { // "T": { @@ -106,19 +125,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "8b11b3d07ddeb034f70a114c9433ec6bd5cbf899", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:9" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/dom.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "f5d48c5a050326190d971fabb76835de31f83b20", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7" -// } -// } +// "TV": null // } // }, // { @@ -154,13 +167,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8", // "ModTime": "0", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", // "RefCount": "1" // } // } -// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:5]={ +// c[1ffd45e074aa1b8df562907c95ad97526b7ca187:7]={ +// "ObjectInfo": { +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7", +// "ModTime": "0", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "869abdac30a3ae78b2191806e1c894c48e399122", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:8" +// } +// } +// } +// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:6]={ // "Fields": [ // { // "T": { @@ -172,30 +204,24 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "a919087d0eba652876f9a8df18b30ec5ddc8c26e", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "e373f3e5c834170fe6e8b6cf5a95d185e80b0ad7", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", -// "ModTime": "5", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6", +// "ModTime": "6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", // "RefCount": "1" // } // } -// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:4]={ +// u[1ffd45e074aa1b8df562907c95ad97526b7ca187:5]={ // "Fields": [ // { // "T": { @@ -214,8 +240,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "05c2d0709574f676715a23d0161d2e151c0b21c7", -// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5" +// "Hash": "dfdeb7ed80c5b030c3a5e9701d00c66203de6f57", +// "ObjectID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:6" // } // }, // { @@ -227,9 +253,9 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", -// "ModTime": "5", -// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:2", +// "ID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:5", +// "ModTime": "6", +// "OwnerID": "1ffd45e074aa1b8df562907c95ad97526b7ca187:4", // "RefCount": "1" // } // } diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_initctx.gno rename to gnovm/tests/files/zrealm_initctx_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_natbind0.gno rename to gnovm/tests/files/zrealm_natbind0_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std0.gno rename to gnovm/tests/files/zrealm_std0_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1_stdlibs.gno similarity index 88% rename from gnovm/tests/files/zrealm_std1.gno rename to gnovm/tests/files/zrealm_std1_stdlibs.gno index 87f75bcb871..d75a2c60b71 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1_stdlibs.gno @@ -25,7 +25,7 @@ func main() { } // Output: -// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:4)] std.AddressList) +// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:5)] std.AddressList) // error: address already exists // has: true // has: false diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2_stdlibs.gno similarity index 90% rename from gnovm/tests/files/zrealm_std2.gno rename to gnovm/tests/files/zrealm_std2_stdlibs.gno index 1ae1fb4a881..810210c6160 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2_stdlibs.gno @@ -26,7 +26,7 @@ func main() { } // Output: -// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:4)] std.AddressList) +// (slice[ref(1ed29bd278d735e20e296bd4afe927501941392f:5)] std.AddressList) // error: address already exists // has: true // has: false diff --git a/gnovm/tests/files/zrealm_std3.gno b/gnovm/tests/files/zrealm_std3_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std3.gno rename to gnovm/tests/files/zrealm_std3_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std4.gno b/gnovm/tests/files/zrealm_std4_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std4.gno rename to gnovm/tests/files/zrealm_std4_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std5.gno b/gnovm/tests/files/zrealm_std5_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_std5.gno rename to gnovm/tests/files/zrealm_std5_stdlibs.gno diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6_stdlibs.gno similarity index 56% rename from gnovm/tests/files/zrealm_std6.gno rename to gnovm/tests/files/zrealm_std6_stdlibs.gno index fb9e0f825a2..17a79c1d43b 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6_stdlibs.gno @@ -1,5 +1,5 @@ -// PKGPATH: gno.land/r/test -package test +// PKGPATH: gno.land/r/std_test +package std_test import ( "std" @@ -14,4 +14,4 @@ func main() { } // Output: -// g148esskg7wzxxw6axwv4j5nkgjzdp6v4zmy3pwc +// g157y5v3k529jyzhjjz4fn49tzzhf4gess6v39xg diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0_stdlibs.gno similarity index 95% rename from gnovm/tests/files/zrealm_tests0.gno rename to gnovm/tests/files/zrealm_tests0_stdlibs.gno index d68c1f652f7..bfd4ad30a1e 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0_stdlibs.gno @@ -23,7 +23,7 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests"] -// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14]={ +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18]={ // "Fields": [ // { // "T": { @@ -37,13 +37,32 @@ func main() { // } // ], // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14", +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18", // "ModTime": "0", -// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17", // "RefCount": "1" // } // } -// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13]={ +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17]={ +// "ObjectInfo": { +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17", +// "ModTime": "0", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d3d6ffa52602f2bc976051d79294d219750aca64", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:18" +// } +// } +// } +// c[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16]={ // "Data": null, // "List": [ // { @@ -56,19 +75,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "4ea1e08156f3849b74a0f41f92cd4b48fb94926b", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:11" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "63ef2b51ca911a9b1727588bacb958ec3cb2a392", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10" -// } -// } +// "TV": null // } // }, // { @@ -81,19 +94,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ce86ea1156e75a44cd9d7ba2261819b100aa4ed1", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "54c554e1d1f61e19feb13bb229f43540338c0f8f", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12" -// } -// } +// "TV": null // } // }, // { @@ -106,24 +113,18 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b66192fbd8a8dde79b6f854b5cc3c4cc965cfd92", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests_foo.FooStringer" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "4e50e2cdaeb022a6fcbdb96d9fbd7f3af8df1379", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14" -// } -// } +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13", +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16", // "ModTime": "0", // "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", // "RefCount": "1" @@ -134,7 +135,7 @@ func main() { // "ObjectInfo": { // "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", // "IsEscaped": true, -// "ModTime": "12", +// "ModTime": "15", // "RefCount": "5" // }, // "Parent": null, @@ -1511,8 +1512,8 @@ func main() { // "@type": "/gno.SliceValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "5f12a61dd16d828be8584debc0e395e8d2136acb", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13" +// "Hash": "ad25f70f66c8c53042afd1377e5ff5ab744bf1a5", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16" // }, // "Length": "3", // "Maxcap": "3", @@ -1529,19 +1530,13 @@ func main() { // }, // "V": { // "@type": "/gno.PointerValue", -// "Base": null, +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "b662d2bfde61831128c908a3d1afff97e05e6227", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7" +// }, // "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Int" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "90b77781ec2b1e153ac020b1102354174bde972e", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7" -// } -// } +// "TV": null // } // }, // { @@ -1595,7 +1590,7 @@ func main() { // } // ] // } -// d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:11] +// d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13] // switchrealm["gno.land/r/demo/tests_foo"] // switchrealm["gno.land/r/demo/tests_foo"] // switchrealm["gno.land/r/demo/tests_foo"] diff --git a/gnovm/tests/files/zrealm_testutils0.gno b/gnovm/tests/files/zrealm_testutils0_stdlibs.gno similarity index 100% rename from gnovm/tests/files/zrealm_testutils0.gno rename to gnovm/tests/files/zrealm_testutils0_stdlibs.gno diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 6a3e6ab2bbb..3dbd292ea68 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -61,8 +61,8 @@ const ( ) // NOTE: this isn't safe, should only be used for testing. -func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { +func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (resStore gno.Store) { + getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -83,7 +83,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, Context: ctx, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) @@ -96,7 +96,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -375,7 +375,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -394,7 +394,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, Context: ctx, }) pn, pv = m2.RunMemPackage(memPkg, true) @@ -402,15 +402,15 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri } return nil, nil } - // NOTE: store is also used in closure above. db := memdb.NewMemDB() baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) - store = gno.NewStore(nil, baseStore, iavlStore) - store.SetPackageGetter(getPackage) - store.SetNativeStore(teststdlibs.NativeStore) - store.SetPackageInjector(testPackageInjector) - store.SetStrictGo2GnoMapping(false) + // make a new store + resStore = gno.NewStore(nil, baseStore, iavlStore) + resStore.SetPackageGetter(getPackage) + resStore.SetNativeStore(teststdlibs.NativeStore) + resStore.SetPackageInjector(testPackageInjector) + resStore.SetStrictGo2GnoMapping(false) return } diff --git a/gnovm/tests/integ/invalid_assign/main.gno b/gnovm/tests/integ/invalid_assign/main.gno new file mode 100644 index 00000000000..9f98ef31213 --- /dev/null +++ b/gnovm/tests/integ/invalid_assign/main.gno @@ -0,0 +1,16 @@ +// This should result in a compilation error for assigning a bool to a custom bool type without conversion + +package main + +func main() { + b := true + var v C = b + fmt.Println(v) +} + +type C bool + +const ( + F C = false + T C = true +) diff --git a/gnovm/tests/package_test.go b/gnovm/tests/package_test.go index ded5bedce5f..8e497941c7f 100644 --- a/gnovm/tests/package_test.go +++ b/gnovm/tests/package_test.go @@ -18,7 +18,7 @@ import ( func TestStdlibs(t *testing.T) { // NOTE: this test only works using _test.gno files; // filetests are not meant to be used for testing standard libraries. - // The examples directory is tested directly using `gno test`. + // The examples directory is tested directly using `gno test`u // find all packages with *_test.gno files. rootDirs := []string{ diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 0f33548054b..d2964a7958c 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -11,20 +11,31 @@ import ( testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "std", "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.AssertOriginCall( m, @@ -38,6 +49,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := testlibs_std.IsOriginCall( m, @@ -57,6 +69,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -76,6 +89,7 @@ var nativeFuncs = [...]nativeFunc{ "ClearStoreCache", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.ClearStoreCache( m, @@ -91,6 +105,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -118,6 +133,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -139,6 +155,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -161,6 +178,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p1"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -188,6 +206,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -220,6 +239,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -250,6 +270,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("string")}, {Name: gno.N("r1"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -282,6 +303,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := testlibs_testing.X_unixNano() diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index e96be939eb5..f583342ae04 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -9,7 +9,7 @@ func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. -// After calling TestSetRealm, calling CurrentRealm() will yield the value of +// After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of // rlm, while if a realm function is called, using PrevRealm() will yield rlm. func TestSetRealm(rlm Realm) { testSetRealm(string(rlm.addr), rlm.pkgPath) diff --git a/go.mod b/go.mod index 76c42f0419c..1bc7c90e2e1 100644 --- a/go.mod +++ b/go.mod @@ -29,22 +29,23 @@ require ( github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 go.etcd.io/bbolt v1.3.9 - go.opentelemetry.io/otel v1.25.0 + go.opentelemetry.io/otel v1.27.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 - go.opentelemetry.io/otel/metric v1.25.0 - go.opentelemetry.io/otel/sdk v1.25.0 - go.opentelemetry.io/otel/sdk/metric v1.25.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 + go.opentelemetry.io/otel/metric v1.27.0 + go.opentelemetry.io/otel/sdk v1.27.0 + go.opentelemetry.io/otel/sdk/metric v1.27.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.2.0 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.23.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/mod v0.16.0 - golang.org/x/net v0.23.0 - golang.org/x/sync v0.6.0 - golang.org/x/term v0.18.0 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.7.0 + golang.org/x/term v0.20.0 golang.org/x/tools v0.19.0 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -56,18 +57,18 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect ) diff --git a/go.sum b/go.sum index e4d728a106d..d563396d801 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -153,20 +153,22 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -179,8 +181,8 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -196,14 +198,14 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -220,21 +222,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -245,22 +248,20 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/docs-linter/errors.go b/misc/docs-linter/errors.go new file mode 100644 index 00000000000..8702f6f7de9 --- /dev/null +++ b/misc/docs-linter/errors.go @@ -0,0 +1,11 @@ +package main + +import "errors" + +var ( + errEmptyPath = errors.New("you need to pass in a path to scan") + err404Link = errors.New("link returned a 404") + errFound404Links = errors.New("found links resulting in a 404 response status") + errFoundUnescapedJSXTags = errors.New("found unescaped JSX tags") + errFoundLintItems = errors.New("found items that need linting") +) diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod new file mode 100644 index 00000000000..2c2840e7a6d --- /dev/null +++ b/misc/docs-linter/go.mod @@ -0,0 +1,19 @@ +module linter + +go 1.21.6 + +require ( + github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c + github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.7.0 + mvdan.cc/xurls/v2 v2.5.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/misc/docs-linter/go.sum b/misc/docs-linter/go.sum new file mode 100644 index 00000000000..ab8c3cf7c48 --- /dev/null +++ b/misc/docs-linter/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c h1:jtZ+oN8ZpBM0wYbcFH0B7NjFFzTFqZZmZellSSKtaCE= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c/go.mod h1:YcZbtNIfXVn4jS1pSG8SeG5RVHjyI7FPS3GypZaXxCI= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= diff --git a/misc/docs-linter/jsx.go b/misc/docs-linter/jsx.go new file mode 100644 index 00000000000..50b16d842c0 --- /dev/null +++ b/misc/docs-linter/jsx.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "regexp" + "strings" +) + +func extractJSX(fileContent []byte) []string { + text := string(fileContent) + + // Remove code blocks + reCodeBlocks := regexp.MustCompile("(?s)```.*?```") + contentNoCodeBlocks := reCodeBlocks.ReplaceAllString(text, "") + + // Remove inline code + reInlineCode := regexp.MustCompile("`[^`]*`") + contentNoInlineCode := reInlineCode.ReplaceAllString(contentNoCodeBlocks, "") + + // Extract JSX/HTML elements + reJSX := regexp.MustCompile("(?s)<[^>]+>") + + matches := reJSX.FindAllString(contentNoInlineCode, -1) + + filteredMatches := make([]string, 0) + // Ignore HTML comments and escaped JSX + for _, m := range matches { + if !strings.Contains(m, "!--") && !strings.Contains(m, "\\>") { + filteredMatches = append(filteredMatches, m) + } + } + + return filteredMatches +} + +func lintJSX(fileUrlMap map[string][]string, ctx context.Context) error { + found := false + for filePath, tags := range fileUrlMap { + filePath := filePath + for _, tag := range tags { + if !found { + fmt.Println("Tags that need checking:") + found = true + } + + fmt.Printf(">>> %s (found in file: %s)\n", tag, filePath) + } + } + + if found { + return errFoundUnescapedJSXTags + } + + return nil +} diff --git a/misc/docs-linter/links.go b/misc/docs-linter/links.go new file mode 100644 index 00000000000..41b90207aa5 --- /dev/null +++ b/misc/docs-linter/links.go @@ -0,0 +1,107 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "golang.org/x/sync/errgroup" + "io" + "mvdan.cc/xurls/v2" + "net/http" + "strings" + "sync" +) + +// extractUrls extracts URLs from a file and maps them to the file +func extractUrls(fileContent []byte) []string { + scanner := bufio.NewScanner(bytes.NewReader(fileContent)) + urls := make([]string, 0) + + // Scan file line by line + for scanner.Scan() { + line := scanner.Text() + + // Extract links + rxStrict := xurls.Strict() + url := rxStrict.FindString(line) + + // Check for empty links and skip them + if url == " " || len(url) == 0 { + continue + } + + // Look for http & https only + if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { + // Ignore localhost + if !strings.Contains(url, "localhost") && !strings.Contains(url, "127.0.0.1") { + urls = append(urls, url) + } + } + } + + return urls +} + +func lintLinks(fileUrlMap map[string][]string, ctx context.Context) error { + // Filter links by prefix & ignore localhost + // Setup parallel checking for links + g, _ := errgroup.WithContext(ctx) + + var ( + lock sync.Mutex + notFoundUrls []string + ) + + for filePath, urls := range fileUrlMap { + filePath := filePath + for _, url := range urls { + url := url + g.Go(func() error { + if err := checkUrl(url); err != nil { + lock.Lock() + notFoundUrls = append(notFoundUrls, fmt.Sprintf(">>> %s (found in file: %s)", url, filePath)) + lock.Unlock() + } + + return nil + }) + } + } + + if err := g.Wait(); err != nil { + return err + } + + // Print out the URLs that returned a 404 along with the file names + if len(notFoundUrls) > 0 { + fmt.Println("Links that need checking:") + for _, result := range notFoundUrls { + fmt.Println(result) + } + + return errFound404Links + } + + return nil +} + +// checkUrl checks if a URL is a 404 +func checkUrl(url string) error { + // Attempt to retrieve the HTTP header + resp, err := http.Get(url) + if err != nil || resp.StatusCode == http.StatusNotFound { + return err404Link + } + + // Ensure the response body is closed properly + cleanup := func(Body io.ReadCloser) error { + if err := Body.Close(); err != nil { + return fmt.Errorf("could not close response properly: %w", err) + } + + return nil + } + + return cleanup(resp.Body) +} diff --git a/misc/docs-linter/main.go b/misc/docs-linter/main.go new file mode 100644 index 00000000000..035170ca5ae --- /dev/null +++ b/misc/docs-linter/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/gnolang/gno/tm2/pkg/commands" + "golang.org/x/sync/errgroup" + "os" + "path/filepath" + "strings" +) + +type cfg struct { + docsPath string +} + +func main() { + cfg := &cfg{} + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "docs-linter", + ShortUsage: "docs-linter [flags]", + ShortHelp: `Lints the .md files in the given folder & subfolders. +Checks for 404 links, as well as improperly escaped JSX tags.`, + }, + cfg, + func(ctx context.Context, args []string) error { + return execLint(cfg, ctx) + }) + + cmd.Execute(context.Background(), os.Args[1:]) +} + +func (c *cfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.docsPath, + "path", + "./", + "path to dir to walk for .md files", + ) +} + +func execLint(cfg *cfg, ctx context.Context) error { + if cfg.docsPath == "" { + return errEmptyPath + } + + absPath, err := filepath.Abs(cfg.docsPath) + if err != nil { + return fmt.Errorf("error getting absolute path for docs folder: %w", err) + } + + fmt.Printf("Linting %s...\n", absPath) + + // Find docs files to lint + mdFiles, err := findFilePaths(cfg.docsPath) + if err != nil { + return fmt.Errorf("error finding .md files: %w", err) + } + + // Make storage maps for tokens to analyze + fileUrlMap := make(map[string][]string) // file path > [urls] + fileJSXMap := make(map[string][]string) // file path > [JSX items] + + // Extract tokens from files + for _, filePath := range mdFiles { + // Read file content once and pass it to linters + fileContents, err := os.ReadFile(filePath) + if err != nil { + return err + } + + fileJSXMap[filePath] = extractJSX(fileContents) + + // Execute URL extractor + fileUrlMap[filePath] = extractUrls(fileContents) + } + + // Run linters in parallel + g, _ := errgroup.WithContext(ctx) + + g.Go(func() error { + return lintJSX(fileJSXMap, ctx) + }) + + g.Go(func() error { + return lintLinks(fileUrlMap, ctx) + }) + + if err := g.Wait(); err != nil { + return errFoundLintItems + } + + fmt.Println("Lint complete, no issues found.") + return nil +} + +// findFilePaths gathers the file paths for specific file types +func findFilePaths(startPath string) ([]string, error) { + filePaths := make([]string, 0) + + walkFn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("error accessing file: %w", err) + } + + // Check if the file is a dir + if info.IsDir() { + return nil + } + + // Check if the file type matches + if !strings.HasSuffix(info.Name(), ".md") { + return nil + } + + // File is not a directory + filePaths = append(filePaths, path) + + return nil + } + + // Walk the directory root recursively + if walkErr := filepath.Walk(startPath, walkFn); walkErr != nil { + return nil, fmt.Errorf("unable to walk directory, %w", walkErr) + } + + return filePaths, nil +} diff --git a/misc/docs-linter/main_test.go b/misc/docs-linter/main_test.go new file mode 100644 index 00000000000..27393236215 --- /dev/null +++ b/misc/docs-linter/main_test.go @@ -0,0 +1,177 @@ +package main + +import ( + "context" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "sort" + "strconv" + "testing" + "time" +) + +func TestEmptyPathError(t *testing.T) { + t.Parallel() + + cfg := &cfg{ + docsPath: "", + } + + ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5) + defer cancelFn() + + assert.ErrorIs(t, execLint(cfg, ctx), errEmptyPath) +} + +func TestExtractLinks(t *testing.T) { + t.Parallel() + + // Create mock file content with random links + mockFileContent := `# Lorem Ipsum +Lorem ipsum dolor sit amet, +[consectetur](https://example.org) +adipiscing elit. Vivamus lacinia odio +vitae [vestibulum vestibulum](http://localhost:3000). +Cras [vel ex](http://192.168.1.1) et +turpis egestas luctus. Nullam +[eleifend](https://www.wikipedia.org) +nulla ac [blandit tempus](https://gitlab.org). +## Valid Links Here are some valid links: +- [Mozilla](https://mozilla.org) +- [Valid URL](https://valid-url.net) +- [Another Valid URL](https://another-valid-url.info) +- [Valid Link](https://valid-link.edu) +` + + // Expected URLs + expectedUrls := []string{ + "https://example.org", + "http://192.168.1.1", + "https://www.wikipedia.org", + "https://gitlab.org", + "https://mozilla.org", + "https://valid-url.net", + "https://another-valid-url.info", + "https://valid-link.edu", + } + + // Extract URLs from each file in the sourceDir + extractedUrls := extractUrls([]byte(mockFileContent)) + + if len(expectedUrls) != len(extractedUrls) { + t.Fatal("did not extract correct amount of URLs") + } + + sort.Strings(extractedUrls) + sort.Strings(expectedUrls) + + for i, u := range expectedUrls { + require.Equal(t, u, extractedUrls[i]) + } +} + +func TestExtractJSX(t *testing.T) { + t.Parallel() + + // Create mock file content with random JSX tags + mockFileContent := ` +#### Usage + +### getFunctionSignatures + +Fetches public facing function signatures + +#### Parameters + +Returns **Promise** + +# test text from gnodev.md + +#### Usage +### evaluateExpression + +Evaluates any expression in readonly mode and returns the results + +#### Parameters + +Returns **Promise** +` + + // Expected JSX tags + expectedTags := []string{ + "", + "", + "", + } + + // Extract JSX tags from the mock file content + extractedTags := extractJSX([]byte(mockFileContent)) + + if len(expectedTags) != len(extractedTags) { + t.Fatal("did not extract the correct amount of JSX tags") + } + + sort.Strings(extractedTags) + sort.Strings(expectedTags) + + for i, tag := range expectedTags { + require.Equal(t, tag, extractedTags[i]) + } +} + +func TestFindFilePaths(t *testing.T) { + t.Parallel() + + tempDir, err := os.MkdirTemp(".", "test") + require.NoError(t, err) + t.Cleanup(removeDir(t, tempDir)) + + numSourceFiles := 20 + testFiles := make([]string, numSourceFiles) + + for i := 0; i < numSourceFiles; i++ { + testFiles[i] = "sourceFile" + strconv.Itoa(i) + ".md" + } + + for _, file := range testFiles { + filePath := filepath.Join(tempDir, file) + err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm) + require.NoError(t, err) + + _, err = os.Create(filePath) + require.NoError(t, err) + } + + results, err := findFilePaths(tempDir) + require.NoError(t, err) + + expectedResults := make([]string, 0, len(testFiles)) + + for _, testFile := range testFiles { + expectedResults = append(expectedResults, filepath.Join(tempDir, testFile)) + } + + sort.Slice(results, func(i, j int) bool { + return results[i] < results[j] + }) + + sort.Slice(expectedResults, func(i, j int) bool { + return expectedResults[i] < expectedResults[j] + }) + + require.Equal(t, len(results), len(expectedResults)) + + for i, result := range results { + if result != expectedResults[i] { + require.Equal(t, result, expectedResults[i]) + } + } +} + +func removeDir(t *testing.T, dirPath string) func() { + return func() { + require.NoError(t, os.RemoveAll(dirPath)) + } +} diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index f2cad0a851b..bfbe252a2d5 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -12,15 +12,25 @@ import ( {{- end }} ) -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ {{- range $i, $m := .Mappings }} { {{ printf "%q" $m.GnoImportPath }}, @@ -36,6 +46,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, {{- end }} }, + {{ if $m.MachineParam }}true{{ else }}false{{ end }}, func(m *gno.Machine) { {{ if $m.Params -}} b := m.LastBlock() diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index 21e9c2fe646..f9e9a0cd899 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -48,12 +48,12 @@ type Config struct { BaseConfig `toml:",squash"` // Options for services - RPC *rpc.RPCConfig `toml:"rpc" comment:"##### rpc server configuration options #####"` - P2P *p2p.P2PConfig `toml:"p2p" comment:"##### peer to peer configuration options #####"` - Mempool *mem.MempoolConfig `toml:"mempool" comment:"##### mempool configuration options #####"` - Consensus *cns.ConsensusConfig `toml:"consensus" comment:"##### consensus configuration options #####"` - TxEventStore *eventstore.Config `toml:"tx_event_store" comment:"##### event store #####"` - Telemetry *telemetry.Config `toml:"telemetry" comment:"##### node telemetry #####"` + RPC *rpc.RPCConfig `json:"rpc" toml:"rpc" comment:"##### rpc server configuration options #####"` + P2P *p2p.P2PConfig `json:"p2p" toml:"p2p" comment:"##### peer to peer configuration options #####"` + Mempool *mem.MempoolConfig `json:"mempool" toml:"mempool" comment:"##### mempool configuration options #####"` + Consensus *cns.ConsensusConfig `json:"consensus" toml:"consensus" comment:"##### consensus configuration options #####"` + TxEventStore *eventstore.Config `json:"tx_event_store" toml:"tx_event_store" comment:"##### event store #####"` + Telemetry *telemetry.Config `json:"telemetry" toml:"telemetry" comment:"##### node telemetry #####"` } // DefaultConfig returns a default configuration for a Tendermint node diff --git a/tm2/pkg/bft/consensus/config/config.go b/tm2/pkg/bft/consensus/config/config.go index 4a350ff3976..c0b5661a0e5 100644 --- a/tm2/pkg/bft/consensus/config/config.go +++ b/tm2/pkg/bft/consensus/config/config.go @@ -16,29 +16,29 @@ const ( // ConsensusConfig defines the configuration for the Tendermint consensus service, // including timeouts and details about the WAL and the block structure. type ConsensusConfig struct { - RootDir string `toml:"home"` - WALPath string `toml:"wal_file"` - WALDisabled bool `toml:"-"` + RootDir string `json:"home" toml:"home"` + WALPath string `json:"wal_file" toml:"wal_file"` + WALDisabled bool `json:"-" toml:"-"` walFile string // overrides WalPath if set - TimeoutPropose time.Duration `toml:"timeout_propose"` - TimeoutProposeDelta time.Duration `toml:"timeout_propose_delta"` - TimeoutPrevote time.Duration `toml:"timeout_prevote"` - TimeoutPrevoteDelta time.Duration `toml:"timeout_prevote_delta"` - TimeoutPrecommit time.Duration `toml:"timeout_precommit"` - TimeoutPrecommitDelta time.Duration `toml:"timeout_precommit_delta"` - TimeoutCommit time.Duration `toml:"timeout_commit"` + TimeoutPropose time.Duration `json:"timeout_propose" toml:"timeout_propose"` + TimeoutProposeDelta time.Duration `json:"timeout_propose_delta" toml:"timeout_propose_delta"` + TimeoutPrevote time.Duration `json:"timeout_prevote" toml:"timeout_prevote"` + TimeoutPrevoteDelta time.Duration `json:"timeout_prevote_delta" toml:"timeout_prevote_delta"` + TimeoutPrecommit time.Duration `json:"timeout_precommit" toml:"timeout_precommit"` + TimeoutPrecommitDelta time.Duration `json:"timeout_precommit_delta" toml:"timeout_precommit_delta"` + TimeoutCommit time.Duration `json:"timeout_commit" toml:"timeout_commit"` // Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) - SkipTimeoutCommit bool `toml:"skip_timeout_commit" comment:"Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)"` + SkipTimeoutCommit bool `json:"skip_timeout_commit" toml:"skip_timeout_commit" comment:"Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)"` // EmptyBlocks mode and possible interval between empty blocks - CreateEmptyBlocks bool `toml:"create_empty_blocks" comment:"EmptyBlocks mode and possible interval between empty blocks"` - CreateEmptyBlocksInterval time.Duration `toml:"create_empty_blocks_interval"` + CreateEmptyBlocks bool `json:"create_empty_blocks" toml:"create_empty_blocks" comment:"EmptyBlocks mode and possible interval between empty blocks"` + CreateEmptyBlocksInterval time.Duration `json:"create_empty_blocks_interval" toml:"create_empty_blocks_interval"` // Reactor sleep duration parameters - PeerGossipSleepDuration time.Duration `toml:"peer_gossip_sleep_duration" comment:"Reactor sleep duration parameters"` - PeerQueryMaj23SleepDuration time.Duration `toml:"peer_query_maj23_sleep_duration"` + PeerGossipSleepDuration time.Duration `json:"peer_gossip_sleep_duration" toml:"peer_gossip_sleep_duration" comment:"Reactor sleep duration parameters"` + PeerQueryMaj23SleepDuration time.Duration `json:"peer_query_maj_23_sleep_duration" toml:"peer_query_maj23_sleep_duration"` } // DefaultConsensusConfig returns a default configuration for the consensus service diff --git a/tm2/pkg/bft/mempool/config/config.go b/tm2/pkg/bft/mempool/config/config.go index 198b34291bc..47df01238e5 100644 --- a/tm2/pkg/bft/mempool/config/config.go +++ b/tm2/pkg/bft/mempool/config/config.go @@ -7,13 +7,13 @@ import "github.com/gnolang/gno/tm2/pkg/errors" // MempoolConfig defines the configuration options for the Tendermint mempool type MempoolConfig struct { - RootDir string `toml:"home"` - Recheck bool `toml:"recheck"` - Broadcast bool `toml:"broadcast"` - WalPath string `toml:"wal_dir"` - Size int `toml:"size" comment:"Maximum number of transactions in the mempool"` - MaxPendingTxsBytes int64 `toml:"max_pending_txs_bytes" comment:"Limit the total size of all txs in the mempool.\n This only accounts for raw transactions (e.g. given 1MB transactions and\n max_txs_bytes=5MB, mempool will only accept 5 transactions)."` - CacheSize int `toml:"cache_size" comment:"Size of the cache (used to filter transactions we saw earlier) in transactions"` + RootDir string `json:"home" toml:"home"` + Recheck bool `json:"recheck" toml:"recheck"` + Broadcast bool `json:"broadcast" toml:"broadcast"` + WalPath string `json:"wal_dir" toml:"wal_dir"` + Size int `json:"size" toml:"size" comment:"Maximum number of transactions in the mempool"` + MaxPendingTxsBytes int64 `json:"max_pending_txs_bytes" toml:"max_pending_txs_bytes" comment:"Limit the total size of all txs in the mempool.\n This only accounts for raw transactions (e.g. given 1MB transactions and\n max_txs_bytes=5MB, mempool will only accept 5 transactions)."` + CacheSize int `json:"cache_size" toml:"cache_size" comment:"Size of the cache (used to filter transactions we saw earlier) in transactions"` } // DefaultMempoolConfig returns a default configuration for the Tendermint mempool diff --git a/tm2/pkg/bft/privval/file.go b/tm2/pkg/bft/privval/file.go index b1bac8416f7..7ed586b7c05 100644 --- a/tm2/pkg/bft/privval/file.go +++ b/tm2/pkg/bft/privval/file.go @@ -38,10 +38,11 @@ func voteToStep(vote *types.Vote) int8 { // ------------------------------------------------------------------------------- // FilePVKey stores the immutable part of PrivValidator. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type FilePVKey struct { - Address types.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - PrivKey crypto.PrivKey `json:"priv_key"` + Address types.Address `json:"address" comment:"the validator address"` + PubKey crypto.PubKey `json:"pub_key" comment:"the validator public key"` + PrivKey crypto.PrivKey `json:"priv_key" comment:"the validator private key"` filePath string } @@ -66,12 +67,13 @@ func (pvKey FilePVKey) Save() { // ------------------------------------------------------------------------------- // FilePVLastSignState stores the mutable part of PrivValidator. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type FilePVLastSignState struct { - Height int64 `json:"height"` - Round int `json:"round"` - Step int8 `json:"step"` - Signature []byte `json:"signature,omitempty"` - SignBytes []byte `json:"signbytes,omitempty"` + Height int64 `json:"height" comment:"the height of the last sign"` + Round int `json:"round" comment:"the round of the last sign"` + Step int8 `json:"step" comment:"the step of the last sign"` + Signature []byte `json:"signature,omitempty" comment:"the signature of the last sign"` + SignBytes []byte `json:"signbytes,omitempty" comment:"the raw signature bytes of the last sign"` filePath string } diff --git a/tm2/pkg/bft/rpc/config/config.go b/tm2/pkg/bft/rpc/config/config.go index de576ddb402..fe527450178 100644 --- a/tm2/pkg/bft/rpc/config/config.go +++ b/tm2/pkg/bft/rpc/config/config.go @@ -16,36 +16,36 @@ const ( // RPCConfig defines the configuration options for the Tendermint RPC server type RPCConfig struct { - RootDir string `toml:"home"` + RootDir string `json:"home" toml:"home"` // TCP or UNIX socket address for the RPC server to listen on - ListenAddress string `toml:"laddr" comment:"TCP or UNIX socket address for the RPC server to listen on"` + ListenAddress string `json:"laddr" toml:"laddr" comment:"TCP or UNIX socket address for the RPC server to listen on"` // A list of origins a cross-domain request can be executed from. // If the special '*' value is present in the list, all origins will be allowed. // An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). // Only one wildcard can be used per origin. - CORSAllowedOrigins []string `toml:"cors_allowed_origins" comment:"A list of origins a cross-domain request can be executed from\n Default value '[]' disables cors support\n Use '[\"*\"]' to allow any origin"` + CORSAllowedOrigins []string `json:"cors_allowed_origins" toml:"cors_allowed_origins" comment:"A list of origins a cross-domain request can be executed from\n Default value '[]' disables cors support\n Use '[\"*\"]' to allow any origin"` // A list of methods the client is allowed to use with cross-domain requests. - CORSAllowedMethods []string `toml:"cors_allowed_methods" comment:"A list of methods the client is allowed to use with cross-domain requests"` + CORSAllowedMethods []string `json:"cors_allowed_methods" toml:"cors_allowed_methods" comment:"A list of methods the client is allowed to use with cross-domain requests"` // A list of non simple headers the client is allowed to use with cross-domain requests. - CORSAllowedHeaders []string `toml:"cors_allowed_headers" comment:"A list of non simple headers the client is allowed to use with cross-domain requests"` + CORSAllowedHeaders []string `json:"cors_allowed_headers" toml:"cors_allowed_headers" comment:"A list of non simple headers the client is allowed to use with cross-domain requests"` // TCP or UNIX socket address for the gRPC server to listen on // NOTE: This server only supports /broadcast_tx_commit - GRPCListenAddress string `toml:"grpc_laddr" comment:"TCP or UNIX socket address for the gRPC server to listen on\n NOTE: This server only supports /broadcast_tx_commit"` + GRPCListenAddress string `json:"grpc_laddr" toml:"grpc_laddr" comment:"TCP or UNIX socket address for the gRPC server to listen on\n NOTE: This server only supports /broadcast_tx_commit"` // Maximum number of simultaneous connections. // Does not include RPC (HTTP&WebSocket) connections. See max_open_connections // If you want to accept a larger number than the default, make sure // you increase your OS limits. // 0 - unlimited. - GRPCMaxOpenConnections int `toml:"grpc_max_open_connections" comment:"Maximum number of simultaneous connections.\n Does not include RPC (HTTP&WebSocket) connections. See max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` + GRPCMaxOpenConnections int `json:"grpc_max_open_connections" toml:"grpc_max_open_connections" comment:"Maximum number of simultaneous connections.\n Does not include RPC (HTTP&WebSocket) connections. See max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` // Activate unsafe RPC commands like /dial_persistent_peers and /unsafe_flush_mempool - Unsafe bool `toml:"unsafe" comment:"Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool"` + Unsafe bool `json:"unsafe" toml:"unsafe" comment:"Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool"` // Maximum number of simultaneous connections (including WebSocket). // Does not include gRPC connections. See grpc_max_open_connections @@ -54,19 +54,19 @@ type RPCConfig struct { // 0 - unlimited. // Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} // 1024 - 40 - 10 - 50 = 924 = ~900 - MaxOpenConnections int `toml:"max_open_connections" comment:"Maximum number of simultaneous connections (including WebSocket).\n Does not include gRPC connections. See grpc_max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` + MaxOpenConnections int `json:"max_open_connections" toml:"max_open_connections" comment:"Maximum number of simultaneous connections (including WebSocket).\n Does not include gRPC connections. See grpc_max_open_connections\n If you want to accept a larger number than the default, make sure\n you increase your OS limits.\n 0 - unlimited.\n Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}\n 1024 - 40 - 10 - 50 = 924 = ~900"` // How long to wait for a tx to be committed during /broadcast_tx_commit // WARNING: Using a value larger than 10s will result in increasing the // global HTTP write timeout, which applies to all connections and endpoints. // See https://github.com/gnolang/gno/tm2/pkg/bft/issues/3435 - TimeoutBroadcastTxCommit time.Duration `toml:"timeout_broadcast_tx_commit" comment:"How long to wait for a tx to be committed during /broadcast_tx_commit.\n WARNING: Using a value larger than 10s will result in increasing the\n global HTTP write timeout, which applies to all connections and endpoints.\n See https://github.com/tendermint/classic/issues/3435"` + TimeoutBroadcastTxCommit time.Duration `json:"timeout_broadcast_tx_commit" toml:"timeout_broadcast_tx_commit" comment:"How long to wait for a tx to be committed during /broadcast_tx_commit.\n WARNING: Using a value larger than 10s will result in increasing the\n global HTTP write timeout, which applies to all connections and endpoints.\n See https://github.com/tendermint/classic/issues/3435"` // Maximum size of request body, in bytes - MaxBodyBytes int64 `toml:"max_body_bytes" comment:"Maximum size of request body, in bytes"` + MaxBodyBytes int64 `json:"max_body_bytes" toml:"max_body_bytes" comment:"Maximum size of request body, in bytes"` // Maximum size of request header, in bytes - MaxHeaderBytes int `toml:"max_header_bytes" comment:"Maximum size of request header, in bytes"` + MaxHeaderBytes int `json:"max_header_bytes" toml:"max_header_bytes" comment:"Maximum size of request header, in bytes"` // The path to a file containing certificate that is used to create the HTTPS server. // Might be either absolute path or path related to tendermint's config directory. @@ -76,13 +76,13 @@ type RPCConfig struct { // and the CA's certificate. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. - TLSCertFile string `toml:"tls_cert_file" comment:"The path to a file containing certificate that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n If the certificate is signed by a certificate authority,\n the certFile should be the concatenation of the server's certificate, any intermediates,\n and the CA's certificate.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` + TLSCertFile string `json:"tls_cert_file" toml:"tls_cert_file" comment:"The path to a file containing certificate that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n If the certificate is signed by a certificate authority,\n the certFile should be the concatenation of the server's certificate, any intermediates,\n and the CA's certificate.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` // The path to a file containing matching private key that is used to create the HTTPS server. // Might be either absolute path or path related to tendermint's config directory. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. - TLSKeyFile string `toml:"tls_key_file" comment:"The path to a file containing matching private key that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` + TLSKeyFile string `json:"tls_key_file" toml:"tls_key_file" comment:"The path to a file containing matching private key that is used to create the HTTPS server.\n Might be either absolute path or path related to tendermint's config directory.\n NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run."` } // DefaultRPCConfig returns a default configuration for the RPC server diff --git a/tm2/pkg/bft/rpc/core/status.go b/tm2/pkg/bft/rpc/core/status.go index 9bc5f3ea6fc..950bac7ddf7 100644 --- a/tm2/pkg/bft/rpc/core/status.go +++ b/tm2/pkg/bft/rpc/core/status.go @@ -125,7 +125,7 @@ func validatorAtHeight(h int64) *types.Validator { lastBlockHeight, vals := consensusState.GetValidators() if lastBlockHeight == h { for _, val := range vals { - if val.Address != privValAddress { + if val.Address == privValAddress { return val } } diff --git a/tm2/pkg/bft/rpc/lib/client/http/client.go b/tm2/pkg/bft/rpc/lib/client/http/client.go index 34d301deba2..290310cea31 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client.go @@ -17,8 +17,6 @@ import ( const ( protoHTTP = "http" protoHTTPS = "https" - protoWSS = "wss" - protoWS = "ws" protoTCP = "tcp" ) @@ -173,12 +171,7 @@ func defaultHTTPClient(remoteAddr string) *http.Client { } func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { - protocol, address, err := parseRemoteAddr(remoteAddr) - if err != nil { - return func(_ string, _ string) (net.Conn, error) { - return nil, err - } - } + protocol, address := parseRemoteAddr(remoteAddr) // net.Dial doesn't understand http/https, so change it to TCP switch protocol { @@ -193,17 +186,14 @@ func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { // protocol - client's protocol (for example, "http", "https", "wss", "ws", "tcp") // trimmedS - rest of the address (for example, "192.0.2.1:25", "[2001:db8::1]:80") with "/" replaced with "." -func toClientAddrAndParse(remoteAddr string) (string, string, error) { - protocol, address, err := parseRemoteAddr(remoteAddr) - if err != nil { - return "", "", err - } +func toClientAddrAndParse(remoteAddr string) (string, string) { + protocol, address := parseRemoteAddr(remoteAddr) // protocol to use for http operations, to support both http and https var clientProtocol string // default to http for unknown protocols (ex. tcp) switch protocol { - case protoHTTP, protoHTTPS, protoWS, protoWSS: + case protoHTTP, protoHTTPS: clientProtocol = protocol default: clientProtocol = protoHTTP @@ -212,14 +202,11 @@ func toClientAddrAndParse(remoteAddr string) (string, string, error) { // replace / with . for http requests (kvstore domain) trimmedAddress := strings.Replace(address, "/", ".", -1) - return clientProtocol, trimmedAddress, nil + return clientProtocol, trimmedAddress } func toClientAddress(remoteAddr string) (string, error) { - clientProtocol, trimmedAddress, err := toClientAddrAndParse(remoteAddr) - if err != nil { - return "", err - } + clientProtocol, trimmedAddress := toClientAddrAndParse(remoteAddr) return clientProtocol + "://" + trimmedAddress, nil } @@ -227,8 +214,9 @@ func toClientAddress(remoteAddr string) (string, error) { // network - name of the network (for example, "tcp", "unix") // s - rest of the address (for example, "192.0.2.1:25", "[2001:db8::1]:80") // TODO: Deprecate support for IP:PORT or /path/to/socket -func parseRemoteAddr(remoteAddr string) (network string, s string, err error) { +func parseRemoteAddr(remoteAddr string) (string, string) { parts := strings.SplitN(remoteAddr, "://", 2) + var protocol, address string switch len(parts) { case 1: @@ -237,7 +225,19 @@ func parseRemoteAddr(remoteAddr string) (network string, s string, err error) { case 2: protocol, address = parts[0], parts[1] } - return protocol, address, nil + + // Append default ports if not specified + if !strings.Contains(address, ":") { + switch protocol { + case protoHTTPS: + address += ":443" + case protoHTTP, protoTCP: + address += ":80" + default: // noop + } + } + + return protocol, address } // isOKStatus returns a boolean indicating if the response diff --git a/tm2/pkg/bft/rpc/lib/client/http/client_test.go b/tm2/pkg/bft/rpc/lib/client/http/client_test.go index 7c4b1e52ac5..cfe69c3015c 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client_test.go @@ -17,24 +17,39 @@ func TestClient_parseRemoteAddr(t *testing.T) { t.Parallel() testTable := []struct { - remoteAddr string - network string - rest string + remoteAddr string + expectedNetwork string + expectedRest string }{ { "127.0.0.1", "tcp", - "127.0.0.1", + "127.0.0.1:80", + }, + { + "127.0.0.1:5000", + "tcp", + "127.0.0.1:5000", + }, + { + "http://example.com", + "http", + "example.com:80", }, { "https://example.com", "https", - "example.com", + "example.com:443", }, { - "wss://[::1]", - "wss", - "[::1]", + "http://example.com:5000", + "http", + "example.com:5000", + }, + { + "https://example.com:5000", + "https", + "example.com:5000", }, } @@ -44,11 +59,10 @@ func TestClient_parseRemoteAddr(t *testing.T) { t.Run(testCase.remoteAddr, func(t *testing.T) { t.Parallel() - n, r, err := parseRemoteAddr(testCase.remoteAddr) - require.NoError(t, err) + n, r := parseRemoteAddr(testCase.remoteAddr) - assert.Equal(t, n, testCase.network) - assert.Equal(t, r, testCase.rest) + assert.Equal(t, testCase.expectedNetwork, n) + assert.Equal(t, testCase.expectedRest, r) }) } } @@ -66,7 +80,6 @@ func TestClient_makeHTTPDialer(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "dial tcp:", "should convert https to tcp") - assert.Contains(t, err.Error(), "address .:", "should have parsed the address (as incorrect)") }) t.Run("udp", func(t *testing.T) { @@ -76,7 +89,6 @@ func TestClient_makeHTTPDialer(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "dial udp:", "udp protocol should remain the same") - assert.Contains(t, err.Error(), "address .:", "should have parsed the address (as incorrect)") }) } diff --git a/tm2/pkg/bft/state/eventstore/types/config.go b/tm2/pkg/bft/state/eventstore/types/config.go index 5b152f254fd..a74d6c9d2ec 100644 --- a/tm2/pkg/bft/state/eventstore/types/config.go +++ b/tm2/pkg/bft/state/eventstore/types/config.go @@ -7,8 +7,8 @@ type EventStoreParams map[string]any // Config defines the specific event store configuration type Config struct { - EventStoreType string `toml:"event_store_type" comment:"Type of event store"` - Params EventStoreParams `toml:"event_store_params" comment:"Event store parameters"` + EventStoreType string `json:"event_store_type" toml:"event_store_type" comment:"Type of event store"` + Params EventStoreParams `json:"event_store_params" toml:"event_store_params" comment:"Event store parameters"` } // GetParam fetches the specific config param, if any. diff --git a/tm2/pkg/colors/colors.go b/tm2/pkg/colors/colors.go index d2af56c892a..84a0353d880 100644 --- a/tm2/pkg/colors/colors.go +++ b/tm2/pkg/colors/colors.go @@ -96,14 +96,80 @@ func Gray(args ...interface{}) string { return treatAll(ANSIFgGray, args...) } -func ColoredBytes(data []byte, textColor, bytesColor func(...interface{}) string) string { +// result may be 4 ASNSII chars longer than they should be to denote the +// elipses (...), and one for a trailing hex nibble in case the last byte is +// non-ascii. +// NOTE: it is annoying to try make this perfect and always fit within n, so we +// don't do this yet, but left as an exercise. :) +func ColoredBytesN(data []byte, n int, textColor, bytesColor func(...interface{}) string) string { + _n := 0 s := "" - for _, b := range data { + buf := "" // buffer + bufIsText := true // is buf text or hex + for i, b := range data { + RESTART: if 0x21 <= b && b < 0x7F { - s += textColor(string(b)) + if !bufIsText { + s += bytesColor(buf) + buf = "" + bufIsText = true + goto RESTART + } + buf += string(b) + _n += 1 + if n != 0 && _n >= n { + if i == len(data)-1 { + // done + s += textColor(buf) + buf = "" + } else { + s += textColor(buf) + "..." + buf = "" + } + break + } + } else { + if bufIsText { + s += textColor(buf) + buf = "" + bufIsText = false + goto RESTART + } + buf += fmt.Sprintf("%02X", b) + _n += 2 + if n != 0 && _n >= n { + if i == len(data)-1 { + // done + s += bytesColor(buf) + buf = "" + } else { + s += bytesColor(buf) + "..." + buf = "" + } + break + } + } + } + if buf != "" { + if bufIsText { + s += textColor(buf) + buf = "" } else { - s += bytesColor(fmt.Sprintf("%02X", b)) + s += bytesColor(buf) + buf = "" } } return s } + +func DefaultColoredBytesN(data []byte, n int) string { + return ColoredBytesN(data, n, Blue, Green) +} + +func ColoredBytes(data []byte, textColor, bytesColor func(...interface{}) string) string { + return ColoredBytesN(data, 0, textColor, bytesColor) +} + +func DefaultColoredBytes(data []byte) string { + return ColoredBytes(data, Blue, Green) +} diff --git a/tm2/pkg/db/goleveldb/go_level_db.go b/tm2/pkg/db/goleveldb/go_level_db.go index 27db61bb680..4f011cb8395 100644 --- a/tm2/pkg/db/goleveldb/go_level_db.go +++ b/tm2/pkg/db/goleveldb/go_level_db.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" + "github.com/gnolang/gno/tm2/pkg/colors" "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/internal" "github.com/syndtr/goleveldb/leveldb" @@ -115,9 +116,9 @@ func (db *GoLevelDB) Print() { itr := db.db.NewIterator(nil, nil) for itr.Next() { - key := itr.Key() - value := itr.Value() - fmt.Printf("[%X]:\t[%X]\n", key, value) + key := colors.DefaultColoredBytesN(itr.Key(), 50) + value := colors.DefaultColoredBytesN(itr.Value(), 100) + fmt.Printf("%v: %v\n", key, value) } } diff --git a/tm2/pkg/db/memdb/mem_db.go b/tm2/pkg/db/memdb/mem_db.go index 6d1d6a35af9..09b90b6be44 100644 --- a/tm2/pkg/db/memdb/mem_db.go +++ b/tm2/pkg/db/memdb/mem_db.go @@ -5,9 +5,9 @@ import ( "sort" "sync" + "github.com/gnolang/gno/tm2/pkg/colors" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/internal" - "github.com/gnolang/gno/tm2/pkg/strings" ) func init() { @@ -128,17 +128,9 @@ func (db *MemDB) Print() { for key, value := range db.db { var keystr, valstr string - if strings.IsASCIIText(key) { - keystr = key - } else { - keystr = fmt.Sprintf("0x%X", []byte(key)) - } - if strings.IsASCIIText(string(value)) { - valstr = string(value) - } else { - valstr = fmt.Sprintf("0x%X", value) - } - fmt.Printf("%s:\t%s\n", keystr, valstr) + keystr = colors.DefaultColoredBytesN([]byte(key), 50) + valstr = colors.DefaultColoredBytesN(value, 100) + fmt.Printf("%s: %s\n", keystr, valstr) } } diff --git a/tm2/pkg/db/prefix_db.go b/tm2/pkg/db/prefix_db.go index 29ed53639e8..a38850c2173 100644 --- a/tm2/pkg/db/prefix_db.go +++ b/tm2/pkg/db/prefix_db.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "sync" + + "github.com/gnolang/gno/tm2/pkg/colors" ) // IteratePrefix is a convenience function for iterating over a key domain @@ -188,15 +190,10 @@ func (pdb *PrefixDB) Close() { // Implements DB. func (pdb *PrefixDB) Print() { - fmt.Printf("prefix: %X\n", pdb.prefix) - - itr := pdb.Iterator(nil, nil) - defer itr.Close() - for ; itr.Valid(); itr.Next() { - key := itr.Key() - value := itr.Value() - fmt.Printf("[%X]:\t[%X]\n", key, value) - } + fmt.Println(colors.Blue("prefix ---------------------")) + fmt.Printf("prefix: %v\n", colors.DefaultColoredBytes(pdb.prefix)) + pdb.db.Print() + fmt.Println(colors.Blue("prefix --------------------- end")) } // Implements DB. diff --git a/tm2/pkg/errors/errors.go b/tm2/pkg/errors/errors.go index c07356f0b06..c72d9c64680 100644 --- a/tm2/pkg/errors/errors.go +++ b/tm2/pkg/errors/errors.go @@ -237,6 +237,9 @@ type FmtError struct { } func (fe FmtError) Error() string { + if len(fe.args) == 0 { + return fe.format + } return fmt.Sprintf(fe.format, fe.args...) } diff --git a/tm2/pkg/libtm/.gitignore b/tm2/pkg/libtm/.gitignore new file mode 100644 index 00000000000..15cf730b967 --- /dev/null +++ b/tm2/pkg/libtm/.gitignore @@ -0,0 +1,9 @@ +# MacOS Leftovers +.DS_Store + +# Editor Leftovers +.vscode +.idea + +# Log files +*.log diff --git a/tm2/pkg/libtm/LICENSE b/tm2/pkg/libtm/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/tm2/pkg/libtm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tm2/pkg/libtm/Makefile b/tm2/pkg/libtm/Makefile new file mode 100644 index 00000000000..0b6622582f6 --- /dev/null +++ b/tm2/pkg/libtm/Makefile @@ -0,0 +1,19 @@ +.PHONY: lint +lint: + golangci-lint run --config golangci.yaml + +.PHONY: gofumpt +gofumpt: + go install mvdan.cc/gofumpt@latest + gofumpt -l -w . + +.PHONY: fixalign +fixalign: + go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + fieldalignment -fix $(filter-out $@,$(MAKECMDGOALS)) # the full package name (not path!) + +.PHONY: protoc +protoc: + # Make sure the following prerequisites are installed before running these commands: + # https://grpc.io/docs/languages/go/quickstart/#prerequisites + protoc --go_out=./ ./messages/types/proto/*.proto diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md new file mode 100644 index 00000000000..d55a4cad305 --- /dev/null +++ b/tm2/pkg/libtm/README.md @@ -0,0 +1,160 @@ +## Overview + +`libtm` is a simple, minimal and compact Go library that implements the Tendermint consensus engine. + +The implementation is based on Algorithm 1, of +the [Tendermint consensus whitepaper](https://arxiv.org/pdf/1807.04938.pdf) and +(more broadly) the [official Tendermint wiki](https://github.com/tendermint/tendermint/wiki). + +There are some implementation design decisions taken by the package authors: + +- it doesn't manage validator sets internally +- it doesn't implement a networking layer, or any kind of broadcast communication +- it doesn't assume, or implement, any kind of signature manipulation logic + +All of these responsibilities are left to the calling context, in the form of interface implementations. +The reason for these choices is simple - _to keep the library minimal_. + +> [!NOTE] +> We aim to make [libtm](https://github.com/gnolang/libtm) an independent project, both in terms of repository and +> governance. This will be pursued once we have successfully integrated `libtm` with `tm2` and demonstrated that the +> codebase and its API are stable and reliable for broader use. Until this integration is complete and stability is +> confirmed, `libtm` will continue to be improved upon within the current structure, in the gno monorepo. + +### What this library is + +This library is meant to be used as a consensus engine base for any distributed system that needs such functionality. +It is not exclusively made for the blockchain context -- you will find no mention or assumptions of blockchains in the +source code. + +### What this library is not + +This library is _not_ meant to replace your entire consensus setup. + +As mentioned before, certain design decisions have been taken to keep the source code minimal, which results in the +calling context being a bit more involved in orchestration. + +Please, before deciding to utilize this library in your project, understand the different moving parts and their +requirements. + +## Installation + +To get up and running with the `libtm` package, you can add it to your project using: + +```shell +go get -u github.com/gnolang/libtm +``` + +Currently, the minimum required go version is `go 1.21`. + +## Usage Examples + +```go +package main + +import ( + "context" + + "github.com/gnolang/libtm/core" + "github.com/gnolang/libtm/messages/types" +) + +// Verifier implements the libtm Verifier interface. +// Verifier is an abstraction over the outer consensus calling context +// that has access to validator set information +type Verifier struct { + // ... +} + +// Node implements the libtm Node interface. +// The Node interface is an abstraction over a single entity (current process) that runs +// the consensus algorithm +type Node struct { + // ... +} + +// Broadcast implements the libtm Broadcast interface. +// Broadcast is an abstraction over the networking / message sharing interface +// that enables message passing between validators +type Broadcast struct { + // ... +} + +// Signer implements the libtm Signer interface. +// Signer is an abstraction over the signature manipulation process +type Signer struct { + // ... +} + +// ... + +func main() { + // verifier, node, broadcast, signer, opts + var ( + verifier = NewVerifier() + node = NewNode() + broadcast = NewBroadcast() + signer = NewSigner() + ) + + tm := core.NewTendermint( + verifier, + node, + broadcast, + signer, + ) + + height := uint64(1) + ctx, cancelFn := context.WithCancel(context.Background()) + + go func() { + // Run the consensus sequence for the given height. + // When the method returns the finalized proposal, that means that + // consensus was reached within the given height (in any round) + finalizedProposal := tm.RunSequence(ctx, height) + + // Use the finalized proposal + // ... + }() + + go func() { + // Pipe messages into the consensus engine + var proposalMessage *types.ProposalMessage + + if err := tm.AddProposalMessage(proposalMessage); err != nil { + // ... + } + + // ... + + var prevoteMessage *types.PrevoteMessage + + if err := tm.AddPrevoteMessage(prevoteMessage); err != nil { + // ... + } + + // ... + + var precommitMessage *types.PrecommitMessage + + if err := tm.AddPrecommitMessage(precommitMessage); err != nil { + // ... + } + }() + + // ... + + // Stop the sequence at any time by cancelling the context + cancelFn() +} + +``` + +### Additional Options + +You can utilize additional options when creating the `Tendermint` consensus engine instance: + +- `WithLogger` - specifies the logger for the Tendermint consensus engine (slog) +- `WithProposeTimeout` specifies the propose state timeout +- `WithPrevoteTimeout` specifies the prevote state timeout +- `WithPrecommitTimeout` specifies the precommit state timeout diff --git a/tm2/pkg/libtm/core/broadcast.go b/tm2/pkg/libtm/core/broadcast.go new file mode 100644 index 00000000000..f636687a10a --- /dev/null +++ b/tm2/pkg/libtm/core/broadcast.go @@ -0,0 +1,77 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// buildProposalMessage builds a proposal message using the given proposal +func (t *Tendermint) buildProposalMessage(proposal []byte, proposalRound int64) *types.ProposalMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + ) + + // Build the proposal message (assumes the node will sign it) + message := &types.ProposalMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: t.node.ID(), + Proposal: proposal, + ProposalRound: proposalRound, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} + +// buildPrevoteMessage builds a prevote message using the given proposal identifier +func (t *Tendermint) buildPrevoteMessage(id []byte) *types.PrevoteMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + processID = t.node.ID() + ) + + message := &types.PrevoteMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: processID, + Identifier: id, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} + +// buildPrecommitMessage builds a precommit message using the given precommit identifier +func (t *Tendermint) buildPrecommitMessage(id []byte) *types.PrecommitMessage { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + processID = t.node.ID() + ) + + message := &types.PrecommitMessage{ + View: &types.View{ + Height: height, + Round: round, + }, + Sender: processID, + Identifier: id, + } + + // Sign the message + message.Signature = t.signer.Sign(message.GetSignaturePayload()) + + return message +} diff --git a/tm2/pkg/libtm/core/cache.go b/tm2/pkg/libtm/core/cache.go new file mode 100644 index 00000000000..3535f528c25 --- /dev/null +++ b/tm2/pkg/libtm/core/cache.go @@ -0,0 +1,63 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// msgType is the combined message type interface +type msgType interface { + *types.ProposalMessage | *types.PrevoteMessage | *types.PrecommitMessage +} + +type cacheMessage interface { + msgType + Message +} + +// messageCache contains filtered messages +// added in by the calling context +type messageCache[T cacheMessage] struct { + isValidFn func(T) bool + seenMap map[string]struct{} + filteredMessages []T +} + +// newMessageCache creates a new incoming message cache +func newMessageCache[T cacheMessage](isValidFn func(T) bool) messageCache[T] { + return messageCache[T]{ + isValidFn: isValidFn, + filteredMessages: make([]T, 0), + seenMap: make(map[string]struct{}), + } +} + +// addMessages pushes a new message list that is filtered +// and parsed by the cache +func (c *messageCache[T]) addMessages(messages []T) { + for _, message := range messages { + sender := message.GetSender() + + // Check if the message has been seen in the past + _, seen := c.seenMap[string(sender)] + if seen { + continue + } + + // Filter the message + if !c.isValidFn(message) { + continue + } + + // Mark the message as seen + c.seenMap[string(sender)] = struct{}{} + + // Save the message as it's + // been filtered, and doesn't exist in the cache + c.filteredMessages = append(c.filteredMessages, message) + } +} + +// getMessages returns the filtered out messages from the cache +func (c *messageCache[T]) getMessages() []T { + return c.filteredMessages +} diff --git a/tm2/pkg/libtm/core/cache_test.go b/tm2/pkg/libtm/core/cache_test.go new file mode 100644 index 00000000000..ea878aacda0 --- /dev/null +++ b/tm2/pkg/libtm/core/cache_test.go @@ -0,0 +1,70 @@ +package core + +import ( + "testing" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" +) + +func TestMessageCache_AddMessages(t *testing.T) { + t.Parallel() + + isValidFn := func(_ *types.PrevoteMessage) bool { + return true + } + + t.Run("non-duplicate messages", func(t *testing.T) { + t.Parallel() + + // Create the cache + cache := newMessageCache[*types.PrevoteMessage](isValidFn) + + // Generate non-duplicate messages + messages := generatePrevoteMessages(t, 10, &types.View{}, nil) + + // Add the messages + cache.addMessages(messages) + + // Make sure all messages are added + fetchedMessages := cache.getMessages() + + for index, message := range messages { + assert.True(t, message.Equals(fetchedMessages[index])) + } + }) + + t.Run("duplicate messages", func(t *testing.T) { + t.Parallel() + + var ( + numMessages = 10 + numDuplicates = numMessages / 2 + ) + + // Create the cache + cache := newMessageCache[*types.PrevoteMessage](isValidFn) + + // Generate non-duplicate messages + messages := generatePrevoteMessages(t, numMessages, &types.View{}, nil) + + // Make sure some are duplicated + for i := 0; i < numDuplicates; i++ { + messages[i].Sender = []byte("common sender") + } + + expectedMessages := messages[numDuplicates-1:] + + // Add the messages + cache.addMessages(messages) + + // Make sure all messages are added + fetchedMessages := cache.getMessages() + + assert.Len(t, fetchedMessages, len(expectedMessages)) + + for index, message := range expectedMessages { + assert.True(t, message.Equals(fetchedMessages[index])) + } + }) +} diff --git a/tm2/pkg/libtm/core/cluster_test.go b/tm2/pkg/libtm/core/cluster_test.go new file mode 100644 index 00000000000..2a3fbbfd763 --- /dev/null +++ b/tm2/pkg/libtm/core/cluster_test.go @@ -0,0 +1,435 @@ +package core + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateNodeAddresses generates dummy node addresses +func generateNodeAddresses(count uint64) [][]byte { + addresses := make([][]byte, count) + + for index := range addresses { + addresses[index] = []byte(fmt.Sprintf("node %d", index)) + } + + return addresses +} + +// TestConsensus_ValidFlow tests the following scenario: +// N = 4 +// +// - Node 0 is the proposer for block 1, round 0 +// - Node 0 proposes a valid block B +// - All nodes go through the consensus states to insert the valid block B +func TestConsensus_ValidFlow(t *testing.T) { + t.Parallel() + + var ( + broadcastProposeFn func(message *types.ProposalMessage) + broadcastPrevoteFn func(message *types.PrevoteMessage) + broadcastPrecommitFn func(message *types.PrecommitMessage) + + proposal = []byte("proposal") + proposalHash = []byte("proposal hash") + signature = []byte("signature") + numNodes = uint64(4) + nodes = generateNodeAddresses(numNodes) + + defaultTimeout = Timeout{ + Initial: 2 * time.Second, + Delta: 200 * time.Millisecond, + } + ) + + // commonBroadcastCallback is the common method modification + // required for Broadcast, for all nodes + commonBroadcastCallback := func(broadcast *mockBroadcast) { + broadcast.broadcastProposeFn = func(message *types.ProposalMessage) { + broadcastProposeFn(message) + } + + broadcast.broadcastPrevoteFn = func(message *types.PrevoteMessage) { + broadcastPrevoteFn(message) + } + + broadcast.broadcastPrecommitFn = func(message *types.PrecommitMessage) { + broadcastPrecommitFn(message) + } + } + + // commonNodeCallback is the common method modification required + // for the Node, for all nodes + commonNodeCallback := func(node *mockNode, nodeIndex int) { + node.idFn = func() []byte { + return nodes[nodeIndex] + } + + node.hashFn = func(_ []byte) []byte { + return proposalHash + } + } + + // commonSignerCallback is the common method modification required + // for the Signer, for all nodes + commonSignerCallback := func(signer *mockSigner) { + signer.signFn = func(_ []byte) []byte { + return signature + } + + signer.isValidSignatureFn = func(_, sig []byte) bool { + return bytes.Equal(sig, signature) + } + } + + // commonVerifierCallback is the common method modification required + // for the Verifier, for all nodes + commonVerifierCallback := func(verifier *mockVerifier) { + verifier.isProposerFn = func(from []byte, _ uint64, _ uint64) bool { + return bytes.Equal(from, nodes[0]) + } + + verifier.isValidProposalFn = func(newProposal []byte, _ uint64) bool { + return bytes.Equal(newProposal, proposal) + } + + verifier.isValidatorFn = func(_ []byte) bool { + return true + } + + verifier.getTotalVotingPowerFn = func(_ uint64) uint64 { + return numNodes + } + + verifier.getSumVotingPowerFn = func(messages []Message) uint64 { + return uint64(len(messages)) + } + } + + var ( + verifierCallbackMap = map[int]verifierConfigCallback{ + 0: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 1: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 2: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 3: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + } + + nodeCallbackMap = map[int]nodeConfigCallback{ + 0: func(node *mockNode) { + commonNodeCallback(node, 0) + + node.buildProposalFn = func(_ uint64) []byte { + return proposal + } + }, + 1: func(node *mockNode) { + commonNodeCallback(node, 1) + }, + 2: func(node *mockNode) { + commonNodeCallback(node, 2) + }, + 3: func(node *mockNode) { + commonNodeCallback(node, 3) + }, + } + + broadcastCallbackMap = map[int]broadcastConfigCallback{ + 0: commonBroadcastCallback, + 1: commonBroadcastCallback, + 2: commonBroadcastCallback, + 3: commonBroadcastCallback, + } + + signerCallbackMap = map[int]signerConfigCallback{ + 0: commonSignerCallback, + 1: commonSignerCallback, + 2: commonSignerCallback, + 3: commonSignerCallback, + } + + commonOptions = []Option{ + WithProposeTimeout(defaultTimeout), + WithPrevoteTimeout(defaultTimeout), + WithPrecommitTimeout(defaultTimeout), + } + + optionsCallbackMap = map[int][]Option{ + 0: commonOptions, + 1: commonOptions, + 2: commonOptions, + 3: commonOptions, + } + ) + + // Create the mock cluster + cluster := newMockCluster( + numNodes, + verifierCallbackMap, + nodeCallbackMap, + broadcastCallbackMap, + signerCallbackMap, + optionsCallbackMap, + ) + + broadcastProposeFn = func(message *types.ProposalMessage) { + require.NoError(t, cluster.pushProposalMessage(message)) + } + + broadcastPrevoteFn = func(message *types.PrevoteMessage) { + require.NoError(t, cluster.pushPrevoteMessage(message)) + } + + broadcastPrecommitFn = func(message *types.PrecommitMessage) { + require.NoError(t, cluster.pushPrecommitMessage(message)) + } + + // Start the main run loops + cluster.runSequence(0) + + // Wait until the main run loops finish + cluster.ensureShutdown(5 * time.Second) + + // Make sure the finalized proposals match what node 0 proposed + for _, finalizedProposal := range cluster.finalizedProposals { + require.NotNil(t, finalizedProposal) + + assert.True(t, bytes.Equal(finalizedProposal.Data, proposal)) + assert.True(t, bytes.Equal(finalizedProposal.ID, proposalHash)) + } +} + +// TestConsensus_InvalidBlock tests the following scenario: +// N = 4 +// +// - Node 0 is the proposer for block 1, round 0 +// - Node 0 proposes an invalid block B +// - Other nodes should verify that the block is invalid +// - All nodes should move to round 1, and start a new consensus round +// - Node 1 is the proposer for block 1, round 1 +// - Node 1 proposes a valid block B' +// - All nodes go through the consensus states to insert the valid block B' +func TestConsensus_InvalidFlow(t *testing.T) { + t.Parallel() + + var ( + broadcastProposeFn func(message *types.ProposalMessage) + broadcastPrevoteFn func(message *types.PrevoteMessage) + broadcastPrecommitFn func(message *types.PrecommitMessage) + + proposals = [][]byte{ + []byte("proposal 1"), // proposed by node 0 + []byte("proposal 2"), // proposed by node 1 + } + + proposalHashes = [][]byte{ + []byte("proposal hash 1"), // for proposal 1 + []byte("proposal hash 2"), // for proposal 2 + } + + signature = []byte("signature") + numNodes = uint64(4) + nodes = generateNodeAddresses(numNodes) + + defaultTimeout = Timeout{ + Initial: 2 * time.Second, + Delta: 200 * time.Millisecond, + } + + precommitTimeout = Timeout{ + Initial: 300 * time.Millisecond, // low timeout, so a new round is started quicker + Delta: 200 * time.Millisecond, + } + ) + + // commonBroadcastCallback is the common method modification + // required for Broadcast, for all nodes + commonBroadcastCallback := func(broadcast *mockBroadcast) { + broadcast.broadcastProposeFn = func(message *types.ProposalMessage) { + broadcastProposeFn(message) + } + + broadcast.broadcastPrevoteFn = func(message *types.PrevoteMessage) { + broadcastPrevoteFn(message) + } + + broadcast.broadcastPrecommitFn = func(message *types.PrecommitMessage) { + broadcastPrecommitFn(message) + } + } + + // commonNodeCallback is the common method modification required + // for the Node, for all nodes + commonNodeCallback := func(node *mockNode, nodeIndex int) { + node.idFn = func() []byte { + return nodes[nodeIndex] + } + + node.hashFn = func(proposal []byte) []byte { + if bytes.Equal(proposal, proposals[0]) { + return proposalHashes[0] + } + + return proposalHashes[1] + } + } + + // commonSignerCallback is the common method modification required + // for the Signer, for all nodes + commonSignerCallback := func(signer *mockSigner) { + signer.signFn = func(_ []byte) []byte { + return signature + } + + signer.isValidSignatureFn = func(_, sig []byte) bool { + return bytes.Equal(sig, signature) + } + } + + // commonVerifierCallback is the common method modification required + // for the Verifier, for all nodes + commonVerifierCallback := func(verifier *mockVerifier) { + verifier.isProposerFn = func(from []byte, _ uint64, round uint64) bool { + // Node 0 is the proposer for round 0 + // Node 1 is the proposer for round 1 + return bytes.Equal(from, nodes[round]) + } + + verifier.isValidProposalFn = func(newProposal []byte, _ uint64) bool { + // Node 1 is the proposer for round 1, + // and their proposal is the only one that's valid + return bytes.Equal(newProposal, proposals[1]) + } + + verifier.isValidatorFn = func(_ []byte) bool { + return true + } + + verifier.getTotalVotingPowerFn = func(_ uint64) uint64 { + return numNodes + } + + verifier.getSumVotingPowerFn = func(messages []Message) uint64 { + return uint64(len(messages)) + } + } + + var ( + verifierCallbackMap = map[int]verifierConfigCallback{ + 0: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 1: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 2: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + 3: func(verifier *mockVerifier) { + commonVerifierCallback(verifier) + }, + } + + nodeCallbackMap = map[int]nodeConfigCallback{ + 0: func(node *mockNode) { + commonNodeCallback(node, 0) + + node.buildProposalFn = func(_ uint64) []byte { + return proposals[0] + } + }, + 1: func(node *mockNode) { + commonNodeCallback(node, 1) + + node.buildProposalFn = func(_ uint64) []byte { + return proposals[1] + } + }, + 2: func(node *mockNode) { + commonNodeCallback(node, 2) + }, + 3: func(node *mockNode) { + commonNodeCallback(node, 3) + }, + } + + broadcastCallbackMap = map[int]broadcastConfigCallback{ + 0: commonBroadcastCallback, + 1: commonBroadcastCallback, + 2: commonBroadcastCallback, + 3: commonBroadcastCallback, + } + + signerCallbackMap = map[int]signerConfigCallback{ + 0: commonSignerCallback, + 1: commonSignerCallback, + 2: commonSignerCallback, + 3: commonSignerCallback, + } + + commonOptions = []Option{ + WithProposeTimeout(defaultTimeout), + WithPrevoteTimeout(defaultTimeout), + WithPrecommitTimeout(precommitTimeout), + } + + optionsCallbackMap = map[int][]Option{ + 0: commonOptions, + 1: commonOptions, + 2: commonOptions, + 3: commonOptions, + } + ) + + // Create the mock cluster + cluster := newMockCluster( + numNodes, + verifierCallbackMap, + nodeCallbackMap, + broadcastCallbackMap, + signerCallbackMap, + optionsCallbackMap, + ) + + broadcastProposeFn = func(message *types.ProposalMessage) { + _ = cluster.pushProposalMessage(message) //nolint:errcheck // No need to check + } + + broadcastPrevoteFn = func(message *types.PrevoteMessage) { + _ = cluster.pushPrevoteMessage(message) //nolint:errcheck // No need to check + } + + broadcastPrecommitFn = func(message *types.PrecommitMessage) { + _ = cluster.pushPrecommitMessage(message) //nolint:errcheck // No need to check + } + + // Start the main run loops + cluster.runSequence(0) + + // Wait until the main run loops finish + cluster.ensureShutdown(5 * time.Second) + + // Make sure the nodes switched to the new round + assert.True(t, cluster.areAllNodesOnRound(1)) + + // Make sure the finalized proposals match what node 0 proposed + for _, finalizedProposal := range cluster.finalizedProposals { + require.NotNil(t, finalizedProposal) + + assert.True(t, bytes.Equal(finalizedProposal.Data, proposals[1])) + assert.True(t, bytes.Equal(finalizedProposal.ID, proposalHashes[1])) + } +} diff --git a/tm2/pkg/libtm/core/messages.go b/tm2/pkg/libtm/core/messages.go new file mode 100644 index 00000000000..961e8586c8d --- /dev/null +++ b/tm2/pkg/libtm/core/messages.go @@ -0,0 +1,97 @@ +package core + +import ( + "errors" + "fmt" + + "github.com/gnolang/libtm/messages/types" +) + +var ( + ErrInvalidMessageSignature = errors.New("invalid message signature") + ErrMessageFromNonValidator = errors.New("message is from a non-validator") + ErrEarlierHeightMessage = errors.New("message is for an earlier height") + ErrEarlierRoundMessage = errors.New("message is for an earlier round") +) + +// AddProposalMessage verifies and adds a new proposal message to the consensus engine +func (t *Tendermint) AddProposalMessage(message *types.ProposalMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify proposal message, %w", err) + } + + // Add the message to the store + t.store.addProposalMessage(message) + + return nil +} + +// AddPrevoteMessage verifies and adds a new prevote message to the consensus engine +func (t *Tendermint) AddPrevoteMessage(message *types.PrevoteMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify prevote message, %w", err) + } + + // Add the message to the store + t.store.addPrevoteMessage(message) + + return nil +} + +// AddPrecommitMessage verifies and adds a new precommit message to the consensus engine +func (t *Tendermint) AddPrecommitMessage(message *types.PrecommitMessage) error { + // Verify the incoming message + if err := t.verifyMessage(message); err != nil { + return fmt.Errorf("unable to verify precommit message, %w", err) + } + + // Add the message to the store + t.store.addPrecommitMessage(message) + + return nil +} + +// verifyMessage is the common base message verification +func (t *Tendermint) verifyMessage(message Message) error { + // Check if the message is valid + if err := message.Verify(); err != nil { + return fmt.Errorf("unable to verify message, %w", err) + } + + // Make sure the message sender is a validator + if !t.verifier.IsValidator(message.GetSender()) { + return ErrMessageFromNonValidator + } + + // Get the signature payload + signPayload := message.GetSignaturePayload() + + // Make sure the signature is valid + if !t.signer.IsValidSignature(signPayload, message.GetSignature()) { + return ErrInvalidMessageSignature + } + + // Make sure the message view is valid + var ( + view = message.GetView() + + currentHeight = t.state.getHeight() + currentRound = t.state.getRound() + ) + + // Make sure the height is valid. + // The message height needs to be the current state height, or greater + if currentHeight > view.GetHeight() { + return ErrEarlierHeightMessage + } + + // Make sure the round is valid. + // The message rounds needs to be >= the current round + if currentRound > view.GetRound() { + return ErrEarlierRoundMessage + } + + return nil +} diff --git a/tm2/pkg/libtm/core/mocks_test.go b/tm2/pkg/libtm/core/mocks_test.go new file mode 100644 index 00000000000..174743f31cf --- /dev/null +++ b/tm2/pkg/libtm/core/mocks_test.go @@ -0,0 +1,446 @@ +package core + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "github.com/gnolang/libtm/messages/types" +) + +type ( + broadcastProposeDelegate func(*types.ProposalMessage) + broadcastPrevoteDelegate func(*types.PrevoteMessage) + broadcastPrecommitDelegate func(*types.PrecommitMessage) +) + +type mockBroadcast struct { + broadcastProposeFn broadcastProposeDelegate + broadcastPrevoteFn broadcastPrevoteDelegate + broadcastPrecommitFn broadcastPrecommitDelegate +} + +func (m *mockBroadcast) BroadcastPropose(message *types.ProposalMessage) { + if m.broadcastProposeFn != nil { + m.broadcastProposeFn(message) + } +} + +func (m *mockBroadcast) BroadcastPrevote(message *types.PrevoteMessage) { + if m.broadcastPrevoteFn != nil { + m.broadcastPrevoteFn(message) + } +} + +func (m *mockBroadcast) BroadcastPrecommit(message *types.PrecommitMessage) { + if m.broadcastPrecommitFn != nil { + m.broadcastPrecommitFn(message) + } +} + +type ( + idDelegate func() []byte + hashDelegate func([]byte) []byte + buildProposalDelegate func(uint64) []byte +) + +type mockNode struct { + idFn idDelegate + hashFn hashDelegate + buildProposalFn buildProposalDelegate +} + +func (m *mockNode) ID() []byte { + if m.idFn != nil { + return m.idFn() + } + + return nil +} + +func (m *mockNode) Hash(proposal []byte) []byte { + if m.hashFn != nil { + return m.hashFn(proposal) + } + + return nil +} + +func (m *mockNode) BuildProposal(height uint64) []byte { + if m.buildProposalFn != nil { + return m.buildProposalFn(height) + } + + return nil +} + +type ( + signDelegate func([]byte) []byte + isValidSignatureDelegate func([]byte, []byte) bool +) + +type mockSigner struct { + signFn signDelegate + isValidSignatureFn isValidSignatureDelegate +} + +func (m *mockSigner) Sign(data []byte) []byte { + if m.signFn != nil { + return m.signFn(data) + } + + return nil +} + +func (m *mockSigner) IsValidSignature(data, signature []byte) bool { + if m.isValidSignatureFn != nil { + return m.isValidSignatureFn(data, signature) + } + + return false +} + +type ( + isProposerDelegate func([]byte, uint64, uint64) bool + isValidatorDelegate func([]byte) bool + isValidProposalDelegate func([]byte, uint64) bool + getTotalVotingPowerDelegate func(uint64) uint64 + getSumVotingPowerDelegate func([]Message) uint64 +) + +type mockVerifier struct { + isProposerFn isProposerDelegate + isValidatorFn isValidatorDelegate + isValidProposalFn isValidProposalDelegate + getTotalVotingPowerFn getTotalVotingPowerDelegate + getSumVotingPowerFn getSumVotingPowerDelegate +} + +func (m *mockVerifier) GetTotalVotingPower(height uint64) uint64 { + if m.getTotalVotingPowerFn != nil { + return m.getTotalVotingPowerFn(height) + } + + return 0 +} + +func (m *mockVerifier) GetSumVotingPower(msgs []Message) uint64 { + if m.getSumVotingPowerFn != nil { + return m.getSumVotingPowerFn(msgs) + } + + return 0 +} + +func (m *mockVerifier) IsProposer(id []byte, height, round uint64) bool { + if m.isProposerFn != nil { + return m.isProposerFn(id, height, round) + } + + return false +} + +func (m *mockVerifier) IsValidator(from []byte) bool { + if m.isValidatorFn != nil { + return m.isValidatorFn(from) + } + + return false +} + +func (m *mockVerifier) IsValidProposal(proposal []byte, height uint64) bool { + if m.isValidProposalFn != nil { + return m.isValidProposalFn(proposal, height) + } + + return false +} + +type ( + getViewDelegate func() *types.View + getSenderDelegate func() []byte + getSignatureDelegate func() []byte + getSignaturePayloadDelegate func() []byte + verifyDelegate func() error +) + +type mockMessage struct { + getViewFn getViewDelegate + getSenderFn getSenderDelegate + getSignatureFn getSignatureDelegate + getSignaturePayloadFn getSignaturePayloadDelegate + verifyFn verifyDelegate +} + +func (m *mockMessage) GetView() *types.View { + if m.getViewFn != nil { + return m.getViewFn() + } + + return nil +} + +func (m *mockMessage) GetSender() []byte { + if m.getSenderFn != nil { + return m.getSenderFn() + } + + return nil +} + +func (m *mockMessage) GetSignature() []byte { + if m.getSignatureFn != nil { + return m.getSignatureFn() + } + + return nil +} + +func (m *mockMessage) GetSignaturePayload() []byte { + if m.getSignaturePayloadFn != nil { + return m.getSignaturePayloadFn() + } + + return nil +} + +func (m *mockMessage) Verify() error { + if m.verifyFn != nil { + return m.verifyFn() + } + + return nil +} + +// mockNodeContext keeps track of the node runtime context +type mockNodeContext struct { + ctx context.Context + cancelFn context.CancelFunc +} + +// mockNodeWg is the WaitGroup wrapper for the cluster nodes +type mockNodeWg struct { + sync.WaitGroup + count int64 +} + +func (wg *mockNodeWg) Add(delta int) { + wg.WaitGroup.Add(delta) +} + +func (wg *mockNodeWg) Done() { + wg.WaitGroup.Done() + atomic.AddInt64(&wg.count, 1) +} + +func (wg *mockNodeWg) getDone() int64 { + return atomic.LoadInt64(&wg.count) +} + +func (wg *mockNodeWg) resetDone() { + atomic.StoreInt64(&wg.count, 0) +} + +type ( + verifierConfigCallback func(*mockVerifier) + nodeConfigCallback func(*mockNode) + broadcastConfigCallback func(*mockBroadcast) + signerConfigCallback func(*mockSigner) +) + +// mockCluster represents a mock Tendermint cluster +type mockCluster struct { + nodes []*Tendermint // references to the nodes in the cluster + ctxs []mockNodeContext // context handlers for the nodes in the cluster + finalizedProposals []*FinalizedProposal // finalized proposals for the nodes + + stoppedWg mockNodeWg +} + +// newMockCluster creates a new mock Tendermint cluster +func newMockCluster( + count uint64, + verifierCallbackMap map[int]verifierConfigCallback, + nodeCallbackMap map[int]nodeConfigCallback, + broadcastCallbackMap map[int]broadcastConfigCallback, + signerCallbackMap map[int]signerConfigCallback, + optionsMap map[int][]Option, +) *mockCluster { + if count < 1 { + return nil + } + + nodes := make([]*Tendermint, count) + nodeCtxs := make([]mockNodeContext, count) + + for index := 0; index < int(count); index++ { + var ( + verifier = &mockVerifier{} + node = &mockNode{} + broadcast = &mockBroadcast{} + signer = &mockSigner{} + options = make([]Option, 0) + ) + + // Execute set callbacks, if any + if verifierCallbackMap != nil { + if verifierCallback, isSet := verifierCallbackMap[index]; isSet { + verifierCallback(verifier) + } + } + + if nodeCallbackMap != nil { + if nodeCallback, isSet := nodeCallbackMap[index]; isSet { + nodeCallback(node) + } + } + + if broadcastCallbackMap != nil { + if broadcastCallback, isSet := broadcastCallbackMap[index]; isSet { + broadcastCallback(broadcast) + } + } + + if signerCallbackMap != nil { + if signerCallback, isSet := signerCallbackMap[index]; isSet { + signerCallback(signer) + } + } + + if optionsMap != nil { + if opts, isSet := optionsMap[index]; isSet { + options = opts + } + } + + // Create a new instance of the Tendermint node + nodes[index] = NewTendermint( + verifier, + node, + broadcast, + signer, + options..., + ) + + // Instantiate context for the nodes + ctx, cancelFn := context.WithCancel(context.Background()) + nodeCtxs[index] = mockNodeContext{ + ctx: ctx, + cancelFn: cancelFn, + } + } + + return &mockCluster{ + nodes: nodes, + ctxs: nodeCtxs, + finalizedProposals: make([]*FinalizedProposal, count), + } +} + +// runSequence runs the cluster sequence for the given height +func (m *mockCluster) runSequence(height uint64) { + m.stoppedWg.resetDone() + + for nodeIndex, node := range m.nodes { + m.stoppedWg.Add(1) + + go func( + ctx context.Context, + node *Tendermint, + nodeIndex int, + height uint64, + ) { + defer m.stoppedWg.Done() + + // Start the main run loop for the node + finalizedProposal := node.RunSequence(ctx, height) + + m.finalizedProposals[nodeIndex] = finalizedProposal + }(m.ctxs[nodeIndex].ctx, node, nodeIndex, height) + } +} + +// awaitCompletion waits for completion of all +// nodes in the cluster +func (m *mockCluster) awaitCompletion() { + // Wait for all main run loops to signalize + // that they're finished + m.stoppedWg.Wait() +} + +// ensureShutdown ensures the cluster is shutdown within the given duration +func (m *mockCluster) ensureShutdown(timeout time.Duration) { + ch := time.After(timeout) + + for { + select { + case <-ch: + m.forceShutdown() + + return + default: + if m.stoppedWg.getDone() == int64(len(m.nodes)) { + // All nodes are finished + return + } + } + } +} + +// forceShutdown sends a stop signal to all running nodes +// in the cluster, and awaits their completion +func (m *mockCluster) forceShutdown() { + // Send a stop signal to all the nodes + for _, ctx := range m.ctxs { + ctx.cancelFn() + } + + // Wait for all the nodes to finish + m.awaitCompletion() +} + +// pushProposalMessage relays the proposal message to all nodes in the cluster +func (m *mockCluster) pushProposalMessage(message *types.ProposalMessage) error { + for _, node := range m.nodes { + if err := node.AddProposalMessage(message); err != nil { + return err + } + } + + return nil +} + +// pushPrevoteMessage relays the prevote message to all nodes in the cluster +func (m *mockCluster) pushPrevoteMessage(message *types.PrevoteMessage) error { + for _, node := range m.nodes { + if err := node.AddPrevoteMessage(message); err != nil { + return err + } + } + + return nil +} + +// pushPrecommitMessage relays the precommit message to all nodes in the cluster +func (m *mockCluster) pushPrecommitMessage(message *types.PrecommitMessage) error { + for _, node := range m.nodes { + if err := node.AddPrecommitMessage(message); err != nil { + return err + } + } + + return nil +} + +// areAllNodesOnRound checks to make sure all nodes +// are on the same specified round +func (m *mockCluster) areAllNodesOnRound(round uint64) bool { + for _, node := range m.nodes { + if node.state.getRound() != round { + return false + } + } + + return true +} diff --git a/tm2/pkg/libtm/core/options.go b/tm2/pkg/libtm/core/options.go new file mode 100644 index 00000000000..9ff8581ada8 --- /dev/null +++ b/tm2/pkg/libtm/core/options.go @@ -0,0 +1,33 @@ +package core + +import "log/slog" + +type Option func(t *Tendermint) + +// WithLogger specifies the logger for the Tendermint consensus engine +func WithLogger(l *slog.Logger) Option { + return func(t *Tendermint) { + t.logger = l + } +} + +// WithProposeTimeout specifies the propose state timeout +func WithProposeTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[propose] = timeout + } +} + +// WithPrevoteTimeout specifies the prevote state timeout +func WithPrevoteTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[prevote] = timeout + } +} + +// WithPrecommitTimeout specifies the precommit state timeout +func WithPrecommitTimeout(timeout Timeout) Option { + return func(t *Tendermint) { + t.timeouts[precommit] = timeout + } +} diff --git a/tm2/pkg/libtm/core/options_test.go b/tm2/pkg/libtm/core/options_test.go new file mode 100644 index 00000000000..9d7909985fe --- /dev/null +++ b/tm2/pkg/libtm/core/options_test.go @@ -0,0 +1,87 @@ +package core + +import ( + "io" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewTendermint_Options(t *testing.T) { + t.Parallel() + + t.Run("Withlogger", func(t *testing.T) { + t.Parallel() + + l := slog.New(slog.NewTextHandler(io.Discard, nil)) + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithLogger(l), + ) + + assert.Equal(t, tm.logger, l) + }) + + t.Run("WithProposeTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithProposeTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[propose], timeout) + }) + + t.Run("WithPrevoteTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithPrevoteTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[prevote], timeout) + }) + + t.Run("WithPrecommitTimeout", func(t *testing.T) { + t.Parallel() + + timeout := Timeout{ + Initial: 500 * time.Millisecond, + Delta: 0, + } + + tm := NewTendermint( + nil, + nil, + nil, + nil, + WithPrecommitTimeout(timeout), + ) + + assert.Equal(t, tm.timeouts[precommit], timeout) + }) +} diff --git a/tm2/pkg/libtm/core/quorum.go b/tm2/pkg/libtm/core/quorum.go new file mode 100644 index 00000000000..cf12bc9b8f5 --- /dev/null +++ b/tm2/pkg/libtm/core/quorum.go @@ -0,0 +1,23 @@ +package core + +// hasSuperMajority verifies that there is a 2F+1 voting power majority +// in the given message set. +// This follows the constraint that N > 3F, i.e., the total voting power of faulty processes is smaller than +// one third of the total voting power +func (t *Tendermint) hasSuperMajority(messages []Message) bool { + sumVotingPower := t.verifier.GetSumVotingPower(messages) + totalVotingPower := t.verifier.GetTotalVotingPower(t.state.getHeight()) + + return sumVotingPower > (2 * totalVotingPower / 3) +} + +// hasFaultyMajority verifies that there is an F+1 voting power majority +// in the given message set. +// This follows the constraint that N > 3F, i.e., the total voting power of faulty processes is smaller than +// one third of the total voting power +func (t *Tendermint) hasFaultyMajority(messages []Message) bool { + sumVotingPower := t.verifier.GetSumVotingPower(messages) + totalVotingPower := t.verifier.GetTotalVotingPower(t.state.getHeight()) + + return sumVotingPower > totalVotingPower/3 +} diff --git a/tm2/pkg/libtm/core/quorum_test.go b/tm2/pkg/libtm/core/quorum_test.go new file mode 100644 index 00000000000..23f804de2b7 --- /dev/null +++ b/tm2/pkg/libtm/core/quorum_test.go @@ -0,0 +1,219 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTendermint_QuorumSuperMajority(t *testing.T) { + t.Parallel() + + var ( + // Equal voting power map + votingPowerMap = map[string]uint64{ + "1": 1, + "2": 1, + "3": 1, + "4": 1, + } + + mockMessages = []*mockMessage{ + { + getSenderFn: func() []byte { + return []byte("1") + }, + }, + { + getSenderFn: func() []byte { + return []byte("2") + }, + }, + { + getSenderFn: func() []byte { + return []byte("3") + }, + }, + { + getSenderFn: func() []byte { + return []byte("4") + }, + }, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(_ uint64) uint64 { + return uint64(len(votingPowerMap)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + sum := uint64(0) + + for _, message := range messages { + sum += votingPowerMap[string(message.GetSender())] + } + + return sum + }, + } + ) + + testTable := []struct { + name string + messages []*mockMessage + shouldHaveMajority bool + }{ + { + "4/4 validators", + mockMessages, + true, + }, + { + "3/4 validators", + mockMessages[1:], + true, + }, + { + "2/4 validators", + mockMessages[2:], + false, + }, + { + "1/4 validators", + mockMessages[:1], + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tm := NewTendermint( + mockVerifier, + nil, + nil, + nil, + ) + + convertedMessages := make([]Message, 0, len(testCase.messages)) + + for _, mockMessage := range testCase.messages { + convertedMessages = append(convertedMessages, mockMessage) + } + + assert.Equal( + t, + testCase.shouldHaveMajority, + tm.hasSuperMajority(convertedMessages), + ) + }) + } +} + +func TestTendermint_QuorumFaultyMajority(t *testing.T) { + t.Parallel() + + var ( + // Equal voting power map + votingPowerMap = map[string]uint64{ + "1": 1, + "2": 1, + "3": 1, + "4": 1, + } + + mockMessages = []*mockMessage{ + { + getSenderFn: func() []byte { + return []byte("1") + }, + }, + { + getSenderFn: func() []byte { + return []byte("2") + }, + }, + { + getSenderFn: func() []byte { + return []byte("3") + }, + }, + { + getSenderFn: func() []byte { + return []byte("4") + }, + }, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(_ uint64) uint64 { + return uint64(len(votingPowerMap)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + sum := uint64(0) + + for _, message := range messages { + sum += votingPowerMap[string(message.GetSender())] + } + + return sum + }, + } + ) + + testTable := []struct { + name string + messages []*mockMessage + shouldHaveMajority bool + }{ + { + "4/4 validators", + mockMessages, + true, + }, + { + "3/4 validators", + mockMessages[1:], + true, + }, + { + "2/4 validators", + mockMessages[2:], + true, + }, + { + "1/4 validators", + mockMessages[:1], + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tm := NewTendermint( + mockVerifier, + nil, + nil, + nil, + ) + + convertedMessages := make([]Message, 0, len(testCase.messages)) + + for _, mockMessage := range testCase.messages { + convertedMessages = append(convertedMessages, mockMessage) + } + + assert.Equal( + t, + testCase.shouldHaveMajority, + tm.hasFaultyMajority(convertedMessages), + ) + }) + } +} diff --git a/tm2/pkg/libtm/core/state.go b/tm2/pkg/libtm/core/state.go new file mode 100644 index 00000000000..6713a411e4d --- /dev/null +++ b/tm2/pkg/libtm/core/state.go @@ -0,0 +1,84 @@ +package core + +import ( + "sync/atomic" + + "github.com/gnolang/libtm/messages/types" +) + +// step is the current state step +type step uint32 + +const ( + propose step = iota + prevote + precommit +) + +// set updates the current step value [THREAD SAFE] +func (s *step) set(n step) { + atomic.SwapUint32((*uint32)(s), uint32(n)) +} + +// get fetches the current step value [THREAD SAFE] +func (s *step) get() step { + return step(atomic.LoadUint32((*uint32)(s))) +} + +// state holds information about the current consensus state +type state struct { + view *types.View + + acceptedProposal []byte + acceptedProposalID []byte + + lockedValue []byte + validValue []byte + + lockedRound int64 + validRound int64 + + step step +} + +// newState creates a fresh state using the given view +func newState() state { + return state{ + view: &types.View{ + Height: 0, // zero height + Round: 0, // zero round + }, + step: propose, + acceptedProposal: nil, + acceptedProposalID: nil, + lockedValue: nil, + lockedRound: -1, + validValue: nil, + validRound: -1, + } +} + +// getHeight fetches the current view height [THREAD SAFE] +func (s *state) getHeight() uint64 { + return atomic.LoadUint64(&s.view.Height) +} + +// getRound fetches the current view round [THREAD SAFE] +func (s *state) getRound() uint64 { + return atomic.LoadUint64(&s.view.Round) +} + +// increaseRound increases the current view round by 1 [THREAD SAFE] +func (s *state) increaseRound() { + atomic.AddUint64(&s.view.Round, 1) +} + +// setRound sets the current view round to the given value [THREAD SAFE] +func (s *state) setRound(r uint64) { + atomic.SwapUint64(&s.view.Round, r) +} + +// setHeight sets the current view height to the given value [THREAD SAFE] +func (s *state) setHeight(h uint64) { + atomic.SwapUint64(&s.view.Height, h) +} diff --git a/tm2/pkg/libtm/core/store.go b/tm2/pkg/libtm/core/store.go new file mode 100644 index 00000000000..395dd2d023e --- /dev/null +++ b/tm2/pkg/libtm/core/store.go @@ -0,0 +1,65 @@ +package core + +import ( + "github.com/gnolang/libtm/messages" + "github.com/gnolang/libtm/messages/types" +) + +// store is the message store +type store struct { + proposeMessages *messages.Collector[types.ProposalMessage] + prevoteMessages *messages.Collector[types.PrevoteMessage] + precommitMessages *messages.Collector[types.PrecommitMessage] +} + +// newStore creates a new message store +func newStore() store { + return store{ + proposeMessages: messages.NewCollector[types.ProposalMessage](), + prevoteMessages: messages.NewCollector[types.PrevoteMessage](), + precommitMessages: messages.NewCollector[types.PrecommitMessage](), + } +} + +// addProposalMessage adds a proposal message to the store +func (s *store) addProposalMessage(proposal *types.ProposalMessage) { + s.proposeMessages.AddMessage(proposal.View, proposal.Sender, proposal) +} + +// addPrevoteMessage adds a prevote message to the store +func (s *store) addPrevoteMessage(prevote *types.PrevoteMessage) { + s.prevoteMessages.AddMessage(prevote.View, prevote.Sender, prevote) +} + +// addPrecommitMessage adds a precommit message to the store +func (s *store) addPrecommitMessage(precommit *types.PrecommitMessage) { + s.precommitMessages.AddMessage(precommit.View, precommit.Sender, precommit) +} + +// subscribeToPropose subscribes to incoming PROPOSE messages +func (s *store) subscribeToPropose() (<-chan func() []*types.ProposalMessage, func()) { + return s.proposeMessages.Subscribe() +} + +// subscribeToPrevote subscribes to incoming PREVOTE messages +func (s *store) subscribeToPrevote() (<-chan func() []*types.PrevoteMessage, func()) { + return s.prevoteMessages.Subscribe() +} + +// subscribeToPrecommit subscribes to incoming PRECOMMIT messages +func (s *store) subscribeToPrecommit() (<-chan func() []*types.PrecommitMessage, func()) { + return s.precommitMessages.Subscribe() +} + +// dropMessages drops all messages from the store that are +// less than the given view (earlier) +func (s *store) dropMessages(view *types.View) { + // Clean up the propose messages + s.proposeMessages.DropMessages(view) + + // Clean up the prevote messages + s.prevoteMessages.DropMessages(view) + + // Clean up the precommit messages + s.precommitMessages.DropMessages(view) +} diff --git a/tm2/pkg/libtm/core/tendermint.go b/tm2/pkg/libtm/core/tendermint.go new file mode 100644 index 00000000000..46b610b6bee --- /dev/null +++ b/tm2/pkg/libtm/core/tendermint.go @@ -0,0 +1,907 @@ +package core + +import ( + "bytes" + "context" + "io" + "log/slog" + "sync" + + "github.com/gnolang/libtm/messages" + + "github.com/gnolang/libtm/messages/types" +) + +// Tendermint is the single consensus engine instance +type Tendermint struct { + // store is the message store + store store + + verifier Verifier + node Node + broadcast Broadcast + signer Signer + + // logger is the consensus engine logger + logger *slog.Logger + + // timeouts hold state timeout information (constant) + timeouts map[step]Timeout + + // state is the current Tendermint consensus state + state state + + // wg is the barrier for keeping all + // parallel consensus processes synced + wg sync.WaitGroup +} + +// FinalizedProposal is the finalized proposal wrapper, that +// contains the raw proposal data, and the ID of the data (usually hash) +type FinalizedProposal struct { + Data []byte // the raw proposal data, accepted proposal + ID []byte // the ID of the proposal (usually hash) +} + +// newFinalizedProposal creates a new finalized proposal wrapper +func newFinalizedProposal(data, id []byte) *FinalizedProposal { + return &FinalizedProposal{ + Data: data, + ID: id, + } +} + +// NewTendermint creates a new instance of the Tendermint consensus engine +func NewTendermint( + verifier Verifier, + node Node, + broadcast Broadcast, + signer Signer, + opts ...Option, +) *Tendermint { + t := &Tendermint{ + state: newState(), + store: newStore(), + verifier: verifier, + node: node, + broadcast: broadcast, + signer: signer, + logger: slog.New(slog.NewTextHandler(io.Discard, nil)), + timeouts: getDefaultTimeoutMap(), + } + + // Apply any options + for _, opt := range opts { + opt(t) + } + + return t +} + +// RunSequence runs the Tendermint consensus sequence for a given height, +// returning only when a proposal has been finalized (consensus reached), or +// the context has been cancelled +func (t *Tendermint) RunSequence(ctx context.Context, h uint64) *FinalizedProposal { + t.logger.Debug( + "RunSequence", + slog.Uint64("height", h), + slog.String("node", string(t.node.ID())), + ) + + // Initialize the state before starting the sequence + t.state.setHeight(h) + + // Grab the process view + view := &types.View{ + Height: h, + Round: t.state.getRound(), + } + + // Drop all old messages + t.store.dropMessages(view) + + for { + // set up the round context + ctxRound, cancelRound := context.WithCancel(ctx) + teardown := func() { + cancelRound() + t.wg.Wait() + } + + select { + case proposal := <-t.finalizeProposal(ctxRound): + teardown() + + // Check if the proposal has been finalized + if proposal != nil { + t.logger.Info( + "RunSequence: proposal finalized", + slog.Uint64("height", h), + slog.String("node", string(t.node.ID())), + ) + + return proposal + } + + t.logger.Info( + "RunSequence round expired", + slog.Uint64("height", h), + slog.Uint64("round", t.state.getRound()), + slog.String("node", string(t.node.ID())), + ) + + // 65: Function OnTimeoutPrecommit(height, round) : + // 66: if height = hP ∧ round = roundP then + // 67: StartRound(roundP + 1) + t.state.increaseRound() + t.state.step.set(propose) + case recvRound := <-t.watchForRoundJumps(ctxRound): + teardown() + + t.logger.Info( + "RunSequence: round jump", + slog.Uint64("height", h), + slog.Uint64("from", t.state.getRound()), + slog.Uint64("to", recvRound), + slog.String("node", string(t.node.ID())), + ) + + t.state.setRound(recvRound) + t.state.step.set(propose) + case <-ctx.Done(): + teardown() + + t.logger.Info( + "RunSequence: context done", + slog.Uint64("height", h), + slog.Uint64("round", t.state.getRound()), + slog.String("node", string(t.node.ID())), + ) + + return nil + } + } +} + +// watchForRoundJumps monitors for F+1 (any) messages from a future round, and +// triggers the round switch context (channel) accordingly +func (t *Tendermint) watchForRoundJumps(ctx context.Context) <-chan uint64 { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + ch = make(chan uint64, 1) + ) + + // Signals the round jump to the given channel + signalRoundJump := func(round uint64) { + select { + case <-ctx.Done(): + case ch <- round: + } + } + + t.wg.Add(1) + + go func() { + defer t.wg.Done() + + var ( + proposeCh, unsubscribeProposeFn = t.store.subscribeToPropose() + prevoteCh, unsubscribePrevoteFn = t.store.subscribeToPrevote() + precommitCh, unsubscribePrecommitFn = t.store.subscribeToPrecommit() + ) + + defer func() { + unsubscribeProposeFn() + unsubscribePrevoteFn() + unsubscribePrecommitFn() + }() + + var ( + isValidProposeFn = func(m *types.ProposalMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + isValidPrevoteFn = func(m *types.PrevoteMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + isValidPrecommitFn = func(m *types.PrecommitMessage) bool { + view := m.GetView() + + return view.GetRound() > round && view.GetHeight() == height + } + ) + + var ( + proposeCache = newMessageCache[*types.ProposalMessage](isValidProposeFn) + prevoteCache = newMessageCache[*types.PrevoteMessage](isValidPrevoteFn) + precommitCache = newMessageCache[*types.PrecommitMessage](isValidPrecommitFn) + ) + + generateRoundMap := func(messages ...[]Message) map[uint64][]Message { + combined := make([]Message, 0) + for _, message := range messages { + combined = append(combined, message...) + } + + // Group messages by round + roundMap := make(map[uint64][]Message) + + for _, message := range combined { + messageRound := message.GetView().GetRound() + roundMap[messageRound] = append(roundMap[messageRound], message) + } + + return roundMap + } + + for { + select { + case <-ctx.Done(): + close(ch) + + return + case getProposeFn := <-proposeCh: + proposeCache.addMessages(getProposeFn()) + case getPrevoteFn := <-prevoteCh: + prevoteCache.addMessages(getPrevoteFn()) + case getPrecommitFn := <-precommitCh: + precommitCache.addMessages(getPrecommitFn()) + } + + var ( + proposeMessages = proposeCache.getMessages() + prevoteMessages = prevoteCache.getMessages() + precommitMessages = precommitCache.getMessages() + ) + + var ( + convertedPropose = make([]Message, 0, len(proposeMessages)) + convertedPrevote = make([]Message, 0, len(prevoteMessages)) + convertedPrecommit = make([]Message, 0, len(precommitMessages)) + ) + + messages.ConvertToInterface(proposeMessages, func(m *types.ProposalMessage) { + convertedPropose = append(convertedPropose, m) + }) + + messages.ConvertToInterface(prevoteMessages, func(m *types.PrevoteMessage) { + convertedPrevote = append(convertedPrevote, m) + }) + + messages.ConvertToInterface(precommitMessages, func(m *types.PrecommitMessage) { + convertedPrecommit = append(convertedPrecommit, m) + }) + + // Generate the round map + roundMap := generateRoundMap( + convertedPropose, + convertedPrevote, + convertedPrecommit, + ) + + // Find the highest round that satisfies an F+1 voting power majority. + // This max round will always need to be > 0 + maxRound := uint64(0) + + for messageRound, roundMessages := range roundMap { + if !t.hasFaultyMajority(roundMessages) { + continue + } + + if messageRound > maxRound { + maxRound = messageRound + } + } + + // Make sure the max round that has a faulty majority + // is actually greater than the process round + if maxRound > round { + signalRoundJump(maxRound) + + return + } + } + }() + + return ch +} + +// finalizeProposal starts the proposal finalization sequence +func (t *Tendermint) finalizeProposal(ctx context.Context) <-chan *FinalizedProposal { + ch := make(chan *FinalizedProposal, 1) + + t.wg.Add(1) + + go func() { + defer func() { + close(ch) + t.wg.Done() + }() + + // Run the consensus state machine, and save the finalized proposal (if any) + if finalizedProposal := t.runStates(ctx); finalizedProposal != nil { + ch <- finalizedProposal + } + }() + + return ch +} + +// startRound starts the consensus round. +// It is a required middle step (proposal evaluation) before +// the state machine is in full swing and +// the runs the same flow for everyone (proposer / non-proposers) +func (t *Tendermint) startRound(height, round uint64) { + // 14: if proposer(hp, roundP) = p then + // + // The proposal value can either be: + // - an old (valid / locked) proposal from a previous round + // - a completely new proposal (built from scratch) + // + // 15: if validValueP != nil then + // 16: proposal ← validValueP + var ( + proposal = t.state.validValue + proposalRound = t.state.validRound + ) + + // Check if a new proposal needs to be built + if proposal == nil { + t.logger.Info( + "building a proposal", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // No previous valid value present, + // build a new proposal + // + // 17: else + // 18: proposal ← getValue() + proposal = t.node.BuildProposal(height) + } + + // Build the propose message + var ( + proposeMessage = t.buildProposalMessage(proposal, proposalRound) + id = t.node.Hash(proposal) + ) + + // Broadcast the proposal to other consensus nodes + // + // 19: broadcast + t.broadcast.BroadcastPropose(proposeMessage) + + // Save the accepted proposal in the state. + // NOTE: This is different from validValue / lockedValue, + // since they require a 2F+1 quorum of specific messages + // in order to be set, whereas this is simply a reference + // value for different states (prevote, precommit) + t.state.acceptedProposal = proposal + t.state.acceptedProposalID = id + + // Build and broadcast the prevote message + // + // 24/30: broadcast + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(id)) + + // Since the current process is the proposer, + // it can directly move to the prevote state + // 27/33: stepP ← prevote + t.state.step.set(prevote) +} + +// runStates runs the consensus states, depending on the current step +func (t *Tendermint) runStates(ctx context.Context) *FinalizedProposal { + for { + currentStep := t.state.step.get() + + select { + case <-ctx.Done(): + return nil + default: + switch currentStep { + case propose: + t.runPropose(ctx) + case prevote: + t.runPrevote(ctx) + case precommit: + return t.runPrecommit(ctx) + } + } + } +} + +// runPropose runs the propose state in which the process +// waits for a valid PROPOSE message. +// This state handles the following situations: +// +// - The proposer for view (hP, roundP) has proposed a value with a proposal round -1 (first ever proposal for height) +// 22: upon from proposer(hP, roundP) while stepP = propose do +// 23: if valid(v) ∧ (lockedRoundP = −1 ∨ lockedValueP = v) then +// 24: broadcast +// 25: else +// 26: broadcast +// 27: stepP ← prevote +// +// - The proposer for view (hP, roundP) has proposed a value that was accepted in some previous round +// 28: upon from proposer(hP, roundP) AND 2f + 1 +// while stepP = propose ∧ (vr >= 0 ∧ vr < roundP) do +// 29: if valid(v) ∧ (lockedRoundP ≤ vr ∨ lockedValueP = v) then +// 30: broadcast +// 31: else +// 32: broadcast +// 33: stepP ← prevote +// +// NOTE: the proposer for view (height, round) will send ONLY 1 proposal, be it a new one or an old agreed value +func (t *Tendermint) runPropose(ctx context.Context) { + var ( + height = t.state.getHeight() + round = t.state.getRound() + + lockedRound = t.state.lockedRound + lockedValue = t.state.lockedValue + ) + + t.logger.Debug( + "entering propose state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Check if the current process is the proposer for this view + if t.verifier.IsProposer(t.node.ID(), height, round) { + // Start the round by constructing and broadcasting a proposal + t.startRound(height, round) + + return + } + + // The current process is NOT the proposer, schedule a timeout + // + // 21: schedule OnTimeoutPropose(hP , roundP) to be executed after timeoutPropose(roundP) + var ( + expiredCh = make(chan struct{}, 1) + timerCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPropose = t.timeouts[propose].CalculateTimeout(round) + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + t.logger.Debug( + "scheduling timeoutPropose", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPropose), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timerCtx, timeoutPropose, expiredCh) + + // Subscribe to all propose messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPropose() + defer unsubscribeFn() + + // Set up the verification callback. + // The idea is to get the single proposal from the proposer for the view (height, round), + // and verify if it is valid. + // If it turns out the proposal is not valid (the first one received), + // then the protocol needs to move to the prevote state, after + // broadcasting a PREVOTE message with a NIL ID + isFromProposerFn := func(proposal *types.ProposalMessage) bool { + // Make sure the proposal view matches the process view + if round != proposal.GetView().GetRound() { + return false + } + + // Check if the proposal came from the proposer + // for the current view + return t.verifier.IsProposer(proposal.GetSender(), height, round) + } + + // Validates the proposal by examining the proposal params + isValidProposal := func(proposal []byte, proposalRound int64) bool { + // Basic proposal message verification + if proposalRound < 0 { + // Make sure there is no locked round (-1), OR + // that the locked value matches the proposal value + if lockedRound != -1 && !bytes.Equal(lockedValue, proposal) { + return false + } + } else { + // Make sure the proposal round is an earlier round + // than the current process round (old proposal) + if proposalRound >= int64(round) { + return false + } + + // Make sure the locked round value is <= the proposal round, OR + // that the locked value matches the proposal value + if lockedRound > proposalRound && !bytes.Equal(lockedValue, proposal) { + return false + } + } + + // Make sure the proposal itself is valid + return t.verifier.IsValidProposal(proposal, height) + } + + // Create the message cache (local to this context only) + cache := newMessageCache[*types.ProposalMessage](isFromProposerFn) + + for { + select { + case <-ctx.Done(): + return + case <-expiredCh: + // Broadcast a PREVOTE message with a NIL ID + // 59: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(nil)) + + // Move to the prevote state + // 60: stepP ← prevote + t.state.step.set(prevote) + + return + case getMessagesFn := <-ch: + // Add the messages to the cache + cache.addMessages(getMessagesFn()) + + // Check if at least 1 proposal message is valid, + // after validation and filtering + proposalMessages := cache.getMessages() + + if len(proposalMessages) == 0 { + // No valid proposal message yet + continue + } + + proposalMessage := proposalMessages[0] + + // Validate the proposal received + if !isValidProposal(proposalMessage.Proposal, proposalMessage.ProposalRound) { + // Broadcast a PREVOTE message with a NIL ID + // 26: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + // 32: broadcast ⟨PREVOTE, hP, roundP, nil⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(nil)) + + // Move to the prevote state + // 27: stepP ← prevote + // 33: stepP ← prevote + t.state.step.set(prevote) + + t.logger.Debug( + "received invalid proposal", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + return + } + + // Get the proposal from the message + proposal := proposalMessage.GetProposal() + + // Generate the proposal ID + id := t.node.Hash(proposal) + + // Accept the proposal, since it is valid + t.state.acceptedProposal = proposal + t.state.acceptedProposalID = id + + // Broadcast the PREVOTE message with a valid ID + // 24: broadcast ⟨PREVOTE, hP, roundP, id(v)⟩ + // 30: broadcast ⟨PREVOTE, hP, roundP, id(v)⟩ + t.broadcast.BroadcastPrevote(t.buildPrevoteMessage(id)) + + // Move to the prevote state + // 27: stepP ← prevote + // 33: stepP ← prevote + t.state.step.set(prevote) + + return + } + } +} + +// runPrevote runs the prevote state in which the process +// waits for a valid PREVOTE messages. +// This state handles the following situations: +// +// - A validator has received 2F+1 PREVOTE messages with a valid ID for the previously accepted proposal +// 36: upon ... AND 2f + 1 while valid(v) ∧ stepP ≥ prevote for the first time do +// 37: if stepP = prevote then +// 38: lockedValueP ← v +// 39: lockedRoundP ← roundP +// 40: broadcast +// 41: stepP ← precommit +// 42: validValueP ← v +// 43: validRoundP ← roundP +// +// - A validator has received 2F+1 PREVOTE messages with a NIL ID +// 44: upon 2f + 1 ⟨PREVOTE, hp, roundP, nil⟩ while stepP = prevote do +// 45: broadcast ⟨PRECOMMIT, hp, roundP, nil⟩ +// 46: stepP ← precommit + +// - A validator has received 2F+1 PREVOTE messages with any kind of ID (valid / NIL) +// 34: upon 2f + 1 while stepP = prevote for the first time do +// 35: schedule OnTimeoutPrevote(hP , roundP) to be executed after timeoutPrevote(roundP) +func (t *Tendermint) runPrevote(ctx context.Context) { + var ( + height = t.state.getHeight() + round = t.state.getRound() + acceptedProposalID = t.state.acceptedProposalID + + expiredCh = make(chan struct{}, 1) + timeoutCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPrevote = t.timeouts[prevote].CalculateTimeout(round) + ) + + t.logger.Debug( + "entering prevote state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + // Subscribe to all prevote messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPrevote() + defer unsubscribeFn() + + var ( + isValidFn = func(prevote *types.PrevoteMessage) bool { + // Make sure the prevote view matches the process view + return round == prevote.GetView().GetRound() + } + nilMiddleware = func(prevote *types.PrevoteMessage) bool { + // Make sure the ID is NIL + return prevote.Identifier == nil + } + matchingIDMiddleware = func(prevote *types.PrevoteMessage) bool { + // Make sure the ID matches the accepted proposal ID + return bytes.Equal(acceptedProposalID, prevote.Identifier) + } + ) + + var ( + summedPrevotes = newMessageCache[*types.PrevoteMessage](isValidFn) + nilCache = newMessageCache[*types.PrevoteMessage](nilMiddleware) + nonNilCache = newMessageCache[*types.PrevoteMessage](matchingIDMiddleware) + + timeoutScheduled = false + ) + + for { + select { + case <-ctx.Done(): + return + case <-expiredCh: + // Build and broadcast the prevote message, with an ID of NIL + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(nil)) + + t.state.step.set(precommit) + + return + case getMessagesFn := <-ch: + // Combine the prevote messages (NIL and non-NIL) + summedPrevotes.addMessages(getMessagesFn()) + prevotes := summedPrevotes.getMessages() + + convertedMessages := make([]Message, 0, len(prevotes)) + messages.ConvertToInterface( + prevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there is a super majority for the sum prevotes, to schedule a timeout + if !timeoutScheduled && t.hasSuperMajority(convertedMessages) { + // 35: schedule OnTimeoutPrevote(hp, roundP) to be executed after timeoutPrevote(roundP) + t.logger.Debug( + "scheduling timeoutPrevote", + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPrevote), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timeoutCtx, timeoutPrevote, expiredCh) + + timeoutScheduled = true + } + + // Filter the NIL prevote messages + nilCache.addMessages(prevotes) + nilPrevotes := nilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nilPrevotes)) + messages.ConvertToInterface( + nilPrevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 NIL prevote messages + if t.hasSuperMajority(convertedMessages) { + // 45: broadcast ⟨PRECOMMIT, hp, roundP, nil⟩ + // 46: stepP ← precommit + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(nil)) + t.state.step.set(precommit) + + return + } + + // Filter the non-NIL prevote messages + nonNilCache.addMessages(prevotes) + nonNilPrevotes := nonNilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nonNilPrevotes)) + messages.ConvertToInterface( + nonNilPrevotes, + func(m *types.PrevoteMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 non-NIL prevote messages + if t.hasSuperMajority(convertedMessages) { + // 38: lockedValueP ← v + // 39: lockedRoundP ← roundP + t.state.lockedRound = int64(round) + t.state.lockedValue = t.state.acceptedProposal + + // 40: broadcast + t.broadcast.BroadcastPrecommit(t.buildPrecommitMessage(acceptedProposalID)) + + // 41: stepP ← precommit + t.state.step.set(precommit) + + // 42: validValueP ← v + // 43: validRoundP ← roundP + t.state.validValue = t.state.acceptedProposal + t.state.validRound = int64(round) + + return + } + } + } +} + +// runPrecommit runs the precommit state in which the process +// waits for a valid PRECOMMIT messages. +// This state handles the following situations: +// +// - A validator has received 2F+1 PRECOMMIT messages with a valid ID for the previously accepted proposal +// 49: upon from proposer(hP, r) AND 2f + 1 +// while decisionP[hP] = nil do +// 50: if valid(v) then +// 51: decisionP[hp] = v +// 52: hP ← hP + 1 +// 53: reset lockedRoundP, lockedValueP, validRoundP and validValueP to initial values and empty message log +// 54: StartRound(0) +// +// - A validator has received 2F+1 PRECOMMIT messages with any value (valid ID or NIL) +// 47: upon 2f + 1 for the first time do +// 48: schedule OnTimeoutPrecommit(hP , roundP) to be executed after timeoutPrecommit(roundP) +func (t *Tendermint) runPrecommit(ctx context.Context) *FinalizedProposal { + var ( + height = t.state.getHeight() + round = t.state.getRound() + acceptedProposalID = t.state.acceptedProposalID + + expiredCh = make(chan struct{}, 1) + timeoutCtx, cancelTimeoutFn = context.WithCancel(ctx) + timeoutPrecommit = t.timeouts[precommit].CalculateTimeout(round) + ) + + t.logger.Debug( + "entering precommit state", + slog.Uint64("height", height), + slog.Uint64("round", round), + slog.String("node", string(t.node.ID())), + ) + + // Defer the timeout timer cancellation + defer cancelTimeoutFn() + + // Subscribe to all precommit messages + // (=current height; unique; >= current round) + ch, unsubscribeFn := t.store.subscribeToPrecommit() + defer unsubscribeFn() + + var ( + isValidFn = func(precommit *types.PrecommitMessage) bool { + // Make sure the precommit view matches the process view + return round == precommit.GetView().GetRound() + } + nonNilIDFn = func(precommit *types.PrecommitMessage) bool { + // Make sure the precommit ID is not nil + if precommit.Identifier == nil { + return false + } + + // Make sure the ID matches the accepted proposal ID + return bytes.Equal(acceptedProposalID, precommit.Identifier) + } + ) + + var ( + summedPrecommits = newMessageCache[*types.PrecommitMessage](isValidFn) + nonNilCache = newMessageCache[*types.PrecommitMessage](nonNilIDFn) + + timeoutScheduled = false + ) + + for { + select { + case <-ctx.Done(): + // Context cancelled, no proposal is finalized + return nil + case <-expiredCh: + // Timeout triggered, no proposal is finalized + return nil + case getMessagesFn := <-ch: + // Combine the precommit messages (NIL and non-NIL) + summedPrecommits.addMessages(getMessagesFn()) + precommits := summedPrecommits.getMessages() + + convertedMessages := make([]Message, 0, len(precommits)) + messages.ConvertToInterface( + precommits, + func(m *types.PrecommitMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there is a super majority for the sum precommits, to schedule a timeout + if !timeoutScheduled && t.hasSuperMajority(convertedMessages) { + // 48: schedule OnTimeoutPrecommit(hP, roundP) to be executed after timeoutPrecommit(roundP) + t.logger.Debug( + "scheduling timeoutPrecommit", + slog.Uint64("round", round), + slog.Duration("timeout", timeoutPrecommit), + slog.String("node", string(t.node.ID())), + ) + + t.scheduleTimeout(timeoutCtx, timeoutPrecommit, expiredCh) + + timeoutScheduled = true + } + + // Filter the non-NIL precommit messages + nonNilCache.addMessages(precommits) + nonNilPrecommits := nonNilCache.getMessages() + + convertedMessages = make([]Message, 0, len(nonNilPrecommits)) + messages.ConvertToInterface( + nonNilPrecommits, + func(m *types.PrecommitMessage) { + convertedMessages = append(convertedMessages, m) + }, + ) + + // Check if there are 2F+1 non-NIL precommit messages + if t.hasSuperMajority(convertedMessages) { + return newFinalizedProposal( + t.state.acceptedProposal, + t.state.acceptedProposalID, + ) + } + } + } +} diff --git a/tm2/pkg/libtm/core/tendermint_test.go b/tm2/pkg/libtm/core/tendermint_test.go new file mode 100644 index 00000000000..db80ba04956 --- /dev/null +++ b/tm2/pkg/libtm/core/tendermint_test.go @@ -0,0 +1,2258 @@ +package core + +import ( + "bytes" + "context" + "fmt" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTendermint_AddMessage_Invalid(t *testing.T) { + t.Parallel() + + t.Run("invalid signature", func(t *testing.T) { + t.Parallel() + + var ( + signature = []byte("invalid signature") + message = &types.PrevoteMessage{ + View: &types.View{}, + Sender: []byte{}, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return false + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(_ []byte) bool { + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrInvalidMessageSignature, + ) + }) + + t.Run("sender is not a validator", func(t *testing.T) { + t.Parallel() + + var ( + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{}, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return false + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrMessageFromNonValidator, + ) + }) + + t.Run("message is for an earlier height", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 10, + Round: 0, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{ + Height: currentView.Height - 1, // earlier height + Round: currentView.Round, + }, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrEarlierHeightMessage, + ) + }) + + t.Run("message is for an earlier round", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: &types.View{ + Height: currentView.Height, + Round: currentView.Round - 1, // earlier round + }, + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + ErrEarlierRoundMessage, + ) + }) + + t.Run("invalid proposal message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.ProposalMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddProposalMessage(message), + types.ErrInvalidMessageView, + ) + }) + + t.Run("invalid prevote message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrevoteMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrevoteMessage(message), + types.ErrInvalidMessageView, + ) + }) + + t.Run("invalid precommit message payload", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + signature = []byte("valid signature") + sender = []byte("sender") + + message = &types.PrecommitMessage{ + View: nil, // invalid view + Sender: sender, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + assert.ErrorIs( + t, + tm.AddPrecommitMessage(message), + types.ErrInvalidMessageView, + ) + }) +} + +func TestTendermint_AddMessage_Valid(t *testing.T) { + t.Parallel() + + t.Run("valid proposal message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + proposal = []byte("proposal") + + message = &types.ProposalMessage{ + View: currentView, + Sender: sender, + Proposal: proposal, + ProposalRound: -1, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPropose() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddProposalMessage(message)) + + // Make sure the message is present in the store + var messages []*types.ProposalMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetProposal(), + storeMessage.GetProposal(), + ) + assert.Equal( + t, + message.GetProposalRound(), + storeMessage.GetProposalRound(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) + + t.Run("valid prevote message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + id = []byte("prevote ID") + + message = &types.PrevoteMessage{ + View: currentView, + Sender: sender, + Identifier: id, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPrevote() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddPrevoteMessage(message)) + + // Make sure the message is present in the store + var messages []*types.PrevoteMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetIdentifier(), + storeMessage.GetIdentifier(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) + + t.Run("valid precommit message", func(t *testing.T) { + t.Parallel() + + var ( + currentView = &types.View{ + Height: 1, + Round: 10, + } + + signature = []byte("valid signature") + sender = []byte("sender") + id = []byte("precommit ID") + + message = &types.PrecommitMessage{ + View: currentView, + Sender: sender, + Identifier: id, + Signature: signature, + } + + signer = &mockSigner{ + isValidSignatureFn: func(_ []byte, sig []byte) bool { + require.Equal(t, signature, sig) + + return true + }, + } + verifier = &mockVerifier{ + isValidatorFn: func(from []byte) bool { + require.Equal(t, sender, from) + + return true + }, + } + ) + + tm := NewTendermint( + verifier, + nil, + nil, + signer, + ) + tm.state.setHeight(currentView.Height) + tm.state.setRound(currentView.Round) + + sub, unsubFn := tm.store.subscribeToPrecommit() + defer unsubFn() + + // Make sure the message is added + require.NoError(t, tm.AddPrecommitMessage(message)) + + // Make sure the message is present in the store + var messages []*types.PrecommitMessage + select { + case getMessages := <-sub: + messages = getMessages() + case <-time.After(5 * time.Second): + } + + require.Len(t, messages, 1) + + storeMessage := messages[0] + + assert.Equal( + t, + message.GetIdentifier(), + storeMessage.GetIdentifier(), + ) + assert.Equal( + t, + message.GetView(), + storeMessage.GetView(), + ) + assert.Equal( + t, + message.GetSender(), + storeMessage.GetSender(), + ) + }) +} + +func TestTendermint_FinalizeProposal_Propose(t *testing.T) { + t.Parallel() + + t.Run("validator is the proposer", func(t *testing.T) { + t.Parallel() + + t.Run("validator builds new proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + broadcastPropose *types.ProposalMessage + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + require.Equal(t, id, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + buildProposalFn: func(h uint64) []byte { + require.Equal(t, view.GetHeight(), h) + + return proposal + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastProposeFn: func(proposalMessage *types.ProposalMessage) { + broadcastPropose = proposalMessage + }, + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast propose was valid + require.NotNil(t, broadcastPropose) + require.NotNil(t, tm.state.acceptedProposal) + assert.Equal(t, broadcastPropose.GetProposal(), tm.state.acceptedProposal) + + assert.True(t, view.Equals(broadcastPropose.GetView())) + assert.Equal(t, id, broadcastPropose.GetSender()) + assert.Equal(t, signature, broadcastPropose.GetSignature()) + assert.Equal(t, proposal, broadcastPropose.GetProposal()) + assert.EqualValues(t, -1, broadcastPropose.GetProposalRound()) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator uses old proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 10, + } + proposal = []byte("old proposal") + proposalRound = int64(5) + + broadcastPropose *types.ProposalMessage + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + require.Equal(t, id, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + buildProposalFn: func(_ uint64) []byte { + t.FailNow() + + return nil + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastProposeFn: func(proposalMessage *types.ProposalMessage) { + broadcastPropose = proposalMessage + }, + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the old proposal + tm.state.validValue = proposal + tm.state.validRound = proposalRound + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast propose was valid + require.NotNil(t, broadcastPropose) + require.NotNil(t, tm.state.acceptedProposal) + assert.Equal(t, broadcastPropose.GetProposal(), tm.state.acceptedProposal) + + assert.True(t, view.Equals(broadcastPropose.GetView())) + assert.Equal(t, id, broadcastPropose.GetSender()) + assert.Equal(t, signature, broadcastPropose.GetSignature()) + assert.Equal(t, proposal, broadcastPropose.GetProposal()) + assert.EqualValues(t, proposalRound, broadcastPropose.GetProposalRound()) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + }) + + t.Run("validator is not the proposer", func(t *testing.T) { + t.Parallel() + + t.Run("validator receives valid fresh proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the correct proposal was accepted + assert.Equal(t, proposalMessage.GetProposal(), tm.state.acceptedProposal) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives valid locked proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 10, + } + proposal = []byte("proposal") + proposalRound = int64(5) + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the correct proposal was accepted + assert.Equal(t, proposalMessage.GetProposal(), tm.state.acceptedProposal) + assert.Equal(t, hash, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was valid + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Equal(t, hash, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives invalid fresh proposal", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives locked proposal from an invalid round", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: lockedRound + 1, // invalid proposal round + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives invalid locked proposal (round mismatch)", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 5, + } + proposal = []byte("proposal") + + lockedRound = int64(view.Round - 1) // earlier round + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: lockedRound - 1, // invalid proposal round + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Set the locked round + tm.state.lockedRound = lockedRound + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator receives a proposal that is not valid", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + hash = []byte("hash") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: proposerID, + Signature: signature, + Proposal: proposal, + ProposalRound: -1, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + isValidProposalFn: func(p []byte, h uint64) bool { + require.Equal(t, proposal, p) + require.EqualValues(t, view.GetHeight(), h) + + return false // invalid proposal + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + hashFn: func(p []byte) []byte { + require.Equal(t, proposal, p) + + return hash + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(raw []byte, signed []byte) bool { + require.Equal(t, proposalMessage.GetSignaturePayload(), raw) + require.Equal(t, signature, signed) + + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Add in the proposal message + require.NoError(t, tm.AddProposalMessage(proposalMessage)) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + + t.Run("validator does not receive a proposal in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + proposerID = []byte("proposer ID") + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + broadcastPrevote *types.PrevoteMessage + + mockVerifier = &mockVerifier{ + isProposerFn: func(nodeID []byte, h uint64, r uint64) bool { + if bytes.Equal(id, nodeID) { + return false + } + + require.Equal(t, proposerID, nodeID) + require.EqualValues(t, view.GetHeight(), h) + require.EqualValues(t, view.GetRound(), r) + + return true + }, + isValidatorFn: func(id []byte) bool { + require.Equal(t, proposerID, id) + + return true + }, + } + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrevoteFn: func(prevoteMessage *types.PrevoteMessage) { + broadcastPrevote = prevoteMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + WithProposeTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, prevote, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + // Make sure the correct proposal was not accepted + assert.Nil(t, tm.state.acceptedProposal) + assert.Nil(t, tm.state.acceptedProposalID) + + // Make sure the broadcast prevote was NIL + require.NotNil(t, broadcastPrevote) + assert.True(t, view.Equals(broadcastPrevote.GetView())) + assert.Equal(t, id, broadcastPrevote.GetSender()) + assert.Equal(t, signature, broadcastPrevote.GetSignature()) + assert.Nil(t, broadcastPrevote.GetIdentifier()) + }) + }) +} + +// generatePrevoteMessages generates basic prevote messages +// using the given view and ID +func generatePrevoteMessages( + t *testing.T, + count int, + view *types.View, + id []byte, +) []*types.PrevoteMessage { + t.Helper() + + messages := make([]*types.PrevoteMessage, count) + + for i := 0; i < count; i++ { + messages[i] = &types.PrevoteMessage{ + View: view, + Sender: []byte(fmt.Sprintf("sender %d", i)), + Signature: []byte("signature"), + Identifier: id, + } + } + + return messages +} + +// generatePrecommitMessages generates basic precommit messages +// using the given view and ID +func generatePrecommitMessages( + t *testing.T, + count int, + view *types.View, + id []byte, +) []*types.PrecommitMessage { + t.Helper() + + messages := make([]*types.PrecommitMessage, count) + + for i := 0; i < count; i++ { + messages[i] = &types.PrecommitMessage{ + View: view, + Sender: []byte(fmt.Sprintf("sender %d", i)), + Signature: []byte("signature"), + Identifier: id, + } + } + + return messages +} + +func TestTendermint_FinalizeProposal_Prevote(t *testing.T) { + t.Parallel() + + t.Run("validator received 2F+1 PREVOTEs with a valid ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + View: view, + Sender: []byte("proposer"), + Signature: []byte("proposer signature"), + Proposal: proposal, + ProposalRound: -1, + } + + numPrevotes = 10 + prevoteMessages = generatePrevoteMessages(t, numPrevotes, view, proposalID) + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrevotes) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL prevote messages + for _, prevoteMessage := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + assert.EqualValues(t, view.Round, tm.state.lockedRound) + assert.Equal(t, proposalMessage.GetProposal(), tm.state.lockedValue) + assert.Equal(t, proposalMessage.GetProposal(), tm.state.validValue) + assert.EqualValues(t, view.Round, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Equal(t, proposalID, broadcastPrecommit.GetIdentifier()) + }) + + t.Run("validator received 2F+1 PREVOTEs with a NIL ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + numPrevotes = 10 + prevoteMessages = generatePrevoteMessages(t, numPrevotes, view, nil) + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrevotes) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, mockBroadcast, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL prevote messages + for _, prevoteMessage := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + assert.EqualValues(t, -1, tm.state.lockedRound) + assert.Nil(t, tm.state.lockedValue) + assert.Nil(t, tm.state.validValue) + assert.EqualValues(t, -1, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Nil(t, broadcastPrecommit.GetIdentifier()) + }) + + t.Run("validator does not receive quorum PREVOTEs in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + totalPrevoteCount = 10 + nilPrevoteMessages = generatePrevoteMessages(t, totalPrevoteCount/2, view, nil) + nonNilPrevoteMessages = generatePrevoteMessages(t, totalPrevoteCount/2, view, proposalID) + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + broadcastPrecommit *types.PrecommitMessage + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(totalPrevoteCount) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + mockBroadcast = &mockBroadcast{ + broadcastPrecommitFn: func(precommitMessage *types.PrecommitMessage) { + broadcastPrecommit = precommitMessage + + // Stop the execution + cancelFn() + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + mockBroadcast, + mockSigner, + WithPrevoteTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = prevote + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in non-NIL prevote messages + for index, prevoteMessage := range nonNilPrevoteMessages { + // Change the senders for the non-NIL prevote messages. + // The reason the senders need to be changed is, so we can simulate the following scenario: + // 1/2 of the total voting power sent in non-NIL prevote messages + // 1/2 of the total voting power sent in NIL prevote messages + // In turn, there is a super majority when their voting powers are summed (non-NIL and NIL) + prevoteMessage.Sender = []byte(fmt.Sprintf("sender %d", index)) + + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Add in NIL prevote messages + for index, prevoteMessage := range nilPrevoteMessages { + // Change the senders for the NIL prevote messages + prevoteMessage.Sender = []byte(fmt.Sprintf("sender %d", index+len(nonNilPrevoteMessages))) + + require.NoError(t, tm.AddPrevoteMessage(prevoteMessage)) + } + + // Run through the states + tm.finalizeProposal(ctx) + + tm.wg.Wait() + + // Make sure the correct state was updated + require.Equal(t, precommit, tm.state.step) + assert.True(t, view.Equals(tm.state.view)) + + assert.EqualValues(t, -1, tm.state.lockedRound) + assert.Nil(t, tm.state.lockedValue) + assert.Nil(t, tm.state.validValue) + assert.EqualValues(t, -1, tm.state.validRound) + + // Make sure the broadcast precommit was valid + require.NotNil(t, broadcastPrecommit) + assert.True(t, view.Equals(broadcastPrecommit.GetView())) + assert.Equal(t, id, broadcastPrecommit.GetSender()) + assert.Equal(t, signature, broadcastPrecommit.GetSignature()) + assert.Nil(t, broadcastPrecommit.GetIdentifier()) + }) +} + +func TestTendermint_FinalizeProposal_Precommit(t *testing.T) { + t.Parallel() + + t.Run("validator received 2F+1 PRECOMMITs with a valid ID", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + proposalMessage = &types.ProposalMessage{ + Proposal: proposal, + } + + numPrecommits = 10 + precommitMessages = generatePrecommitMessages(t, numPrecommits, view, proposalID) + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(numPrecommits) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint(mockVerifier, mockNode, &mockBroadcast{}, mockSigner) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = precommit + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in 2F+1 non-NIL precommit messages + for _, precommitMessage := range precommitMessages { + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Run through the states + finalizedProposalCh := tm.finalizeProposal(ctx) + + // Get the finalized proposal + finalizedProposal := <-finalizedProposalCh + + cancelFn() + tm.wg.Wait() + + // Make sure the finalized proposal is valid + require.NotNil(t, finalizedProposal) + assert.Equal(t, proposalMessage.Proposal, finalizedProposal.Data) + }) + + t.Run("validator does not receive quorum PRECOMMITs in time", func(t *testing.T) { + t.Parallel() + + // Create the execution context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + var ( + id = []byte("node ID") + signature = []byte("signature") + view = &types.View{ + Height: 10, + Round: 0, + } + proposalID = []byte("proposal ID") + proposal = []byte("proposal") + + totalPrecommitCount = 10 + nilPrecommitMessages = generatePrecommitMessages(t, totalPrecommitCount/2, view, nil) + nonNilPrecommitMessages = generatePrecommitMessages(t, totalPrecommitCount/2, view, proposalID) + + timeout = Timeout{ + Initial: 100 * time.Millisecond, + Delta: 0, + } + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(totalPrecommitCount) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + + mockNode = &mockNode{ + idFn: func() []byte { + return id + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return signature + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + mockNode, + &mockBroadcast{}, + mockSigner, + WithPrecommitTimeout(timeout), + ) + tm.state.setHeight(view.Height) + tm.state.setRound(view.Round) + tm.state.step = precommit + tm.state.acceptedProposal = proposal + tm.state.acceptedProposalID = proposalID + + // Add in non-NIL precommit messages + for index, precommitMessage := range nonNilPrecommitMessages { + // Change the senders for the non-NIL precommit messages. + // The reason the senders need to be changed is, so we can simulate the following scenario: + // 1/2 of the total voting power sent in non-NIL precommit messages + // 1/2 of the total voting power sent in NIL precommit messages + // In turn, there is a super majority when their voting powers are summed (non-NIL and NIL) + precommitMessage.Sender = []byte(fmt.Sprintf("sender %d", index)) + + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Add in NIL precommit messages + for index, precommitMessage := range nilPrecommitMessages { + // Change the senders for the NIL precommit messages + precommitMessage.Sender = []byte(fmt.Sprintf("sender %d", index+len(nonNilPrecommitMessages))) + + require.NoError(t, tm.AddPrecommitMessage(precommitMessage)) + } + + // Run through the states + finalizedProposal := <-tm.finalizeProposal(ctx) + + cancelFn() + tm.wg.Wait() + + // Make sure the finalized proposal is NIL + assert.Nil(t, finalizedProposal) + }) +} + +func TestTendermint_WatchForFutureRounds(t *testing.T) { + t.Parallel() + + var ( + processView = &types.View{ + Height: 10, + Round: 5, + } + view = &types.View{ + Height: processView.Height, + Round: processView.Round + 5, // higher round + } + + totalMessagesPerType = 10 + prevoteMessages = generatePrevoteMessages(t, totalMessagesPerType/4, view, []byte("proposal ID")) + precommitMessages = generatePrecommitMessages(t, totalMessagesPerType, view, []byte("proposal ID")) + + mockVerifier = &mockVerifier{ + getTotalVotingPowerFn: func(h uint64) uint64 { + require.EqualValues(t, view.Height, h) + + return uint64(len(prevoteMessages) + len(precommitMessages)) + }, + getSumVotingPowerFn: func(messages []Message) uint64 { + return uint64(len(messages)) + }, + isValidatorFn: func(_ []byte) bool { + return true + }, + } + mockSigner = &mockSigner{ + signFn: func(b []byte) []byte { + require.NotNil(t, b) + + return []byte("signature") + }, + isValidSignatureFn: func(_ []byte, _ []byte) bool { + return true + }, + } + ) + + // Create the tendermint instance + tm := NewTendermint( + mockVerifier, + nil, + nil, + mockSigner, + ) + + // Set the process view + tm.state.setHeight(processView.GetHeight()) + tm.state.setRound(processView.GetRound()) + + // Make sure F+1 messages are added to the + // message queue, with a higher round + for _, message := range prevoteMessages { + require.NoError(t, tm.AddPrevoteMessage(message)) + } + + for _, message := range precommitMessages { + require.NoError(t, tm.AddPrecommitMessage(message)) + } + + // Set up the wait context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + // Wait for future round jumps + nextRound := <-tm.watchForRoundJumps(ctx) + + tm.wg.Wait() + + // Make sure the correct round was returned + assert.Equal(t, view.GetRound(), nextRound) +} diff --git a/tm2/pkg/libtm/core/timeout.go b/tm2/pkg/libtm/core/timeout.go new file mode 100644 index 00000000000..71462f87a3a --- /dev/null +++ b/tm2/pkg/libtm/core/timeout.go @@ -0,0 +1,62 @@ +package core + +import ( + "context" + "time" +) + +// getDefaultTimeoutMap returns the default timeout map +// for the Tendermint consensus engine +func getDefaultTimeoutMap() map[step]Timeout { + return map[step]Timeout{ + propose: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + prevote: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + precommit: { + Initial: 10 * time.Second, // 10s + Delta: 500 * time.Millisecond, // 0.5 + }, + } +} + +// Timeout is a holder for timeout duration information (constant) +type Timeout struct { + Initial time.Duration // the initial timeout duration + Delta time.Duration // the delta for future timeouts +} + +// CalculateTimeout calculates a new timeout duration using +// the formula: +// +// timeout(r) = initTimeout + r * timeoutDelta +func (t Timeout) CalculateTimeout(round uint64) time.Duration { + return t.Initial + time.Duration(round)*t.Delta +} + +// scheduleTimeout schedules a state timeout to be executed +func (t *Tendermint) scheduleTimeout( + ctx context.Context, + timeout time.Duration, + expiredCh chan<- struct{}, +) { + t.wg.Add(1) + + go func() { + defer t.wg.Done() + + select { + case <-ctx.Done(): + case <-time.After(timeout): + // Signal that the state expired + select { + case expiredCh <- struct{}{}: + default: + } + } + }() +} diff --git a/tm2/pkg/libtm/core/timeout_test.go b/tm2/pkg/libtm/core/timeout_test.go new file mode 100644 index 00000000000..ec57cc385fd --- /dev/null +++ b/tm2/pkg/libtm/core/timeout_test.go @@ -0,0 +1,93 @@ +package core + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeout_CalculateTimeout(t *testing.T) { + t.Parallel() + + var ( + initial = 10 * time.Second + delta = 200 * time.Millisecond + + tm = Timeout{ + Initial: initial, + Delta: delta, + } + ) + + for round := uint64(0); round < 100; round++ { + assert.Equal( + t, + initial+time.Duration(round)*delta, + tm.CalculateTimeout(round), + ) + } +} + +func TestTimeout_ScheduleTimeoutPropose(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + step step + }{ + { + "OnTimeoutPropose", + propose, + }, + { + "OnTimeoutPrevote", + prevote, + }, + { + "OnTimeoutPrecommit", + precommit, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + expiredCh := make(chan struct{}, 1) + + tm := NewTendermint( + nil, + nil, + nil, + nil, + ) + + // Set the timeout data for the step + tm.timeouts[testCase.step] = Timeout{ + Initial: 50 * time.Millisecond, + Delta: 50 * time.Millisecond, + } + + // Schedule the timeout + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + timeoutPropose := tm.timeouts[testCase.step].CalculateTimeout(0) + + tm.scheduleTimeout(ctx, timeoutPropose, expiredCh) + + // Wait for the timer to trigger + select { + case <-time.After(5 * time.Second): + t.Fatal("timer not triggered") + case <-expiredCh: + } + + tm.wg.Wait() + }) + } +} diff --git a/tm2/pkg/libtm/core/types.go b/tm2/pkg/libtm/core/types.go new file mode 100644 index 00000000000..ba31f454d90 --- /dev/null +++ b/tm2/pkg/libtm/core/types.go @@ -0,0 +1,83 @@ +package core + +import ( + "github.com/gnolang/libtm/messages/types" +) + +// Signer is an abstraction over the signature manipulation process +type Signer interface { + // Sign generates a signature for the given raw data + Sign(data []byte) []byte + + // IsValidSignature verifies whether the signature matches the raw data + IsValidSignature(data []byte, signature []byte) bool +} + +// Verifier is an abstraction over the outer consensus calling context +// that has access to validator set information +type Verifier interface { + // IsProposer checks if the given ID matches the proposer for the given height + IsProposer(id []byte, height uint64, round uint64) bool + + // IsValidator checks if the given message sender ID belongs to a validator + IsValidator(id []byte) bool + + // IsValidProposal checks if the given proposal is valid, for the given height + IsValidProposal(proposal []byte, height uint64) bool + + // GetSumVotingPower returns the summed voting power from + // the given unique message authors + GetSumVotingPower(msgs []Message) uint64 + + // GetTotalVotingPower returns the total voting power + // of the entire validator set for the given height + GetTotalVotingPower(height uint64) uint64 +} + +// Node interface is an abstraction over a single entity (current process) that runs +// the consensus algorithm +type Node interface { + // ID returns the ID associated with the current process (validator) + ID() []byte + + // Hash generates a hash of the given data. + // It must not modify the slice proposal, even temporarily + // and must not retain the data + Hash(proposal []byte) []byte + + // BuildProposal generates a raw proposal for the given height + BuildProposal(height uint64) []byte +} + +// Broadcast is an abstraction over the networking / message sharing interface +// that enables message passing between validators +type Broadcast interface { + // BroadcastPropose broadcasts a PROPOSAL message + BroadcastPropose(message *types.ProposalMessage) + + // BroadcastPrevote broadcasts a PREVOTE message + BroadcastPrevote(message *types.PrevoteMessage) + + // BroadcastPrecommit broadcasts a PRECOMMIT message + BroadcastPrecommit(message *types.PrecommitMessage) +} + +// Message is the content being passed around +// between consensus validators. +// Message types: PROPOSAL, PREVOTE, PRECOMMIT +type Message interface { + // GetView fetches the message view + GetView() *types.View + + // GetSender fetches the message sender + GetSender() []byte + + // GetSignature fetches the message signature + GetSignature() []byte + + // GetSignaturePayload fetches the signature payload (sign data) + GetSignaturePayload() []byte + + // Verify verifies the message content is valid (base verification) + Verify() error +} diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod new file mode 100644 index 00000000000..a611b2a4454 --- /dev/null +++ b/tm2/pkg/libtm/go.mod @@ -0,0 +1,15 @@ +module github.com/gnolang/libtm + +go 1.21 + +require ( + github.com/rs/xid v1.5.0 + github.com/stretchr/testify v1.9.0 + google.golang.org/protobuf v1.34.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/tm2/pkg/libtm/go.sum b/tm2/pkg/libtm/go.sum new file mode 100644 index 00000000000..2a141a0fa78 --- /dev/null +++ b/tm2/pkg/libtm/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tm2/pkg/libtm/golangci.yaml b/tm2/pkg/libtm/golangci.yaml new file mode 100644 index 00000000000..00982e70b54 --- /dev/null +++ b/tm2/pkg/libtm/golangci.yaml @@ -0,0 +1,116 @@ +run: + concurrency: 8 + timeout: 10m + issue-exit-code: 1 + tests: true + skip-dirs-use-default: true + modules-download-mode: readonly + allow-parallel-runners: false + go: "" + +output: + uniq-by-line: false + path-prefix: "" + sort-results: true + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + new: false + fix: false + exclude-rules: + - path: (.+)_test.go + linters: + - nilnil + - gosec + +linters: + fast: false + disable-all: true + enable: + - asasalint # Check for pass []any as any in variadic func(...any) + - asciicheck # Detects funky ASCII characters + - bidichk # Checks for dangerous unicode character sequences + - durationcheck # Check for two durations multiplied together + - errcheck # Forces to not skip error check + - exportloopref # Checks for pointers to enclosing loop variables + - gocritic # Bundles different linting checks + - godot # Checks for periods at the end of comments + - gomoddirectives # Allow or ban replace directives in go.mod + - gosimple # Code simplification + - govet # Official Go tool + - ineffassign # Detects when assignments to existing variables are not used + - nakedret # Finds naked/bare returns and requires change them + - nilerr # Requires explicit returns + - nilnil # Requires explicit returns + - promlinter # Lints Prometheus metrics names + - reassign # Checks that package variables are not reassigned + - revive # Drop-in replacement for golint + - tenv # Detects using os.Setenv instead of t.Setenv + - testableexamples # Checks if examples are testable (have expected output) + - unparam # Finds unused params + - usestdlibvars # Detects the possibility to use variables/constants from stdlib + - wastedassign # Finds wasted assignment statements + - loggercheck # Checks the odd number of key and value pairs for common logger libraries + - nestif # Finds deeply nested if statements + - nonamedreturns # Reports all named returns + - decorder # Check declaration order of types, consts, vars and funcs + - gocheckcompilerdirectives # Checks that compiler directive comments (//go:) are valid + - gochecknoinits # Checks for init methods + - whitespace # Tool for detection of leading and trailing whitespace + - wsl # Forces you to use empty lines + - unconvert # Unnecessary type conversions + - tparallel # Detects inappropriate usage of t.Parallel() method in your Go test codes + - thelper # Detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - stylecheck # Stylecheck is a replacement for golint + - prealloc # Finds slice declarations that could potentially be pre-allocated + - predeclared # Finds code that shadows one of Go's predeclared identifiers + - nolintlint # Ill-formed or insufficient nolint directives + - nlreturn # Checks for a new line before return and branch statements to increase code clarity + - misspell # Misspelled English words in comments + - makezero # Finds slice declarations with non-zero initial length + - lll # Long lines + - importas # Enforces consistent import aliases + - gosec # Security problems + - gofmt # Whether the code was gofmt-ed + - gofumpt # Stricter gofmt + - goimports # Unused imports + - goconst # Repeated strings that could be replaced by a constant + - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) + - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - unused # Checks Go code for unused constants, variables, functions and types + +linters-settings: + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - hugeParam + - rangeExprCopy + - rangeValCopy + - importShadow + - unnamedResult + errcheck: + check-type-assertions: false + check-blank: true + exclude-functions: + - io/ioutil.ReadFile + - io.Copy(*bytes.Buffer) + - io.Copy(os.Stdout) + nakedret: + max-func-lines: 1 + govet: + enable-all: true + gofmt: + simplify: true + goconst: + min-len: 3 + min-occurrences: 3 + godot: + scope: all + period: false diff --git a/tm2/pkg/libtm/messages/collector.go b/tm2/pkg/libtm/messages/collector.go new file mode 100644 index 00000000000..e94283f0ac1 --- /dev/null +++ b/tm2/pkg/libtm/messages/collector.go @@ -0,0 +1,176 @@ +package messages + +import ( + "fmt" + "strconv" + "strings" + "sync" + + "github.com/gnolang/libtm/messages/types" +) + +// msgType is the combined message type interface, +// for easy reference and type safety +type msgType interface { + types.ProposalMessage | types.PrevoteMessage | types.PrecommitMessage +} + +// this is because Go doesn't support covariance on slices +// []*T -> []I does not work +func ConvertToInterface[T msgType](msgs []*T, convertFunc func(m *T)) { + for _, msg := range msgs { + convertFunc(msg) + } +} + +type ( + // collection are the actual received messages. + // Maps a unique identifier -> their message (of a specific type) to avoid duplicates. + // Identifiers are derived from . + // Each validator in the consensus needs to send at most 1 message of every type + // (minus the PROPOSAL, which is only sent by the proposer), + // so the message system needs to keep track of only 1 message per type, per validator, per view + collection[T msgType] map[string]*T +) + +// Collector is a single message type collector +type Collector[T msgType] struct { + collection collection[T] // the message storage + subscriptions subscriptions[T] // the active message subscriptions + + collectionMux sync.RWMutex + subscriptionsMux sync.RWMutex +} + +// NewCollector creates a new message collector +func NewCollector[T msgType]() *Collector[T] { + return &Collector[T]{ + collection: make(collection[T]), + subscriptions: make(subscriptions[T]), + } +} + +// Subscribe creates a new collector subscription. +// Returns the channel for receiving messages, +// as well as the unsubscribe method +func (c *Collector[T]) Subscribe() (<-chan func() []*T, func()) { + c.subscriptionsMux.Lock() + defer c.subscriptionsMux.Unlock() + + // Create a new subscription + id, ch := c.subscriptions.add() + + // Create the unsubscribe callback + unsubscribeFn := func() { + c.subscriptionsMux.Lock() + defer c.subscriptionsMux.Unlock() + + c.subscriptions.remove(id) + } + + // Notify the subscription immediately, + // since there can be existing messages in the collection. + // This action assumes the channel is not blocking (created with initial size), + // since the calling context does not have access to it yet at this point + notifySubscription(ch, c.GetMessages) + + return ch, unsubscribeFn +} + +// GetMessages returns the currently present messages in the collector +func (c *Collector[T]) GetMessages() []*T { + c.collectionMux.RLock() + defer c.collectionMux.RUnlock() + + // Fetch the messages in the collection + return c.collection.getMessages() +} + +// getMessages fetches the messages in the collection +func (c *collection[T]) getMessages() []*T { + messages := make([]*T, 0, len(*c)) + + for _, senderMessage := range *c { + messages = append(messages, senderMessage) + } + + return messages +} + +// DropMessages drops all messages from the collection if their view +// is less than the given view (earlier) +func (c *Collector[T]) DropMessages(view *types.View) { + c.collectionMux.RLock() + defer c.collectionMux.RUnlock() + + // Filter out messages from the collection + shouldStay := func(key string) bool { + // Construct the view from the message + messageView := getViewFromKey(key) + + // Only messages who are >= the current view stay + return messageView.Height >= view.Height && + messageView.Round >= view.Round + } + + // Filter out the messages + c.collection.dropMessages(shouldStay) +} + +// dropMessages drops messages from the collection using the given filter +func (c *collection[T]) dropMessages(shouldStay func(string) bool) { + for key := range *c { + if shouldStay(key) { + continue + } + + delete(*c, key) + } +} + +// AddMessage adds a new message to the collector +func (c *Collector[T]) AddMessage(view *types.View, from []byte, message *T) { + c.collectionMux.Lock() + + // Add the message + c.collection.addMessage( + getCollectionKey(from, view), + message, + ) + + c.collectionMux.Unlock() + + // Notify the subscriptions + c.subscriptionsMux.RLock() + defer c.subscriptionsMux.RUnlock() + + c.subscriptions.notify(c.GetMessages) +} + +// addMessage adds a new message to the collection +func (c *collection[T]) addMessage(key string, message *T) { + (*c)[key] = message +} + +// getCollectionKey constructs a key based on the +// message sender and view information. +// This key guarantees uniqueness in the message store +func getCollectionKey(from []byte, view *types.View) string { + return fmt.Sprintf("%s_%d_%d", from, view.Height, view.Round) +} + +// getViewFromKey constructs the view information, +// based on the collection key +func getViewFromKey(key string) *types.View { + // Split the key + keyParts := strings.Split(key, "_") + + // Parse the view values + height, _ := strconv.ParseUint(keyParts[1], 10, 64) //nolint:errcheck // Key is valid + round, _ := strconv.ParseUint(keyParts[2], 10, 64) //nolint:errcheck // Key is valid + + return &types.View{ + Height: height, + Round: round, + } +} diff --git a/tm2/pkg/libtm/messages/collector_test.go b/tm2/pkg/libtm/messages/collector_test.go new file mode 100644 index 00000000000..192b3d088e8 --- /dev/null +++ b/tm2/pkg/libtm/messages/collector_test.go @@ -0,0 +1,440 @@ +package messages + +import ( + "sort" + "strconv" + "sync" + "testing" + "time" + + "github.com/gnolang/libtm/messages/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateProposalMessages generates dummy proposal messages +// for the given view and type +func generateProposalMessages( + t *testing.T, + count int, + view *types.View, +) []*types.ProposalMessage { + t.Helper() + + messages := make([]*types.ProposalMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.ProposalMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +// generatePrevoteMessages generates dummy prevote messages +// for the given view and type +func generatePrevoteMessages( + t *testing.T, + count int, + view *types.View, +) []*types.PrevoteMessage { + t.Helper() + + messages := make([]*types.PrevoteMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.PrevoteMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +// generatePrevoteMessages generates dummy prevote messages +// for the given view and type +func generatePrecommitMessages( + t *testing.T, + count int, + view *types.View, +) []*types.PrecommitMessage { + t.Helper() + + messages := make([]*types.PrecommitMessage, 0, count) + + for index := 0; index < count; index++ { + message := &types.PrecommitMessage{ + Sender: []byte(strconv.Itoa(index)), + View: view, + } + + messages = append(messages, message) + } + + return messages +} + +func TestCollector_AddMessage(t *testing.T) { + t.Parallel() + + t.Run("empty message queue", func(t *testing.T) { + t.Parallel() + + // Create the collector + c := NewCollector[types.ProposalMessage]() + + // Fetch the messages + messages := c.GetMessages() + + require.NotNil(t, messages) + assert.Len(t, messages, 0) + }) + + t.Run("valid PROPOSAL messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.ProposalMessage]() + + generatedMessages := generateProposalMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.ProposalMessage, 0, count) + + for _, proposal := range generatedMessages { + c.AddMessage(proposal.View, proposal.Sender, proposal) + + expectedMessages = append(expectedMessages, proposal) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages Sender the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("valid PREVOTE messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages from the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("valid PRECOMMIT messages fetched", func(t *testing.T) { + t.Parallel() + + var ( + count = 5 + initialView = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrecommitMessage]() + + generatedMessages := generatePrecommitMessages( + t, + count, + initialView, + ) + + expectedMessages := make([]*types.PrecommitMessage, 0, count) + + for _, precommit := range generatedMessages { + c.AddMessage(precommit.View, precommit.Sender, precommit) + + expectedMessages = append(expectedMessages, precommit) + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Get the messages Sender the store + messages := c.GetMessages() + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) +} + +func TestCollector_AddDuplicateMessages(t *testing.T) { + t.Parallel() + + var ( + count = 5 + commonSender = []byte("sender 1") + view = &types.View{ + Height: 1, + Round: 1, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + for _, prevote := range generatedMessages { + // Make sure each message is from the same sender + prevote.Sender = commonSender + + c.AddMessage(prevote.View, prevote.Sender, prevote) + } + + // Check that only 1 message has been added + assert.Len(t, c.GetMessages(), 1) +} + +func TestCollector_Subscribe(t *testing.T) { + t.Parallel() + + t.Run("subscribe with pre-existing messages", func(t *testing.T) { + t.Parallel() + + var ( + count = 100 + view = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + // Create a subscription + notifyCh, unsubscribeFn := c.Subscribe() + defer unsubscribeFn() + + var messages []*types.PrevoteMessage + + select { + case callback := <-notifyCh: + messages = callback() + case <-time.After(5 * time.Second): + } + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) + + t.Run("subscribe with no pre-existing messages", func(t *testing.T) { + t.Parallel() + + var ( + count = 100 + view = &types.View{ + Height: 1, + Round: 0, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + generatedMessages := generatePrevoteMessages( + t, + count, + view, + ) + + expectedMessages := make([]*types.PrevoteMessage, 0, count) + + // Create a subscription + notifyCh, unsubscribeFn := c.Subscribe() + defer unsubscribeFn() + + for _, prevote := range generatedMessages { + c.AddMessage(prevote.View, prevote.Sender, prevote) + + expectedMessages = append(expectedMessages, prevote) + } + + var ( + messages []*types.PrevoteMessage + + wg sync.WaitGroup + ) + + wg.Add(1) + + go func() { + defer wg.Done() + + select { + case callback := <-notifyCh: + messages = callback() + case <-time.After(5 * time.Second): + } + }() + + wg.Wait() + + // Sort the messages for the test + sort.SliceStable(expectedMessages, func(i, j int) bool { + return string(expectedMessages[i].Sender) < string(expectedMessages[j].Sender) + }) + + // Sort the messages for the test + sort.SliceStable(messages, func(i, j int) bool { + return string(messages[i].Sender) < string(messages[j].Sender) + }) + + // Make sure the messages match + assert.Equal(t, expectedMessages, messages) + }) +} + +func TestCollector_DropMessages(t *testing.T) { + t.Parallel() + + var ( + count = 5 + view = &types.View{ + Height: 10, + Round: 5, + } + earlierView = &types.View{ + Height: view.Height, + Round: view.Round - 1, + } + ) + + // Create the collector + c := NewCollector[types.PrevoteMessage]() + + // Generate latest round messages + latestRoundMessages := generatePrevoteMessages( + t, + count, + view, + ) + + // Generate earlier round messages + earlierRoundMessages := generatePrevoteMessages( + t, + count, + earlierView, + ) + + for _, message := range latestRoundMessages { + c.AddMessage(message.GetView(), message.GetSender(), message) + } + + for _, message := range earlierRoundMessages { + c.AddMessage(message.GetView(), message.GetSender(), message) + } + + // Drop the older messages + c.DropMessages(view) + + // Make sure the messages were dropped + fetchedMessages := c.GetMessages() + + require.Len(t, fetchedMessages, len(latestRoundMessages)) + assert.ElementsMatch(t, fetchedMessages, latestRoundMessages) +} diff --git a/tm2/pkg/libtm/messages/subscription.go b/tm2/pkg/libtm/messages/subscription.go new file mode 100644 index 00000000000..c3d3c23333d --- /dev/null +++ b/tm2/pkg/libtm/messages/subscription.go @@ -0,0 +1,57 @@ +package messages + +import "github.com/rs/xid" + +type ( + // MsgCallback is the callback that returns all given messages + MsgCallback[T msgType] func() []*T + + // subscriptions is the subscription store, + // maps subscription id -> notification channel. + // Usage of this type is NOT thread safe + subscriptions[T msgType] map[string]chan func() []*T +) + +// add adds a new subscription to the subscription map. +// Returns the subscription ID, and update channel +func (s *subscriptions[T]) add() (string, chan func() []*T) { + var ( + id = xid.New().String() + ch = make(chan func() []*T, 1) + ) + + (*s)[id] = ch + + return id, ch +} + +// remove removes the given subscription +func (s *subscriptions[T]) remove(id string) { + if ch := (*s)[id]; ch != nil { + // Close the notification channel + close(ch) + } + + // Delete the subscription + delete(*s, id) +} + +// notify notifies all subscription listeners +func (s *subscriptions[T]) notify(callback func() []*T) { + // Notify the listeners + for _, ch := range *s { + notifySubscription(ch, callback) + } +} + +// notifySubscription alerts the notification channel +// about a callback. This function is pure syntactic sugar +func notifySubscription[T msgType]( + ch chan func() []*T, + callback MsgCallback[T], +) { + select { + case ch <- callback: + default: + } +} diff --git a/tm2/pkg/libtm/messages/types/messages.go b/tm2/pkg/libtm/messages/types/messages.go new file mode 100644 index 00000000000..d43e0be8a27 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages.go @@ -0,0 +1,208 @@ +package types + +import ( + "bytes" + "errors" + + "google.golang.org/protobuf/proto" +) + +var ( + ErrInvalidMessageView = errors.New("invalid message view") + ErrInvalidMessageSender = errors.New("invalid message sender") + ErrInvalidMessageProposal = errors.New("invalid message proposal") + ErrInvalidMessageProposalRound = errors.New("invalid message proposal round") +) + +func (v *View) Equals(view *View) bool { + if v.GetHeight() != view.GetHeight() { + return false + } + + return v.GetRound() == view.GetRound() +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *ProposalMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&ProposalMessage{ + View: m.View, + Sender: m.Sender, + Proposal: m.Proposal, + ProposalRound: m.ProposalRound, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *ProposalMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&ProposalMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Proposal: m.Proposal, + ProposalRound: m.ProposalRound, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *ProposalMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + // Make sure the proposal is present + if m.Proposal == nil { + return ErrInvalidMessageProposal + } + + // Make sure the proposal round is + // for a good round value + if m.ProposalRound < -1 { + return ErrInvalidMessageProposalRound + } + + return nil +} + +func (m *ProposalMessage) Equals(message *ProposalMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + if !bytes.Equal(m.GetProposal(), message.GetProposal()) { + return false + } + + return m.GetProposalRound() == message.GetProposalRound() +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *PrevoteMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrevoteMessage{ + View: m.View, + Sender: m.Sender, + Identifier: m.Identifier, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *PrevoteMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrevoteMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Identifier: m.Identifier, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *PrevoteMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + return nil +} + +func (m *PrevoteMessage) Equals(message *PrevoteMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + return bytes.Equal(m.GetIdentifier(), message.GetIdentifier()) +} + +// GetSignaturePayload returns the sign payload for the proposal message +func (m *PrecommitMessage) GetSignaturePayload() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrecommitMessage{ + View: m.View, + Sender: m.Sender, + Identifier: m.Identifier, + }) + + return raw +} + +// Marshal returns the marshalled message +func (m *PrecommitMessage) Marshal() []byte { + //nolint:errcheck // No need to verify the error + raw, _ := proto.Marshal(&PrecommitMessage{ + View: m.View, + Sender: m.Sender, + Signature: m.Signature, + Identifier: m.Identifier, + }) + + return raw +} + +// Verify validates that the given message is valid +func (m *PrecommitMessage) Verify() error { + // Make sure the view is present + if m.View == nil { + return ErrInvalidMessageView + } + + // Make sure the sender is present + if m.Sender == nil { + return ErrInvalidMessageSender + } + + return nil +} + +func (m *PrecommitMessage) Equals(message *PrecommitMessage) bool { + if !m.GetView().Equals(message.GetView()) { + return false + } + + if !bytes.Equal(m.GetSender(), message.GetSender()) { + return false + } + + if !bytes.Equal(m.GetSignature(), message.GetSignature()) { + return false + } + + return bytes.Equal(m.GetIdentifier(), message.GetIdentifier()) +} diff --git a/tm2/pkg/libtm/messages/types/messages.pb.go b/tm2/pkg/libtm/messages/types/messages.pb.go new file mode 100644 index 00000000000..daa70cb84de --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages.pb.go @@ -0,0 +1,542 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.3 +// source: messages/types/proto/messages.proto + +package types + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// MessageType defines the types of messages +// that are related to the consensus process +type MessageType int32 + +const ( + MessageType_PROPOSAL MessageType = 0 + MessageType_PREVOTE MessageType = 1 + MessageType_PRECOMMIT MessageType = 2 +) + +// Enum value maps for MessageType. +var ( + MessageType_name = map[int32]string{ + 0: "PROPOSAL", + 1: "PREVOTE", + 2: "PRECOMMIT", + } + MessageType_value = map[string]int32{ + "PROPOSAL": 0, + "PREVOTE": 1, + "PRECOMMIT": 2, + } +) + +func (x MessageType) Enum() *MessageType { + p := new(MessageType) + *p = x + return p +} + +func (x MessageType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MessageType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_types_proto_messages_proto_enumTypes[0].Descriptor() +} + +func (MessageType) Type() protoreflect.EnumType { + return &file_messages_types_proto_messages_proto_enumTypes[0] +} + +func (x MessageType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MessageType.Descriptor instead. +func (MessageType) EnumDescriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{0} +} + +// View is the consensus state associated with the message +type View struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // height represents the number of the proposal + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // round represents the round number within a + // specific height (starts from 0) + Round uint64 `protobuf:"varint,2,opt,name=round,proto3" json:"round,omitempty"` +} + +func (x *View) Reset() { + *x = View{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *View) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*View) ProtoMessage() {} + +func (x *View) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use View.ProtoReflect.Descriptor instead. +func (*View) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{0} +} + +func (x *View) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *View) GetRound() uint64 { + if x != nil { + return x.Round + } + return 0 +} + +// ProposalMessage is the message containing +// the consensus proposal for the view +// +type ProposalMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // proposal is the actual consensus proposal + Proposal []byte `protobuf:"bytes,4,opt,name=proposal,proto3" json:"proposal,omitempty"` + // proposalRound is the round associated with the + // proposal in the PROPOSE message. + // NOTE: this round value DOES NOT have + // to match the message view (proposal from an earlier round) + ProposalRound int64 `protobuf:"varint,5,opt,name=proposalRound,proto3" json:"proposalRound,omitempty"` +} + +func (x *ProposalMessage) Reset() { + *x = ProposalMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProposalMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposalMessage) ProtoMessage() {} + +func (x *ProposalMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposalMessage.ProtoReflect.Descriptor instead. +func (*ProposalMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{1} +} + +func (x *ProposalMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *ProposalMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *ProposalMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *ProposalMessage) GetProposal() []byte { + if x != nil { + return x.Proposal + } + return nil +} + +func (x *ProposalMessage) GetProposalRound() int64 { + if x != nil { + return x.ProposalRound + } + return 0 +} + +// PrevoteMessage is the message +// containing the consensus proposal prevote. +// The prevote message is pretty light, +// apart from containing the view, it just +// contains a unique identifier of the proposal +// for which this prevote is meant for (ex. proposal hash) +// +type PrevoteMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // identifier is the unique identifier for + // the proposal associated with this + // prevote message (ex. proposal hash) + Identifier []byte `protobuf:"bytes,4,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +func (x *PrevoteMessage) Reset() { + *x = PrevoteMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrevoteMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrevoteMessage) ProtoMessage() {} + +func (x *PrevoteMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrevoteMessage.ProtoReflect.Descriptor instead. +func (*PrevoteMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *PrevoteMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *PrevoteMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *PrevoteMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *PrevoteMessage) GetIdentifier() []byte { + if x != nil { + return x.Identifier + } + return nil +} + +// PrecommitMessage is the message +// containing the consensus proposal precommit. +// The precommit message, same as the prevote message, +// contains a unique identifier for the proposal +// for which this precommit is meant for (ex. proposal hash) +// +type PrecommitMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // view is the current view for the message + // (the view in which the message was sent) + View *View `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` + // sender is the message sender (unique identifier) + Sender []byte `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + // signature is the message signature of the sender + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + // identifier is the unique identifier for + // the proposal associated with this + // precommit message (ex. proposal hash) + Identifier []byte `protobuf:"bytes,4,opt,name=identifier,proto3" json:"identifier,omitempty"` +} + +func (x *PrecommitMessage) Reset() { + *x = PrecommitMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_types_proto_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrecommitMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrecommitMessage) ProtoMessage() {} + +func (x *PrecommitMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_types_proto_messages_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrecommitMessage.ProtoReflect.Descriptor instead. +func (*PrecommitMessage) Descriptor() ([]byte, []int) { + return file_messages_types_proto_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *PrecommitMessage) GetView() *View { + if x != nil { + return x.View + } + return nil +} + +func (x *PrecommitMessage) GetSender() []byte { + if x != nil { + return x.Sender + } + return nil +} + +func (x *PrecommitMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *PrecommitMessage) GetIdentifier() []byte { + if x != nil { + return x.Identifier + } + return nil +} + +var File_messages_types_proto_messages_proto protoreflect.FileDescriptor + +var file_messages_types_proto_messages_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, 0x04, 0x56, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xa4, 0x01, 0x0a, 0x0f, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x19, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, + 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, + 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0d, + 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x65, 0x76, 0x6f, 0x74, 0x65, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x76, + 0x69, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x56, 0x69, 0x65, 0x77, + 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2a, 0x37, 0x0a, 0x0b, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x50, + 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x45, + 0x56, 0x4f, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x10, 0x02, 0x42, 0x11, 0x5a, 0x0f, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_messages_types_proto_messages_proto_rawDescOnce sync.Once + file_messages_types_proto_messages_proto_rawDescData = file_messages_types_proto_messages_proto_rawDesc +) + +func file_messages_types_proto_messages_proto_rawDescGZIP() []byte { + file_messages_types_proto_messages_proto_rawDescOnce.Do(func() { + file_messages_types_proto_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_types_proto_messages_proto_rawDescData) + }) + return file_messages_types_proto_messages_proto_rawDescData +} + +var file_messages_types_proto_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_messages_types_proto_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_messages_types_proto_messages_proto_goTypes = []interface{}{ + (MessageType)(0), // 0: MessageType + (*View)(nil), // 1: View + (*ProposalMessage)(nil), // 2: ProposalMessage + (*PrevoteMessage)(nil), // 3: PrevoteMessage + (*PrecommitMessage)(nil), // 4: PrecommitMessage +} +var file_messages_types_proto_messages_proto_depIdxs = []int32{ + 1, // 0: ProposalMessage.view:type_name -> View + 1, // 1: PrevoteMessage.view:type_name -> View + 1, // 2: PrecommitMessage.view:type_name -> View + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_messages_types_proto_messages_proto_init() } +func file_messages_types_proto_messages_proto_init() { + if File_messages_types_proto_messages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_messages_types_proto_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*View); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProposalMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrevoteMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_types_proto_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrecommitMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_types_proto_messages_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_types_proto_messages_proto_goTypes, + DependencyIndexes: file_messages_types_proto_messages_proto_depIdxs, + EnumInfos: file_messages_types_proto_messages_proto_enumTypes, + MessageInfos: file_messages_types_proto_messages_proto_msgTypes, + }.Build() + File_messages_types_proto_messages_proto = out.File + file_messages_types_proto_messages_proto_rawDesc = nil + file_messages_types_proto_messages_proto_goTypes = nil + file_messages_types_proto_messages_proto_depIdxs = nil +} diff --git a/tm2/pkg/libtm/messages/types/messages_test.go b/tm2/pkg/libtm/messages/types/messages_test.go new file mode 100644 index 00000000000..516b5436098 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/messages_test.go @@ -0,0 +1,872 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestProposalMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &ProposalMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + Proposal: []byte("proposal"), + ProposalRound: 0, + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw ProposalMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetProposal(), raw.GetProposal()) + assert.Equal(t, m.GetProposalRound(), raw.GetProposalRound()) +} + +func TestProposalMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &ProposalMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + Proposal: []byte("proposal"), + ProposalRound: 0, + } + + // Marshal the message + marshalled := m.Marshal() + + var raw ProposalMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) + assert.Equal(t, m.GetProposal(), raw.GetProposal()) + assert.Equal(t, m.GetProposalRound(), raw.GetProposalRound()) +} + +func TestProposalMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) + + t.Run("invalid proposal", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: []byte{}, + Proposal: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageProposal) + }) + + t.Run("invalid proposal round", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{}, + Sender: []byte{}, + Proposal: []byte{}, + ProposalRound: -2, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageProposalRound) + }) + + t.Run("valid proposal message", func(t *testing.T) { + t.Parallel() + + m := &ProposalMessage{ + View: &View{ + Height: 1, + Round: 0, + }, + Sender: []byte("sender"), + Proposal: []byte("proposal"), + ProposalRound: -1, + } + + assert.NoError(t, m.Verify()) + }) +} + +func TestPrevoteMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrevoteMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw PrevoteMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) +} + +func TestPrevoteMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrevoteMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Marshal the message + marshalled := m.Marshal() + + var raw PrevoteMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) +} + +func TestPrevoteMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &PrevoteMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &PrevoteMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) +} + +func TestPrecommitMessage_GetSignaturePayload(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrecommitMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Get the signature payload + payload := m.GetSignaturePayload() + + var raw PrecommitMessage + + require.NoError(t, proto.Unmarshal(payload, &raw)) + + // Make sure the signature was not marshalled + assert.Nil(t, raw.Signature) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) +} + +func TestPrecommitMessage_Marshal(t *testing.T) { + t.Parallel() + + // Create the proposal message + m := &PrecommitMessage{ + View: &View{ + Height: 10, + Round: 10, + }, + Sender: []byte("sender"), + Signature: []byte("signature"), + } + + // Marshal the message + marshalled := m.Marshal() + + var raw PrecommitMessage + + require.NoError(t, proto.Unmarshal(marshalled, &raw)) + + // Make sure other fields are intact + assert.Equal(t, m.GetView().GetHeight(), raw.GetView().GetHeight()) + assert.Equal(t, m.GetView().GetRound(), raw.GetView().GetRound()) + assert.Equal(t, m.GetSender(), raw.GetSender()) + assert.Equal(t, m.GetSignature(), raw.GetSignature()) +} + +func TestPrecommitMessage_Verify(t *testing.T) { + t.Parallel() + + t.Run("invalid view", func(t *testing.T) { + t.Parallel() + + m := &PrecommitMessage{ + View: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageView) + }) + + t.Run("invalid sender", func(t *testing.T) { + t.Parallel() + + m := &PrecommitMessage{ + View: &View{}, + Sender: nil, + } + + assert.ErrorIs(t, m.Verify(), ErrInvalidMessageSender) + }) +} + +func TestView_Equals(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + views []*View + shouldEqual bool + }{ + { + "equal views", + []*View{ + { + Height: 10, + Round: 10, + }, + { + Height: 10, + Round: 10, + }, + }, + true, + }, + { + "not equal views", + []*View{ + { + Height: 10, + Round: 10, + }, + { + Height: 10, + Round: 5, // different round + }, + }, + false, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + assert.Equal( + t, + testCase.shouldEqual, + testCase.views[0].Equals(testCase.views[1]), + ) + }) + } +} + +func TestProposalMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal proposal messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Proposal: proposal, + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("proposal mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: []byte("different proposal"), // proposal mismatch + ProposalRound: proposalRound, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("proposal round mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + proposal = []byte("proposal") + proposalRound = int64(-1) + + left = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound, + } + + right = &ProposalMessage{ + View: view, + Sender: sender, + Signature: signature, + Proposal: proposal, + ProposalRound: proposalRound + 1, // proposal round mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} + +func TestPrevoteMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal prevote messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("identifier mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrevoteMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: []byte("different identifier"), // identifier mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} + +func TestPrecommitMessage_Equals(t *testing.T) { + t.Parallel() + + t.Run("equal precommit messages", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.True(t, left.Equals(right)) + }) + + t.Run("view mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: &View{ + Height: view.Height, + Round: view.Round + 1, // round mismatch + }, + Sender: sender, + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("sender mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: []byte("different sender"), // sender mismatch + Signature: signature, + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("signature mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: []byte("different signature"), // signature mismatch + Identifier: id, + } + ) + + assert.False(t, left.Equals(right)) + }) + + t.Run("identifier mismatch", func(t *testing.T) { + t.Parallel() + + var ( + view = &View{ + Height: 10, + Round: 0, + } + sender = []byte("sender") + signature = []byte("signature") + id = []byte("id") + + left = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: id, + } + + right = &PrecommitMessage{ + View: view, + Sender: sender, + Signature: signature, + Identifier: []byte("different identifier"), // identifier mismatch + } + ) + + assert.False(t, left.Equals(right)) + }) +} diff --git a/tm2/pkg/libtm/messages/types/proto/messages.proto b/tm2/pkg/libtm/messages/types/proto/messages.proto new file mode 100644 index 00000000000..b41c0e5efe8 --- /dev/null +++ b/tm2/pkg/libtm/messages/types/proto/messages.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +option go_package = "/messages/types"; + +// MessageType defines the types of messages +// that are related to the consensus process +enum MessageType { + PROPOSAL = 0; + PREVOTE = 1; + PRECOMMIT = 2; +} + +// View is the consensus state associated with the message +message View { + // height represents the number of the proposal + uint64 height = 1; + + // round represents the round number within a + // specific height (starts from 0) + uint64 round = 2; +} + +// ProposalMessage is the message containing +// the consensus proposal for the view +// +message ProposalMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // proposal is the actual consensus proposal + bytes proposal = 4; + + // proposalRound is the round associated with the + // proposal in the PROPOSE message. + // NOTE: this round value DOES NOT have + // to match the message view (proposal from an earlier round) + int64 proposalRound = 5; +} + +// PrevoteMessage is the message +// containing the consensus proposal prevote. +// The prevote message is pretty light, +// apart from containing the view, it just +// contains a unique identifier of the proposal +// for which this prevote is meant for (ex. proposal hash) +// +message PrevoteMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // identifier is the unique identifier for + // the proposal associated with this + // prevote message (ex. proposal hash) + bytes identifier = 4; +} + +// PrecommitMessage is the message +// containing the consensus proposal precommit. +// The precommit message, same as the prevote message, +// contains a unique identifier for the proposal +// for which this precommit is meant for (ex. proposal hash) +// +message PrecommitMessage { + // view is the current view for the message + // (the view in which the message was sent) + View view = 1; + + // sender is the message sender (unique identifier) + bytes sender = 2; + + // signature is the message signature of the sender + bytes signature = 3; + + // identifier is the unique identifier for + // the proposal associated with this + // precommit message (ex. proposal hash) + bytes identifier = 4; +} \ No newline at end of file diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 48aae35e10a..07692145fee 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -18,67 +18,67 @@ const ( // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer type P2PConfig struct { - RootDir string `toml:"home"` + RootDir string `json:"rpc" toml:"home"` // Address to listen for incoming connections - ListenAddress string `toml:"laddr" comment:"Address to listen for incoming connections"` + ListenAddress string `json:"laddr" toml:"laddr" comment:"Address to listen for incoming connections"` // Address to advertise to peers for them to dial - ExternalAddress string `toml:"external_address" comment:"Address to advertise to peers for them to dial\n If empty, will use the same port as the laddr,\n and will introspect on the listener or use UPnP\n to figure out the address."` + ExternalAddress string `json:"external_address" toml:"external_address" comment:"Address to advertise to peers for them to dial\n If empty, will use the same port as the laddr,\n and will introspect on the listener or use UPnP\n to figure out the address."` // Comma separated list of seed nodes to connect to - Seeds string `toml:"seeds" comment:"Comma separated list of seed nodes to connect to"` + Seeds string `json:"seeds" toml:"seeds" comment:"Comma separated list of seed nodes to connect to"` // Comma separated list of nodes to keep persistent connections to - PersistentPeers string `toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` + PersistentPeers string `json:"persistent_peers" toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` // UPNP port forwarding - UPNP bool `toml:"upnp" comment:"UPNP port forwarding"` + UPNP bool `json:"upnp" toml:"upnp" comment:"UPNP port forwarding"` // Maximum number of inbound peers - MaxNumInboundPeers int `toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` + MaxNumInboundPeers int `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` // Maximum number of outbound peers to connect to, excluding persistent peers - MaxNumOutboundPeers int `toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` + MaxNumOutboundPeers int `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` // Time to wait before flushing messages out on the connection - FlushThrottleTimeout time.Duration `toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` + FlushThrottleTimeout time.Duration `json:"flush_throttle_timeout" toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` // Maximum size of a message packet payload, in bytes - MaxPacketMsgPayloadSize int `toml:"max_packet_msg_payload_size" comment:"Maximum size of a message packet payload, in bytes"` + MaxPacketMsgPayloadSize int `json:"max_packet_msg_payload_size" toml:"max_packet_msg_payload_size" comment:"Maximum size of a message packet payload, in bytes"` // Rate at which packets can be sent, in bytes/second - SendRate int64 `toml:"send_rate" comment:"Rate at which packets can be sent, in bytes/second"` + SendRate int64 `json:"send_rate" toml:"send_rate" comment:"Rate at which packets can be sent, in bytes/second"` // Rate at which packets can be received, in bytes/second - RecvRate int64 `toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` + RecvRate int64 `json:"recv_rate" toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` // Set true to enable the peer-exchange reactor - PexReactor bool `toml:"pex" comment:"Set true to enable the peer-exchange reactor"` + PexReactor bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` // Seed mode, in which node constantly crawls the network and looks for // peers. If another node asks it for addresses, it responds and disconnects. // // Does not work if the peer-exchange reactor is disabled. - SeedMode bool `toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` + SeedMode bool `json:"seed_mode" toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` // Comma separated list of peer IDs to keep private (will not be gossiped to // other peers) - PrivatePeerIDs string `toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` + PrivatePeerIDs string `json:"private_peer_ids" toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` // Toggle to disable guard against peers connecting from the same ip. - AllowDuplicateIP bool `toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` + AllowDuplicateIP bool `json:"allow_duplicate_ip" toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` // Peer connection configuration. - HandshakeTimeout time.Duration `toml:"handshake_timeout" comment:"Peer connection configuration."` - DialTimeout time.Duration `toml:"dial_timeout"` + HandshakeTimeout time.Duration `json:"handshake_timeout" toml:"handshake_timeout" comment:"Peer connection configuration."` + DialTimeout time.Duration `json:"dial_timeout" toml:"dial_timeout"` // Testing params. // Force dial to fail - TestDialFail bool `toml:"test_dial_fail"` + TestDialFail bool `json:"test_dial_fail" toml:"test_dial_fail"` // FUzz connection - TestFuzz bool `toml:"test_fuzz"` - TestFuzzConfig *FuzzConnConfig `toml:"test_fuzz_config"` + TestFuzz bool `json:"test_fuzz" toml:"test_fuzz"` + TestFuzzConfig *FuzzConnConfig `json:"test_fuzz_config" toml:"test_fuzz_config"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer diff --git a/tm2/pkg/p2p/key.go b/tm2/pkg/p2p/key.go index 71e3459f418..a41edeb07f8 100644 --- a/tm2/pkg/p2p/key.go +++ b/tm2/pkg/p2p/key.go @@ -17,6 +17,7 @@ import ( // NodeKey is the persistent peer key. // It contains the nodes private key for authentication. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go type NodeKey struct { crypto.PrivKey `json:"priv_key"` // our priv key } diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 5066a7b1fde..49662b47a55 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -157,7 +157,6 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature if err != nil { return newCtx, res, true } - signerAccs[i], res = processSig(newCtx, sacc, stdSigs[i], signBytes, simulate, params, sigGasConsumer) if !res.IsOK() { return newCtx, res, true diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index f7c7f5c73b0..748aa424111 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -615,6 +615,9 @@ func (app *BaseApp) getContextForTx(mode RunTxMode, txBytes []byte) (ctx Context WithVoteInfos(app.voteInfos). WithConsensusParams(app.consensusParams) + // NOTE: This is especially required to simulate transactions because + // otherwise baseapp writes the antehandler mods (sequence and balance) + // to the underlying store for deliver and checktx. if mode == RunTxModeSimulate { ctx, _ = ctx.CacheContext() } diff --git a/tm2/pkg/sdk/sdk.proto b/tm2/pkg/sdk/sdk.proto index 828b17950cf..62fbfc19758 100644 --- a/tm2/pkg/sdk/sdk.proto +++ b/tm2/pkg/sdk/sdk.proto @@ -12,4 +12,4 @@ message Result { abci.ResponseBase response_base = 1 [json_name = "ResponseBase"]; sint64 gas_wanted = 2 [json_name = "GasWanted"]; sint64 gas_used = 3 [json_name = "GasUsed"]; -} +} \ No newline at end of file diff --git a/tm2/pkg/std/memfile.go b/tm2/pkg/std/memfile.go index f41619b56bd..01bc18c1487 100644 --- a/tm2/pkg/std/memfile.go +++ b/tm2/pkg/std/memfile.go @@ -8,8 +8,8 @@ import ( ) type MemFile struct { - Name string - Body string + Name string `json:"name" yaml:"name"` + Body string `json:"body" yaml:"body"` } // MemPackage represents the information and files of a package which will be @@ -19,9 +19,9 @@ type MemFile struct { // NOTE: in the future, a MemPackage may represent // updates/additional-files for an existing package. type MemPackage struct { - Name string // package name as declared by `package` - Path string // import path - Files []*MemFile + Name string `json:"name" yaml:"name"` // package name as declared by `package` + Path string `json:"path" yaml:"path"` // import path + Files []*MemFile `json:"files" yaml:"files"` } func (mempkg *MemPackage) GetFile(name string) *MemFile { @@ -90,18 +90,23 @@ func (mempkg *MemPackage) Validate() error { return nil } +const licenseName = "LICENSE" + // Splits a path into the dirpath and filename. func SplitFilepath(filepath string) (dirpath string, filename string) { parts := strings.Split(filepath, "/") if len(parts) == 1 { return parts[0], "" } - last := parts[len(parts)-1] - if strings.Contains(last, ".") { + + switch last := parts[len(parts)-1]; { + case strings.Contains(last, "."): return strings.Join(parts[:len(parts)-1], "/"), last - } else if last == "" { + case last == "": return strings.Join(parts[:len(parts)-1], "/"), "" - } else { - return strings.Join(parts, "/"), "" + case last == licenseName: + return strings.Join(parts[:len(parts)-1], "/"), licenseName } + + return strings.Join(parts, "/"), "" } diff --git a/tm2/pkg/std/memfile_test.go b/tm2/pkg/std/memfile_test.go index 7b944f75c4f..3e1fb49e131 100644 --- a/tm2/pkg/std/memfile_test.go +++ b/tm2/pkg/std/memfile_test.go @@ -270,3 +270,54 @@ func TestMemPackage_Validate(t *testing.T) { }) } } + +func TestSplitFilepath(t *testing.T) { + t.Parallel() + tests := []struct { + name string + filepath string + expDirPath string + expFilename string + }{ + { + name: "empty", + }, + { + name: "one part", + filepath: "root", + expDirPath: "root", + }, + { + name: "file", + filepath: "gno.land/r/demo/avl/avl.gno", + expDirPath: "gno.land/r/demo/avl", + expFilename: "avl.gno", + }, + { + name: "trailing slash", + filepath: "gno.land/r/demo/avl/", + expDirPath: "gno.land/r/demo/avl", + }, + { + name: "license", + filepath: "gno.land/r/demo/avl/LICENSE", + expDirPath: "gno.land/r/demo/avl", + expFilename: "LICENSE", + }, + { + name: "regular path", + filepath: "gno.land/p/demo/avl", + expDirPath: "gno.land/p/demo/avl", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + dirPath, filename := SplitFilepath(tt.filepath) + assert.Equal(t, tt.expDirPath, dirPath) + assert.Equal(t, tt.expFilename, filename) + }) + } +} diff --git a/tm2/pkg/store/cache/store.go b/tm2/pkg/store/cache/store.go index 1c5c2e27783..6bc6ad3b71f 100644 --- a/tm2/pkg/store/cache/store.go +++ b/tm2/pkg/store/cache/store.go @@ -3,13 +3,17 @@ package cache import ( "bytes" "container/list" + "fmt" + "reflect" "sort" "sync" + "github.com/gnolang/gno/tm2/pkg/colors" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" ) // If value is nil but deleted is false, it means the parent doesn't have the @@ -20,6 +24,12 @@ type cValue struct { dirty bool } +func (cv cValue) String() string { + return fmt.Sprintf("cValue{%s,%v,%v}", + colors.DefaultColoredBytes(cv.value), + cv.deleted, cv.dirty) +} + // cacheStore wraps an in-memory cache around an underlying types.Store. type cacheStore struct { mtx sync.Mutex @@ -32,12 +42,13 @@ type cacheStore struct { var _ types.Store = (*cacheStore)(nil) func New(parent types.Store) *cacheStore { - return &cacheStore{ + cs := &cacheStore{ cache: make(map[string]*cValue), unsortedCache: make(map[string]struct{}), sortedCache: list.New(), parent: parent, } + return cs } // Implements types.Store. @@ -112,6 +123,17 @@ func (store *cacheStore) Write() { } // Clear the cache + store.clear() +} + +func (store *cacheStore) Flush() { + store.Write() + if fs, ok := store.parent.(types.Flusher); ok { + fs.Flush() + } +} + +func (store *cacheStore) clear() { store.cache = make(map[string]*cValue) store.unsortedCache = make(map[string]struct{}) store.sortedCache = list.New() @@ -209,3 +231,23 @@ func (store *cacheStore) setCacheValue(key, value []byte, deleted bool, dirty bo store.unsortedCache[string(key)] = struct{}{} } } + +func (store *cacheStore) Print() { + fmt.Println(colors.Cyan("cacheStore.Print"), fmt.Sprintf("%p", store)) + for key, value := range store.cache { + fmt.Println( + colors.DefaultColoredBytesN([]byte(key), 50), + colors.DefaultColoredBytesN(value.value, 100), + "deleted", value.deleted, + "dirty", value.dirty, + ) + } + fmt.Println(colors.Cyan("cacheStore.Print"), fmt.Sprintf("%p", store), + "print parent", fmt.Sprintf("%p", store.parent), reflect.TypeOf(store.parent)) + if ps, ok := store.parent.(types.Printer); ok { + ps.Print() + } else { + utils.Print(store.parent) + } + fmt.Println(colors.Cyan("cacheStore.Print END"), fmt.Sprintf("%p", store)) +} diff --git a/tm2/pkg/store/gas/store.go b/tm2/pkg/store/gas/store.go index 4ffe46dc275..db5ea7a79b0 100644 --- a/tm2/pkg/store/gas/store.go +++ b/tm2/pkg/store/gas/store.go @@ -2,6 +2,7 @@ package gas import ( "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/store/utils" "github.com/gnolang/overflow" ) @@ -100,6 +101,22 @@ func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator { return gi } +func (gs *Store) Print() { + if ps, ok := gs.parent.(types.Printer); ok { + ps.Print() + } else { + utils.Print(gs.parent) + } +} + +func (gs *Store) Flush() { + if cts, ok := gs.parent.(types.Flusher); ok { + cts.Flush() + } else { + panic("underlying store does not implement Flush()") + } +} + type gasIterator struct { gasMeter types.GasMeter gasConfig types.GasConfig diff --git a/tm2/pkg/store/store.go b/tm2/pkg/store/store.go index 2950937f951..ba939fa1793 100644 --- a/tm2/pkg/store/store.go +++ b/tm2/pkg/store/store.go @@ -1,10 +1,7 @@ package store import ( - "fmt" - dbm "github.com/gnolang/gno/tm2/pkg/db" - "github.com/gnolang/gno/tm2/pkg/strings" "github.com/gnolang/gno/tm2/pkg/store/rootmulti" "github.com/gnolang/gno/tm2/pkg/store/types" @@ -27,26 +24,3 @@ func NewPruningOptionsFromString(strategy string) (opt PruningOptions) { } return } - -// TODO move to another file. -func Print(store Store) { - fmt.Println("//----------------------------------------") - fmt.Println("// store:", store) - itr := store.Iterator(nil, nil) - defer itr.Close() - for ; itr.Valid(); itr.Next() { - key, value := itr.Key(), itr.Value() - var keystr, valuestr string - if strings.IsASCIIText(string(key)) { - keystr = string(key) - } else { - keystr = fmt.Sprintf("0x%X", key) - } - if strings.IsASCIIText(string(value)) { - valuestr = string(value) - } else { - valuestr = fmt.Sprintf("0x%X", value) - } - fmt.Printf("%s: %s\n", keystr, valuestr) - } -} diff --git a/tm2/pkg/store/types/store.go b/tm2/pkg/store/types/store.go index 68ee110586c..a0e3925a265 100644 --- a/tm2/pkg/store/types/store.go +++ b/tm2/pkg/store/types/store.go @@ -40,7 +40,8 @@ type Store interface { // Returns a cache-wrapped store. CacheWrap() Store - // If cache-wrapped store, flushes to underlying store. + // If cache-wrapped store, writes to underlying store. + // Does not writes through layers of cache. Write() } @@ -55,6 +56,23 @@ type Queryable interface { Query(abci.RequestQuery) abci.ResponseQuery } +// Useful for debugging. +type Printer interface { + Print() +} + +// Write through all caches. +// You probably don't want this, rather write your program to Write() where +// appropriate. Not included in the main Store interface to discourage usage. +type Flusher interface { + Flush() +} + +// Write throgh +type Writer interface { + Write() +} + // ---------------------------------------- // MultiStore diff --git a/tm2/pkg/store/utils/print.go b/tm2/pkg/store/utils/print.go new file mode 100644 index 00000000000..d47f4188db7 --- /dev/null +++ b/tm2/pkg/store/utils/print.go @@ -0,0 +1,38 @@ +package utils + +import ( + "fmt" + "reflect" + + "github.com/gnolang/gno/tm2/pkg/colors" + "github.com/gnolang/gno/tm2/pkg/store/types" +) + +// TODO move to another file. +func Print(store types.Store) { + fmt.Println(colors.Blue("//----------------------------------------")) + if store == nil { + fmt.Println("") + } else if ps, ok := store.(types.Printer); ok { + ps.Print() + } else { + fmt.Println(colors.Blue(fmt.Sprintf("// store:%p %v", store, reflect.TypeOf(store)))) + itr := store.Iterator(nil, nil) + defer itr.Close() + for ; itr.Valid(); itr.Next() { + key, value := itr.Key(), itr.Value() + var keystr, valuestr string + keystr = colors.DefaultColoredBytesN(key, 100) + valuestr = fmt.Sprintf("(%d)", len(value)) + /* + if true || strings.IsASCIIText(string(value)) { + valuestr = string(value) + } else { + valuestr = fmt.Sprintf("0x%X", value) + } + */ + fmt.Printf("%s: %s\n", keystr, valuestr) + } + } + fmt.Println(colors.Blue("//------------------------------------ end")) +} diff --git a/tm2/pkg/strings/string.go b/tm2/pkg/strings/string.go index 46675923319..86ed70373a3 100644 --- a/tm2/pkg/strings/string.go +++ b/tm2/pkg/strings/string.go @@ -75,3 +75,12 @@ func StringSliceEqual(a, b []string) bool { } return true } + +// TrimN naively appens "..." to fit within n bytes. +func TrimN(s string, n int) string { + if len(s) <= n { + return s + } else { + return s[:n-3] + "..." + } +} diff --git a/tm2/pkg/telemetry/config/config.go b/tm2/pkg/telemetry/config/config.go index a5e991fbc89..a9aa24d7848 100644 --- a/tm2/pkg/telemetry/config/config.go +++ b/tm2/pkg/telemetry/config/config.go @@ -8,19 +8,21 @@ var errEndpointNotSet = errors.New("telemetry exporter endpoint not set") // Config is the configuration struct for the tm2 telemetry package type Config struct { - MetricsEnabled bool `toml:"enabled"` - MeterName string `toml:"meter_name"` - ServiceName string `toml:"service_name"` - ExporterEndpoint string `toml:"exporter_endpoint" comment:"the endpoint to export metrics to, like a local OpenTelemetry collector"` + MetricsEnabled bool `json:"enabled" toml:"enabled"` + MeterName string `json:"meter_name" toml:"meter_name"` + ServiceName string `json:"service_name" toml:"service_name"` + ServiceInstanceID string `json:"service_instance_id" toml:"service_instance_id" comment:"the ID helps to distinguish instances of the same service that exist at the same time (e.g. instances of a horizontally scaled service)"` + ExporterEndpoint string `json:"exporter_endpoint" toml:"exporter_endpoint" comment:"the endpoint to export metrics to, like a local OpenTelemetry collector"` } // DefaultTelemetryConfig is the default configuration used for the node func DefaultTelemetryConfig() *Config { return &Config{ - MetricsEnabled: false, - MeterName: "gno.land", - ServiceName: "gno.land", - ExporterEndpoint: "", + MetricsEnabled: false, + MeterName: "gno.land", + ServiceName: "gno.land", + ServiceInstanceID: "gno-node-1", + ExporterEndpoint: "", } } diff --git a/tm2/pkg/telemetry/metrics/metrics.go b/tm2/pkg/telemetry/metrics/metrics.go index 9d1839fa862..2b04769fe0c 100644 --- a/tm2/pkg/telemetry/metrics/metrics.go +++ b/tm2/pkg/telemetry/metrics/metrics.go @@ -3,10 +3,12 @@ package metrics import ( "context" "fmt" + "net/url" "github.com/gnolang/gno/tm2/pkg/telemetry/config" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/metric" sdkMetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" @@ -112,14 +114,35 @@ var ( ) func Init(config config.Config) error { - // Use oltp metric exporter - exp, err := otlpmetricgrpc.New( - context.Background(), - otlpmetricgrpc.WithEndpoint(config.ExporterEndpoint), - otlpmetricgrpc.WithInsecure(), + var ( + ctx = context.Background() + exp sdkMetric.Exporter ) + + u, err := url.Parse(config.ExporterEndpoint) if err != nil { - return fmt.Errorf("unable to create metrics exporter, %w", err) + return fmt.Errorf("error parsing exporter endpoint: %s, %w", config.ExporterEndpoint, err) + } + + // Use oltp metric exporter with http/https or grpc + switch u.Scheme { + case "http", "https": + exp, err = otlpmetrichttp.New( + ctx, + otlpmetrichttp.WithEndpointURL(config.ExporterEndpoint), + ) + if err != nil { + return fmt.Errorf("unable to create http metrics exporter, %w", err) + } + default: + exp, err = otlpmetricgrpc.New( + ctx, + otlpmetricgrpc.WithEndpoint(config.ExporterEndpoint), + otlpmetricgrpc.WithInsecure(), + ) + if err != nil { + return fmt.Errorf("unable to create grpc metrics exporter, %w", err) + } } provider := sdkMetric.NewMeterProvider( @@ -130,7 +153,7 @@ func Init(config config.Config) error { semconv.SchemaURL, semconv.ServiceNameKey.String(config.ServiceName), semconv.ServiceVersionKey.String("1.0.0"), - semconv.ServiceInstanceIDKey.String("gno-node-1"), + semconv.ServiceInstanceIDKey.String(config.ServiceInstanceID), ), ), )