diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml
index 64be9b4e829..bd8b53e7a25 100644
--- a/.github/workflows/misc.yml
+++ b/.github/workflows/misc.yml
@@ -71,7 +71,7 @@ jobs:
- name: Check go.mods
run: |
- set -e
+ set -xe
# Find all go.mod files
gomods=$(find . -type f -name go.mod)
diff --git a/contribs/Makefile b/contribs/Makefile
index b816987b733..a7414771ffa 100644
--- a/contribs/Makefile
+++ b/contribs/Makefile
@@ -4,9 +4,10 @@ help:
@cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /'
.PHONY: install
-install: install.gnomd
+install: install.gnomd install.gnodev
-install.gnomd:; cd gnomd && go install .
+install.gnomd:; cd gnomd && go install .
+install.gnodev:; $(MAKE) -C ./gnodev install
.PHONY: clean
clean:
@@ -21,6 +22,15 @@ GOFMT_FLAGS ?= -w
fmt:
$(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) .
+.PHONY: tidy
+tidy:
+ @for gomod in `find . -name go.mod`; do ( \
+ dir=`dirname $$gomod`; \
+ set -xe; \
+ cd $$dir; \
+ go mod tidy -v; \
+ ); done
+
########################################
# Test suite
GOTEST_FLAGS ?= -v -p 1 -timeout=30m
diff --git a/contribs/gnodev/Makefile b/contribs/gnodev/Makefile
new file mode 100644
index 00000000000..23fb22a372d
--- /dev/null
+++ b/contribs/gnodev/Makefile
@@ -0,0 +1,9 @@
+GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../)
+
+GOBUILD_FLAGS := -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)"
+
+install:
+ go install $(GOBUILD_FLAGS) .
+
+build:
+ go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gno
diff --git a/contribs/gnodev/README.md b/contribs/gnodev/README.md
new file mode 100644
index 00000000000..b3148c5dea3
--- /dev/null
+++ b/contribs/gnodev/README.md
@@ -0,0 +1,24 @@
+## `gnodev`: Your Gno Companion Tool
+
+`gnodev` is designed to be a robust and user-friendly tool in your realm package development journey, streamlining your workflow and enhancing productivity.
+
+### Synopsis
+**gnodev** [**-minimal**] [**-no-watch**] [**PKGS_PATH ...**]
+
+### Features
+- **In-Memory Node**: Automatically loads the **example** folder and any user-specified paths.
+- **Web Interface Server**: Starts a gno.land web server on `:8888`.
+- **Hot Reload**: Monitors the example packages folder and additional directories for file changes, reloading the package and restarting the node as needed.
+- **State Maintenance**: Ensures the current state is maintained by reapplying all previous blocks.
+
+### Commands
+- **H**: Display help information.
+- **R**: Reload the node.
+- **Ctrl+R**: Reset the current node state.
+- **Ctrl+C**: Exit the command.
+
+### Example Folder Loading
+The **example** package folder is loaded automatically. If working within this folder, you don't have to specify any additional paths to `gnodev`. Use `--minimal` to prevent this.
+
+### Installation
+Run `make install` to install `gnodev`.
diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod
new file mode 100644
index 00000000000..32f70de42cc
--- /dev/null
+++ b/contribs/gnodev/go.mod
@@ -0,0 +1,60 @@
+module github.com/gnolang/gno/contribs/gnodev
+
+go 1.20
+
+replace github.com/gnolang/gno => ../..
+
+require (
+ github.com/fsnotify/fsnotify v1.7.0
+ github.com/gnolang/gno v0.0.0-00010101000000-000000000000
+ golang.org/x/term v0.15.0
+)
+
+require (
+ github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
+ github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
+ github.com/cespare/xxhash v1.1.0 // indirect
+ github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/cockroachdb/apd/v3 v3.2.1 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
+ github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
+ github.com/dgraph-io/ristretto v0.1.1 // indirect
+ github.com/dustin/go-humanize v1.0.0 // indirect
+ github.com/gnolang/goleveldb v0.0.9 // indirect
+ github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
+ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/flatbuffers v1.12.1 // indirect
+ github.com/gorilla/mux v1.8.1 // indirect
+ github.com/gorilla/securecookie v1.1.1 // indirect
+ github.com/gorilla/sessions v1.2.1 // indirect
+ github.com/gorilla/websocket v1.5.1 // indirect
+ github.com/gotuna/gotuna v0.6.0 // indirect
+ github.com/jaekwon/testify v1.6.1 // indirect
+ github.com/jmhodges/levigo v1.0.0 // indirect
+ github.com/klauspost/compress v1.12.3 // indirect
+ github.com/libp2p/go-buffer-pool v0.1.0 // indirect
+ github.com/linxGnu/grocksdb v1.8.5 // indirect
+ github.com/pelletier/go-toml v1.9.5 // indirect
+ github.com/peterbourgon/ff/v3 v3.4.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/rs/cors v1.10.1 // indirect
+ github.com/stretchr/testify v1.8.4 // indirect
+ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
+ go.etcd.io/bbolt v1.3.8 // indirect
+ go.opencensus.io v0.22.5 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ golang.org/x/crypto v0.15.0 // indirect
+ golang.org/x/mod v0.14.0 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/tools v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum
new file mode 100644
index 00000000000..bdc46387a5e
--- /dev/null
+++ b/contribs/gnodev/go.sum
@@ -0,0 +1,299 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
+github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA=
+github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
+github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
+github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
+github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
+github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
+github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
+github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
+github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
+github.com/dgraph-io/badger/v3 v3.2103.4 h1:WE1B07YNTTJTtG9xjBcSW2wn0RJLyiV99h959RKZqM4=
+github.com/dgraph-io/badger/v3 v3.2103.4/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
+github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
+github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=
+github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
+github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE=
+github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E=
+github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
+github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
+github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+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/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw=
+github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U=
+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=
+github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
+github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
+github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
+github.com/linxGnu/grocksdb v1.8.5 h1:Okfk5B1h0ikCYdDM7Tc5yJUS8LTwAmMBq5IPWTmOLPs=
+github.com/linxGnu/grocksdb v1.8.5/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
+github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
+go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
+go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-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.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go
new file mode 100644
index 00000000000..894d8b36738
--- /dev/null
+++ b/contribs/gnodev/main.go
@@ -0,0 +1,378 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev"
+ "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm"
+ "github.com/gnolang/gno/gno.land/pkg/gnoweb"
+ "github.com/gnolang/gno/gnovm/pkg/gnoenv"
+ "github.com/gnolang/gno/gnovm/pkg/gnomod"
+ "github.com/gnolang/gno/tm2/pkg/commands"
+ tmlog "github.com/gnolang/gno/tm2/pkg/log"
+ osm "github.com/gnolang/gno/tm2/pkg/os"
+)
+
+const (
+ NodeLogName = "Node"
+ WebLogName = "GnoWeb"
+ KeyPressLogName = "KeyPress"
+ HotReloadLogName = "HotReload"
+)
+
+type devCfg struct {
+ webListenerAddr string
+ minimal bool
+ verbose bool
+ noWatch bool
+}
+
+var defaultDevOptions = &devCfg{
+ webListenerAddr: "127.0.0.1:8888",
+}
+
+func main() {
+ cfg := &devCfg{}
+
+ stdio := commands.NewDefaultIO()
+ cmd := commands.NewCommand(
+ commands.Metadata{
+ Name: "gnodev",
+ ShortUsage: "gnodev [flags] [path ...]",
+ ShortHelp: "Runs an in-memory node and gno.land web server for development purposes.",
+ LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface
+primarily for realm package development. It automatically loads the example folder and any
+additional specified paths.`,
+ },
+ cfg,
+ func(_ context.Context, args []string) error {
+ return execDev(cfg, args, stdio)
+ })
+
+ if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
+ fmt.Fprintf(os.Stderr, "%+v\n", err)
+ os.Exit(1)
+ }
+}
+func (c *devCfg) RegisterFlags(fs *flag.FlagSet) {
+ fs.StringVar(
+ &c.webListenerAddr,
+ "web-listener",
+ defaultDevOptions.webListenerAddr,
+ "web server listening address",
+ )
+
+ fs.BoolVar(
+ &c.minimal,
+ "minimal",
+ defaultDevOptions.verbose,
+ "do not load example folder packages",
+ )
+
+ fs.BoolVar(
+ &c.verbose,
+ "verbose",
+ defaultDevOptions.verbose,
+ "verbose output when deving",
+ )
+
+ fs.BoolVar(
+ &c.noWatch,
+ "no-watch",
+ defaultDevOptions.noWatch,
+ "do not watch for files change",
+ )
+
+}
+
+func execDev(cfg *devCfg, args []string, io commands.IO) error {
+ ctx, cancel := context.WithCancelCause(context.Background())
+ defer cancel(nil)
+
+ // guess root dir
+ gnoroot := gnoenv.RootDir()
+
+ // Check and Parse packages
+ pkgpaths, err := parseArgsPackages(args)
+ if err != nil {
+ return fmt.Errorf("unable to parse package paths: %w", err)
+ }
+
+ if !cfg.minimal {
+ examplesDir := filepath.Join(gnoroot, "examples")
+ pkgpaths = append(pkgpaths, examplesDir)
+ }
+
+ // Setup Raw Terminal
+ rt, restore, err := setupRawTerm(io)
+ if err != nil {
+ return fmt.Errorf("unable to init raw term: %w", err)
+ }
+ defer restore()
+
+ // Setup trap signal
+ osm.TrapSignal(func() {
+ restore()
+ cancel(nil)
+ })
+
+ // Setup Dev Node
+ // XXX: find a good way to export or display node logs
+ devNode, err := setupDevNode(ctx, rt, pkgpaths, gnoroot)
+ if err != nil {
+ return err
+ }
+ defer devNode.Close()
+
+ rt.Taskf(NodeLogName, "Listener: %s\n", devNode.GetRemoteAddress())
+ rt.Taskf(NodeLogName, "Default Address: %s\n", gnodev.DefaultCreator.String())
+ rt.Taskf(NodeLogName, "Chain ID: %s\n", devNode.Config().ChainID())
+
+ // Setup packages watcher
+ pathChangeCh := make(chan []string, 1)
+ go func() {
+ defer close(pathChangeCh)
+
+ cancel(runPkgsWatcher(ctx, cfg, devNode.ListPkgs(), pathChangeCh))
+ }()
+
+ // Setup GnoWeb listener
+ l, err := net.Listen("tcp", cfg.webListenerAddr)
+ if err != nil {
+ return fmt.Errorf("unable to listen to %q: %w", cfg.webListenerAddr, err)
+ }
+ defer l.Close()
+
+ // Run GnoWeb server
+ go func() {
+ cancel(serveGnoWebServer(l, devNode, rt))
+ }()
+
+ rt.Taskf(WebLogName, "Listener: http://%s\n", l.Addr())
+
+ // GnoDev should be ready, run event loop
+ rt.Taskf("[Ready]", "for commands and help, press `h`")
+
+ // Run the main event loop
+ return runEventLoop(ctx, cfg, rt, devNode, pathChangeCh)
+}
+
+// XXX: Automatize this the same way command does
+func printHelper(rt *rawterm.RawTerm) {
+ rt.Taskf("Helper", `
+Gno Dev Helper:
+ h, H Help - display this message
+ r, R Reload - Reload all packages to take change into account.
+ Ctrl+R Reset - Reset application state.
+ Ctrl+C Exit - Exit the application
+`)
+}
+
+func runEventLoop(ctx context.Context,
+ cfg *devCfg,
+ rt *rawterm.RawTerm,
+ dnode *dev.Node,
+ pathsCh <-chan []string,
+) error {
+ nodeOut := rt.NamespacedWriter(NodeLogName)
+ keyOut := rt.NamespacedWriter(KeyPressLogName)
+
+ keyPressCh := listenForKeyPress(keyOut, rt)
+ for {
+ var err error
+
+ select {
+ case <-ctx.Done():
+ return context.Cause(ctx)
+ case paths, ok := <-pathsCh:
+ if !ok {
+ return nil
+ }
+
+ if cfg.verbose {
+ for _, path := range paths {
+ rt.Taskf(HotReloadLogName, "path %q has been modified", path)
+ }
+ }
+
+ fmt.Fprintln(nodeOut, "Loading package updates...")
+ if err = dnode.UpdatePackages(paths...); err != nil {
+ checkForError(rt, err)
+ continue
+ }
+
+ fmt.Fprintln(nodeOut, "Reloading...")
+ err = dnode.Reload(ctx)
+ checkForError(rt, err)
+ case key, ok := <-keyPressCh:
+ if !ok {
+ return nil
+ }
+
+ if cfg.verbose {
+ fmt.Fprintf(keyOut, "<%s>\n", key.String())
+ }
+
+ switch key.Upper() {
+ case rawterm.KeyH:
+ printHelper(rt)
+ case rawterm.KeyR:
+ fmt.Fprintln(nodeOut, "Reloading all packages...")
+ checkForError(nodeOut, dnode.ReloadAll(ctx))
+ case rawterm.KeyCtrlR:
+ fmt.Fprintln(nodeOut, "Reseting state...")
+ checkForError(nodeOut, dnode.Reset(ctx))
+ case rawterm.KeyCtrlC:
+ return nil
+ default:
+ }
+
+ // Listen for the next keypress
+ keyPressCh = listenForKeyPress(keyOut, rt)
+ }
+ }
+}
+
+func runPkgsWatcher(ctx context.Context, cfg *devCfg, pkgs []gnomod.Pkg, changedPathsCh chan<- []string) error {
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ return fmt.Errorf("unable to watch files: %w", err)
+ }
+
+ if cfg.noWatch {
+ // noop watcher, wait until context has been cancel
+ <-ctx.Done()
+ return ctx.Err()
+ }
+
+ for _, pkg := range pkgs {
+ if err := watcher.Add(pkg.Dir); err != nil {
+ return fmt.Errorf("unable to watch %q: %w", pkg.Dir, err)
+ }
+ }
+
+ const timeout = time.Millisecond * 500
+
+ var debounceTimer <-chan time.Time
+ pathList := []string{}
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case watchErr := <-watcher.Errors:
+ return fmt.Errorf("watch error: %w", watchErr)
+ case <-debounceTimer:
+ changedPathsCh <- pathList
+ // Reset pathList and debounceTimer
+ pathList = []string{}
+ debounceTimer = nil
+ case evt := <-watcher.Events:
+ if evt.Op != fsnotify.Write {
+ continue
+ }
+
+ pathList = append(pathList, evt.Name)
+ debounceTimer = time.After(timeout)
+ }
+ }
+}
+
+func setupRawTerm(io commands.IO) (rt *rawterm.RawTerm, restore func() error, err error) {
+ rt = rawterm.NewRawTerm()
+
+ restore, err = rt.Init()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // correctly format output for terminal
+ io.SetOut(commands.WriteNopCloser(rt))
+
+ return rt, restore, nil
+}
+
+// setupDevNode initializes and returns a new DevNode.
+func setupDevNode(ctx context.Context, rt *rawterm.RawTerm, pkgspath []string, gnoroot string) (*gnodev.Node, error) {
+ nodeOut := rt.NamespacedWriter("Node")
+
+ logger := tmlog.NewTMLogger(nodeOut)
+ logger.SetLevel(tmlog.LevelError)
+ return gnodev.NewDevNode(ctx, logger, pkgspath)
+}
+
+// setupGnowebServer initializes and starts the Gnoweb server.
+func serveGnoWebServer(l net.Listener, dnode *gnodev.Node, rt *rawterm.RawTerm) error {
+ var server http.Server
+
+ webConfig := gnoweb.NewDefaultConfig()
+ webConfig.RemoteAddr = dnode.GetRemoteAddress()
+
+ loggerweb := tmlog.NewTMLogger(rt.NamespacedWriter("GnoWeb"))
+ loggerweb.SetLevel(tmlog.LevelDebug)
+
+ app := gnoweb.MakeApp(loggerweb, webConfig)
+
+ server.ReadHeaderTimeout = 60 * time.Second
+ server.Handler = app.Router
+
+ if err := server.Serve(l); err != nil {
+ return fmt.Errorf("unable to serve GnoWeb: %w", err)
+ }
+
+ return nil
+}
+
+func parseArgsPackages(args []string) (paths []string, err error) {
+ paths = make([]string, len(args))
+ for i, arg := range args {
+ abspath, err := filepath.Abs(arg)
+ if err != nil {
+ return nil, fmt.Errorf("invalid path %q: %w", arg, err)
+ }
+
+ ppath, err := gnomod.FindRootDir(abspath)
+ if err != nil {
+ return nil, fmt.Errorf("unable to find root dir of %q: %w", abspath, err)
+ }
+
+ paths[i] = ppath
+ }
+
+ return paths, nil
+}
+
+func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress {
+ cc := make(chan rawterm.KeyPress, 1)
+ go func() {
+ defer close(cc)
+ key, err := rt.ReadKeyPress()
+ if err != nil {
+ fmt.Fprintf(w, "unable to read keypress: %s\n", err.Error())
+ return
+ }
+
+ cc <- key
+ }()
+
+ return cc
+}
+
+func checkForError(w io.Writer, err error) {
+ if err != nil {
+ fmt.Fprintf(w, "[ERROR] - %s\n", err.Error())
+ return
+ }
+
+ fmt.Fprintln(w, "[DONE]")
+}
diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go
new file mode 100644
index 00000000000..6624edc13c1
--- /dev/null
+++ b/contribs/gnodev/pkg/dev/node.go
@@ -0,0 +1,406 @@
+package dev
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/gno.land/pkg/integration"
+ vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
+ "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/tm2/pkg/amino"
+ abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
+ "github.com/gnolang/gno/tm2/pkg/bft/node"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/log"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ //backup "github.com/gnolang/tx-archive/backup/client"
+ //restore "github.com/gnolang/tx-archive/restore/client"
+)
+
+const gnoDevChainID = "tendermint_test" // XXX: this is hardcoded and cannot be change bellow
+
+// DevNode is a backup.Client
+// var _ backup.Client = (*Node)(nil)
+
+// DevNode is a restore.Client
+// var _ restore.Client = (*Node)(nil)
+
+// Node is not thread safe
+type Node struct {
+ *node.Node
+
+ logger log.Logger
+ pkgs PkgsMap // path -> pkg
+}
+
+var (
+ DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
+ DefaultCreator = crypto.MustAddressFromString(integration.DefaultAccount_Address)
+ DefaultBalance = []gnoland.Balance{
+ {
+ Address: DefaultCreator,
+ Amount: std.MustParseCoins("10000000000000ugnot"),
+ },
+ }
+)
+
+func NewDevNode(ctx context.Context, logger log.Logger, pkgslist []string) (*Node, error) {
+ mpkgs, err := newPkgsMap(pkgslist)
+ if err != nil {
+ return nil, fmt.Errorf("unable map pkgs list: %w", err)
+ }
+
+ txs, err := mpkgs.Load(DefaultCreator, DefaultFee, nil)
+ if err != nil {
+ return nil, fmt.Errorf("unable to load genesis packages: %w", err)
+ }
+
+ // generate genesis state
+ genesis := gnoland.GnoGenesisState{
+ Balances: DefaultBalance,
+ Txs: txs,
+ }
+
+ node, err := newNode(logger, genesis)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create the node: %w", err)
+ }
+
+ if err := node.Start(); err != nil {
+ return nil, fmt.Errorf("unable to start node: %w", err)
+ }
+
+ // Wait for readiness
+ select {
+ case <-gnoland.GetNodeReadiness(node): // ok
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+
+ return &Node{
+ Node: node,
+ pkgs: mpkgs,
+ logger: logger,
+ }, nil
+}
+
+func (d *Node) getLatestBlockNumber() uint64 {
+ return uint64(d.Node.BlockStore().Height())
+}
+
+func (d *Node) Close() error {
+ return d.Node.Stop()
+}
+
+func (d *Node) ListPkgs() []gnomod.Pkg {
+ return d.pkgs.toList()
+}
+
+func (d *Node) GetNodeReadiness() <-chan struct{} {
+ return gnoland.GetNodeReadiness(d.Node)
+}
+
+func (d *Node) GetRemoteAddress() string {
+ return d.Node.Config().RPC.ListenAddress
+}
+
+func (d *Node) UpdatePackages(paths ...string) error {
+ for _, path := range paths {
+ // list all packages from target path
+ pkgslist, err := gnomod.ListPkgs(path)
+ if err != nil {
+ return fmt.Errorf("failed to list gno packages for %q: %w", path, err)
+ }
+
+ for _, pkg := range pkgslist {
+ d.pkgs[pkg.Dir] = pkg
+ }
+ }
+
+ return nil
+}
+
+func (d *Node) Reset(ctx context.Context) error {
+ if d.Node.IsRunning() {
+ if err := d.Node.Stop(); err != nil {
+ return fmt.Errorf("unable to stop the node: %w", err)
+ }
+ }
+
+ // generate genesis
+ txs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil)
+ if err != nil {
+ return fmt.Errorf("unable to load pkgs: %w", err)
+ }
+
+ genesis := gnoland.GnoGenesisState{
+ Balances: DefaultBalance,
+ Txs: txs,
+ }
+
+ return d.reset(ctx, genesis)
+}
+
+func (d *Node) ReloadAll(ctx context.Context) error {
+ pkgs := d.ListPkgs()
+ paths := make([]string, len(pkgs))
+ for i, pkg := range pkgs {
+ paths[i] = pkg.Dir
+ }
+
+ if err := d.UpdatePackages(paths...); err != nil {
+ return fmt.Errorf("unable to reload packages: %w", err)
+ }
+
+ return d.Reload(ctx)
+}
+
+func (d *Node) Reload(ctx context.Context) error {
+
+ // save current state
+ state, err := d.saveState(ctx)
+ if err != nil {
+ return fmt.Errorf("unable to save state: %s", err.Error())
+ }
+
+ // stop the node if not already stopped
+ if d.Node.IsRunning() {
+ if err := d.Node.Stop(); err != nil {
+ return fmt.Errorf("unable to stop the node: %w", err)
+ }
+ }
+
+ // generate genesis
+ txs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil)
+ if err != nil {
+ return fmt.Errorf("unable to load pkgs: %w", err)
+ }
+
+ genesis := gnoland.GnoGenesisState{
+ Balances: DefaultBalance,
+ Txs: txs,
+ }
+
+ // try to reset the node
+ if err := d.reset(ctx, genesis); err != nil {
+ return fmt.Errorf("unable to reset the node: %w", err)
+ }
+
+ for _, tx := range state {
+ // skip empty transaction
+ if len(tx.Msgs) == 0 {
+ continue
+ }
+
+ if err := d.SendTransaction(&tx); err != nil {
+ return fmt.Errorf("unable to send transaction: %w", err)
+ }
+ }
+
+ return nil
+}
+
+func (d *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) error {
+ var err error
+
+ recoverError := func() {
+ if r := recover(); r != nil {
+ panicErr, ok := r.(error)
+ if !ok {
+ panic(r)
+ }
+
+ err = panicErr
+ }
+ }
+
+ createNode := func() {
+ defer recoverError()
+
+ node, nodeErr := newNode(d.logger, genesis)
+ if nodeErr != nil {
+ err = fmt.Errorf("unable to create node: %w", nodeErr)
+ return
+ }
+
+ if startErr := node.Start(); startErr != nil {
+ err = fmt.Errorf("unable to start the node: %w", startErr)
+ return
+ }
+
+ d.Node = node
+ }
+
+ // create the node
+ createNode()
+ if err != nil {
+ return err
+ }
+
+ // wait for readiness
+ select {
+ case <-d.GetNodeReadiness(): // ok
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+
+ return err
+}
+
+// GetBlockTransactions returns the transactions contained
+// within the specified block, if any
+func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) {
+ b := d.Node.BlockStore().LoadBlock(int64(blockNum))
+ txs := make([]std.Tx, len(b.Txs))
+ for i, encodedTx := range b.Txs {
+ var tx std.Tx
+ if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil {
+ return nil, fmt.Errorf("unable to unmarshal amino tx, %w", unmarshalErr)
+ }
+
+ txs[i] = tx
+ }
+
+ return txs, nil
+}
+
+// GetBlockTransactions returns the transactions contained
+// within the specified block, if any
+// GetLatestBlockNumber returns the latest block height from the chain
+func (d *Node) GetLatestBlockNumber() (uint64, error) {
+ return d.getLatestBlockNumber(), nil
+}
+
+// SendTransaction executes a broadcast sync send
+// of the specified transaction to the chain
+func (d *Node) SendTransaction(tx *std.Tx) error {
+ resCh := make(chan abci.Response, 1)
+
+ aminoTx, err := amino.Marshal(tx)
+ if err != nil {
+ return fmt.Errorf("unable to marshal transaction to amino binary, %w", err)
+ }
+
+ err = d.Node.Mempool().CheckTx(aminoTx, func(res abci.Response) {
+ resCh <- res
+ })
+ if err != nil {
+ return fmt.Errorf("unable to check tx: %w", err)
+ }
+
+ res := <-resCh
+ r := res.(abci.ResponseCheckTx)
+ if r.Error != nil {
+ return fmt.Errorf("unable to broadcast tx: %w\nLog: %s", r.Error, r.Log)
+ }
+
+ return nil
+}
+
+func (n *Node) saveState(ctx context.Context) ([]std.Tx, error) {
+ lastBlock := n.getLatestBlockNumber()
+
+ state := make([]std.Tx, 0, int(lastBlock))
+ var blocnum uint64 = 1
+ for ; blocnum <= lastBlock; blocnum++ {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ txs, txErr := n.GetBlockTransactions(blocnum)
+ if txErr != nil {
+ return nil, fmt.Errorf("unable to fetch block transactions, %w", txErr)
+ }
+
+ // Skip empty blocks
+ state = append(state, txs...)
+ }
+
+ // override current state
+ return state, nil
+}
+
+type PkgsMap map[string]gnomod.Pkg
+
+func newPkgsMap(paths []string) (PkgsMap, error) {
+ pkgs := make(map[string]gnomod.Pkg)
+ for _, path := range paths {
+ // list all packages from target path
+ pkgslist, err := gnomod.ListPkgs(path)
+ if err != nil {
+ return nil, fmt.Errorf("listing gno packages: %w", err)
+ }
+
+ for _, pkg := range pkgslist {
+ if pkg.Dir == "" {
+ continue
+ }
+
+ if _, ok := pkgs[pkg.Dir]; ok {
+ continue // skip
+ }
+ pkgs[pkg.Dir] = pkg
+ }
+ }
+
+ // Filter out draft packages.
+ return pkgs, nil
+}
+
+func (pm PkgsMap) toList() gnomod.PkgList {
+ list := make([]gnomod.Pkg, 0, len(pm))
+ for _, pkg := range pm {
+ list = append(list, pkg)
+ }
+ return list
+}
+
+func (pm PkgsMap) Load(creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) {
+ pkgs := pm.toList()
+
+ sorted, err := pkgs.Sort()
+ if err != nil {
+ return nil, fmt.Errorf("unable to sort pkgs: %w", err)
+ }
+
+ nonDraft := sorted.GetNonDraftPkgs()
+ txs := []std.Tx{}
+ for _, pkg := range nonDraft {
+ // Open files in directory as MemPackage.
+ memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
+ if err := memPkg.Validate(); err != nil {
+ return nil, fmt.Errorf("invalid package: %w", err)
+ }
+
+ // Create transaction
+ tx := std.Tx{
+ Fee: fee,
+ Msgs: []std.Msg{
+ vmm.MsgAddPackage{
+ Creator: creator,
+ Package: memPkg,
+ Deposit: deposit,
+ },
+ },
+ }
+
+ tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
+ txs = append(txs, tx)
+ }
+
+ return txs, nil
+}
+
+func newNode(logger log.Logger, genesis gnoland.GnoGenesisState) (*node.Node, error) {
+ rootdir := gnoenv.RootDir()
+
+ nodeConfig := gnoland.NewDefaultInMemoryNodeConfig(rootdir)
+ nodeConfig.Genesis.AppState = genesis
+ return gnoland.NewInMemoryNode(logger, nodeConfig)
+}
diff --git a/contribs/gnodev/pkg/rawterm/keypress.go b/contribs/gnodev/pkg/rawterm/keypress.go
new file mode 100644
index 00000000000..20503476a9b
--- /dev/null
+++ b/contribs/gnodev/pkg/rawterm/keypress.go
@@ -0,0 +1,55 @@
+package rawterm
+
+import (
+ "fmt"
+ "unicode"
+)
+
+type KeyPress byte
+
+// key representation
+const (
+ KeyNone KeyPress = 0 // None
+ KeyCtrlC KeyPress = '\x03' // Ctrl+C
+ KeyCtrlD KeyPress = '\x04' // Ctrl+D
+ KeyCtrlE KeyPress = '\x05' // Ctrl+E
+ KeyCtrlL KeyPress = '\x0c' // Ctrl+L
+ KeyCtrlO KeyPress = '\x0f' // Ctrl+O
+ KeyCtrlR KeyPress = '\x12' // Ctrl+R
+ KeyCtrlT KeyPress = '\x14' // Ctrl+T
+
+ KeyH KeyPress = 'H'
+ KeyR KeyPress = 'R'
+)
+
+func (k KeyPress) Upper() KeyPress {
+ return KeyPress(unicode.ToUpper(rune(k)))
+}
+
+func (k KeyPress) String() string {
+ switch k {
+ case KeyNone:
+ return "Null"
+ case KeyCtrlC:
+ return "Ctrl+C"
+ case KeyCtrlD:
+ return "Ctrl+D"
+ case KeyCtrlE:
+ return "Ctrl+E"
+ case KeyCtrlL:
+ return "Ctrl+L"
+ case KeyCtrlO:
+ return "Ctrl+O"
+ case KeyCtrlR:
+ return "Ctrl+R"
+ case KeyCtrlT:
+ return "Ctrl+T"
+ default:
+ // For printable ASCII characters
+ if k > 0x20 && k < 0x7e {
+ return fmt.Sprintf("%c", k)
+ }
+
+ return fmt.Sprintf("Unknown (0x%02x)", byte(k))
+ }
+}
diff --git a/contribs/gnodev/pkg/rawterm/rawterm.go b/contribs/gnodev/pkg/rawterm/rawterm.go
new file mode 100644
index 00000000000..8f29e88fc92
--- /dev/null
+++ b/contribs/gnodev/pkg/rawterm/rawterm.go
@@ -0,0 +1,180 @@
+package rawterm
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+
+ "golang.org/x/term"
+)
+
+var (
+ CRLF = []byte{'\r', '\n'}
+)
+
+// rawTerminal wraps an io.Writer, converting \n to \r\n
+type RawTerm struct {
+ syncWriter sync.Mutex
+
+ fsin *os.File
+ reader io.Reader
+ taskWriter TaskWriter
+}
+
+func NewRawTerm() *RawTerm {
+ return &RawTerm{
+ fsin: os.Stdin,
+ reader: os.Stdin,
+ taskWriter: &rawTaskWriter{os.Stdout},
+ }
+}
+
+func (rt *RawTerm) Init() (restore func() error, err error) {
+ fd := int(rt.fsin.Fd())
+ oldstate, err := term.MakeRaw(fd)
+ if err != nil {
+ return nil, fmt.Errorf("unable to init raw term: %w", err)
+ }
+
+ rt.reader = rt.fsin
+ rt.taskWriter = &columnTaskWriter{os.Stdout}
+ return func() error {
+ return term.Restore(fd, oldstate)
+ }, nil
+}
+
+func (rt *RawTerm) Taskf(task string, format string, args ...interface{}) (n int, err error) {
+ format = strings.TrimSpace(format)
+ if len(args) > 0 {
+ str := fmt.Sprintf(format, args...)
+ return rt.taskWriter.WriteTask(task, []byte(str+"\n"))
+ }
+
+ return rt.taskWriter.WriteTask(task, []byte(format+"\n"))
+}
+
+func (rt *RawTerm) Write(buf []byte) (n int, err error) {
+ rt.syncWriter.Lock()
+ defer rt.syncWriter.Unlock()
+
+ return rt.taskWriter.Write(buf)
+}
+
+func (rt *RawTerm) WriteTask(name string, buf []byte) (n int, err error) {
+ rt.syncWriter.Lock()
+ defer rt.syncWriter.Unlock()
+
+ return rt.taskWriter.WriteTask(name, buf)
+}
+
+func (rt *RawTerm) NamespacedWriter(namepsace string) io.Writer {
+ return &namespaceWriter{namepsace, rt}
+}
+
+func (rt *RawTerm) read(buf []byte) (n int, err error) {
+ return rt.fsin.Read(buf)
+}
+
+func (rt *RawTerm) ReadKeyPress() (KeyPress, error) {
+ buf := make([]byte, 1)
+ if _, err := rt.read(buf); err != nil {
+ return KeyNone, err
+ }
+
+ return KeyPress(buf[0]), nil
+}
+
+type namespaceWriter struct {
+ namespace string
+ writer TaskWriter
+}
+
+func (r *namespaceWriter) Write(buf []byte) (n int, err error) {
+ return r.writer.WriteTask(r.namespace, buf)
+}
+
+type TaskWriter interface {
+ io.Writer
+ WriteTask(task string, buf []byte) (n int, err error)
+}
+
+type columnTaskWriter struct {
+ writer io.Writer
+}
+
+func (r *columnTaskWriter) Write(buf []byte) (n int, err error) {
+ return r.WriteTask("", buf)
+}
+
+func (r *columnTaskWriter) WriteTask(left string, buf []byte) (n int, err error) {
+ var nline int
+ for nline = 0; len(buf) > 0; nline++ {
+ i := bytes.IndexByte(buf, '\n')
+ todo := len(buf)
+ if i >= 0 {
+ todo = i
+ }
+
+ var nn int
+ switch {
+ case nline == 0, left == "": // first line or left side is empty
+ nn, err = r.writeColumnLine(left, buf[:todo])
+ case i < 0 || i+1 == len(buf): // last line
+ nn, err = r.writeColumnLine(" └─", buf[:todo])
+ default: // middle lines
+ nn, err = r.writeColumnLine(" │", buf[:todo])
+ }
+
+ n += nn
+ if err != nil {
+ return n, err
+ }
+ buf = buf[todo:]
+
+ if i >= 0 { // always jump a line on the last line
+ if _, err = r.writer.Write(CRLF); err != nil {
+ return n, err
+ }
+ n++
+ buf = buf[1:]
+ }
+ }
+
+ return
+}
+
+func (r *columnTaskWriter) writeColumnLine(left string, line []byte) (n int, err error) {
+ // Write left column
+ if n, err = fmt.Fprintf(r.writer, "%-15s | ", left); err != nil {
+ return n, err
+ }
+
+ // Write left line
+ var nn int
+ nn, err = r.writer.Write(line)
+ n += nn
+
+ return
+}
+
+type rawTaskWriter struct {
+ writer io.Writer
+}
+
+func (r *rawTaskWriter) Write(buf []byte) (n int, err error) {
+ return r.writer.Write(buf)
+}
+
+func (r *rawTaskWriter) WriteTask(task string, buf []byte) (n int, err error) {
+ if task != "" {
+ n, err = r.writer.Write([]byte(task + ": "))
+ }
+
+ var nn int
+ nn, err = r.writer.Write(buf)
+ n += nn
+ return
+}
diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go
index b080e0b403d..f9e8c973cb4 100644
--- a/gno.land/cmd/gnoweb/main.go
+++ b/gno.land/cmd/gnoweb/main.go
@@ -1,505 +1,54 @@
-// main.go
-
package main
import (
- "encoding/json"
- "errors"
"flag"
- "fmt"
- "io"
"net/http"
"os"
- "path/filepath"
- "runtime"
- "strings"
"time"
- "github.com/gnolang/gno/tm2/pkg/amino"
- abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
- "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
- "github.com/gnolang/gno/tm2/pkg/std"
- "github.com/gorilla/mux"
- "github.com/gotuna/gotuna"
-
- "github.com/gnolang/gno/gno.land/cmd/gnoweb/static" // for static files
- "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types
+ // for static files
+ "github.com/gnolang/gno/gno.land/pkg/gnoweb"
+ "github.com/gnolang/gno/tm2/pkg/log"
+ // for error types
// "github.com/gnolang/gno/tm2/pkg/sdk" // for baseapp (info, status)
)
-const (
- qFileStr = "vm/qfile"
-)
+func parseConfigFlags(fs *flag.FlagSet, args []string) (gnoweb.Config, error) {
+ cfg := gnoweb.NewDefaultConfig()
-var startedAt time.Time
+ fs.StringVar(&cfg.RemoteAddr, "remote", cfg.RemoteAddr, "remote gnoland node address")
+ fs.StringVar(&cfg.CaptchaSite, "captcha-site", cfg.CaptchaSite, "recaptcha site key (if empty, captcha are disabled)")
+ fs.StringVar(&cfg.FaucetURL, "faucet-url", cfg.FaucetURL, "faucet server URL")
+ fs.StringVar(&cfg.ViewsDir, "views-dir", cfg.ViewsDir, "views directory location") // XXX: replace with goembed
+ fs.StringVar(&cfg.HelpChainID, "help-chainid", cfg.HelpChainID, "help page's chainid")
+ fs.StringVar(&cfg.HelpRemote, "help-remote", cfg.HelpRemote, "help page's remote addr")
+ fs.BoolVar(&cfg.WithAnalytics, "with-analytics", cfg.WithAnalytics, "enable privacy-first analytics")
-var flags struct {
- BindAddr string
- RemoteAddr string
- CaptchaSite string
- FaucetURL string
- ViewsDir string
- HelpChainID string
- HelpRemote string
- WithAnalytics bool
+ return cfg, fs.Parse(args)
}
-func init() {
- flag.StringVar(&flags.RemoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address")
- flag.StringVar(&flags.BindAddr, "bind", "127.0.0.1:8888", "server listening address")
- flag.StringVar(&flags.CaptchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)")
- flag.StringVar(&flags.FaucetURL, "faucet-url", "http://localhost:5050", "faucet server URL")
- flag.StringVar(&flags.ViewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") // XXX: replace with goembed
- flag.StringVar(&flags.HelpChainID, "help-chainid", "dev", "help page's chainid")
- flag.StringVar(&flags.HelpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr")
- flag.BoolVar(&flags.WithAnalytics, "with-analytics", false, "enable privacy-first analytics")
- startedAt = time.Now()
-}
+func main() {
+ fs := flag.NewFlagSet("gnoweb", flag.PanicOnError)
-func makeApp() gotuna.App {
- app := gotuna.App{
- ViewFiles: os.DirFS(flags.ViewsDir),
- Router: gotuna.NewMuxRouter(),
- Static: static.EmbeddedStatic,
- // StaticPrefix: "static/",
- }
+ var bindAddress string
+ fs.StringVar(&bindAddress, "bind", "127.0.0.1:8888", "server listening address")
- // realm aliases
- aliases := map[string]string{
- "/": "/r/gnoland/home",
- "/about": "/r/gnoland/pages:p/about",
- "/gnolang": "/r/gnoland/pages:p/gnolang",
- "/ecosystem": "/r/gnoland/pages:p/ecosystem",
- "/partners": "/r/gnoland/pages:p/partners",
- "/testnets": "/r/gnoland/pages:p/testnets",
- "/start": "/r/gnoland/pages:p/start",
- "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm
- "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm
- }
- for from, to := range aliases {
- app.Router.Handle(from, handlerRealmAlias(app, to))
- }
- // http redirects
- redirects := map[string]string{
- "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary
- "/blog": "/r/gnoland/blog",
- "/gor": "/game-of-realms",
- "/grants": "/partners",
- "/language": "/gnolang",
- "/getting-started": "/start",
- }
- for from, to := range redirects {
- app.Router.Handle(from, handlerRedirect(app, to))
+ cfg, err := parseConfigFlags(fs, os.Args)
+ if err != nil {
+ panic("unable to parse flags: " + err.Error())
}
- // realm routes
- // NOTE: see rePathPart.
- app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(app))
- app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(app))
- app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(app))
- app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(app))
-
- // other
- app.Router.Handle("/faucet", handlerFaucet(app))
- app.Router.Handle("/static/{path:.+}", handlerStaticFile(app))
- app.Router.Handle("/favicon.ico", handlerFavicon(app))
- // api
- app.Router.Handle("/status.json", handlerStatusJSON(app))
+ logger := log.NewTMLogger(os.Stdout)
+ logger.SetLevel(log.LevelDebug)
- app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- path := r.RequestURI
- handleNotFound(app, path, w, r)
- })
- return app
-}
-
-func main() {
- flag.Parse()
- fmt.Printf("Running on http://%s\n", flags.BindAddr)
+ logger.Debug("", "Running", "http://"+cfg.BindAddr)
server := &http.Server{
- Addr: flags.BindAddr,
+ Addr: cfg.BindAddr,
ReadHeaderTimeout: 60 * time.Second,
- Handler: makeApp().Router,
+ Handler: gnoweb.MakeApp(logger, cfg).Router,
}
if err := server.ListenAndServe(); err != nil {
- fmt.Fprintf(os.Stderr, "HTTP server stopped with error: %+v\n", err)
- }
-}
-
-// handlerRealmAlias is used to render official pages from realms.
-// url is intended to be shorter.
-// UX is intended to be more minimalistic.
-// A link to the realm realm is added.
-func handlerRealmAlias(app gotuna.App, rlmpath string) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- rlmfullpath := "gno.land" + rlmpath
- querystr := "" // XXX: "?gnoweb-alias=1"
- parts := strings.Split(rlmpath, ":")
- switch len(parts) {
- case 1: // continue
- case 2: // r/realm:querystr
- rlmfullpath = "gno.land" + parts[0]
- querystr = parts[1] + querystr
- default:
- panic("should not happen")
- }
- rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/")
- qpath := "vm/qrender"
- data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr))
- res, err := makeRequest(qpath, data)
- if err != nil {
- writeError(w, fmt.Errorf("gnoweb failed to query gnoland: %w", err))
- return
- }
-
- queryParts := strings.Split(querystr, "/")
- pathLinks := []pathLink{}
- for i, part := range queryParts {
- pathLinks = append(pathLinks, pathLink{
- URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"),
- Text: part,
- })
- }
-
- tmpl := app.NewTemplatingEngine()
- // XXX: extract title from realm's output
- // XXX: extract description from realm's output
- tmpl.Set("RealmName", rlmname)
- tmpl.Set("RealmPath", rlmpath)
- tmpl.Set("Query", querystr)
- tmpl.Set("PathLinks", pathLinks)
- tmpl.Set("Contents", string(res.Data))
- tmpl.Set("Flags", flags)
- tmpl.Set("IsAlias", true)
- tmpl.Render(w, r, "realm_render.html", "funcs.html")
- })
-}
-
-func handlerFaucet(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- app.NewTemplatingEngine().
- Set("Flags", flags).
- Render(w, r, "faucet.html", "funcs.html")
- })
-}
-
-func handlerStatusJSON(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var ret struct {
- Gnoland struct {
- Connected bool `json:"connected"`
- Error *string `json:"error,omitempty"`
- Height *int64 `json:"height,omitempty"`
- // processed txs
- // active connections
-
- Version *string `json:"version,omitempty"`
- // Uptime *float64 `json:"uptime-seconds,omitempty"`
- // Goarch *string `json:"goarch,omitempty"`
- // Goos *string `json:"goos,omitempty"`
- // GoVersion *string `json:"go-version,omitempty"`
- // NumCPU *int `json:"num_cpu,omitempty"`
- } `json:"gnoland"`
- Website struct {
- // Version string `json:"version"`
- Uptime float64 `json:"uptime-seconds"`
- Goarch string `json:"goarch"`
- Goos string `json:"goos"`
- GoVersion string `json:"go-version"`
- NumCPU int `json:"num_cpu"`
- } `json:"website"`
- }
- ret.Website.Uptime = time.Since(startedAt).Seconds()
- ret.Website.Goarch = runtime.GOARCH
- ret.Website.Goos = runtime.GOOS
- ret.Website.NumCPU = runtime.NumCPU()
- ret.Website.GoVersion = runtime.Version()
-
- ret.Gnoland.Connected = true
- res, err := makeRequest(".app/version", []byte{})
- if err != nil {
- ret.Gnoland.Connected = false
- errmsg := err.Error()
- ret.Gnoland.Error = &errmsg
- } else {
- version := string(res.Value)
- ret.Gnoland.Version = &version
- ret.Gnoland.Height = &res.Height
- }
-
- out, _ := json.MarshalIndent(ret, "", " ")
- w.Header().Set("Content-Type", "application/json")
- w.Write(out)
- })
-}
-
-func handlerRedirect(app gotuna.App, to string) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- http.Redirect(w, r, to, http.StatusFound)
- tmpl := app.NewTemplatingEngine()
- tmpl.Set("To", to)
- tmpl.Set("Flags", flags)
- tmpl.Render(w, r, "redirect.html", "funcs.html")
- })
-}
-
-func handlerRealmMain(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- rlmname := vars["rlmname"]
- rlmpath := "gno.land/r/" + rlmname
- query := r.URL.Query()
- if query.Has("help") {
- // Render function helper.
- funcName := query.Get("__func")
- qpath := "vm/qfuncs"
- data := []byte(rlmpath)
- res, err := makeRequest(qpath, data)
- if err != nil {
- writeError(w, err)
- return
- }
- var fsigs vm.FunctionSignatures
- amino.MustUnmarshalJSON(res.Data, &fsigs)
- // Fill fsigs with query parameters.
- for i := range fsigs {
- fsig := &(fsigs[i])
- for j := range fsig.Params {
- param := &(fsig.Params[j])
- value := query.Get(param.Name)
- param.Value = value
- }
- }
- // Render template.
- tmpl := app.NewTemplatingEngine()
- tmpl.Set("FuncName", funcName)
- tmpl.Set("RealmPath", rlmpath)
- tmpl.Set("DirPath", pathOf(rlmpath))
- tmpl.Set("FunctionSignatures", fsigs)
- tmpl.Set("Flags", flags)
- tmpl.Render(w, r, "realm_help.html", "funcs.html")
- } else {
- // Ensure realm exists. TODO optimize.
- qpath := qFileStr
- data := []byte(rlmpath)
- _, err := makeRequest(qpath, data)
- if err != nil {
- writeError(w, errors.New("error querying realm package"))
- return
- }
- // Render blank query path, /r/REALM:.
- handleRealmRender(app, w, r)
- }
- })
-}
-
-type pathLink struct {
- URL string
- Text string
-}
-
-func handlerRealmRender(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- handleRealmRender(app, w, r)
- })
-}
-
-func handleRealmRender(app gotuna.App, w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- rlmname := vars["rlmname"]
- rlmpath := "gno.land/r/" + rlmname
- querystr := vars["querystr"]
- if r.URL.Path == "/r/"+rlmname+":" {
- // Redirect to /r/REALM if querypath is empty.
- http.Redirect(w, r, "/r/"+rlmname, http.StatusFound)
- return
- }
- qpath := "vm/qrender"
- data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr))
- res, err := makeRequest(qpath, data)
- if err != nil {
- // XXX hack
- if strings.Contains(err.Error(), "Render not declared") {
- res = &abci.ResponseQuery{}
- res.Data = []byte("realm package has no Render() function")
- } else {
- writeError(w, err)
- return
- }
- }
- // linkify querystr.
- queryParts := strings.Split(querystr, "/")
- pathLinks := []pathLink{}
- for i, part := range queryParts {
- pathLinks = append(pathLinks, pathLink{
- URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"),
- Text: part,
- })
- }
- // Render template.
- tmpl := app.NewTemplatingEngine()
- // XXX: extract title from realm's output
- // XXX: extract description from realm's output
- tmpl.Set("RealmName", rlmname)
- tmpl.Set("RealmPath", rlmpath)
- tmpl.Set("Query", querystr)
- tmpl.Set("PathLinks", pathLinks)
- tmpl.Set("Contents", string(res.Data))
- tmpl.Set("Flags", flags)
- tmpl.Render(w, r, "realm_render.html", "funcs.html")
-}
-
-func handlerRealmFile(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- diruri := "gno.land/r/" + vars["rlmname"]
- filename := vars["filename"]
- renderPackageFile(app, w, r, diruri, filename)
- })
-}
-
-func handlerPackageFile(app gotuna.App) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- pkgpath := "gno.land/p/" + vars["filepath"]
- diruri, filename := std.SplitFilepath(pkgpath)
- if filename == "" && diruri == pkgpath {
- // redirect to diruri + "/"
- http.Redirect(w, r, "/p/"+vars["filepath"]+"/", http.StatusFound)
- return
- }
- renderPackageFile(app, w, r, diruri, filename)
- })
-}
-
-func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string, filename string) {
- if filename == "" {
- // Request is for a folder.
- qpath := qFileStr
- data := []byte(diruri)
- res, err := makeRequest(qpath, data)
- if err != nil {
- writeError(w, err)
- return
- }
- files := strings.Split(string(res.Data), "\n")
- // Render template.
- tmpl := app.NewTemplatingEngine()
- tmpl.Set("DirURI", diruri)
- tmpl.Set("DirPath", pathOf(diruri))
- tmpl.Set("Files", files)
- tmpl.Set("Flags", flags)
- tmpl.Render(w, r, "package_dir.html", "funcs.html")
- } else {
- // Request is for a file.
- filepath := diruri + "/" + filename
- qpath := qFileStr
- data := []byte(filepath)
- res, err := makeRequest(qpath, data)
- if err != nil {
- writeError(w, err)
- return
- }
- // Render template.
- tmpl := app.NewTemplatingEngine()
- tmpl.Set("DirURI", diruri)
- tmpl.Set("DirPath", pathOf(diruri))
- tmpl.Set("FileName", filename)
- tmpl.Set("FileContents", string(res.Data))
- tmpl.Set("Flags", flags)
- tmpl.Render(w, r, "package_file.html", "funcs.html")
- }
-}
-
-func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) {
- opts2 := client.ABCIQueryOptions{
- // Height: height, XXX
- // Prove: false, XXX
- }
- remote := flags.RemoteAddr
- cli := client.NewHTTP(remote, "/websocket")
- qres, err := cli.ABCIQueryWithOptions(
- qpath, data, opts2)
- if err != nil {
- return nil, err
- }
- if qres.Response.Error != nil {
- fmt.Printf("Log: %s\n",
- qres.Response.Log)
- return nil, qres.Response.Error
- }
- return &qres.Response, nil
-}
-
-func handlerStaticFile(app gotuna.App) http.Handler {
- fs := http.FS(app.Static)
- fileapp := http.StripPrefix("/static", http.FileServer(fs))
-
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- fpath := filepath.Clean(vars["path"])
- f, err := fs.Open(fpath)
- if os.IsNotExist(err) {
- handleNotFound(app, fpath, w, r)
- return
- }
- stat, err := f.Stat()
- if err != nil || stat.IsDir() {
- handleNotFound(app, fpath, w, r)
- return
- }
-
- // TODO: ModTime doesn't work for embed?
- // w.Header().Set("ETag", fmt.Sprintf("%x", stat.ModTime().UnixNano()))
- // w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s", "31536000"))
- fileapp.ServeHTTP(w, r)
- })
-}
-
-func handlerFavicon(app gotuna.App) http.Handler {
- fs := http.FS(app.Static)
-
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fpath := "img/favicon.ico"
- f, err := fs.Open(fpath)
- if os.IsNotExist(err) {
- handleNotFound(app, fpath, w, r)
- return
- }
- w.Header().Set("Content-Type", "image/x-icon")
- w.Header().Set("Cache-Control", "public, max-age=604800") // 7d
- io.Copy(w, f)
- })
-}
-
-func handleNotFound(app gotuna.App, path string, w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- app.NewTemplatingEngine().
- Set("title", "Not found").
- Set("path", path).
- Set("Flags", flags).
- Render(w, r, "404.html", "funcs.html")
-}
-
-func writeError(w http.ResponseWriter, err error) {
- // XXX: writeError should return an error page template.
- w.WriteHeader(500)
-
- fmt.Println("main", err.Error())
-
- if details := errors.Unwrap(err); details != nil {
- fmt.Println("details", details.Error())
- }
-
- w.Write([]byte(err.Error()))
-}
-
-func pathOf(diruri string) string {
- parts := strings.Split(diruri, "/")
- if parts[0] == "gno.land" {
- return "/" + strings.Join(parts[1:], "/")
- } else {
- panic(fmt.Sprintf("invalid dir-URI %q", diruri))
+ logger.Error("HTTP server stopped", " error:", err)
}
}
diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go
index e809103469d..8f2fa0debf6 100644
--- a/gno.land/pkg/gnoland/genesis.go
+++ b/gno.land/pkg/gnoland/genesis.go
@@ -105,6 +105,9 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit s
for _, pkg := range nonDraftPkgs {
// Open files in directory as MemPackage.
memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
+ if err := memPkg.Validate(); err != nil {
+ return nil, fmt.Errorf("invalid package: %w", err)
+ }
// Create transaction
tx := std.Tx{
diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go
index 0edef304281..bb6dae0085f 100644
--- a/gno.land/pkg/gnoland/node_inmemory.go
+++ b/gno.land/pkg/gnoland/node_inmemory.go
@@ -2,6 +2,7 @@ package gnoland
import (
"fmt"
+ "sync"
"time"
abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
@@ -12,6 +13,7 @@ import (
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/ed25519"
"github.com/gnolang/gno/tm2/pkg/db"
+ "github.com/gnolang/gno/tm2/pkg/events"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/gnolang/gno/tm2/pkg/p2p"
"github.com/gnolang/gno/tm2/pkg/std"
@@ -51,7 +53,9 @@ func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc {
}
func NewDefaultTMConfig(rootdir string) *tmcfg.Config {
- return tmcfg.DefaultConfig().SetRootDir(rootdir)
+ // We use `TestConfig` here otherwise ChainID will be empty, and
+ // there is no other way to update it than using a config file
+ return tmcfg.TestConfig().SetRootDir(rootdir)
}
// NewInMemoryNodeConfig creates a default configuration for an in-memory node.
@@ -145,3 +149,29 @@ func NewInMemoryNode(logger log.Logger, cfg *InMemoryNodeConfig) (*node.Node, er
logger,
)
}
+
+// GetNodeReadiness waits until the node is ready, signaling via the EventNewBlock event.
+// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216
+func GetNodeReadiness(n *node.Node) <-chan struct{} {
+ const listenerID = "first_block_listener"
+
+ var once sync.Once
+
+ nb := make(chan struct{})
+ ready := func() {
+ close(nb)
+ n.EventSwitch().RemoveListener(listenerID)
+ }
+
+ n.EventSwitch().AddListener(listenerID, func(ev events.Event) {
+ if _, ok := ev.(bft.EventNewBlock); ok {
+ once.Do(ready)
+ }
+ })
+
+ if n.BlockStore().Height() > 0 {
+ once.Do(ready)
+ }
+
+ return nb
+}
diff --git a/gno.land/pkg/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go
new file mode 100644
index 00000000000..b09ce6e3487
--- /dev/null
+++ b/gno.land/pkg/gnoweb/gnoweb.go
@@ -0,0 +1,509 @@
+package gnoweb
+
+import (
+ "embed"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "net/http"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/gnolang/gno/tm2/pkg/amino"
+ abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/gnolang/gno/tm2/pkg/log"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/gorilla/mux"
+ "github.com/gotuna/gotuna"
+
+ // for static files
+ "github.com/gnolang/gno/gno.land/pkg/gnoweb/static"
+ "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types
+ // "github.com/gnolang/gno/tm2/pkg/sdk" // for baseapp (info, status)
+)
+
+const (
+ qFileStr = "vm/qfile"
+)
+
+//go:embed views/*
+var defaultViewsFiles embed.FS
+
+type Config struct {
+ RemoteAddr string
+ CaptchaSite string
+ FaucetURL string
+ ViewsDir string
+ HelpChainID string
+ HelpRemote string
+ WithAnalytics bool
+
+ BindAddr string
+}
+
+func NewDefaultConfig() Config {
+ return Config{
+ RemoteAddr: "127.0.0.1:26657",
+ CaptchaSite: "",
+ FaucetURL: "http://localhost:5050",
+ ViewsDir: "",
+ HelpChainID: "dev",
+ HelpRemote: "127.0.0.1:26657",
+ WithAnalytics: false,
+ }
+}
+
+func MakeApp(logger log.Logger, cfg Config) gotuna.App {
+ var viewFiles fs.FS
+
+ // Get specific views directory if specified
+ if cfg.ViewsDir != "" {
+ viewFiles = os.DirFS(cfg.ViewsDir)
+ } else {
+ // Get embed views
+ var err error
+ viewFiles, err = fs.Sub(defaultViewsFiles, "views")
+ if err != nil {
+ panic("unable to get views directory from embed fs: " + err.Error())
+ }
+ }
+
+ app := gotuna.App{
+ ViewFiles: viewFiles,
+ Router: gotuna.NewMuxRouter(),
+ Static: static.EmbeddedStatic,
+ }
+
+ // realm aliases
+ aliases := map[string]string{
+ "/": "/r/gnoland/home",
+ "/about": "/r/gnoland/pages:p/about",
+ "/gnolang": "/r/gnoland/pages:p/gnolang",
+ "/ecosystem": "/r/gnoland/pages:p/ecosystem",
+ "/partners": "/r/gnoland/pages:p/partners",
+ "/testnets": "/r/gnoland/pages:p/testnets",
+ "/start": "/r/gnoland/pages:p/start",
+ "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm
+ "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm
+ }
+ for from, to := range aliases {
+ app.Router.Handle(from, handlerRealmAlias(logger, app, &cfg, to))
+ }
+ // http redirects
+ redirects := map[string]string{
+ "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary
+ "/blog": "/r/gnoland/blog",
+ "/gor": "/game-of-realms",
+ "/grants": "/partners",
+ "/language": "/gnolang",
+ "/getting-started": "/start",
+ }
+ for from, to := range redirects {
+ app.Router.Handle(from, handlerRedirect(logger, app, &cfg, to))
+ }
+ // realm routes
+ // NOTE: see rePathPart.
+ app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(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))
+
+ // other
+ app.Router.Handle("/faucet", handlerFaucet(logger, app, &cfg))
+ app.Router.Handle("/static/{path:.+}", handlerStaticFile(logger, app, &cfg))
+ app.Router.Handle("/favicon.ico", handlerFavicon(logger, app, &cfg))
+
+ // api
+ app.Router.Handle("/status.json", handlerStatusJSON(logger, app, &cfg))
+
+ app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ path := r.RequestURI
+ handleNotFound(app, &cfg, path, w, r)
+ })
+ return app
+}
+
+// handlerRealmAlias is used to render official pages from realms.
+// url is intended to be shorter.
+// UX is intended to be more minimalistic.
+// A link to the realm realm is added.
+func handlerRealmAlias(logger log.Logger, app gotuna.App, cfg *Config, rlmpath string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ rlmfullpath := "gno.land" + rlmpath
+ querystr := "" // XXX: "?gnoweb-alias=1"
+ parts := strings.Split(rlmpath, ":")
+ switch len(parts) {
+ case 1: // continue
+ case 2: // r/realm:querystr
+ rlmfullpath = "gno.land" + parts[0]
+ querystr = parts[1] + querystr
+ default:
+ panic("should not happen")
+ }
+ rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/")
+ qpath := "vm/qrender"
+ data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr))
+ res, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ writeError(logger, w, fmt.Errorf("gnoweb failed to query gnoland: %w", err))
+ return
+ }
+
+ queryParts := strings.Split(querystr, "/")
+ pathLinks := []pathLink{}
+ for i, part := range queryParts {
+ pathLinks = append(pathLinks, pathLink{
+ URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"),
+ Text: part,
+ })
+ }
+
+ tmpl := app.NewTemplatingEngine()
+ // XXX: extract title from realm's output
+ // XXX: extract description from realm's output
+ tmpl.Set("RealmName", rlmname)
+ tmpl.Set("RealmPath", rlmpath)
+ tmpl.Set("Query", querystr)
+ tmpl.Set("PathLinks", pathLinks)
+ tmpl.Set("Contents", string(res.Data))
+ tmpl.Set("Config", cfg)
+ tmpl.Set("IsAlias", true)
+ tmpl.Render(w, r, "realm_render.html", "funcs.html")
+ })
+}
+
+func handlerFaucet(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ app.NewTemplatingEngine().
+ Set("Config", cfg).
+ Render(w, r, "faucet.html", "funcs.html")
+ })
+}
+
+func handlerStatusJSON(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ startedAt := time.Now()
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var ret struct {
+ Gnoland struct {
+ Connected bool `json:"connected"`
+ Error *string `json:"error,omitempty"`
+ Height *int64 `json:"height,omitempty"`
+ // processed txs
+ // active connections
+
+ Version *string `json:"version,omitempty"`
+ // Uptime *float64 `json:"uptime-seconds,omitempty"`
+ // Goarch *string `json:"goarch,omitempty"`
+ // Goos *string `json:"goos,omitempty"`
+ // GoVersion *string `json:"go-version,omitempty"`
+ // NumCPU *int `json:"num_cpu,omitempty"`
+ } `json:"gnoland"`
+ Website struct {
+ // Version string `json:"version"`
+ Uptime float64 `json:"uptime-seconds"`
+ Goarch string `json:"goarch"`
+ Goos string `json:"goos"`
+ GoVersion string `json:"go-version"`
+ NumCPU int `json:"num_cpu"`
+ } `json:"website"`
+ }
+ ret.Website.Uptime = time.Since(startedAt).Seconds()
+ ret.Website.Goarch = runtime.GOARCH
+ ret.Website.Goos = runtime.GOOS
+ ret.Website.NumCPU = runtime.NumCPU()
+ ret.Website.GoVersion = runtime.Version()
+
+ ret.Gnoland.Connected = true
+ res, err := makeRequest(logger, cfg, ".app/version", []byte{})
+ if err != nil {
+ ret.Gnoland.Connected = false
+ errmsg := err.Error()
+ ret.Gnoland.Error = &errmsg
+ } else {
+ version := string(res.Value)
+ ret.Gnoland.Version = &version
+ ret.Gnoland.Height = &res.Height
+ }
+
+ out, _ := json.MarshalIndent(ret, "", " ")
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(out)
+ })
+}
+
+func handlerRedirect(logger log.Logger, app gotuna.App, cfg *Config, to string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, to, http.StatusFound)
+ tmpl := app.NewTemplatingEngine()
+ tmpl.Set("To", to)
+ tmpl.Set("Config", cfg)
+ tmpl.Render(w, r, "redirect.html", "funcs.html")
+ })
+}
+
+func handlerRealmMain(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ rlmname := vars["rlmname"]
+ rlmpath := "gno.land/r/" + rlmname
+ query := r.URL.Query()
+
+ logger.Info("handling", "name", rlmname, "path", rlmpath)
+ if query.Has("help") {
+ // Render function helper.
+ funcName := query.Get("__func")
+ qpath := "vm/qfuncs"
+ data := []byte(rlmpath)
+ res, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ writeError(logger, w, fmt.Errorf("request failed: %w", err))
+ return
+ }
+ var fsigs vm.FunctionSignatures
+ amino.MustUnmarshalJSON(res.Data, &fsigs)
+ // Fill fsigs with query parameters.
+ for i := range fsigs {
+ fsig := &(fsigs[i])
+ for j := range fsig.Params {
+ param := &(fsig.Params[j])
+ value := query.Get(param.Name)
+ param.Value = value
+ }
+ }
+ // Render template.
+ tmpl := app.NewTemplatingEngine()
+ tmpl.Set("FuncName", funcName)
+ tmpl.Set("RealmPath", rlmpath)
+ tmpl.Set("DirPath", pathOf(rlmpath))
+ tmpl.Set("FunctionSignatures", fsigs)
+ tmpl.Set("Config", cfg)
+ tmpl.Render(w, r, "realm_help.html", "funcs.html")
+ } else {
+ // Ensure realm exists. TODO optimize.
+ qpath := qFileStr
+ data := []byte(rlmpath)
+ _, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ writeError(logger, w, errors.New("error querying realm package"))
+ return
+ }
+ // Render blank query path, /r/REALM:.
+ handleRealmRender(logger, app, cfg, w, r)
+ }
+ })
+}
+
+type pathLink struct {
+ URL string
+ Text string
+}
+
+func handlerRealmRender(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ handleRealmRender(logger, app, cfg, w, r)
+ })
+}
+
+func handleRealmRender(logger log.Logger, app gotuna.App, cfg *Config, w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ rlmname := vars["rlmname"]
+ rlmpath := "gno.land/r/" + rlmname
+ querystr := vars["querystr"]
+ if r.URL.Path == "/r/"+rlmname+":" {
+ // Redirect to /r/REALM if querypath is empty.
+ http.Redirect(w, r, "/r/"+rlmname, http.StatusFound)
+ return
+ }
+ qpath := "vm/qrender"
+ data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr))
+ res, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ // XXX hack
+ if strings.Contains(err.Error(), "Render not declared") {
+ res = &abci.ResponseQuery{}
+ res.Data = []byte("realm package has no Render() function")
+ } else {
+ writeError(logger, w, err)
+ return
+ }
+ }
+ // linkify querystr.
+ queryParts := strings.Split(querystr, "/")
+ pathLinks := []pathLink{}
+ for i, part := range queryParts {
+ pathLinks = append(pathLinks, pathLink{
+ URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"),
+ Text: part,
+ })
+ }
+ // Render template.
+ tmpl := app.NewTemplatingEngine()
+ // XXX: extract title from realm's output
+ // XXX: extract description from realm's output
+ tmpl.Set("RealmName", rlmname)
+ tmpl.Set("RealmPath", rlmpath)
+ tmpl.Set("Query", querystr)
+ tmpl.Set("PathLinks", pathLinks)
+ tmpl.Set("Contents", string(res.Data))
+ tmpl.Set("Config", cfg)
+ tmpl.Render(w, r, "realm_render.html", "funcs.html")
+}
+
+func handlerRealmFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ diruri := "gno.land/r/" + vars["rlmname"]
+ filename := vars["filename"]
+ renderPackageFile(logger, app, cfg, w, r, diruri, filename)
+ })
+}
+
+func handlerPackageFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ pkgpath := "gno.land/p/" + vars["filepath"]
+ diruri, filename := std.SplitFilepath(pkgpath)
+ if filename == "" && diruri == pkgpath {
+ // redirect to diruri + "/"
+ http.Redirect(w, r, "/p/"+vars["filepath"]+"/", http.StatusFound)
+ return
+ }
+ renderPackageFile(logger, app, cfg, w, r, diruri, filename)
+ })
+}
+
+func renderPackageFile(logger log.Logger, app gotuna.App, cfg *Config, w http.ResponseWriter, r *http.Request, diruri string, filename string) {
+ if filename == "" {
+ // Request is for a folder.
+ qpath := qFileStr
+ data := []byte(diruri)
+ res, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ writeError(logger, w, err)
+ return
+ }
+ files := strings.Split(string(res.Data), "\n")
+ // Render template.
+ tmpl := app.NewTemplatingEngine()
+ tmpl.Set("DirURI", diruri)
+ tmpl.Set("DirPath", pathOf(diruri))
+ tmpl.Set("Files", files)
+ tmpl.Set("Config", cfg)
+ tmpl.Render(w, r, "package_dir.html", "funcs.html")
+ } else {
+ // Request is for a file.
+ filepath := diruri + "/" + filename
+ qpath := qFileStr
+ data := []byte(filepath)
+ res, err := makeRequest(logger, cfg, qpath, data)
+ if err != nil {
+ writeError(logger, w, err)
+ return
+ }
+ // Render template.
+ tmpl := app.NewTemplatingEngine()
+ tmpl.Set("DirURI", diruri)
+ tmpl.Set("DirPath", pathOf(diruri))
+ tmpl.Set("FileName", filename)
+ tmpl.Set("FileContents", string(res.Data))
+ tmpl.Set("Config", cfg)
+ tmpl.Render(w, r, "package_file.html", "funcs.html")
+ }
+}
+
+func makeRequest(log log.Logger, cfg *Config, qpath string, data []byte) (res *abci.ResponseQuery, err error) {
+ opts2 := client.ABCIQueryOptions{
+ // Height: height, XXX
+ // Prove: false, XXX
+ }
+ remote := cfg.RemoteAddr
+ cli := client.NewHTTP(remote, "/websocket")
+ qres, err := cli.ABCIQueryWithOptions(
+ qpath, data, opts2)
+ if err != nil {
+ log.Error("request error", "path", qpath, "error", err)
+ return nil, fmt.Errorf("unable to query path %q: %w", qpath, err)
+ }
+ if qres.Response.Error != nil {
+ log.Error("response error", "path", qpath, "log", qres.Response.Log)
+ return nil, qres.Response.Error
+ }
+ return &qres.Response, nil
+}
+
+func handlerStaticFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ fs := http.FS(app.Static)
+ fileapp := http.StripPrefix("/static", http.FileServer(fs))
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ fpath := filepath.Clean(vars["path"])
+ f, err := fs.Open(fpath)
+ if os.IsNotExist(err) {
+ handleNotFound(app, cfg, fpath, w, r)
+ return
+ }
+ stat, err := f.Stat()
+ if err != nil || stat.IsDir() {
+ handleNotFound(app, cfg, fpath, w, r)
+ return
+ }
+
+ // TODO: ModTime doesn't work for embed?
+ // w.Header().Set("ETag", fmt.Sprintf("%x", stat.ModTime().UnixNano()))
+ // w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s", "31536000"))
+ fileapp.ServeHTTP(w, r)
+ })
+}
+
+func handlerFavicon(logger log.Logger, app gotuna.App, cfg *Config) http.Handler {
+ fs := http.FS(app.Static)
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fpath := "img/favicon.ico"
+ f, err := fs.Open(fpath)
+ if os.IsNotExist(err) {
+ handleNotFound(app, cfg, fpath, w, r)
+ return
+ }
+ w.Header().Set("Content-Type", "image/x-icon")
+ w.Header().Set("Cache-Control", "public, max-age=604800") // 7d
+ io.Copy(w, f)
+ })
+}
+
+func handleNotFound(app gotuna.App, cfg *Config, path string, w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ app.NewTemplatingEngine().
+ Set("title", "Not found").
+ Set("path", path).
+ Set("Config", cfg).
+ Render(w, r, "404.html", "funcs.html")
+}
+
+func writeError(logger log.Logger, w http.ResponseWriter, err error) {
+ if details := errors.Unwrap(err); details != nil {
+ logger.Error("handler", "error", err, "details", details)
+ } else {
+ logger.Error("handler", "error:", err)
+ }
+
+ // XXX: writeError should return an error page template.
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error()))
+}
+
+func pathOf(diruri string) string {
+ parts := strings.Split(diruri, "/")
+ if parts[0] == "gno.land" {
+ return "/" + strings.Join(parts[1:], "/")
+ } else {
+ panic(fmt.Sprintf("invalid dir-URI %q", diruri))
+ }
+}
diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go
similarity index 88%
rename from gno.land/cmd/gnoweb/main_test.go
rename to gno.land/pkg/gnoweb/gnoweb_test.go
index 2bec0a9ac37..61f7244141e 100644
--- a/gno.land/cmd/gnoweb/main_test.go
+++ b/gno.land/pkg/gnoweb/gnoweb_test.go
@@ -1,4 +1,4 @@
-package main
+package gnoweb
import (
"fmt"
@@ -48,14 +48,14 @@ func TestRoutes(t *testing.T) {
node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config)
defer node.Stop()
+ cfg := NewDefaultConfig()
+
+ logger := log.TestingLogger()
+
// set the `remoteAddr` of the client to the listening address of the
// node, which is randomly assigned.
- flags.RemoteAddr = remoteAddr
- flags.HelpChainID = "dev"
- flags.CaptchaSite = ""
- flags.ViewsDir = "../../cmd/gnoweb/views"
- flags.WithAnalytics = false
- app := makeApp()
+ cfg.RemoteAddr = remoteAddr
+ app := MakeApp(logger, cfg)
for _, r := range routes {
t.Run(fmt.Sprintf("test route %s", r.route), func(t *testing.T) {
@@ -63,6 +63,7 @@ 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)
// println(response.Body.String())
})
@@ -96,13 +97,17 @@ func TestAnalytics(t *testing.T) {
node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config)
defer node.Stop()
- flags.ViewsDir = "../../cmd/gnoweb/views"
+ cfg := NewDefaultConfig()
+ cfg.RemoteAddr = remoteAddr
+
+ logger := log.TestingLogger()
+
t.Run("with", func(t *testing.T) {
for _, route := range routes {
t.Run(route, func(t *testing.T) {
- flags.RemoteAddr = remoteAddr
- flags.WithAnalytics = true
- app := makeApp()
+ ccfg := cfg // clone config
+ ccfg.WithAnalytics = true
+ app := MakeApp(logger, ccfg)
request := httptest.NewRequest(http.MethodGet, route, nil)
response := httptest.NewRecorder()
app.Router.ServeHTTP(response, request)
@@ -113,9 +118,9 @@ func TestAnalytics(t *testing.T) {
t.Run("without", func(t *testing.T) {
for _, route := range routes {
t.Run(route, func(t *testing.T) {
- flags.RemoteAddr = remoteAddr
- flags.WithAnalytics = false
- app := makeApp()
+ ccfg := cfg // clone config
+ ccfg.WithAnalytics = false
+ app := MakeApp(logger, ccfg)
request := httptest.NewRequest(http.MethodGet, route, nil)
response := httptest.NewRecorder()
app.Router.ServeHTTP(response, request)
diff --git a/gno.land/cmd/gnoweb/static/css/app.css b/gno.land/pkg/gnoweb/static/css/app.css
similarity index 100%
rename from gno.land/cmd/gnoweb/static/css/app.css
rename to gno.land/pkg/gnoweb/static/css/app.css
diff --git a/gno.land/cmd/gnoweb/static/css/normalize.css b/gno.land/pkg/gnoweb/static/css/normalize.css
similarity index 100%
rename from gno.land/cmd/gnoweb/static/css/normalize.css
rename to gno.land/pkg/gnoweb/static/css/normalize.css
diff --git a/gno.land/cmd/gnoweb/static/font/README.md b/gno.land/pkg/gnoweb/static/font/README.md
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/README.md
rename to gno.land/pkg/gnoweb/static/font/README.md
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/LICENSE.txt b/gno.land/pkg/gnoweb/static/font/roboto/LICENSE.txt
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/LICENSE.txt
rename to gno.land/pkg/gnoweb/static/font/roboto/LICENSE.txt
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Bold.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Bold.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Bold.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Bold.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Italic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Italic.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Italic.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Italic.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Light.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Light.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Light.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Light.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Medium.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Medium.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Medium.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Medium.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Regular.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Regular.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Regular.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Regular.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Thin.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Thin.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Thin.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Thin.woff
diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff
similarity index 100%
rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff
rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff
diff --git a/gno.land/cmd/gnoweb/static/img/favicon.ico b/gno.land/pkg/gnoweb/static/img/favicon.ico
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/favicon.ico
rename to gno.land/pkg/gnoweb/static/img/favicon.ico
diff --git a/gno.land/cmd/gnoweb/static/img/github-mark-32px.png b/gno.land/pkg/gnoweb/static/img/github-mark-32px.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/github-mark-32px.png
rename to gno.land/pkg/gnoweb/static/img/github-mark-32px.png
diff --git a/gno.land/cmd/gnoweb/static/img/github-mark-64px.png b/gno.land/pkg/gnoweb/static/img/github-mark-64px.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/github-mark-64px.png
rename to gno.land/pkg/gnoweb/static/img/github-mark-64px.png
diff --git a/gno.land/cmd/gnoweb/static/img/ico-discord.svg b/gno.land/pkg/gnoweb/static/img/ico-discord.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/ico-discord.svg
rename to gno.land/pkg/gnoweb/static/img/ico-discord.svg
diff --git a/gno.land/cmd/gnoweb/static/img/ico-email.svg b/gno.land/pkg/gnoweb/static/img/ico-email.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/ico-email.svg
rename to gno.land/pkg/gnoweb/static/img/ico-email.svg
diff --git a/gno.land/cmd/gnoweb/static/img/ico-telegram.svg b/gno.land/pkg/gnoweb/static/img/ico-telegram.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/ico-telegram.svg
rename to gno.land/pkg/gnoweb/static/img/ico-telegram.svg
diff --git a/gno.land/cmd/gnoweb/static/img/ico-twitter.svg b/gno.land/pkg/gnoweb/static/img/ico-twitter.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/ico-twitter.svg
rename to gno.land/pkg/gnoweb/static/img/ico-twitter.svg
diff --git a/gno.land/cmd/gnoweb/static/img/ico-youtube.svg b/gno.land/pkg/gnoweb/static/img/ico-youtube.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/ico-youtube.svg
rename to gno.land/pkg/gnoweb/static/img/ico-youtube.svg
diff --git a/gno.land/cmd/gnoweb/static/img/list-alt.png b/gno.land/pkg/gnoweb/static/img/list-alt.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/list-alt.png
rename to gno.land/pkg/gnoweb/static/img/list-alt.png
diff --git a/gno.land/cmd/gnoweb/static/img/list.png b/gno.land/pkg/gnoweb/static/img/list.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/list.png
rename to gno.land/pkg/gnoweb/static/img/list.png
diff --git a/gno.land/cmd/gnoweb/static/img/logo-square.png b/gno.land/pkg/gnoweb/static/img/logo-square.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/logo-square.png
rename to gno.land/pkg/gnoweb/static/img/logo-square.png
diff --git a/gno.land/cmd/gnoweb/static/img/logo-square.svg b/gno.land/pkg/gnoweb/static/img/logo-square.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/logo-square.svg
rename to gno.land/pkg/gnoweb/static/img/logo-square.svg
diff --git a/gno.land/cmd/gnoweb/static/img/logo-v1.png b/gno.land/pkg/gnoweb/static/img/logo-v1.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/logo-v1.png
rename to gno.land/pkg/gnoweb/static/img/logo-v1.png
diff --git a/gno.land/cmd/gnoweb/static/img/logo.png b/gno.land/pkg/gnoweb/static/img/logo.png
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/logo.png
rename to gno.land/pkg/gnoweb/static/img/logo.png
diff --git a/gno.land/cmd/gnoweb/static/img/logo.svg b/gno.land/pkg/gnoweb/static/img/logo.svg
similarity index 100%
rename from gno.land/cmd/gnoweb/static/img/logo.svg
rename to gno.land/pkg/gnoweb/static/img/logo.svg
diff --git a/gno.land/cmd/gnoweb/static/invites.txt b/gno.land/pkg/gnoweb/static/invites.txt
similarity index 100%
rename from gno.land/cmd/gnoweb/static/invites.txt
rename to gno.land/pkg/gnoweb/static/invites.txt
diff --git a/gno.land/cmd/gnoweb/static/js/highlight.min.js b/gno.land/pkg/gnoweb/static/js/highlight.min.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/highlight.min.js
rename to gno.land/pkg/gnoweb/static/js/highlight.min.js
diff --git a/gno.land/cmd/gnoweb/static/js/marked.min.js b/gno.land/pkg/gnoweb/static/js/marked.min.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/marked.min.js
rename to gno.land/pkg/gnoweb/static/js/marked.min.js
diff --git a/gno.land/cmd/gnoweb/static/js/purify.min.js b/gno.land/pkg/gnoweb/static/js/purify.min.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/purify.min.js
rename to gno.land/pkg/gnoweb/static/js/purify.min.js
diff --git a/gno.land/cmd/gnoweb/static/js/realm_help.js b/gno.land/pkg/gnoweb/static/js/realm_help.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/realm_help.js
rename to gno.land/pkg/gnoweb/static/js/realm_help.js
diff --git a/gno.land/cmd/gnoweb/static/js/renderer.js b/gno.land/pkg/gnoweb/static/js/renderer.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/renderer.js
rename to gno.land/pkg/gnoweb/static/js/renderer.js
diff --git a/gno.land/cmd/gnoweb/static/js/umbrella.js b/gno.land/pkg/gnoweb/static/js/umbrella.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/umbrella.js
rename to gno.land/pkg/gnoweb/static/js/umbrella.js
diff --git a/gno.land/cmd/gnoweb/static/js/umbrella.min.js b/gno.land/pkg/gnoweb/static/js/umbrella.min.js
similarity index 100%
rename from gno.land/cmd/gnoweb/static/js/umbrella.min.js
rename to gno.land/pkg/gnoweb/static/js/umbrella.min.js
diff --git a/gno.land/cmd/gnoweb/static/static.go b/gno.land/pkg/gnoweb/static/static.go
similarity index 100%
rename from gno.land/cmd/gnoweb/static/static.go
rename to gno.land/pkg/gnoweb/static/static.go
diff --git a/gno.land/cmd/gnoweb/views/404.html b/gno.land/pkg/gnoweb/views/404.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/404.html
rename to gno.land/pkg/gnoweb/views/404.html
diff --git a/gno.land/cmd/gnoweb/views/faucet.html b/gno.land/pkg/gnoweb/views/faucet.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/faucet.html
rename to gno.land/pkg/gnoweb/views/faucet.html
diff --git a/gno.land/cmd/gnoweb/views/funcs.html b/gno.land/pkg/gnoweb/views/funcs.html
similarity index 99%
rename from gno.land/cmd/gnoweb/views/funcs.html
rename to gno.land/pkg/gnoweb/views/funcs.html
index c8d643ef655..391675755aa 100644
--- a/gno.land/cmd/gnoweb/views/funcs.html
+++ b/gno.land/pkg/gnoweb/views/funcs.html
@@ -155,7 +155,7 @@
{{- end -}}
{{- define "analytics" -}}
-{{- if .Data.Flags.WithAnalytics -}}
+{{- if .Data.Config.WithAnalytics -}}
diff --git a/gno.land/cmd/gnoweb/views/generic.html b/gno.land/pkg/gnoweb/views/generic.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/generic.html
rename to gno.land/pkg/gnoweb/views/generic.html
diff --git a/gno.land/cmd/gnoweb/views/package_dir.html b/gno.land/pkg/gnoweb/views/package_dir.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/package_dir.html
rename to gno.land/pkg/gnoweb/views/package_dir.html
diff --git a/gno.land/cmd/gnoweb/views/package_file.html b/gno.land/pkg/gnoweb/views/package_file.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/package_file.html
rename to gno.land/pkg/gnoweb/views/package_file.html
diff --git a/gno.land/cmd/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/realm_help.html
rename to gno.land/pkg/gnoweb/views/realm_help.html
diff --git a/gno.land/cmd/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/realm_render.html
rename to gno.land/pkg/gnoweb/views/realm_render.html
diff --git a/gno.land/cmd/gnoweb/views/redirect.html b/gno.land/pkg/gnoweb/views/redirect.html
similarity index 100%
rename from gno.land/cmd/gnoweb/views/redirect.html
rename to gno.land/pkg/gnoweb/views/redirect.html
diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go
index 1ca7e11eb63..4705f4e3d73 100644
--- a/gno.land/pkg/integration/testing_node.go
+++ b/gno.land/pkg/integration/testing_node.go
@@ -2,7 +2,6 @@ package integration
import (
"path/filepath"
- "sync"
"time"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
@@ -11,7 +10,6 @@ import (
"github.com/gnolang/gno/tm2/pkg/bft/node"
bft "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/events"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/jaekwon/testify/require"
@@ -33,7 +31,7 @@ func TestingInMemoryNode(t TestingTS, logger log.Logger, config *gnoland.InMemor
require.NoError(t, err)
select {
- case <-waitForNodeReadiness(node):
+ case <-gnoland.GetNodeReadiness(node):
case <-time.After(time.Second * 6):
require.FailNow(t, "timeout while waiting for the node to start")
}
@@ -116,8 +114,7 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std
examplesDir := filepath.Join(gnoroot, "examples")
defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
- defaultCreator := crypto.MustAddressFromString(DefaultAccount_Address) // test1
- txs, err := gnoland.LoadPackagesFromDir(examplesDir, defaultCreator, defaultFee, nil)
+ txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee, nil)
require.NoError(t, err)
return txs
@@ -156,29 +153,3 @@ func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config {
tmconfig.P2P.ListenAddress = defaultListner
return tmconfig
}
-
-// waitForNodeReadiness waits until the node is ready, signaling via the EventNewBlock event.
-// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216
-func waitForNodeReadiness(n *node.Node) <-chan struct{} {
- const listenerID = "first_block_listener"
-
- var once sync.Once
-
- nb := make(chan struct{})
- ready := func() {
- close(nb)
- n.EventSwitch().RemoveListener(listenerID)
- }
-
- n.EventSwitch().AddListener(listenerID, func(ev events.Event) {
- if _, ok := ev.(bft.EventNewBlock); ok {
- once.Do(ready)
- }
- })
-
- if n.BlockStore().Height() > 0 {
- once.Do(ready)
- }
-
- return nb
-}