From f2451278eb637bb7dd92eaf4c95901aec89ebddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 7 Jan 2025 14:38:25 +0100 Subject: [PATCH 01/75] feat: clean up `p2p` & implement missing peering functionality (#2852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Closes #2308 There is a lot happening in this PR, so don't be discouraged by the files changed. I'll outline the way the PR should be reviewed, and give you pointers along the way. ## What this PR set out to do This PR initially set out to implement peer discovery in the TM2 `p2p` module -- that's it, basically. The change was meant to be minimal, and not involve larger changes. After spending more time than I'd like to admit in the `p2p` codebase, I've come to a couple of realizations: - the code is insanely complex for what it needs to be doing. - there are premature "optimizations" on every corner, with no real reason for them. - the unit tests in the `p2p` package are sketchy to say the least, and not convincing at all that we were covering actual functionality we needed to cover. - there are random disconnection issues with larger clusters. All of these are temporarily fine, and not blocking us. But the pattern was there -- to add peer discovery, it would require continuing the same pattern of code gymnastics present in the `p2p` module for the last 5+ years. I took this opportunity, ahead of the mainnet launch, to fill out a few checkboxes: - **simplify the code, trim _everything_ that's excess**. This is in line with our project `PHILOSOPHY.md`. - create a safety net in the form of integration tests, and unit tests, that run _instantly_, and give us the confidence stuff actually works. - make it easy peasy for us to debug future p2p problems, **when** they arise (we already keep seeing them on existing testnets, like `test4` and `test5`. I wanted to implement all of this, without breaking any existing TM2 functionality that relies on the `p2p` module, or introducing additional complexities. ## What this PR actually accomplished I'm proud to say that this PR brings more than a few bells and whistles to the table, in terms of TM2 improvements: - _greatly_ simplified and faster `Switch` and `Transport` implementations. No more gazillion redundant checks, or expensive lookups, or convoluted APIs. - a unit testing suite we can be proud of, and have confidence in. I rewrote the entire testing suite for the module, because the old implementation had severe limitations on mock-ability. - peer discovery that works, and is not invasive. Goodbye random network pockets, and hanging nodes! - many bugs, and potential issues squashed and erased. Regressions added, and passing. For the sake of not making groundbreaking changes ahead of mainnet, I didn't touch a few things, and this will be evident from the code: - I didn't touch the `conn` package, or how the multiplex connections are established and maintained (or the STS implementation) -- this would be too much, and require an exponential amount of time to get right. - the `Peer` abstraction is still the same, and TM2 modules interact with the peers in the same way as before (directly, as `Reactor`s). I've outlined the issues with this in the README, so check it out. In retrospect, I should've limited the scope of this PR by a lot. At the time I was at the mid-way point, I committed fully to leaving this module in a better state than I found it in, rather than leave additional tech debt for future cleanup. The other primary goal of this PR is to scope out changes needed to upgrade the networking layer implementation into utilizing a stack like libp2p. I am happy to say that we have 0 limitations in terms of p2p functionality to make the switch. We're just bound by time. This upgrade is scheduled for after the mainnet MVP launch 🤞 ## How do I review this PR? There is no point in looking at the older implementation, and trying to figure out the changes from there. There are just too many, and it can get overwhelming quickly. Instead, as the **first step** -- read the new `p2p` [README](https://github.com/gnolang/gno/blob/dev/zivkovicmilos/bootnodes/tm2/pkg/p2p/README.md). It outlines how the `p2p` module works on a core level, and highlights current challenges. After the README, open the `p2p` package in `tm2/pkg/p2p`, and start looking at the implementation from there. Leave comments on things that are unclear, or can be improved. I'll try to answer and give as much context as I can. ## What `p2p` config params are changed? Here is a complete list of changed `p2p` configuration params, and the reasoning behind them: - `UPNP` - UPNP port forwarding, it was completely unused, **removed**. - `PexReactor` - peer exchange reactor enable-ment flag, renamed to `PeerExchange`, **rename** - `SeedMode` - enabled network crawls, was tied to `PexReactor` being `true`. Useless flag, since `PeerExchange` exists, **removed** - `AllowDuplicateIP` - useless flag to prevent same-IP dials, even outside previous dial "filters", **removed** - `HandshakeTimeout` - excessive config option for setting an STS timeout, sane default is `3s`, **removed** - `DialTimeout` - excessive config option for finishing a peer dial, sane default is `3s`, **removed** The following config options were **removed**, as they related to testing, and were replaced by unit / integration tests. - `TestDialFail` - `TestFuzz` - `TestFuzzConfig`
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--- .github/golangci.yml | 1 + contribs/gnodev/go.mod | 1 + contribs/gnodev/go.sum | 2 + contribs/gnofaucet/go.mod | 1 + contribs/gnofaucet/go.sum | 2 + contribs/gnogenesis/go.mod | 2 + contribs/gnogenesis/go.sum | 2 + contribs/gnohealth/go.mod | 2 + contribs/gnohealth/go.sum | 4 + contribs/gnokeykc/go.mod | 2 + contribs/gnokeykc/go.sum | 4 + contribs/gnomigrate/go.mod | 2 + contribs/gnomigrate/go.sum | 2 + gno.land/cmd/gnoland/config_get_test.go | 54 +- gno.land/cmd/gnoland/config_set_test.go | 74 +- gno.land/cmd/gnoland/secrets_common.go | 6 +- gno.land/cmd/gnoland/secrets_common_test.go | 8 +- gno.land/cmd/gnoland/secrets_get.go | 6 +- gno.land/cmd/gnoland/secrets_get_test.go | 4 +- gno.land/cmd/gnoland/secrets_init.go | 10 +- gno.land/cmd/gnoland/secrets_init_test.go | 4 +- gno.land/cmd/gnoland/secrets_verify.go | 4 +- gno.land/cmd/gnoland/secrets_verify_test.go | 4 +- gno.land/pkg/gnoland/node_inmemory.go | 4 +- go.mod | 1 + go.sum | 2 + misc/autocounterd/go.mod | 2 + misc/autocounterd/go.sum | 4 + misc/loop/go.mod | 2 + misc/loop/go.sum | 2 + tm2/pkg/bft/blockchain/pool.go | 40 +- tm2/pkg/bft/blockchain/pool_test.go | 16 +- tm2/pkg/bft/blockchain/reactor.go | 54 +- tm2/pkg/bft/blockchain/reactor_test.go | 113 +- tm2/pkg/bft/config/config.go | 17 +- tm2/pkg/bft/consensus/reactor.go | 30 +- tm2/pkg/bft/consensus/reactor_test.go | 72 +- tm2/pkg/bft/consensus/state.go | 18 +- tm2/pkg/bft/consensus/state_test.go | 4 +- .../bft/consensus/types/height_vote_set.go | 14 +- tm2/pkg/bft/mempool/reactor.go | 49 +- tm2/pkg/bft/mempool/reactor_test.go | 187 ++- tm2/pkg/bft/node/node.go | 327 ++--- tm2/pkg/bft/node/node_test.go | 37 - tm2/pkg/bft/rpc/client/batch_test.go | 6 +- tm2/pkg/bft/rpc/client/client_test.go | 10 +- tm2/pkg/bft/rpc/client/e2e_test.go | 4 +- tm2/pkg/bft/rpc/client/local.go | 8 - tm2/pkg/bft/rpc/core/net.go | 42 +- tm2/pkg/bft/rpc/core/net_test.go | 77 - tm2/pkg/bft/rpc/core/pipe.go | 8 +- tm2/pkg/bft/rpc/core/routes.go | 2 - tm2/pkg/bft/rpc/core/types/responses.go | 9 +- tm2/pkg/bft/rpc/core/types/responses_test.go | 15 +- tm2/pkg/bft/rpc/lib/client/http/client.go | 10 +- .../bft/rpc/lib/client/http/client_test.go | 4 +- tm2/pkg/crypto/crypto.go | 13 +- tm2/pkg/crypto/ed25519/ed25519.go | 8 +- tm2/pkg/internal/p2p/p2p.go | 252 ++++ tm2/pkg/p2p/README.md | 604 +++++++- tm2/pkg/p2p/base_reactor.go | 32 +- tm2/pkg/p2p/cmd/stest/main.go | 86 -- tm2/pkg/p2p/config/config.go | 107 +- tm2/pkg/p2p/config/config_test.go | 59 + tm2/pkg/p2p/conn/conn.go | 22 + tm2/pkg/p2p/conn/conn_go110.go | 15 - tm2/pkg/p2p/conn/conn_notgo110.go | 36 - tm2/pkg/p2p/conn/connection.go | 18 +- tm2/pkg/p2p/conn/connection_test.go | 20 +- tm2/pkg/p2p/conn/secret_connection.go | 15 +- tm2/pkg/p2p/conn_set.go | 81 -- tm2/pkg/p2p/dial/dial.go | 83 ++ tm2/pkg/p2p/dial/dial_test.go | 147 ++ tm2/pkg/p2p/dial/doc.go | 10 + tm2/pkg/p2p/discovery/discovery.go | 242 ++++ tm2/pkg/p2p/discovery/discovery_test.go | 453 ++++++ tm2/pkg/p2p/discovery/doc.go | 9 + tm2/pkg/p2p/discovery/mock_test.go | 135 ++ tm2/pkg/p2p/discovery/option.go | 12 + tm2/pkg/p2p/discovery/package.go | 16 + tm2/pkg/p2p/discovery/types.go | 44 + tm2/pkg/p2p/discovery/types_test.go | 80 ++ tm2/pkg/p2p/errors.go | 184 --- tm2/pkg/p2p/events/doc.go | 3 + tm2/pkg/p2p/events/events.go | 104 ++ tm2/pkg/p2p/events/events_test.go | 94 ++ tm2/pkg/p2p/events/types.go | 39 + tm2/pkg/p2p/fuzz.go | 131 -- tm2/pkg/p2p/key.go | 94 -- tm2/pkg/p2p/key_test.go | 53 - tm2/pkg/p2p/mock/peer.go | 415 +++++- tm2/pkg/p2p/mock/reactor.go | 23 - tm2/pkg/p2p/mock_test.go | 404 ++++++ tm2/pkg/p2p/netaddress_test.go | 178 --- tm2/pkg/p2p/node_info.go | 156 --- tm2/pkg/p2p/node_info_test.go | 134 -- tm2/pkg/p2p/peer.go | 342 ++--- tm2/pkg/p2p/peer_set.go | 147 -- tm2/pkg/p2p/peer_set_test.go | 190 --- tm2/pkg/p2p/peer_test.go | 761 +++++++--- tm2/pkg/p2p/set.go | 121 ++ tm2/pkg/p2p/set_test.go | 146 ++ tm2/pkg/p2p/switch.go | 1052 +++++++------- tm2/pkg/p2p/switch_option.go | 61 + tm2/pkg/p2p/switch_test.go | 1233 +++++++++-------- tm2/pkg/p2p/test_util.go | 238 ---- tm2/pkg/p2p/transport.go | 707 ++++------ tm2/pkg/p2p/transport_test.go | 909 ++++++------ tm2/pkg/p2p/types.go | 105 ++ tm2/pkg/p2p/types/key.go | 113 ++ tm2/pkg/p2p/types/key_test.go | 158 +++ tm2/pkg/p2p/{ => types}/netaddress.go | 322 ++--- tm2/pkg/p2p/types/netaddress_test.go | 323 +++++ tm2/pkg/p2p/types/node_info.go | 141 ++ tm2/pkg/p2p/types/node_info_test.go | 321 +++++ tm2/pkg/p2p/upnp/probe.go | 110 -- tm2/pkg/p2p/upnp/upnp.go | 392 ------ tm2/pkg/telemetry/metrics/metrics.go | 30 +- 118 files changed, 8091 insertions(+), 5833 deletions(-) delete mode 100644 tm2/pkg/bft/rpc/core/net_test.go create mode 100644 tm2/pkg/internal/p2p/p2p.go delete mode 100644 tm2/pkg/p2p/cmd/stest/main.go create mode 100644 tm2/pkg/p2p/config/config_test.go create mode 100644 tm2/pkg/p2p/conn/conn.go delete mode 100644 tm2/pkg/p2p/conn/conn_go110.go delete mode 100644 tm2/pkg/p2p/conn/conn_notgo110.go delete mode 100644 tm2/pkg/p2p/conn_set.go create mode 100644 tm2/pkg/p2p/dial/dial.go create mode 100644 tm2/pkg/p2p/dial/dial_test.go create mode 100644 tm2/pkg/p2p/dial/doc.go create mode 100644 tm2/pkg/p2p/discovery/discovery.go create mode 100644 tm2/pkg/p2p/discovery/discovery_test.go create mode 100644 tm2/pkg/p2p/discovery/doc.go create mode 100644 tm2/pkg/p2p/discovery/mock_test.go create mode 100644 tm2/pkg/p2p/discovery/option.go create mode 100644 tm2/pkg/p2p/discovery/package.go create mode 100644 tm2/pkg/p2p/discovery/types.go create mode 100644 tm2/pkg/p2p/discovery/types_test.go delete mode 100644 tm2/pkg/p2p/errors.go create mode 100644 tm2/pkg/p2p/events/doc.go create mode 100644 tm2/pkg/p2p/events/events.go create mode 100644 tm2/pkg/p2p/events/events_test.go create mode 100644 tm2/pkg/p2p/events/types.go delete mode 100644 tm2/pkg/p2p/fuzz.go delete mode 100644 tm2/pkg/p2p/key.go delete mode 100644 tm2/pkg/p2p/key_test.go delete mode 100644 tm2/pkg/p2p/mock/reactor.go create mode 100644 tm2/pkg/p2p/mock_test.go delete mode 100644 tm2/pkg/p2p/netaddress_test.go delete mode 100644 tm2/pkg/p2p/node_info.go delete mode 100644 tm2/pkg/p2p/node_info_test.go delete mode 100644 tm2/pkg/p2p/peer_set.go delete mode 100644 tm2/pkg/p2p/peer_set_test.go create mode 100644 tm2/pkg/p2p/set.go create mode 100644 tm2/pkg/p2p/set_test.go create mode 100644 tm2/pkg/p2p/switch_option.go delete mode 100644 tm2/pkg/p2p/test_util.go create mode 100644 tm2/pkg/p2p/types/key.go create mode 100644 tm2/pkg/p2p/types/key_test.go rename tm2/pkg/p2p/{ => types}/netaddress.go (52%) create mode 100644 tm2/pkg/p2p/types/netaddress_test.go create mode 100644 tm2/pkg/p2p/types/node_info.go create mode 100644 tm2/pkg/p2p/types/node_info_test.go delete mode 100644 tm2/pkg/p2p/upnp/probe.go delete mode 100644 tm2/pkg/p2p/upnp/upnp.go diff --git a/.github/golangci.yml b/.github/golangci.yml index ca85620b7e6..afc581d2ec5 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -85,6 +85,7 @@ issues: - gosec # Disabled linting of weak number generators - makezero # Disabled linting of intentional slice appends - goconst # Disabled linting of common mnemonics and test case strings + - unused # Disabled linting of unused mock methods - path: _\.gno linters: - errorlint # Disabled linting of error comparisons, because of lacking std lib support diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 6ca47408a75..52e1e23f27b 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -77,6 +77,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.2 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index 912345d61a8..e1792734b95 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -209,6 +209,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 3abc189b86a..3d1e5f54c54 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -31,6 +31,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index fafbc4d1060..10e2c19b408 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -111,6 +111,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index f1b316c2bee..3056af1d4cc 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -32,6 +32,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect @@ -49,6 +50,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index 7ba3aede534..7e4a683cad1 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -120,6 +120,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod index 4f5862a0d2e..4a3f6392804 100644 --- a/contribs/gnohealth/go.mod +++ b/contribs/gnohealth/go.mod @@ -21,6 +21,7 @@ require ( github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect @@ -34,6 +35,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnohealth/go.sum b/contribs/gnohealth/go.sum index dd287d9ca84..02e8893406a 100644 --- a/contribs/gnohealth/go.sum +++ b/contribs/gnohealth/go.sum @@ -103,6 +103,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -149,6 +151,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 479daed22f6..157b5585828 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -35,6 +35,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -52,6 +53,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index cacf6788d45..7aac05b84a0 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -124,6 +124,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -180,6 +182,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index cd31adc4f6f..49f40eb79af 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -29,6 +29,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opentelemetry.io/otel v1.29.0 // indirect @@ -44,6 +45,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index 7ba3aede534..7e4a683cad1 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -120,6 +120,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/gno.land/cmd/gnoland/config_get_test.go b/gno.land/cmd/gnoland/config_get_test.go index f2ddc5ca6d0..84cf0ba3d37 100644 --- a/gno.land/cmd/gnoland/config_get_test.go +++ b/gno.land/cmd/gnoland/config_get_test.go @@ -289,14 +289,6 @@ func TestConfig_Get_Base(t *testing.T) { }, true, }, - { - "filter peers flag fetched", - "filter_peers", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.FilterPeers, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, } verifyGetTestTableCommon(t, testTable) @@ -616,19 +608,11 @@ func TestConfig_Get_P2P(t *testing.T) { }, true, }, - { - "upnp toggle", - "p2p.upnp", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.UPNP, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, { "max inbound peers", "p2p.max_num_inbound_peers", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[int](t, value)) + assert.Equal(t, loadedCfg.P2P.MaxNumInboundPeers, unmarshalJSONCommon[uint64](t, value)) }, false, }, @@ -636,7 +620,7 @@ func TestConfig_Get_P2P(t *testing.T) { "max outbound peers", "p2p.max_num_outbound_peers", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[int](t, value)) + assert.Equal(t, loadedCfg.P2P.MaxNumOutboundPeers, unmarshalJSONCommon[uint64](t, value)) }, false, }, @@ -676,15 +660,7 @@ func TestConfig_Get_P2P(t *testing.T) { "pex reactor toggle", "p2p.pex", func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.PexReactor, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, - { - "seed mode", - "p2p.seed_mode", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.SeedMode, unmarshalJSONCommon[bool](t, value)) + assert.Equal(t, loadedCfg.P2P.PeerExchange, unmarshalJSONCommon[bool](t, value)) }, false, }, @@ -704,30 +680,6 @@ func TestConfig_Get_P2P(t *testing.T) { }, true, }, - { - "allow duplicate IP", - "p2p.allow_duplicate_ip", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.AllowDuplicateIP, unmarshalJSONCommon[bool](t, value)) - }, - false, - }, - { - "handshake timeout", - "p2p.handshake_timeout", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.HandshakeTimeout, unmarshalJSONCommon[time.Duration](t, value)) - }, - false, - }, - { - "dial timeout", - "p2p.dial_timeout", - func(loadedCfg *config.Config, value []byte) { - assert.Equal(t, loadedCfg.P2P.DialTimeout, unmarshalJSONCommon[time.Duration](t, value)) - }, - false, - }, } verifyGetTestTableCommon(t, testTable) diff --git a/gno.land/cmd/gnoland/config_set_test.go b/gno.land/cmd/gnoland/config_set_test.go index cb831f0e502..39880313043 100644 --- a/gno.land/cmd/gnoland/config_set_test.go +++ b/gno.land/cmd/gnoland/config_set_test.go @@ -244,19 +244,6 @@ func TestConfig_Set_Base(t *testing.T) { assert.Equal(t, value, loadedCfg.ProfListenAddress) }, }, - { - "filter peers flag updated", - []string{ - "filter_peers", - "true", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.FilterPeers) - }, - }, } verifySetTestTableCommon(t, testTable) @@ -505,19 +492,6 @@ func TestConfig_Set_P2P(t *testing.T) { assert.Equal(t, value, loadedCfg.P2P.PersistentPeers) }, }, - { - "upnp toggle updated", - []string{ - "p2p.upnp", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.UPNP) - }, - }, { "max inbound peers updated", []string{ @@ -588,20 +562,7 @@ func TestConfig_Set_P2P(t *testing.T) { boolVal, err := strconv.ParseBool(value) require.NoError(t, err) - assert.Equal(t, boolVal, loadedCfg.P2P.PexReactor) - }, - }, - { - "seed mode updated", - []string{ - "p2p.seed_mode", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.SeedMode) + assert.Equal(t, boolVal, loadedCfg.P2P.PeerExchange) }, }, { @@ -614,39 +575,6 @@ func TestConfig_Set_P2P(t *testing.T) { assert.Equal(t, value, loadedCfg.P2P.PrivatePeerIDs) }, }, - { - "allow duplicate IPs updated", - []string{ - "p2p.allow_duplicate_ip", - "false", - }, - func(loadedCfg *config.Config, value string) { - boolVal, err := strconv.ParseBool(value) - require.NoError(t, err) - - assert.Equal(t, boolVal, loadedCfg.P2P.AllowDuplicateIP) - }, - }, - { - "handshake timeout updated", - []string{ - "p2p.handshake_timeout", - "1s", - }, - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.HandshakeTimeout.String()) - }, - }, - { - "dial timeout updated", - []string{ - "p2p.dial_timeout", - "1s", - }, - func(loadedCfg *config.Config, value string) { - assert.Equal(t, value, loadedCfg.P2P.DialTimeout.String()) - }, - }, } verifySetTestTableCommon(t, testTable) diff --git a/gno.land/cmd/gnoland/secrets_common.go b/gno.land/cmd/gnoland/secrets_common.go index d40e90f6b48..500336e3489 100644 --- a/gno.land/cmd/gnoland/secrets_common.go +++ b/gno.land/cmd/gnoland/secrets_common.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var ( @@ -54,7 +54,7 @@ func isValidDirectory(dirPath string) bool { } type secretData interface { - privval.FilePVKey | privval.FilePVLastSignState | p2p.NodeKey + privval.FilePVKey | privval.FilePVLastSignState | types.NodeKey } // readSecretData reads the secret data from the given path @@ -145,7 +145,7 @@ func validateValidatorStateSignature( } // validateNodeKey validates the node's p2p key -func validateNodeKey(key *p2p.NodeKey) error { +func validateNodeKey(key *types.NodeKey) error { if key.PrivKey == nil { return errInvalidNodeKey } diff --git a/gno.land/cmd/gnoland/secrets_common_test.go b/gno.land/cmd/gnoland/secrets_common_test.go index 34592c3bd8f..38c4772c705 100644 --- a/gno.land/cmd/gnoland/secrets_common_test.go +++ b/gno.land/cmd/gnoland/secrets_common_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ func TestCommon_SaveReadData(t *testing.T) { t.Run("invalid data read path", func(t *testing.T) { t.Parallel() - readData, err := readSecretData[p2p.NodeKey]("") + readData, err := readSecretData[types.NodeKey]("") assert.Nil(t, readData) assert.ErrorContains( @@ -44,7 +44,7 @@ func TestCommon_SaveReadData(t *testing.T) { require.NoError(t, saveSecretData("totally valid key", path)) - readData, err := readSecretData[p2p.NodeKey](path) + readData, err := readSecretData[types.NodeKey](path) require.Nil(t, readData) assert.ErrorContains(t, err, "unable to unmarshal data") @@ -59,7 +59,7 @@ func TestCommon_SaveReadData(t *testing.T) { require.NoError(t, saveSecretData(key, path)) - readKey, err := readSecretData[p2p.NodeKey](path) + readKey, err := readSecretData[types.NodeKey](path) require.NoError(t, err) assert.Equal(t, key, readKey) diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 8d111516816..0a0a714f6ee 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var errInvalidSecretsGetArgs = errors.New("invalid number of secrets get arguments provided") @@ -169,7 +169,7 @@ func readValidatorState(path string) (*validatorStateInfo, error) { // readNodeID reads the node p2p info from the given path func readNodeID(path string) (*nodeIDInfo, error) { - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) if err != nil { return nil, fmt.Errorf("unable to read node key, %w", err) } @@ -199,7 +199,7 @@ func readNodeID(path string) (*nodeIDInfo, error) { // constructP2PAddress constructs the P2P address other nodes can use // to connect directly -func constructP2PAddress(nodeID p2p.ID, listenAddress string) string { +func constructP2PAddress(nodeID types.ID, listenAddress string) string { var ( address string parts = strings.SplitN(listenAddress, "://", 2) diff --git a/gno.land/cmd/gnoland/secrets_get_test.go b/gno.land/cmd/gnoland/secrets_get_test.go index 66e6e3509fc..3dfe0c727dd 100644 --- a/gno.land/cmd/gnoland/secrets_get_test.go +++ b/gno.land/cmd/gnoland/secrets_get_test.go @@ -13,7 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,7 +66,7 @@ func TestSecrets_Get_All(t *testing.T) { // Get the node key nodeKeyPath := filepath.Join(tempDir, defaultNodeKeyName) - nodeKey, err := readSecretData[p2p.NodeKey](nodeKeyPath) + nodeKey, err := readSecretData[types.NodeKey](nodeKeyPath) require.NoError(t, err) // Get the validator private key diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 58dd0783f66..9a7ddd106c3 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) var errOverwriteNotEnabled = errors.New("overwrite not enabled") @@ -200,10 +200,6 @@ func generateLastSignValidatorState() *privval.FilePVLastSignState { } // generateNodeKey generates the p2p node key -func generateNodeKey() *p2p.NodeKey { - privKey := ed25519.GenPrivKey() - - return &p2p.NodeKey{ - PrivKey: privKey, - } +func generateNodeKey() *types.NodeKey { + return types.GenerateNodeKey() } diff --git a/gno.land/cmd/gnoland/secrets_init_test.go b/gno.land/cmd/gnoland/secrets_init_test.go index 20e061447f5..7be3650fb4b 100644 --- a/gno.land/cmd/gnoland/secrets_init_test.go +++ b/gno.land/cmd/gnoland/secrets_init_test.go @@ -7,7 +7,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -37,7 +37,7 @@ func verifyValidatorState(t *testing.T, path string) { func verifyNodeKey(t *testing.T, path string) { t.Helper() - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) require.NoError(t, err) assert.NoError(t, validateNodeKey(nodeKey)) diff --git a/gno.land/cmd/gnoland/secrets_verify.go b/gno.land/cmd/gnoland/secrets_verify.go index 32e563c1c6f..15fef6649ec 100644 --- a/gno.land/cmd/gnoland/secrets_verify.go +++ b/gno.land/cmd/gnoland/secrets_verify.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type secretsVerifyCfg struct { @@ -146,7 +146,7 @@ func readAndVerifyValidatorState(path string, io commands.IO) (*privval.FilePVLa // readAndVerifyNodeKey reads the node p2p key from the given path and verifies it func readAndVerifyNodeKey(path string, io commands.IO) error { - nodeKey, err := readSecretData[p2p.NodeKey](path) + nodeKey, err := readSecretData[types.NodeKey](path) if err != nil { return fmt.Errorf("unable to read node p2p key, %w", err) } diff --git a/gno.land/cmd/gnoland/secrets_verify_test.go b/gno.land/cmd/gnoland/secrets_verify_test.go index 513d7c8b503..67630aaaa4a 100644 --- a/gno.land/cmd/gnoland/secrets_verify_test.go +++ b/gno.land/cmd/gnoland/secrets_verify_test.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/privval" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -347,7 +347,7 @@ func TestSecrets_Verify_Single(t *testing.T) { dirPath := t.TempDir() path := filepath.Join(dirPath, defaultNodeKeyName) - invalidNodeKey := &p2p.NodeKey{ + invalidNodeKey := &types.NodeKey{ PrivKey: nil, // invalid } diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index a89f39b0b4a..56a32e6e025 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -16,7 +16,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" - "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type InMemoryNodeConfig struct { @@ -138,7 +138,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, dbProvider := func(*node.DBContext) (db.DB, error) { return cfg.DB, nil } // Generate p2p node identity - nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} + nodekey := &types.NodeKey{PrivKey: ed25519.GenPrivKey()} // Create and return the in-memory node instance return node.NewNode(cfg.TMConfig, diff --git a/go.mod b/go.mod index bf7c2df94b6..ed7c3b75528 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 github.com/rs/cors v1.11.1 github.com/rs/xid v1.6.0 + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/yuin/goldmark v1.7.2 diff --git a/go.sum b/go.sum index 917270fd5a6..a4ccfbbdd66 100644 --- a/go.sum +++ b/go.sum @@ -131,6 +131,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 30a6f23b458..82e8b0081ce 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -26,6 +26,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -43,6 +44,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index 28959bf214e..e4051e3a5a4 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -120,6 +120,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -176,6 +178,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index af7783e57bb..fc2c5daac59 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -52,6 +52,7 @@ require ( github.com/prometheus/procfs v0.11.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect @@ -68,6 +69,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 0d235f2cfb1..1ed786fb82d 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -162,6 +162,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/tm2/pkg/bft/blockchain/pool.go b/tm2/pkg/bft/blockchain/pool.go index b610a0c0e7a..5fdd23af910 100644 --- a/tm2/pkg/bft/blockchain/pool.go +++ b/tm2/pkg/bft/blockchain/pool.go @@ -12,7 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/flow" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -69,7 +69,7 @@ type BlockPool struct { requesters map[int64]*bpRequester height int64 // the lowest key in requesters. // peers - peers map[p2p.ID]*bpPeer + peers map[p2pTypes.ID]*bpPeer maxPeerHeight int64 // the biggest reported height // atomic @@ -83,7 +83,7 @@ type BlockPool struct { // requests and errors will be sent to requestsCh and errorsCh accordingly. func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool { bp := &BlockPool{ - peers: make(map[p2p.ID]*bpPeer), + peers: make(map[p2pTypes.ID]*bpPeer), requesters: make(map[int64]*bpRequester), height: start, @@ -226,13 +226,13 @@ func (pool *BlockPool) PopRequest() { // RedoRequest invalidates the block at pool.height, // Remove the peer and redo request from others. // Returns the ID of the removed peer. -func (pool *BlockPool) RedoRequest(height int64) p2p.ID { +func (pool *BlockPool) RedoRequest(height int64) p2pTypes.ID { pool.mtx.Lock() defer pool.mtx.Unlock() request := pool.requesters[height] peerID := request.getPeerID() - if peerID != p2p.ID("") { + if peerID != p2pTypes.ID("") { // RemovePeer will redo all requesters associated with this peer. pool.removePeer(peerID) } @@ -241,7 +241,7 @@ func (pool *BlockPool) RedoRequest(height int64) p2p.ID { // AddBlock validates that the block comes from the peer it was expected from and calls the requester to store it. // TODO: ensure that blocks come in order for each peer. -func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) { +func (pool *BlockPool) AddBlock(peerID p2pTypes.ID, block *types.Block, blockSize int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -278,7 +278,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 { } // SetPeerHeight sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { +func (pool *BlockPool) SetPeerHeight(peerID p2pTypes.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -298,14 +298,14 @@ func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { // RemovePeer removes the peer with peerID from the pool. If there's no peer // with peerID, function is a no-op. -func (pool *BlockPool) RemovePeer(peerID p2p.ID) { +func (pool *BlockPool) RemovePeer(peerID p2pTypes.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() pool.removePeer(peerID) } -func (pool *BlockPool) removePeer(peerID p2p.ID) { +func (pool *BlockPool) removePeer(peerID p2pTypes.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { requester.redo(peerID) @@ -386,14 +386,14 @@ func (pool *BlockPool) requestersLen() int64 { return int64(len(pool.requesters)) } -func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) { +func (pool *BlockPool) sendRequest(height int64, peerID p2pTypes.ID) { if !pool.IsRunning() { return } pool.requestsCh <- BlockRequest{height, peerID} } -func (pool *BlockPool) sendError(err error, peerID p2p.ID) { +func (pool *BlockPool) sendError(err error, peerID p2pTypes.ID) { if !pool.IsRunning() { return } @@ -424,7 +424,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { pool *BlockPool - id p2p.ID + id p2pTypes.ID recvMonitor *flow.Monitor height int64 @@ -435,7 +435,7 @@ type bpPeer struct { logger *slog.Logger } -func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer { +func newBPPeer(pool *BlockPool, peerID p2pTypes.ID, height int64) *bpPeer { peer := &bpPeer{ pool: pool, id: peerID, @@ -499,10 +499,10 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan p2p.ID // redo may send multitime, add peerId to identify repeat + redoCh chan p2pTypes.ID // redo may send multitime, add peerId to identify repeat mtx sync.Mutex - peerID p2p.ID + peerID p2pTypes.ID block *types.Block } @@ -511,7 +511,7 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester { pool: pool, height: height, gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan p2p.ID, 1), + redoCh: make(chan p2pTypes.ID, 1), peerID: "", block: nil, @@ -526,7 +526,7 @@ func (bpr *bpRequester) OnStart() error { } // Returns true if the peer matches and block doesn't already exist. -func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool { +func (bpr *bpRequester) setBlock(block *types.Block, peerID p2pTypes.ID) bool { bpr.mtx.Lock() if bpr.block != nil || bpr.peerID != peerID { bpr.mtx.Unlock() @@ -548,7 +548,7 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() p2p.ID { +func (bpr *bpRequester) getPeerID() p2pTypes.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() return bpr.peerID @@ -570,7 +570,7 @@ func (bpr *bpRequester) reset() { // Tells bpRequester to pick another peer and try again. // NOTE: Nonblocking, and does nothing if another redo // was already requested. -func (bpr *bpRequester) redo(peerID p2p.ID) { +func (bpr *bpRequester) redo(peerID p2pTypes.ID) { select { case bpr.redoCh <- peerID: default: @@ -631,5 +631,5 @@ OUTER_LOOP: // delivering the block type BlockRequest struct { Height int64 - PeerID p2p.ID + PeerID p2pTypes.ID } diff --git a/tm2/pkg/bft/blockchain/pool_test.go b/tm2/pkg/bft/blockchain/pool_test.go index a4d5636d5e3..ee58d672e75 100644 --- a/tm2/pkg/bft/blockchain/pool_test.go +++ b/tm2/pkg/bft/blockchain/pool_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/random" ) @@ -19,7 +19,7 @@ func init() { } type testPeer struct { - id p2p.ID + id p2pTypes.ID height int64 inputChan chan inputData // make sure each peer's data is sequential } @@ -47,7 +47,7 @@ func (p testPeer) simulateInput(input inputData) { // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) } -type testPeers map[p2p.ID]testPeer +type testPeers map[p2pTypes.ID]testPeer func (ps testPeers) start() { for _, v := range ps { @@ -64,7 +64,7 @@ func (ps testPeers) stop() { func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { peers := make(testPeers, numPeers) for i := 0; i < numPeers; i++ { - peerID := p2p.ID(random.RandStr(12)) + peerID := p2pTypes.ID(random.RandStr(12)) height := minHeight + random.RandInt63n(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height, make(chan inputData, 10)} } @@ -172,7 +172,7 @@ func TestBlockPoolTimeout(t *testing.T) { // Pull from channels counter := 0 - timedOut := map[p2p.ID]struct{}{} + timedOut := map[p2pTypes.ID]struct{}{} for { select { case err := <-errorsCh: @@ -195,7 +195,7 @@ func TestBlockPoolRemovePeer(t *testing.T) { peers := make(testPeers, 10) for i := 0; i < 10; i++ { - peerID := p2p.ID(fmt.Sprintf("%d", i+1)) + peerID := p2pTypes.ID(fmt.Sprintf("%d", i+1)) height := int64(i + 1) peers[peerID] = testPeer{peerID, height, make(chan inputData)} } @@ -215,10 +215,10 @@ func TestBlockPoolRemovePeer(t *testing.T) { assert.EqualValues(t, 10, pool.MaxPeerHeight()) // remove not-existing peer - assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) }) + assert.NotPanics(t, func() { pool.RemovePeer(p2pTypes.ID("Superman")) }) // remove peer with biggest height - pool.RemovePeer(p2p.ID("10")) + pool.RemovePeer(p2pTypes.ID("10")) assert.EqualValues(t, 9, pool.MaxPeerHeight()) // remove all peers diff --git a/tm2/pkg/bft/blockchain/reactor.go b/tm2/pkg/bft/blockchain/reactor.go index 09e1225b717..417e96ad383 100644 --- a/tm2/pkg/bft/blockchain/reactor.go +++ b/tm2/pkg/bft/blockchain/reactor.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/store" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -37,15 +38,14 @@ const ( bcBlockResponseMessageFieldKeySize ) -type consensusReactor interface { - // for when we switch from blockchain reactor and fast sync to - // the consensus machine - SwitchToConsensus(sm.State, int) -} +// SwitchToConsensusFn is a callback method that is meant to +// stop the syncing process as soon as the latest known height is reached, +// and start the consensus process for the validator node +type SwitchToConsensusFn func(sm.State, int) type peerError struct { err error - peerID p2p.ID + peerID p2pTypes.ID } func (e peerError) Error() string { @@ -66,11 +66,17 @@ type BlockchainReactor struct { requestsCh <-chan BlockRequest errorsCh <-chan peerError + + switchToConsensusFn SwitchToConsensusFn } // NewBlockchainReactor returns new reactor instance. -func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, +func NewBlockchainReactor( + state sm.State, + blockExec *sm.BlockExecutor, + store *store.BlockStore, fastSync bool, + switchToConsensusFn SwitchToConsensusFn, ) *BlockchainReactor { if state.LastBlockHeight != store.Height() { panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, @@ -89,13 +95,14 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *st ) bcR := &BlockchainReactor{ - initialState: state, - blockExec: blockExec, - store: store, - pool: pool, - fastSync: fastSync, - requestsCh: requestsCh, - errorsCh: errorsCh, + initialState: state, + blockExec: blockExec, + store: store, + pool: pool, + fastSync: fastSync, + requestsCh: requestsCh, + errorsCh: errorsCh, + switchToConsensusFn: switchToConsensusFn, } bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR) return bcR @@ -138,7 +145,7 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { } // AddPeer implements Reactor by sending our state to peer. -func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { +func (bcR *BlockchainReactor) AddPeer(peer p2p.PeerConn) { msgBytes := amino.MustMarshalAny(&bcStatusResponseMessage{bcR.store.Height()}) peer.Send(BlockchainChannel, msgBytes) // it's OK if send fails. will try later in poolRoutine @@ -148,7 +155,7 @@ func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { } // RemovePeer implements Reactor by removing peer from the pool. -func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { +func (bcR *BlockchainReactor) RemovePeer(peer p2p.PeerConn, reason interface{}) { bcR.pool.RemovePeer(peer.ID()) } @@ -157,7 +164,7 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { // According to the Tendermint spec, if all nodes are honest, // no node should be requesting for a block that's non-existent. func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, - src p2p.Peer, + src p2p.PeerConn, ) (queued bool) { block := bcR.store.LoadBlock(msg.Height) if block != nil { @@ -172,7 +179,7 @@ func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, } // Receive implements Reactor by handling 4 types of messages (look below). -func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { +func (bcR *BlockchainReactor) Receive(chID byte, src p2p.PeerConn, msgBytes []byte) { msg, err := decodeMsg(msgBytes) if err != nil { bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) @@ -257,16 +264,13 @@ FOR_LOOP: select { case <-switchToConsensusTicker.C: height, numPending, lenRequesters := bcR.pool.GetStatus() - outbound, inbound, _ := bcR.Switch.NumPeers() - bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters, - "outbound", outbound, "inbound", inbound) + + bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters) if bcR.pool.IsCaughtUp() { bcR.Logger.Info("Time to switch to consensus reactor!", "height", height) bcR.pool.Stop() - conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) - if ok { - conR.SwitchToConsensus(state, blocksSynced) - } + + bcR.switchToConsensusFn(state, blocksSynced) // else { // should only happen during testing // } diff --git a/tm2/pkg/bft/blockchain/reactor_test.go b/tm2/pkg/bft/blockchain/reactor_test.go index a40dbc6376b..1bc2df59055 100644 --- a/tm2/pkg/bft/blockchain/reactor_test.go +++ b/tm2/pkg/bft/blockchain/reactor_test.go @@ -1,14 +1,13 @@ package blockchain import ( + "context" "log/slog" "os" "sort" "testing" "time" - "github.com/stretchr/testify/assert" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/appconn" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -20,9 +19,12 @@ import ( tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/errors" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) var config *cfg.Config @@ -110,7 +112,7 @@ func newBlockchainReactor(logger *slog.Logger, genDoc *types.GenesisDoc, privVal blockStore.SaveBlock(thisBlock, thisParts, lastCommit) } - bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync, nil) bcReactor.SetLogger(logger.With("module", "blockchain")) return BlockchainReactorPair{bcReactor, proxyApp} @@ -125,15 +127,35 @@ func TestNoBlockResponse(t *testing.T) { maxBlockHeight := int64(65) - reactorPairs := make([]BlockchainReactorPair, 2) + var ( + reactorPairs = make([]BlockchainReactorPair, 2) + options = make(map[int][]p2p.SwitchOption) + ) - reactorPairs[0] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, maxBlockHeight) - reactorPairs[1] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, 0) + for i := range reactorPairs { + height := int64(0) + if i == 0 { + height = maxBlockHeight + } - p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { - s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) - return s - }, p2p.Connect2Switches) + reactorPairs[i] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, height) + + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("BLOCKCHAIN", reactorPairs[i].reactor), + } + } + + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + Count: 2, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) defer func() { for _, r := range reactorPairs { @@ -194,17 +216,35 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { otherChain.app.Stop() }() - reactorPairs := make([]BlockchainReactorPair, 4) + var ( + reactorPairs = make([]BlockchainReactorPair, 4) + options = make(map[int][]p2p.SwitchOption) + ) + + for i := range reactorPairs { + height := int64(0) + if i == 0 { + height = maxBlockHeight + } + + reactorPairs[i] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, height) - reactorPairs[0] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight) - reactorPairs[1] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) - reactorPairs[2] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) - reactorPairs[3] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("BLOCKCHAIN", reactorPairs[i].reactor), + } + } - switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch { - s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) - return s - }, p2p.Connect2Switches) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + Count: 4, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + switches, transports := p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) defer func() { for _, r := range reactorPairs { @@ -222,7 +262,7 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { } // at this time, reactors[0-3] is the newest - assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) + assert.Equal(t, 3, len(reactorPairs[1].reactor.Switch.Peers().List())) // mark reactorPairs[3] is an invalid peer reactorPairs[3].reactor.store = otherChain.reactor.store @@ -230,24 +270,41 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { lastReactorPair := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) reactorPairs = append(reactorPairs, lastReactorPair) - switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { - s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) - return s - }, p2p.Connect2Switches)...) + persistentPeers := make([]*p2pTypes.NetAddress, 0, len(transports)) - for i := 0; i < len(reactorPairs)-1; i++ { - p2p.Connect2Switches(switches, i, len(reactorPairs)-1) + for _, tr := range transports { + addr := tr.NetAddress() + persistentPeers = append(persistentPeers, &addr) } + for i, opt := range options { + opt = append(opt, p2p.WithPersistentPeers(persistentPeers)) + + options[i] = opt + } + + ctx, cancelFn = context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg = p2pTesting.TestingConfig{ + Count: 1, + P2PCfg: config.P2P, + SwitchOptions: options, + Channels: []byte{BlockchainChannel}, + } + + sw, _ := p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) + switches = append(switches, sw...) + for { - if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { + if lastReactorPair.reactor.pool.IsCaughtUp() || len(lastReactorPair.reactor.Switch.Peers().List()) == 0 { break } time.Sleep(1 * time.Second) } - assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) + assert.True(t, len(lastReactorPair.reactor.Switch.Peers().List()) < len(reactorPairs)-1) } func TestBcBlockRequestMessageValidateBasic(t *testing.T) { diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index d290dba6b26..b745c815886 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -6,6 +6,7 @@ import ( "path/filepath" "regexp" "slices" + "time" "dario.cat/mergo" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -163,12 +164,21 @@ func LoadOrMakeConfigWithOptions(root string, opts ...Option) (*Config, error) { return cfg, nil } +// testP2PConfig returns a configuration for testing the peer-to-peer layer +func testP2PConfig() *p2p.P2PConfig { + cfg := p2p.DefaultP2PConfig() + cfg.ListenAddress = "tcp://0.0.0.0:26656" + cfg.FlushThrottleTimeout = 10 * time.Millisecond + + return cfg +} + // TestConfig returns a configuration that can be used for testing func TestConfig() *Config { return &Config{ BaseConfig: testBaseConfig(), RPC: rpc.TestRPCConfig(), - P2P: p2p.TestP2PConfig(), + P2P: testP2PConfig(), Mempool: mem.TestMempoolConfig(), Consensus: cns.TestConsensusConfig(), TxEventStore: eventstore.DefaultEventStoreConfig(), @@ -318,10 +328,6 @@ type BaseConfig struct { // TCP or UNIX socket address for the profiling server to listen on ProfListenAddress string `toml:"prof_laddr" comment:"TCP or UNIX socket address for the profiling server to listen on"` - - // If true, query the ABCI app on connecting to a new peer - // so the app can decide if we should keep the connection or not - FilterPeers bool `toml:"filter_peers" comment:"If true, query the ABCI app on connecting to a new peer\n so the app can decide if we should keep the connection or not"` // false } // DefaultBaseConfig returns a default base configuration for a Tendermint node @@ -335,7 +341,6 @@ func DefaultBaseConfig() BaseConfig { ABCI: SocketABCI, ProfListenAddress: "", FastSyncMode: true, - FilterPeers: false, DBBackend: db.GoLevelDBBackend.String(), DBPath: DefaultDBDir, } diff --git a/tm2/pkg/bft/consensus/reactor.go b/tm2/pkg/bft/consensus/reactor.go index aee695114f8..f39f012b289 100644 --- a/tm2/pkg/bft/consensus/reactor.go +++ b/tm2/pkg/bft/consensus/reactor.go @@ -35,7 +35,7 @@ const ( // ConsensusReactor defines a reactor for the consensus service. type ConsensusReactor struct { - p2p.BaseReactor // BaseService + p2p.Switch + p2p.BaseReactor // BaseService + p2p.MultiplexSwitch conS *ConsensusState @@ -157,7 +157,7 @@ func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor { } // InitPeer implements Reactor by creating a state for the peer. -func (conR *ConsensusReactor) InitPeer(peer p2p.Peer) p2p.Peer { +func (conR *ConsensusReactor) InitPeer(peer p2p.PeerConn) p2p.PeerConn { peerState := NewPeerState(peer).SetLogger(conR.Logger) peer.Set(types.PeerStateKey, peerState) return peer @@ -165,7 +165,7 @@ func (conR *ConsensusReactor) InitPeer(peer p2p.Peer) p2p.Peer { // AddPeer implements Reactor by spawning multiple gossiping goroutines for the // peer. -func (conR *ConsensusReactor) AddPeer(peer p2p.Peer) { +func (conR *ConsensusReactor) AddPeer(peer p2p.PeerConn) { if !conR.IsRunning() { return } @@ -187,7 +187,7 @@ func (conR *ConsensusReactor) AddPeer(peer p2p.Peer) { } // RemovePeer is a noop. -func (conR *ConsensusReactor) RemovePeer(peer p2p.Peer, reason interface{}) { +func (conR *ConsensusReactor) RemovePeer(peer p2p.PeerConn, reason interface{}) { if !conR.IsRunning() { return } @@ -205,7 +205,7 @@ func (conR *ConsensusReactor) RemovePeer(peer p2p.Peer, reason interface{}) { // Peer state updates can happen in parallel, but processing of // proposals, block parts, and votes are ordered by the receiveRoutine // NOTE: blocks on consensus state for proposals, block parts, and votes -func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { +func (conR *ConsensusReactor) Receive(chID byte, src p2p.PeerConn, msgBytes []byte) { if !conR.IsRunning() { conR.Logger.Debug("Receive", "src", src, "chId", chID, "bytes", msgBytes) return @@ -417,7 +417,7 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { conR.Switch.Broadcast(StateChannel, amino.MustMarshalAny(msg)) /* // TODO: Make this broadcast more selective. - for _, peer := range conR.Switch.Peers().List() { + for _, peer := range conR.MultiplexSwitch.Peers().List() { ps, ok := peer.Get(PeerStateKey).(*PeerState) if !ok { panic(fmt.Sprintf("Peer %v has no state", peer)) @@ -446,13 +446,13 @@ func makeRoundStepMessage(event cstypes.EventNewRoundStep) (nrsMsg *NewRoundStep return } -func (conR *ConsensusReactor) sendNewRoundStepMessage(peer p2p.Peer) { +func (conR *ConsensusReactor) sendNewRoundStepMessage(peer p2p.PeerConn) { rs := conR.conS.GetRoundState() nrsMsg := makeRoundStepMessage(rs.EventNewRoundStep()) peer.Send(StateChannel, amino.MustMarshalAny(nrsMsg)) } -func (conR *ConsensusReactor) gossipDataRoutine(peer p2p.Peer, ps *PeerState) { +func (conR *ConsensusReactor) gossipDataRoutine(peer p2p.PeerConn, ps *PeerState) { logger := conR.Logger.With("peer", peer) OUTER_LOOP: @@ -547,7 +547,7 @@ OUTER_LOOP: } func (conR *ConsensusReactor) gossipDataForCatchup(logger *slog.Logger, rs *cstypes.RoundState, - prs *cstypes.PeerRoundState, ps *PeerState, peer p2p.Peer, + prs *cstypes.PeerRoundState, ps *PeerState, peer p2p.PeerConn, ) { if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { // Ensure that the peer's PartSetHeader is correct @@ -589,7 +589,7 @@ func (conR *ConsensusReactor) gossipDataForCatchup(logger *slog.Logger, rs *csty time.Sleep(conR.conS.config.PeerGossipSleepDuration) } -func (conR *ConsensusReactor) gossipVotesRoutine(peer p2p.Peer, ps *PeerState) { +func (conR *ConsensusReactor) gossipVotesRoutine(peer p2p.PeerConn, ps *PeerState) { logger := conR.Logger.With("peer", peer) // Simple hack to throttle logs upon sleep. @@ -715,7 +715,7 @@ func (conR *ConsensusReactor) gossipVotesForHeight(logger *slog.Logger, rs *csty // NOTE: `queryMaj23Routine` has a simple crude design since it only comes // into play for liveness when there's a signature DDoS attack happening. -func (conR *ConsensusReactor) queryMaj23Routine(peer p2p.Peer, ps *PeerState) { +func (conR *ConsensusReactor) queryMaj23Routine(peer p2p.PeerConn, ps *PeerState) { logger := conR.Logger.With("peer", peer) OUTER_LOOP: @@ -826,12 +826,12 @@ func (conR *ConsensusReactor) peerStatsRoutine() { case *VoteMessage: if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 { // TODO: peer metrics. - // conR.Switch.MarkPeerAsGood(peer) + // conR.MultiplexSwitch.MarkPeerAsGood(peer) } case *BlockPartMessage: if numParts := ps.RecordBlockPart(); numParts%blocksToContributeToBecomeGoodPeer == 0 { // TODO: peer metrics. - // conR.Switch.MarkPeerAsGood(peer) + // conR.MultiplexSwitch.MarkPeerAsGood(peer) } } case <-conR.conS.Quit(): @@ -878,7 +878,7 @@ var ( // NOTE: PeerStateExposed gets dumped with rpc/core/consensus.go. // Be mindful of what you Expose. type PeerState struct { - peer p2p.Peer + peer p2p.PeerConn logger *slog.Logger mtx sync.Mutex // NOTE: Modify below using setters, never directly. @@ -886,7 +886,7 @@ type PeerState struct { } // NewPeerState returns a new PeerState for the given Peer -func NewPeerState(peer p2p.Peer) *PeerState { +func NewPeerState(peer p2p.PeerConn) *PeerState { return &PeerState{ peer: peer, logger: log.NewNoopLogger(), diff --git a/tm2/pkg/bft/consensus/reactor_test.go b/tm2/pkg/bft/consensus/reactor_test.go index 42f944b7481..0e1d6249783 100644 --- a/tm2/pkg/bft/consensus/reactor_test.go +++ b/tm2/pkg/bft/consensus/reactor_test.go @@ -1,14 +1,13 @@ package consensus import ( + "context" "fmt" "log/slog" "sync" "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" @@ -18,27 +17,39 @@ import ( "github.com/gnolang/gno/tm2/pkg/bitarray" "github.com/gnolang/gno/tm2/pkg/crypto/tmhash" "github.com/gnolang/gno/tm2/pkg/events" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" "github.com/gnolang/gno/tm2/pkg/log" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) // ---------------------------------------------- // in-process testnets -func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.Switch) { +func startConsensusNet( + t *testing.T, + css []*ConsensusState, + n int, +) ([]*ConsensusReactor, []<-chan events.Event, []events.EventSwitch, []*p2p.MultiplexSwitch) { + t.Helper() + reactors := make([]*ConsensusReactor, n) blocksSubs := make([]<-chan events.Event, 0) eventSwitches := make([]events.EventSwitch, n) - p2pSwitches := ([]*p2p.Switch)(nil) + p2pSwitches := ([]*p2p.MultiplexSwitch)(nil) + options := make(map[int][]p2p.SwitchOption) for i := 0; i < n; i++ { /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") if err != nil { t.Fatal(err)}*/ reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states reactors[i].SetLogger(css[i].Logger) + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("CONSENSUS", reactors[i]), + } + // evsw is already started with the cs eventSwitches[i] = css[i].evsw reactors[i].SetEventSwitch(eventSwitches[i]) @@ -51,11 +62,22 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c } } // make connected switches and start all reactors - p2pSwitches = p2p.MakeConnectedSwitches(config.P2P, n, func(i int, s *p2p.Switch) *p2p.Switch { - s.AddReactor("CONSENSUS", reactors[i]) - s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) - return s - }, p2p.Connect2Switches) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + testingCfg := p2pTesting.TestingConfig{ + P2PCfg: config.P2P, + Count: n, + SwitchOptions: options, + Channels: []byte{ + StateChannel, + DataChannel, + VoteChannel, + VoteSetBitsChannel, + }, + } + + p2pSwitches, _ = p2pTesting.MakeConnectedPeers(t, ctx, testingCfg) // now that everyone is connected, start the state machines // If we started the state machines before everyone was connected, @@ -68,11 +90,15 @@ func startConsensusNet(css []*ConsensusState, n int) ([]*ConsensusReactor, []<-c return reactors, blocksSubs, eventSwitches, p2pSwitches } -func stopConsensusNet(logger *slog.Logger, reactors []*ConsensusReactor, eventSwitches []events.EventSwitch, p2pSwitches []*p2p.Switch) { +func stopConsensusNet( + logger *slog.Logger, + reactors []*ConsensusReactor, + eventSwitches []events.EventSwitch, + p2pSwitches []*p2p.MultiplexSwitch, +) { logger.Info("stopConsensusNet", "n", len(reactors)) - for i, r := range reactors { + for i := range reactors { logger.Info("stopConsensusNet: Stopping ConsensusReactor", "i", i) - r.Switch.Stop() } for i, b := range eventSwitches { logger.Info("stopConsensusNet: Stopping evsw", "i", i) @@ -92,7 +118,7 @@ func TestReactorBasic(t *testing.T) { N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block timeoutWaitGroup(t, N, func(j int) { @@ -112,7 +138,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { c.Consensus.CreateEmptyBlocks = false }) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // send a tx @@ -132,12 +158,12 @@ func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, _, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, _, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) var ( reactor = reactors[0] - peer = mock.NewPeer(nil) + peer = p2pTesting.NewPeer(t) msg = amino.MustMarshalAny(&HasVoteMessage{Height: 1, Round: 1, Index: 1, Type: types.PrevoteType}) ) @@ -156,12 +182,12 @@ func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, _, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, _, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) var ( reactor = reactors[0] - peer = mock.NewPeer(nil) + peer = p2pTesting.NewPeer(t) msg = amino.MustMarshalAny(&HasVoteMessage{Height: 1, Round: 1, Index: 1, Type: types.PrevoteType}) ) @@ -182,7 +208,7 @@ func TestFlappyReactorRecordsVotesAndBlockParts(t *testing.T) { N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block @@ -210,7 +236,7 @@ func TestReactorVotingPowerChange(t *testing.T) { css, cleanup := randConsensusNet(nVals, "consensus_voting_power_changes_test", newMockTickerFunc(true), newPersistentKVStore) defer cleanup() - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, nVals) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, nVals) defer stopConsensusNet(logger, reactors, eventSwitches, p2pSwitches) // map of active validators @@ -276,7 +302,7 @@ func TestReactorValidatorSetChanges(t *testing.T) { logger := log.NewTestingLogger(t) - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, nPeers) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, nPeers) defer stopConsensusNet(logger, reactors, eventSwitches, p2pSwitches) // map of active validators @@ -375,7 +401,7 @@ func TestReactorWithTimeoutCommit(t *testing.T) { css[i].config.SkipTimeoutCommit = false } - reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(css, N-1) + reactors, blocksSubs, eventSwitches, p2pSwitches := startConsensusNet(t, css, N-1) defer stopConsensusNet(log.NewTestingLogger(t), reactors, eventSwitches, p2pSwitches) // wait till everyone makes the first new block diff --git a/tm2/pkg/bft/consensus/state.go b/tm2/pkg/bft/consensus/state.go index 8b2653813e3..d9c78ec1bdf 100644 --- a/tm2/pkg/bft/consensus/state.go +++ b/tm2/pkg/bft/consensus/state.go @@ -23,7 +23,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" @@ -53,7 +53,7 @@ type newRoundStepInfo struct { // msgs from the reactor which may update the state type msgInfo struct { Msg ConsensusMessage `json:"msg"` - PeerID p2p.ID `json:"peer_key"` + PeerID p2pTypes.ID `json:"peer_key"` } // WAL message. @@ -399,7 +399,7 @@ func (cs *ConsensusState) OpenWAL(walFile string) (walm.WAL, error) { // TODO: should these return anything or let callers just use events? // AddVote inputs a vote. -func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { if peerID == "" { cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} } else { @@ -411,7 +411,7 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, } // SetProposal inputs a proposal. -func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { +func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2pTypes.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} } else { @@ -423,7 +423,7 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) e } // AddProposalBlockPart inputs a part of the proposal block. -func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error { +func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2pTypes.ID) error { if peerID == "" { cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} } else { @@ -435,7 +435,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty } // SetProposalAndBlock inputs the proposal and all block parts. -func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2p.ID) error { +func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2pTypes.ID) error { if err := cs.SetProposal(proposal, peerID); err != nil { return err } @@ -1444,7 +1444,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { // NOTE: block is not necessarily valid. // Asynchronously triggers either enterPrevote (before we timeout of propose) or tryFinalizeCommit, once we have the full block. -func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2pTypes.ID) (added bool, err error) { height, round, part := msg.Height, msg.Round, msg.Part // Blocks might be reused, so round mismatch is OK @@ -1514,7 +1514,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p } // Attempt to add the vote. if its a duplicate signature, dupeout the validator -func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { +func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2pTypes.ID) (bool, error) { added, err := cs.addVote(vote, peerID) if err != nil { // If the vote height is off, we'll just ignore it, @@ -1547,7 +1547,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, err // ----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height) // A precommit for the previous height? diff --git a/tm2/pkg/bft/consensus/state_test.go b/tm2/pkg/bft/consensus/state_test.go index 201cf8906b3..4f4b3e3eb05 100644 --- a/tm2/pkg/bft/consensus/state_test.go +++ b/tm2/pkg/bft/consensus/state_test.go @@ -1733,7 +1733,7 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { // create dummy peer cs, _ := randConsensusState(1) - peer := p2pmock.NewPeer(nil) + peer := p2pmock.Peer{} // 1) new block part parts := types.NewPartSetFromData(random.RandBytes(100), 10) @@ -1777,7 +1777,7 @@ func TestStateOutputVoteStats(t *testing.T) { cs, vss := randConsensusState(2) // create dummy peer - peer := p2pmock.NewPeer(nil) + peer := p2pmock.Peer{} vote := signVote(vss[1], types.PrecommitType, []byte("test"), types.PartSetHeader{}) diff --git a/tm2/pkg/bft/consensus/types/height_vote_set.go b/tm2/pkg/bft/consensus/types/height_vote_set.go index b81937ebd1e..7f3d52022ad 100644 --- a/tm2/pkg/bft/consensus/types/height_vote_set.go +++ b/tm2/pkg/bft/consensus/types/height_vote_set.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) type RoundVoteSet struct { @@ -39,9 +39,9 @@ type HeightVoteSet struct { valSet *types.ValidatorSet mtx sync.Mutex - round int // max tracked round - roundVoteSets map[int]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds + round int // max tracked round + roundVoteSets map[int]RoundVoteSet // keys: [0...round] + peerCatchupRounds map[p2pTypes.ID][]int // keys: peer.ID; values: at most 2 rounds } func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { @@ -59,7 +59,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { hvs.height = height hvs.valSet = valSet hvs.roundVoteSets = make(map[int]RoundVoteSet) - hvs.peerCatchupRounds = make(map[p2p.ID][]int) + hvs.peerCatchupRounds = make(map[p2pTypes.ID][]int) hvs.addRound(0) hvs.round = 0 @@ -108,7 +108,7 @@ func (hvs *HeightVoteSet) addRound(round int) { // Duplicate votes return added=false, err=nil. // By convention, peerID is "" if origin is self. -func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { +func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2pTypes.ID) (added bool, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(vote.Type) { @@ -176,7 +176,7 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *type // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2p.ID, blockID types.BlockID) error { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2pTypes.ID, blockID types.BlockID) error { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/tm2/pkg/bft/mempool/reactor.go b/tm2/pkg/bft/mempool/reactor.go index 3ef85b80a21..cf253999fb3 100644 --- a/tm2/pkg/bft/mempool/reactor.go +++ b/tm2/pkg/bft/mempool/reactor.go @@ -13,6 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/clist" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -39,25 +40,25 @@ type Reactor struct { type mempoolIDs struct { mtx sync.RWMutex - peerMap map[p2p.ID]uint16 + peerMap map[p2pTypes.ID]uint16 nextID uint16 // assumes that a node will never have over 65536 active peers - activeIDs map[uint16]struct{} // used to check if a given peerID key is used, the value doesn't matter + activeIDs map[uint16]struct{} // used to check if a given mempoolID key is used, the value doesn't matter } // Reserve searches for the next unused ID and assigns it to the // peer. -func (ids *mempoolIDs) ReserveForPeer(peer p2p.Peer) { +func (ids *mempoolIDs) ReserveForPeer(id p2pTypes.ID) { ids.mtx.Lock() defer ids.mtx.Unlock() - curID := ids.nextPeerID() - ids.peerMap[peer.ID()] = curID + curID := ids.nextMempoolPeerID() + ids.peerMap[id] = curID ids.activeIDs[curID] = struct{}{} } -// nextPeerID returns the next unused peer ID to use. +// nextMempoolPeerID returns the next unused peer ID to use. // This assumes that ids's mutex is already locked. -func (ids *mempoolIDs) nextPeerID() uint16 { +func (ids *mempoolIDs) nextMempoolPeerID() uint16 { if len(ids.activeIDs) == maxActiveIDs { panic(fmt.Sprintf("node has maximum %d active IDs and wanted to get one more", maxActiveIDs)) } @@ -73,28 +74,28 @@ func (ids *mempoolIDs) nextPeerID() uint16 { } // Reclaim returns the ID reserved for the peer back to unused pool. -func (ids *mempoolIDs) Reclaim(peer p2p.Peer) { +func (ids *mempoolIDs) Reclaim(id p2pTypes.ID) { ids.mtx.Lock() defer ids.mtx.Unlock() - removedID, ok := ids.peerMap[peer.ID()] + removedID, ok := ids.peerMap[id] if ok { delete(ids.activeIDs, removedID) - delete(ids.peerMap, peer.ID()) + delete(ids.peerMap, id) } } // GetForPeer returns an ID reserved for the peer. -func (ids *mempoolIDs) GetForPeer(peer p2p.Peer) uint16 { +func (ids *mempoolIDs) GetForPeer(id p2pTypes.ID) uint16 { ids.mtx.RLock() defer ids.mtx.RUnlock() - return ids.peerMap[peer.ID()] + return ids.peerMap[id] } func newMempoolIDs() *mempoolIDs { return &mempoolIDs{ - peerMap: make(map[p2p.ID]uint16), + peerMap: make(map[p2pTypes.ID]uint16), activeIDs: map[uint16]struct{}{0: {}}, nextID: 1, // reserve unknownPeerID(0) for mempoolReactor.BroadcastTx } @@ -138,20 +139,20 @@ func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { // AddPeer implements Reactor. // It starts a broadcast routine ensuring all txs are forwarded to the given peer. -func (memR *Reactor) AddPeer(peer p2p.Peer) { - memR.ids.ReserveForPeer(peer) +func (memR *Reactor) AddPeer(peer p2p.PeerConn) { + memR.ids.ReserveForPeer(peer.ID()) go memR.broadcastTxRoutine(peer) } // RemovePeer implements Reactor. -func (memR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { - memR.ids.Reclaim(peer) +func (memR *Reactor) RemovePeer(peer p2p.PeerConn, reason interface{}) { + memR.ids.Reclaim(peer.ID()) // broadcast routine checks if peer is gone and returns } // Receive implements Reactor. // It adds any received transactions to the mempool. -func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { +func (memR *Reactor) Receive(chID byte, src p2p.PeerConn, msgBytes []byte) { msg, err := memR.decodeMsg(msgBytes) if err != nil { memR.Logger.Error("Error decoding mempool message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) @@ -162,8 +163,8 @@ func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { switch msg := msg.(type) { case *TxMessage: - peerID := memR.ids.GetForPeer(src) - err := memR.mempool.CheckTxWithInfo(msg.Tx, nil, TxInfo{SenderID: peerID}) + mempoolID := memR.ids.GetForPeer(src.ID()) + err := memR.mempool.CheckTxWithInfo(msg.Tx, nil, TxInfo{SenderID: mempoolID}) if err != nil { memR.Logger.Info("Could not check tx", "tx", txID(msg.Tx), "err", err) } @@ -179,12 +180,12 @@ type PeerState interface { } // Send new mempool txs to peer. -func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { +func (memR *Reactor) broadcastTxRoutine(peer p2p.PeerConn) { if !memR.config.Broadcast { return } - peerID := memR.ids.GetForPeer(peer) + mempoolID := memR.ids.GetForPeer(peer.ID()) var next *clist.CElement for { // In case of both next.NextWaitChan() and peer.Quit() are variable at the same time @@ -213,7 +214,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { peerState, ok := peer.Get(types.PeerStateKey).(PeerState) if !ok { // Peer does not have a state yet. We set it in the consensus reactor, but - // when we add peer in Switch, the order we call reactors#AddPeer is + // when we add peer in MultiplexSwitch, the order we call reactors#AddPeer is // different every time due to us using a map. Sometimes other reactors // will be initialized before the consensus reactor. We should wait a few // milliseconds and retry. @@ -226,7 +227,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { } // ensure peer hasn't already sent us this tx - if _, ok := memTx.senders.Load(peerID); !ok { + if _, ok := memTx.senders.Load(mempoolID); !ok { // send memTx msg := &TxMessage{Tx: memTx.tx} success := peer.Send(MempoolChannel, amino.MustMarshalAny(msg)) diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index e7a3c43a6b9..2d20fb252e2 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -1,26 +1,36 @@ package mempool import ( - "net" + "context" + "fmt" "sync" "testing" "time" "github.com/fortytw2/leaktest" - "github.com/stretchr/testify/assert" - "github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore" memcfg "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" "github.com/gnolang/gno/tm2/pkg/bft/proxy" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/errors" + p2pTesting "github.com/gnolang/gno/tm2/pkg/internal/p2p" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/mock" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" ) +// testP2PConfig returns a configuration for testing the peer-to-peer layer +func testP2PConfig() *p2pcfg.P2PConfig { + cfg := p2pcfg.DefaultP2PConfig() + cfg.ListenAddress = "tcp://0.0.0.0:26656" + cfg.FlushThrottleTimeout = 10 * time.Millisecond + + return cfg +} + type peerState struct { height int64 } @@ -30,65 +40,108 @@ func (ps peerState) GetHeight() int64 { } // connect N mempool reactors through N switches -func makeAndConnectReactors(mconfig *memcfg.MempoolConfig, pconfig *p2pcfg.P2PConfig, n int) []*Reactor { - reactors := make([]*Reactor, n) - logger := log.NewNoopLogger() +func makeAndConnectReactors(t *testing.T, mconfig *memcfg.MempoolConfig, pconfig *p2pcfg.P2PConfig, n int) []*Reactor { + t.Helper() + + var ( + reactors = make([]*Reactor, n) + logger = log.NewNoopLogger() + options = make(map[int][]p2p.SwitchOption) + ) + for i := 0; i < n; i++ { app := kvstore.NewKVStoreApplication() cc := proxy.NewLocalClientCreator(app) mempool, cleanup := newMempoolWithApp(cc) defer cleanup() - reactors[i] = NewReactor(mconfig, mempool) // so we dont start the consensus states - reactors[i].SetLogger(logger.With("validator", i)) + reactor := NewReactor(mconfig, mempool) // so we dont start the consensus states + reactor.SetLogger(logger.With("validator", i)) + + options[i] = []p2p.SwitchOption{ + p2p.WithReactor("MEMPOOL", reactor), + } + + reactors[i] = reactor + } + + // "Simulate" the networking layer + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + + cfg := p2pTesting.TestingConfig{ + Count: n, + P2PCfg: pconfig, + SwitchOptions: options, + Channels: []byte{MempoolChannel}, } - p2p.MakeConnectedSwitches(pconfig, n, func(i int, s *p2p.Switch) *p2p.Switch { - s.AddReactor("MEMPOOL", reactors[i]) - return s - }, p2p.Connect2Switches) + p2pTesting.MakeConnectedPeers(t, ctx, cfg) + return reactors } -func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { +func waitForTxsOnReactors( + t *testing.T, + txs types.Txs, + reactors []*Reactor, +) { t.Helper() - // wait for the txs in all mempools - wg := new(sync.WaitGroup) + ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelFn() + + // Wait for the txs to propagate in all mempools + var wg sync.WaitGroup + for i, reactor := range reactors { wg.Add(1) + go func(r *Reactor, reactorIndex int) { defer wg.Done() - waitForTxsOnReactor(t, txs, r, reactorIndex) + + reapedTxs := waitForTxsOnReactor(t, ctx, len(txs), r) + + for i, tx := range txs { + assert.Equalf(t, tx, reapedTxs[i], + fmt.Sprintf( + "txs at index %d on reactor %d don't match: %v vs %v", + i, reactorIndex, + tx, + reapedTxs[i], + ), + ) + } }(reactor, i) } - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - - timer := time.After(timeout) - select { - case <-timer: - t.Fatal("Timed out waiting for txs") - case <-done: - } + wg.Wait() } -func waitForTxsOnReactor(t *testing.T, txs types.Txs, reactor *Reactor, reactorIndex int) { +func waitForTxsOnReactor( + t *testing.T, + ctx context.Context, + expectedLength int, + reactor *Reactor, +) types.Txs { t.Helper() - mempool := reactor.mempool - for mempool.Size() < len(txs) { - time.Sleep(time.Millisecond * 100) - } - - reapedTxs := mempool.ReapMaxTxs(len(txs)) - for i, tx := range txs { - assert.Equalf(t, tx, reapedTxs[i], - "txs at index %d on reactor %d don't match: %v vs %v", i, reactorIndex, tx, reapedTxs[i]) + var ( + mempool = reactor.mempool + ticker = time.NewTicker(100 * time.Millisecond) + ) + + for { + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for txs") + case <-ticker.C: + if mempool.Size() < expectedLength { + continue + } + + return mempool.ReapMaxTxs(expectedLength) + } } } @@ -100,32 +153,29 @@ func ensureNoTxs(t *testing.T, reactor *Reactor, timeout time.Duration) { assert.Zero(t, reactor.mempool.Size()) } -const ( - numTxs = 1000 - timeout = 120 * time.Second // ridiculously high because CircleCI is slow -) - func TestReactorBroadcastTxMessage(t *testing.T) { t.Parallel() mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 4 - reactors := makeAndConnectReactors(mconfig, pconfig, N) - defer func() { + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) + t.Cleanup(func() { for _, r := range reactors { - r.Stop() + assert.NoError(t, r.Stop()) } - }() + }) + for _, r := range reactors { for _, peer := range r.Switch.Peers().List() { + fmt.Printf("Setting peer %s\n", peer.ID()) peer.Set(types.PeerStateKey, peerState{1}) } } // send a bunch of txs to the first reactor's mempool // and wait for them all to be received in the others - txs := checkTxs(t, reactors[0].mempool, numTxs, UnknownPeerID, true) + txs := checkTxs(t, reactors[0].mempool, 1000, UnknownPeerID, true) waitForTxsOnReactors(t, txs, reactors) } @@ -133,9 +183,9 @@ func TestReactorNoBroadcastToSender(t *testing.T) { t.Parallel() mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) defer func() { for _, r := range reactors { r.Stop() @@ -144,7 +194,7 @@ func TestReactorNoBroadcastToSender(t *testing.T) { // send a bunch of txs to the first reactor's mempool, claiming it came from peer // ensure peer gets no txs - checkTxs(t, reactors[0].mempool, numTxs, 1, true) + checkTxs(t, reactors[0].mempool, 1000, 1, true) ensureNoTxs(t, reactors[1], 100*time.Millisecond) } @@ -158,9 +208,9 @@ func TestFlappyBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { } mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) defer func() { for _, r := range reactors { r.Stop() @@ -186,9 +236,9 @@ func TestFlappyBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { } mconfig := memcfg.TestMempoolConfig() - pconfig := p2pcfg.TestP2PConfig() + pconfig := testP2PConfig() const N = 2 - reactors := makeAndConnectReactors(mconfig, pconfig, N) + reactors := makeAndConnectReactors(t, mconfig, pconfig, N) // stop reactors for _, r := range reactors { @@ -205,15 +255,15 @@ func TestMempoolIDsBasic(t *testing.T) { ids := newMempoolIDs() - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) + id := p2pTypes.GenerateNodeKey().ID() - ids.ReserveForPeer(peer) - assert.EqualValues(t, 1, ids.GetForPeer(peer)) - ids.Reclaim(peer) + ids.ReserveForPeer(id) + assert.EqualValues(t, 1, ids.GetForPeer(id)) + ids.Reclaim(id) - ids.ReserveForPeer(peer) - assert.EqualValues(t, 2, ids.GetForPeer(peer)) - ids.Reclaim(peer) + ids.ReserveForPeer(id) + assert.EqualValues(t, 2, ids.GetForPeer(id)) + ids.Reclaim(id) } func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { @@ -227,12 +277,13 @@ func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { ids := newMempoolIDs() for i := 0; i < maxActiveIDs-1; i++ { - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) - ids.ReserveForPeer(peer) + id := p2pTypes.GenerateNodeKey().ID() + ids.ReserveForPeer(id) } assert.Panics(t, func() { - peer := mock.NewPeer(net.IP{127, 0, 0, 1}) - ids.ReserveForPeer(peer) + id := p2pTypes.GenerateNodeKey().ID() + + ids.ReserveForPeer(id) }) } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index e29de3dd1ae..c1afb2996fa 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -12,12 +12,16 @@ import ( "sync" "time" + goErrors "errors" + "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/discovery" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" bc "github.com/gnolang/gno/tm2/pkg/bft/blockchain" cfg "github.com/gnolang/gno/tm2/pkg/bft/config" cs "github.com/gnolang/gno/tm2/pkg/bft/consensus" @@ -43,6 +47,23 @@ import ( verset "github.com/gnolang/gno/tm2/pkg/versionset" ) +// Reactors are hooks for the p2p module, +// to alert of connecting / disconnecting peers +const ( + mempoolReactorName = "MEMPOOL" + blockchainReactorName = "BLOCKCHAIN" + consensusReactorName = "CONSENSUS" + discoveryReactorName = "DISCOVERY" +) + +const ( + mempoolModuleName = "mempool" + blockchainModuleName = "blockchain" + consensusModuleName = "consensus" + p2pModuleName = "p2p" + discoveryModuleName = "discovery" +) + // ------------------------------------------------------------------------------ // DBContext specifies config information for loading a new DB. @@ -87,7 +108,7 @@ func DefaultNewNode( logger *slog.Logger, ) (*Node, error) { // Generate node PrivKey - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + nodeKey, err := p2pTypes.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { return nil, err } @@ -118,30 +139,6 @@ func DefaultNewNode( // Option sets a parameter for the node. type Option func(*Node) -// CustomReactors allows you to add custom reactors (name -> p2p.Reactor) to -// the node's Switch. -// -// WARNING: using any name from the below list of the existing reactors will -// result in replacing it with the custom one. -// -// - MEMPOOL -// - BLOCKCHAIN -// - CONSENSUS -// - EVIDENCE -// - PEX -func CustomReactors(reactors map[string]p2p.Reactor) Option { - return func(n *Node) { - for name, reactor := range reactors { - if existingReactor := n.sw.Reactor(name); existingReactor != nil { - n.sw.Logger.Info("Replacing existing reactor with a custom one", - "name", name, "existing", existingReactor, "custom", reactor) - n.sw.RemoveReactor(name, existingReactor) - } - n.sw.AddReactor(name, reactor) - } - } -} - // ------------------------------------------------------------------------------ // Node is the highest level interface to a full Tendermint node. @@ -155,11 +152,12 @@ type Node struct { privValidator types.PrivValidator // local node's validator key // network - transport *p2p.MultiplexTransport - sw *p2p.Switch // p2p connections - nodeInfo p2p.NodeInfo - nodeKey *p2p.NodeKey // our node privkey - isListening bool + transport *p2p.MultiplexTransport + sw *p2p.MultiplexSwitch // p2p connections + discoveryReactor *discovery.Reactor // discovery reactor + nodeInfo p2pTypes.NodeInfo + nodeKey *p2pTypes.NodeKey // our node privkey + isListening bool // services evsw events.EventSwitch @@ -279,7 +277,7 @@ func createMempoolAndMempoolReactor(config *cfg.Config, proxyApp appconn.AppConn state.ConsensusParams.Block.MaxTxBytes, mempl.WithPreCheck(sm.TxPreCheck(state)), ) - mempoolLogger := logger.With("module", "mempool") + mempoolLogger := logger.With("module", mempoolModuleName) mempoolReactor := mempl.NewReactor(config.Mempool, mempool) mempoolReactor.SetLogger(mempoolLogger) @@ -289,16 +287,23 @@ func createMempoolAndMempoolReactor(config *cfg.Config, proxyApp appconn.AppConn return mempoolReactor, mempool } -func createBlockchainReactor(config *cfg.Config, +func createBlockchainReactor( state sm.State, blockExec *sm.BlockExecutor, blockStore *store.BlockStore, fastSync bool, + switchToConsensusFn bc.SwitchToConsensusFn, logger *slog.Logger, ) (bcReactor p2p.Reactor, err error) { - bcReactor = bc.NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor = bc.NewBlockchainReactor( + state.Copy(), + blockExec, + blockStore, + fastSync, + switchToConsensusFn, + ) - bcReactor.SetLogger(logger.With("module", "blockchain")) + bcReactor.SetLogger(logger.With("module", blockchainModuleName)) return bcReactor, nil } @@ -331,93 +336,15 @@ func createConsensusReactor(config *cfg.Config, return consensusReactor, consensusState } -func createTransport(config *cfg.Config, nodeInfo p2p.NodeInfo, nodeKey *p2p.NodeKey, proxyApp appconn.AppConns) (*p2p.MultiplexTransport, []p2p.PeerFilterFunc) { - var ( - mConnConfig = p2p.MConnConfig(config.P2P) - transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) - connFilters = []p2p.ConnFilterFunc{} - peerFilters = []p2p.PeerFilterFunc{} - ) - - if !config.P2P.AllowDuplicateIP { - connFilters = append(connFilters, p2p.ConnDuplicateIPFilter()) - } - - // Filter peers by addr or pubkey with an ABCI query. - // If the query return code is OK, add peer. - if config.FilterPeers { - connFilters = append( - connFilters, - // ABCI query for address filtering. - func(_ p2p.ConnSet, c net.Conn, _ []net.IP) error { - res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ - Path: fmt.Sprintf("/p2p/filter/addr/%s", c.RemoteAddr().String()), - }) - if err != nil { - return err - } - if res.IsErr() { - return fmt.Errorf("error querying abci app: %v", res) - } - - return nil - }, - ) - - peerFilters = append( - peerFilters, - // ABCI query for ID filtering. - func(_ p2p.IPeerSet, p p2p.Peer) error { - res, err := proxyApp.Query().QuerySync(abci.RequestQuery{ - Path: fmt.Sprintf("/p2p/filter/id/%s", p.ID()), - }) - if err != nil { - return err - } - if res.IsErr() { - return fmt.Errorf("error querying abci app: %v", res) - } - - return nil - }, - ) - } - - p2p.MultiplexTransportConnFilters(connFilters...)(transport) - return transport, peerFilters -} - -func createSwitch(config *cfg.Config, - transport *p2p.MultiplexTransport, - peerFilters []p2p.PeerFilterFunc, - mempoolReactor *mempl.Reactor, - bcReactor p2p.Reactor, - consensusReactor *cs.ConsensusReactor, - nodeInfo p2p.NodeInfo, - nodeKey *p2p.NodeKey, - p2pLogger *slog.Logger, -) *p2p.Switch { - sw := p2p.NewSwitch( - config.P2P, - transport, - p2p.SwitchPeerFilters(peerFilters...), - ) - sw.SetLogger(p2pLogger) - sw.AddReactor("MEMPOOL", mempoolReactor) - sw.AddReactor("BLOCKCHAIN", bcReactor) - sw.AddReactor("CONSENSUS", consensusReactor) - - sw.SetNodeInfo(nodeInfo) - sw.SetNodeKey(nodeKey) - - p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) - return sw +type nodeReactor struct { + name string + reactor p2p.Reactor } // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, - nodeKey *p2p.NodeKey, + nodeKey *p2pTypes.NodeKey, clientCreator appconn.ClientCreator, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, @@ -463,7 +390,7 @@ func NewNode(config *cfg.Config, // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, // and replays any blocks as necessary to sync tendermint with the app. - consensusLogger := logger.With("module", "consensus") + consensusLogger := logger.With("module", consensusModuleName) if err := doHandshake(stateDB, state, blockStore, genDoc, evsw, proxyApp, consensusLogger); err != nil { return nil, err } @@ -506,38 +433,103 @@ func NewNode(config *cfg.Config, mempool, ) - // Make BlockchainReactor - bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, fastSync, logger) - if err != nil { - return nil, errors.Wrap(err, "could not create blockchain reactor") - } - // Make ConsensusReactor consensusReactor, consensusState := createConsensusReactor( config, state, blockExec, blockStore, mempool, privValidator, fastSync, evsw, consensusLogger, ) + // Make BlockchainReactor + bcReactor, err := createBlockchainReactor( + state, + blockExec, + blockStore, + fastSync, + consensusReactor.SwitchToConsensus, + logger, + ) + if err != nil { + return nil, errors.Wrap(err, "could not create blockchain reactor") + } + + reactors := []nodeReactor{ + { + mempoolReactorName, mempoolReactor, + }, + { + blockchainReactorName, bcReactor, + }, + { + consensusReactorName, consensusReactor, + }, + } + nodeInfo, err := makeNodeInfo(config, nodeKey, txEventStore, genDoc, state) if err != nil { return nil, errors.Wrap(err, "error making NodeInfo") } - // Setup Transport. - transport, peerFilters := createTransport(config, nodeInfo, nodeKey, proxyApp) + p2pLogger := logger.With("module", p2pModuleName) - // Setup Switch. - p2pLogger := logger.With("module", "p2p") - sw := createSwitch( - config, transport, peerFilters, mempoolReactor, bcReactor, - consensusReactor, nodeInfo, nodeKey, p2pLogger, + // Setup the multiplex transport, used by the P2P switch + transport := p2p.NewMultiplexTransport( + nodeInfo, + *nodeKey, + conn.MConfigFromP2P(config.P2P), + p2pLogger.With("transport", "multiplex"), ) - err = sw.AddPersistentPeers(splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " ")) - if err != nil { - return nil, errors.Wrap(err, "could not add peers from persistent_peers field") + var discoveryReactor *discovery.Reactor + + if config.P2P.PeerExchange { + discoveryReactor = discovery.NewReactor() + + discoveryReactor.SetLogger(logger.With("module", discoveryModuleName)) + + reactors = append(reactors, nodeReactor{ + name: discoveryReactorName, + reactor: discoveryReactor, + }) + } + + // Setup MultiplexSwitch. + peerAddrs, errs := p2pTypes.NewNetAddressFromStrings( + splitAndTrimEmpty(config.P2P.PersistentPeers, ",", " "), + ) + for _, err = range errs { + p2pLogger.Error("invalid persistent peer address", "err", err) + } + + // Parse the private peer IDs + privatePeerIDs, errs := p2pTypes.NewIDFromStrings( + splitAndTrimEmpty(config.P2P.PrivatePeerIDs, ",", " "), + ) + for _, err = range errs { + p2pLogger.Error("invalid private peer ID", "err", err) + } + + // Prepare the misc switch options + opts := []p2p.SwitchOption{ + p2p.WithPersistentPeers(peerAddrs), + p2p.WithPrivatePeers(privatePeerIDs), + p2p.WithMaxInboundPeers(config.P2P.MaxNumInboundPeers), + p2p.WithMaxOutboundPeers(config.P2P.MaxNumOutboundPeers), + } + + // Prepare the reactor switch options + for _, r := range reactors { + opts = append(opts, p2p.WithReactor(r.name, r.reactor)) } + sw := p2p.NewMultiplexSwitch( + transport, + opts..., + ) + + sw.SetLogger(p2pLogger) + + p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile()) + if config.ProfListenAddress != "" { server := &http.Server{ Addr: config.ProfListenAddress, @@ -554,10 +546,11 @@ func NewNode(config *cfg.Config, genesisDoc: genDoc, privValidator: privValidator, - transport: transport, - sw: sw, - nodeInfo: nodeInfo, - nodeKey: nodeKey, + transport: transport, + sw: sw, + discoveryReactor: discoveryReactor, + nodeInfo: nodeInfo, + nodeKey: nodeKey, evsw: evsw, stateDB: stateDB, @@ -611,10 +604,16 @@ func (n *Node) OnStart() error { } // Start the transport. - addr, err := p2p.NewNetAddressFromString(p2p.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress)) + lAddr := n.config.P2P.ExternalAddress + if lAddr == "" { + lAddr = n.config.P2P.ListenAddress + } + + addr, err := p2pTypes.NewNetAddressFromString(p2pTypes.NetAddressString(n.nodeKey.ID(), lAddr)) if err != nil { - return err + return fmt.Errorf("unable to parse network address, %w", err) } + if err := n.transport.Listen(*addr); err != nil { return err } @@ -639,11 +638,14 @@ func (n *Node) OnStart() error { } // Always connect to persistent peers - err = n.sw.DialPeersAsync(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) - if err != nil { - return errors.Wrap(err, "could not dial peers from persistent_peers field") + peerAddrs, errs := p2pTypes.NewNetAddressFromStrings(splitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) + for _, err := range errs { + n.Logger.Error("invalid persistent peer address", "err", err) } + // Dial the persistent peers + n.sw.DialPeers(peerAddrs...) + return nil } @@ -657,8 +659,15 @@ func (n *Node) OnStop() { n.evsw.Stop() n.eventStoreService.Stop() + // Stop the node p2p transport + if err := n.transport.Close(); err != nil { + n.Logger.Error("unable to gracefully close transport", "err", err) + } + // now stop the reactors - n.sw.Stop() + if err := n.sw.Stop(); err != nil { + n.Logger.Error("unable to gracefully close switch", "err", err) + } // stop mempool WAL if n.config.Mempool.WalEnabled() { @@ -791,7 +800,7 @@ func joinListenerAddresses(ll []net.Listener) string { } // Switch returns the Node's Switch. -func (n *Node) Switch() *p2p.Switch { +func (n *Node) Switch() *p2p.MultiplexSwitch { return n.sw } @@ -859,17 +868,17 @@ func (n *Node) IsListening() bool { } // NodeInfo returns the Node's Info from the Switch. -func (n *Node) NodeInfo() p2p.NodeInfo { +func (n *Node) NodeInfo() p2pTypes.NodeInfo { return n.nodeInfo } func makeNodeInfo( config *cfg.Config, - nodeKey *p2p.NodeKey, + nodeKey *p2pTypes.NodeKey, txEventStore eventstore.TxEventStore, genDoc *types.GenesisDoc, state sm.State, -) (p2p.NodeInfo, error) { +) (p2pTypes.NodeInfo, error) { txIndexerStatus := eventstore.StatusOff if txEventStore.GetType() != null.EventStoreType { txIndexerStatus = eventstore.StatusOn @@ -882,8 +891,9 @@ func makeNodeInfo( Version: state.AppVersion, }) - nodeInfo := p2p.NodeInfo{ + nodeInfo := p2pTypes.NodeInfo{ VersionSet: vset, + PeerID: nodeKey.ID(), Network: genDoc.ChainID, Version: version.Version, Channels: []byte{ @@ -892,24 +902,23 @@ func makeNodeInfo( mempl.MempoolChannel, }, Moniker: config.Moniker, - Other: p2p.NodeInfoOther{ + Other: p2pTypes.NodeInfoOther{ TxIndex: txIndexerStatus, RPCAddress: config.RPC.ListenAddress, }, } - lAddr := config.P2P.ExternalAddress - if lAddr == "" { - lAddr = config.P2P.ListenAddress + if config.P2P.PeerExchange { + nodeInfo.Channels = append(nodeInfo.Channels, discovery.Channel) } - addr, err := p2p.NewNetAddressFromString(p2p.NetAddressString(nodeKey.ID(), lAddr)) - if err != nil { - return nodeInfo, errors.Wrap(err, "invalid (local) node net address") + + // Validate the node info + err := nodeInfo.Validate() + if err != nil && !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { + return p2pTypes.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } - nodeInfo.NetAddress = addr - err = nodeInfo.Validate() - return nodeInfo, err + return nodeInfo, nil } // ------------------------------------------------------------------------------ diff --git a/tm2/pkg/bft/node/node_test.go b/tm2/pkg/bft/node/node_test.go index 6e86a0bcc6f..1ea789d31c2 100644 --- a/tm2/pkg/bft/node/node_test.go +++ b/tm2/pkg/bft/node/node_test.go @@ -25,8 +25,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - p2pmock "github.com/gnolang/gno/tm2/pkg/p2p/mock" "github.com/gnolang/gno/tm2/pkg/random" ) @@ -40,8 +38,6 @@ func TestNodeStartStop(t *testing.T) { err = n.Start() require.NoError(t, err) - t.Logf("Started node %v", n.sw.NodeInfo()) - // wait for the node to produce a block blocksSub := events.SubscribeToEvent(n.EventSwitch(), "node_test", types.EventNewBlock{}) require.NoError(t, err) @@ -308,39 +304,6 @@ func TestCreateProposalBlock(t *testing.T) { assert.NoError(t, err) } -func TestNodeNewNodeCustomReactors(t *testing.T) { - config, genesisFile := cfg.ResetTestRoot("node_new_node_custom_reactors_test") - defer os.RemoveAll(config.RootDir) - - cr := p2pmock.NewReactor() - customBlockchainReactor := p2pmock.NewReactor() - - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - require.NoError(t, err) - - n, err := NewNode(config, - privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), - nodeKey, - proxy.DefaultClientCreator(nil, config.ProxyApp, config.ABCI, config.DBDir()), - DefaultGenesisDocProviderFunc(genesisFile), - DefaultDBProvider, - events.NewEventSwitch(), - log.NewTestingLogger(t), - CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}), - ) - require.NoError(t, err) - - err = n.Start() - require.NoError(t, err) - defer n.Stop() - - assert.True(t, cr.IsRunning()) - assert.Equal(t, cr, n.Switch().Reactor("FOO")) - - assert.True(t, customBlockchainReactor.IsRunning()) - assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN")) -} - func state(nVals int, height int64) (sm.State, dbm.DB) { vals := make([]types.GenesisValidator, nVals) for i := 0; i < nVals; i++ { diff --git a/tm2/pkg/bft/rpc/client/batch_test.go b/tm2/pkg/bft/rpc/client/batch_test.go index 52930e5c372..fcd0f3f834d 100644 --- a/tm2/pkg/bft/rpc/client/batch_test.go +++ b/tm2/pkg/bft/rpc/client/batch_test.go @@ -10,7 +10,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -116,7 +116,7 @@ func TestRPCBatch_Send(t *testing.T) { var ( numRequests = 10 expectedStatus = &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, } @@ -160,7 +160,7 @@ func TestRPCBatch_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/client_test.go b/tm2/pkg/bft/rpc/client/client_test.go index cb88c91fc5f..31889f59883 100644 --- a/tm2/pkg/bft/rpc/client/client_test.go +++ b/tm2/pkg/bft/rpc/client/client_test.go @@ -14,7 +14,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -114,7 +114,7 @@ func TestRPCClient_Status(t *testing.T) { var ( expectedStatus = &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, } @@ -811,17 +811,17 @@ func TestRPCClient_Batch(t *testing.T) { var ( expectedStatuses = []*ctypes.ResultStatus{ { - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, { - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/e2e_test.go b/tm2/pkg/bft/rpc/client/e2e_test.go index 08d4b9b735d..358c66b0b26 100644 --- a/tm2/pkg/bft/rpc/client/e2e_test.go +++ b/tm2/pkg/bft/rpc/client/e2e_test.go @@ -13,7 +13,7 @@ import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -170,7 +170,7 @@ func TestRPCClient_E2E_Endpoints(t *testing.T) { { statusMethod, &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ + NodeInfo: p2pTypes.NodeInfo{ Moniker: "dummy", }, }, diff --git a/tm2/pkg/bft/rpc/client/local.go b/tm2/pkg/bft/rpc/client/local.go index 59c4216a468..4bc724e7d70 100644 --- a/tm2/pkg/bft/rpc/client/local.go +++ b/tm2/pkg/bft/rpc/client/local.go @@ -106,14 +106,6 @@ func (c *Local) Health() (*ctypes.ResultHealth, error) { return core.Health(c.ctx) } -func (c *Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(c.ctx, seeds) -} - -func (c *Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - return core.UnsafeDialPeers(c.ctx, peers, persistent) -} - func (c *Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { return core.BlockchainInfo(c.ctx, minHeight, maxHeight) } diff --git a/tm2/pkg/bft/rpc/core/net.go b/tm2/pkg/bft/rpc/core/net.go index 975d5ed822f..f8839b7d91f 100644 --- a/tm2/pkg/bft/rpc/core/net.go +++ b/tm2/pkg/bft/rpc/core/net.go @@ -3,7 +3,6 @@ package core import ( ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" - "github.com/gnolang/gno/tm2/pkg/errors" ) // Get network info. @@ -154,10 +153,14 @@ import ( // } // // ``` -func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { - out, in, _ := p2pPeers.NumPeers() +func NetInfo(_ *rpctypes.Context) (*ctypes.ResultNetInfo, error) { + var ( + set = p2pPeers.Peers() + out, in = set.NumOutbound(), set.NumInbound() + ) + peers := make([]ctypes.Peer, 0, out+in) - for _, peer := range p2pPeers.Peers().List() { + for _, peer := range set.List() { nodeInfo := peer.NodeInfo() peers = append(peers, ctypes.Peer{ NodeInfo: nodeInfo, @@ -166,9 +169,7 @@ func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { RemoteIP: peer.RemoteIP().String(), }) } - // TODO: Should we include PersistentPeers and Seeds in here? - // PRO: useful info - // CON: privacy + return &ctypes.ResultNetInfo{ Listening: p2pTransport.IsListening(), Listeners: p2pTransport.Listeners(), @@ -177,33 +178,6 @@ func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialSeeds(ctx *rpctypes.Context, seeds []string) (*ctypes.ResultDialSeeds, error) { - if len(seeds) == 0 { - return &ctypes.ResultDialSeeds{}, errors.New("No seeds provided") - } - logger.Info("DialSeeds", "seeds", seeds) - if err := p2pPeers.DialPeersAsync(seeds); err != nil { - return &ctypes.ResultDialSeeds{}, err - } - return &ctypes.ResultDialSeeds{Log: "Dialing seeds in progress. See /net_info for details"}, nil -} - -func UnsafeDialPeers(ctx *rpctypes.Context, peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - if len(peers) == 0 { - return &ctypes.ResultDialPeers{}, errors.New("No peers provided") - } - logger.Info("DialPeers", "peers", peers, "persistent", persistent) - if persistent { - if err := p2pPeers.AddPersistentPeers(peers); err != nil { - return &ctypes.ResultDialPeers{}, err - } - } - if err := p2pPeers.DialPeersAsync(peers); err != nil { - return &ctypes.ResultDialPeers{}, err - } - return &ctypes.ResultDialPeers{Log: "Dialing peers in progress. See /net_info for details"}, nil -} - // Get genesis file. // // ```shell diff --git a/tm2/pkg/bft/rpc/core/net_test.go b/tm2/pkg/bft/rpc/core/net_test.go deleted file mode 100644 index 3273837b6ce..00000000000 --- a/tm2/pkg/bft/rpc/core/net_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package core - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" -) - -func TestUnsafeDialSeeds(t *testing.T) { - t.Parallel() - - sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - logger = log.NewNoopLogger() - p2pPeers = sw - - testCases := []struct { - seeds []string - isErr bool - }{ - {[]string{}, true}, - {[]string{"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:41198"}, false}, - {[]string{"127.0.0.1:41198"}, true}, - } - - for _, tc := range testCases { - res, err := UnsafeDialSeeds(&rpctypes.Context{}, tc.seeds) - if tc.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - } - } -} - -func TestUnsafeDialPeers(t *testing.T) { - t.Parallel() - - sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", - func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - logger = log.NewNoopLogger() - p2pPeers = sw - - testCases := []struct { - peers []string - isErr bool - }{ - {[]string{}, true}, - {[]string{"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:41198"}, false}, - {[]string{"127.0.0.1:41198"}, true}, - } - - for _, tc := range testCases { - res, err := UnsafeDialPeers(&rpctypes.Context{}, tc.peers, false) - if tc.isErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - } - } -} diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index 9493e7c5873..085fc35da55 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -15,6 +15,7 @@ import ( dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) const ( @@ -38,14 +39,11 @@ type Consensus interface { type transport interface { Listeners() []string IsListening() bool - NodeInfo() p2p.NodeInfo + NodeInfo() p2pTypes.NodeInfo } type peers interface { - AddPersistentPeers([]string) error - DialPeersAsync([]string) error - NumPeers() (outbound, inbound, dialig int) - Peers() p2p.IPeerSet + Peers() p2p.PeerSet } // ---------------------------------------------- diff --git a/tm2/pkg/bft/rpc/core/routes.go b/tm2/pkg/bft/rpc/core/routes.go index 8d210f67985..76217a7cbd9 100644 --- a/tm2/pkg/bft/rpc/core/routes.go +++ b/tm2/pkg/bft/rpc/core/routes.go @@ -36,8 +36,6 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") - Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/tm2/pkg/bft/rpc/core/types/responses.go b/tm2/pkg/bft/rpc/core/types/responses.go index 2874517147d..76474867b27 100644 --- a/tm2/pkg/bft/rpc/core/types/responses.go +++ b/tm2/pkg/bft/rpc/core/types/responses.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/p2p" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" ) // List of blocks @@ -74,9 +75,9 @@ type ValidatorInfo struct { // Node Status type ResultStatus struct { - NodeInfo p2p.NodeInfo `json:"node_info"` - SyncInfo SyncInfo `json:"sync_info"` - ValidatorInfo ValidatorInfo `json:"validator_info"` + NodeInfo p2pTypes.NodeInfo `json:"node_info"` + SyncInfo SyncInfo `json:"sync_info"` + ValidatorInfo ValidatorInfo `json:"validator_info"` } // Is TxIndexing enabled @@ -107,7 +108,7 @@ type ResultDialPeers struct { // A peer type Peer struct { - NodeInfo p2p.NodeInfo `json:"node_info"` + NodeInfo p2pTypes.NodeInfo `json:"node_info"` IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` RemoteIP string `json:"remote_ip"` diff --git a/tm2/pkg/bft/rpc/core/types/responses_test.go b/tm2/pkg/bft/rpc/core/types/responses_test.go index 268a8d25c34..7d03addc546 100644 --- a/tm2/pkg/bft/rpc/core/types/responses_test.go +++ b/tm2/pkg/bft/rpc/core/types/responses_test.go @@ -3,9 +3,8 @@ package core_types import ( "testing" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" - - "github.com/gnolang/gno/tm2/pkg/p2p" ) func TestStatusIndexer(t *testing.T) { @@ -17,17 +16,17 @@ func TestStatusIndexer(t *testing.T) { status = &ResultStatus{} assert.False(t, status.TxIndexEnabled()) - status.NodeInfo = p2p.NodeInfo{} + status.NodeInfo = types.NodeInfo{} assert.False(t, status.TxIndexEnabled()) cases := []struct { expected bool - other p2p.NodeInfoOther + other types.NodeInfoOther }{ - {false, p2p.NodeInfoOther{}}, - {false, p2p.NodeInfoOther{TxIndex: "aa"}}, - {false, p2p.NodeInfoOther{TxIndex: "off"}}, - {true, p2p.NodeInfoOther{TxIndex: "on"}}, + {false, types.NodeInfoOther{}}, + {false, types.NodeInfoOther{TxIndex: "aa"}}, + {false, types.NodeInfoOther{TxIndex: "off"}}, + {true, types.NodeInfoOther{TxIndex: "on"}}, } for _, tc := range cases { diff --git a/tm2/pkg/bft/rpc/lib/client/http/client.go b/tm2/pkg/bft/rpc/lib/client/http/client.go index aa4fc5c5392..288eca57300 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client.go @@ -166,14 +166,14 @@ func defaultHTTPClient(remoteAddr string) *http.Client { Transport: &http.Transport{ // Set to true to prevent GZIP-bomb DoS attacks DisableCompression: true, - DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { - return makeHTTPDialer(remoteAddr)(network, addr) + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return makeHTTPDialer(remoteAddr) }, }, } } -func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { +func makeHTTPDialer(remoteAddr string) (net.Conn, error) { protocol, address := parseRemoteAddr(remoteAddr) // net.Dial doesn't understand http/https, so change it to TCP @@ -182,9 +182,7 @@ func makeHTTPDialer(remoteAddr string) func(string, string) (net.Conn, error) { protocol = protoTCP } - return func(proto, addr string) (net.Conn, error) { - return net.Dial(protocol, address) - } + return net.Dial(protocol, address) } // protocol - client's protocol (for example, "http", "https", "wss", "ws", "tcp") diff --git a/tm2/pkg/bft/rpc/lib/client/http/client_test.go b/tm2/pkg/bft/rpc/lib/client/http/client_test.go index 4ccbfdc2d1e..0d88ee32650 100644 --- a/tm2/pkg/bft/rpc/lib/client/http/client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/http/client_test.go @@ -76,7 +76,7 @@ func TestClient_makeHTTPDialer(t *testing.T) { t.Run("http", func(t *testing.T) { t.Parallel() - _, err := makeHTTPDialer("https://.")("hello", "world") + _, err := makeHTTPDialer("https://.") require.Error(t, err) assert.Contains(t, err.Error(), "dial tcp:", "should convert https to tcp") @@ -85,7 +85,7 @@ func TestClient_makeHTTPDialer(t *testing.T) { t.Run("udp", func(t *testing.T) { t.Parallel() - _, err := makeHTTPDialer("udp://.")("hello", "world") + _, err := makeHTTPDialer("udp://.") require.Error(t, err) assert.Contains(t, err.Error(), "dial udp:", "udp protocol should remain the same") diff --git a/tm2/pkg/crypto/crypto.go b/tm2/pkg/crypto/crypto.go index 7757b75354e..7908a082d3b 100644 --- a/tm2/pkg/crypto/crypto.go +++ b/tm2/pkg/crypto/crypto.go @@ -3,6 +3,7 @@ package crypto import ( "bytes" "encoding/json" + "errors" "fmt" "github.com/gnolang/gno/tm2/pkg/bech32" @@ -128,6 +129,8 @@ func (addr *Address) DecodeString(str string) error { // ---------------------------------------- // ID +var ErrZeroID = errors.New("address ID is zero") + // The bech32 representation w/ bech32 prefix. type ID string @@ -141,16 +144,12 @@ func (id ID) String() string { func (id ID) Validate() error { if id.IsZero() { - return fmt.Errorf("zero ID is invalid") + return ErrZeroID } + var addr Address - err := addr.DecodeID(id) - return err -} -func AddressFromID(id ID) (addr Address, err error) { - err = addr.DecodeString(string(id)) - return + return addr.DecodeID(id) } func (addr Address) ID() ID { diff --git a/tm2/pkg/crypto/ed25519/ed25519.go b/tm2/pkg/crypto/ed25519/ed25519.go index 8976994986c..f8b9529b788 100644 --- a/tm2/pkg/crypto/ed25519/ed25519.go +++ b/tm2/pkg/crypto/ed25519/ed25519.go @@ -68,11 +68,9 @@ func (privKey PrivKeyEd25519) PubKey() crypto.PubKey { // Equals - you probably don't need to use this. // Runs in constant time based on length of the keys. func (privKey PrivKeyEd25519) Equals(other crypto.PrivKey) bool { - if otherEd, ok := other.(PrivKeyEd25519); ok { - return subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1 - } else { - return false - } + otherEd, ok := other.(PrivKeyEd25519) + + return ok && subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1 } // GenPrivKey generates a new ed25519 private key. diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go new file mode 100644 index 00000000000..2a16646a159 --- /dev/null +++ b/tm2/pkg/internal/p2p/p2p.go @@ -0,0 +1,252 @@ +// Package p2p contains testing code that is moved over, and adapted from p2p/test_utils.go. +// This isn't a good way to simulate the networking layer in TM2 modules. +// It actually isn't a good way to simulate the networking layer, in anything. +// +// Code is carried over to keep the testing code of p2p-dependent modules happy +// and "working". We should delete this entire package the second TM2 module unit tests don't +// need to rely on a live p2p cluster to pass. +package p2p + +import ( + "context" + "crypto/rand" + "errors" + "fmt" + "net" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/p2p" + p2pcfg "github.com/gnolang/gno/tm2/pkg/p2p/config" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/service" + "github.com/gnolang/gno/tm2/pkg/versionset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" +) + +// TestingConfig is the P2P cluster testing config +type TestingConfig struct { + P2PCfg *p2pcfg.P2PConfig // the common p2p configuration + Count int // the size of the cluster + SwitchOptions map[int][]p2p.SwitchOption // multiplex switch options + Channels []byte // the common p2p peer multiplex channels +} + +// MakeConnectedPeers creates a cluster of peers, with the given options. +// Used to simulate the networking layer for a TM2 module +func MakeConnectedPeers( + t *testing.T, + ctx context.Context, + cfg TestingConfig, +) ([]*p2p.MultiplexSwitch, []*p2p.MultiplexTransport) { + t.Helper() + + // Initialize collections for switches, transports, and addresses. + var ( + sws = make([]*p2p.MultiplexSwitch, 0, cfg.Count) + ts = make([]*p2p.MultiplexTransport, 0, cfg.Count) + addrs = make([]*p2pTypes.NetAddress, 0, cfg.Count) + ) + + createTransport := func(index int) *p2p.MultiplexTransport { + // Generate a fresh key + key := p2pTypes.GenerateNodeKey() + + addr, err := p2pTypes.NewNetAddress( + key.ID(), + &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 0, // random free port + }, + ) + require.NoError(t, err) + + info := p2pTypes.NodeInfo{ + VersionSet: versionset.VersionSet{ + versionset.VersionInfo{Name: "p2p", Version: "v0.0.0"}, + }, + PeerID: key.ID(), + Network: "testing", + Software: "p2ptest", + Version: "v1.2.3-rc.0-deadbeef", + Channels: cfg.Channels, + Moniker: fmt.Sprintf("node-%d", index), + Other: p2pTypes.NodeInfoOther{ + TxIndex: "off", + RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), + }, + } + + transport := p2p.NewMultiplexTransport( + info, + *key, + conn.MConfigFromP2P(cfg.P2PCfg), + log.NewNoopLogger(), + ) + + require.NoError(t, transport.Listen(*addr)) + t.Cleanup(func() { assert.NoError(t, transport.Close()) }) + + return transport + } + + // Create transports and gather addresses + for i := 0; i < cfg.Count; i++ { + transport := createTransport(i) + addr := transport.NetAddress() + + addrs = append(addrs, &addr) + ts = append(ts, transport) + } + + // Connect switches and ensure all peers are connected + connectPeers := func(switchIndex int) error { + multiplexSwitch := p2p.NewMultiplexSwitch( + ts[switchIndex], + cfg.SwitchOptions[switchIndex]..., + ) + + ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { + return event.Type() == events.PeerConnected + }) + defer unsubFn() + + // Start the switch + require.NoError(t, multiplexSwitch.Start()) + + // Save it + sws = append(sws, multiplexSwitch) + + if cfg.Count == 1 { + // No peers to dial, switch is alone + return nil + } + + // Async dial the other peers + multiplexSwitch.DialPeers(addrs...) + + // Set up an exit timer + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + + connectedPeers := make(map[p2pTypes.ID]struct{}) + + for { + select { + case evRaw := <-ch: + ev := evRaw.(events.PeerConnectedEvent) + + connectedPeers[ev.PeerID] = struct{}{} + + if len(connectedPeers) == cfg.Count-1 { + return nil + } + case <-timer.C: + return errors.New("timed out waiting for peer switches to connect") + } + } + } + + g, _ := errgroup.WithContext(ctx) + for i := 0; i < cfg.Count; i++ { + g.Go(func() error { return connectPeers(i) }) + } + + require.NoError(t, g.Wait()) + + return sws, ts +} + +// createRoutableAddr generates a valid, routable NetAddress for the given node ID using a secure random IP +func createRoutableAddr(t *testing.T, id p2pTypes.ID) *p2pTypes.NetAddress { + t.Helper() + + generateIP := func() string { + ip := make([]byte, 4) + + _, err := rand.Read(ip) + require.NoError(t, err) + + return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + } + + for { + addrStr := fmt.Sprintf("%s@%s:26656", id, generateIP()) + + netAddr, err := p2pTypes.NewNetAddressFromString(addrStr) + require.NoError(t, err) + + if netAddr.Routable() { + return netAddr + } + } +} + +// Peer is a live peer, utilized for testing purposes. +// This Peer implementation is NOT thread safe +type Peer struct { + *service.BaseService + ip net.IP + id p2pTypes.ID + addr *p2pTypes.NetAddress + kv map[string]any + + Outbound, Persistent, Private bool +} + +// NewPeer creates and starts a new mock peer. +// It generates a new routable address for the peer +func NewPeer(t *testing.T) *Peer { + t.Helper() + + var ( + nodeKey = p2pTypes.GenerateNodeKey() + netAddr = createRoutableAddr(t, nodeKey.ID()) + ) + + mp := &Peer{ + ip: netAddr.IP, + id: nodeKey.ID(), + addr: netAddr, + kv: make(map[string]any), + } + + mp.BaseService = service.NewBaseService(nil, "MockPeer", mp) + + require.NoError(t, mp.Start()) + + return mp +} + +func (mp *Peer) FlushStop() { mp.Stop() } +func (mp *Peer) TrySend(_ byte, _ []byte) bool { return true } +func (mp *Peer) Send(_ byte, _ []byte) bool { return true } +func (mp *Peer) NodeInfo() p2pTypes.NodeInfo { + return p2pTypes.NodeInfo{ + PeerID: mp.id, + } +} +func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } +func (mp *Peer) ID() p2pTypes.ID { return mp.id } +func (mp *Peer) IsOutbound() bool { return mp.Outbound } +func (mp *Peer) IsPersistent() bool { return mp.Persistent } +func (mp *Peer) IsPrivate() bool { return mp.Private } +func (mp *Peer) Get(key string) interface{} { + if value, ok := mp.kv[key]; ok { + return value + } + return nil +} + +func (mp *Peer) Set(key string, value interface{}) { + mp.kv[key] = value +} +func (mp *Peer) RemoteIP() net.IP { return mp.ip } +func (mp *Peer) SocketAddr() *p2pTypes.NetAddress { return mp.addr } +func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } +func (mp *Peer) CloseConn() error { return nil } diff --git a/tm2/pkg/p2p/README.md b/tm2/pkg/p2p/README.md index 81888403e1c..f64fafb53e0 100644 --- a/tm2/pkg/p2p/README.md +++ b/tm2/pkg/p2p/README.md @@ -1,4 +1,604 @@ -# p2p +## Overview -The p2p package provides an abstraction around peer-to-peer communication. +The `p2p` package, and its “sub-packages” contain the required building blocks for Tendermint2’s networking layer. +This document aims to explain the `p2p` terminology, and better document the way the `p2p` module works within the TM2 +ecosystem, especially in relation to other modules like `consensus`, `blockchain` and `mempool`. + +## Common Types + +To fully understand the `Concepts` section of the `p2p` documentation, there must be at least a basic understanding of +the terminology of the `p2p` module, because there are types that keep popping up constantly, and it’s worth +understanding what they’re about. + +### `NetAddress` + +```go +package types + +// NetAddress defines information about a peer on the network +// including its ID, IP address, and port +type NetAddress struct { + ID ID `json:"id"` // unique peer identifier (public key address) + IP net.IP `json:"ip"` // the IP part of the dial address + Port uint16 `json:"port"` // the port part of the dial address +} +``` + +A `NetAddress` is simply a wrapper for a unique peer in the network. + +This address consists of several parts: + +- the peer’s ID, derived from the peer’s public key (it’s the address). +- the peer’s dial address, used for executing TCP dials. + +### `ID` + +```go +// ID represents the cryptographically unique Peer ID +type ID = crypto.ID +``` + +The peer ID is the unique peer identifier. It is used for unambiguously resolving who a peer is, during communication. + +The reason the peer ID is utilized is because it is derived from the peer’s public key, used to encrypt communication, +and it needs to match the public key used in p2p communication. It can, and should be, considered unique. + +### `Reactor` + +Without going too much into detail in the terminology section, as a much more detailed explanation is discussed below: + +A `Reactor` is an abstraction of a Tendermint2 module, that needs to utilize the `p2p` layer. + +Currently active reactors in TM2, that utilize the p2p layer: + +- the consensus reactor, that handles consensus message passing +- the blockchain reactor, that handles block syncing +- the mempool reactor, that handles transaction gossiping + +All of these functionalities require a live p2p network to work, and `Reactor`s are the answer for how they can be aware +of things happening in the network (like new peers joining, for example). + +## Concepts + +### Peer + +`Peer` is an abstraction over a p2p connection that is: + +- **verified**, meaning it went through the handshaking process and the information the other peer shared checked out ( + this process is discussed in detail later). +- **multiplexed over TCP** (the only kind of p2p connections TM2 supports). + +```go +package p2p + +// Peer is a wrapper for a connected peer +type Peer interface { + service.Service + + FlushStop() + + ID() types.ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer + + CloseConn() error // close original connection + + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + Set(string, any) + Get(string) any +} +``` + +There are more than a few things to break down here, so let’s tackle them individually. + +The `Peer` abstraction holds callbacks relating to information about the actual live peer connection, such as what kind +of direction it is, what is the connection status, and others. + +```go +package p2p + +type Peer interface { + // ... + + ID() types.ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer + + // ... +} + +``` + +However, there is part of the `Peer` abstraction that outlines the flipped design of the entire `p2p` module, and a +severe limitation of this implementation. + +```go +package p2p + +type Peer interface { + // ... + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + // ... +} +``` + +The `Peer` abstraction is used internally in `p2p`, but also by other modules that need to interact with the networking +layer — this is in itself the biggest crux of the current `p2p` implementation: modules *need to understand* how to use +and communicate with peers, regardless of the protocol logic. Networking is not an abstraction for the modules, but a +spec requirement. What this essentially means is there is heavy implementation leaking to parts of the TM2 codebase that +shouldn’t need to know how to handle individual peer broadcasts, or how to trigger custom protocol communication (like +syncing for example). +If `module A` wants to broadcast something to the peer network of the node, it needs to do something like this: + +```go +package main + +func main() { + // ... + + peers := sw.Peers().List() // fetch the peer list + + for _, p := range peers { + p.Send(...) // directly message the peer (imitate broadcast) + } + + // ... +} +``` + +An additional odd choice in the `Peer` API is the ability to use the peer as a KV store: + +```go +package p2p + +type Peer interface { + // ... + + Set(string, any) + Get(string) any + + // ... +} +``` + +For example, these methods are used within the `consensus` and `mempool` modules to keep track of active peer states ( +like current HRS data, or current peer mempool metadata). Instead of the module handling individual peer state, this +responsibility is shifted to the peer implementation, causing an odd code dependency situation. + +The root of this “flipped” design (modules needing to understand how to interact with peers) stems from the fact that +peers are instantiated with a multiplex TCP connection under the hood, and basically just wrap that connection. The +`Peer` API is an abstraction for the multiplexed TCP connection, under the hood. + +Changing this dependency stew would require a much larger rewrite of not just the `p2p` module, but other modules ( +`consensus`, `blockchain`, `mempool`) as well, and is as such left as-is. + +### Switch + +In short, a `Switch` is just the middleware layer that handles module <> `Transport` requests, and manages peers on a +high application level (that the `Transport` doesn’t concern itself with). + +The `Switch` is the entity that manages active peer connections. + +```go +package p2p + +// Switch is the abstraction in the p2p module that handles +// and manages peer connections thorough a Transport +type Switch interface { + // Broadcast publishes data on the given channel, to all peers + Broadcast(chID byte, data []byte) + + // Peers returns the latest peer set + Peers() PeerSet + + // Subscribe subscribes to active switch events + Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) + + // StopPeerForError stops the peer with the given reason + StopPeerForError(peer Peer, err error) + + // DialPeers marks the given peers as ready for async dialing + DialPeers(peerAddrs ...*types.NetAddress) +} + +``` + +The API of the `Switch` is relatively straightforward. Users of the `Switch` instantiate it with a `Transport`, and +utilize it as-is. + +The API of the `Switch` is geared towards asynchronicity, and as such users of the `Switch` need to adapt to some +limitations, such as not having synchronous dials, or synchronous broadcasts. + +#### Services + +There are 3 services that run on top of the `MultiplexSwitch`, upon startup: + +- **the accept service** +- **the dial service** +- **the redial service** + +```go +package p2p + +// OnStart implements BaseService. It starts all the reactors and peers. +func (sw *MultiplexSwitch) OnStart() error { + // Start reactors + for _, reactor := range sw.reactors { + if err := reactor.Start(); err != nil { + return fmt.Errorf("unable to start reactor %w", err) + } + } + + // Run the peer accept routine. + // The accept routine asynchronously accepts + // and processes incoming peer connections + go sw.runAcceptLoop(sw.ctx) + + // Run the dial routine. + // The dial routine parses items in the dial queue + // and initiates outbound peer connections + go sw.runDialLoop(sw.ctx) + + // Run the redial routine. + // The redial routine monitors for important + // peer disconnects, and attempts to reconnect + // to them + go sw.runRedialLoop(sw.ctx) + + return nil +} +``` + +##### Accept Service + +The `MultiplexSwitch` needs to actively listen for incoming connections, and handle them accordingly. These situations +occur when a peer *Dials* (more on this later) another peer, and wants to establish a connection. This connection is +outbound for one peer, and inbound for the other. + +Depending on what kind of security policies or configuration the peer has in place, the connection can be accepted, or +rejected for a number of reasons: + +- the maximum number of inbound peers is reached +- the multiplex connection fails upon startup (rare) + +The `Switch` relies on the `Transport` to return a **verified and valid** peer connection. After the `Transport` +delivers, the `Switch` makes sure having the peer makes sense, given the p2p configuration of the node. + +```go +package p2p + +func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { + // ... + + p, err := sw.transport.Accept(ctx, sw.peerBehavior) + if err != nil { + sw.Logger.Error( + "error encountered during peer connection accept", + "err", err, + ) + + continue + } + + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.maxInboundPeers, + ) + + sw.transport.Remove(p) + + continue + } + + // ... +} + +``` + +In fact, this is the central point in the relationship between the `Switch` and `Transport`. +The `Transport` is responsible for establishing the connection, and the `Switch` is responsible for handling it after +it’s been established. + +When TM2 modules communicate with the `p2p` module, they communicate *with the `Switch`, not the `Transport`* to execute +peer-related actions. + +##### Dial Service + +Peers are dialed asynchronously in the `Switch`, as is suggested by the `Switch` API: + +```go +DialPeers(peerAddrs ...*types.NetAddress) +``` + +The `MultiplexSwitch` implementation utilizes a concept called a *dial queue*. + +A dial queue is a priority-based queue (sorted by dial time, ascending) from which dial requests are taken out of and +executed in the form of peer dialing (through the `Transport`, of course). + +The queue needs to be sorted by the dial time, since there are asynchronous dial requests that need to be executed as +soon as possible, while others can wait to be executed up until a certain point in time. + +```go +package p2p + +func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { + // ... + + // Grab a dial item + item := sw.dialQueue.Peek() + if item == nil { + // Nothing to dial + continue + } + + // Check if the dial time is right + // for the item + if time.Now().Before(item.Time) { + // Nothing to dial + continue + } + + // Pop the item from the dial queue + item = sw.dialQueue.Pop() + + // Dial the peer + sw.Logger.Info( + "dialing peer", + "address", item.Address.String(), + ) + + // ... +} +``` + +To follow the outcomes of dial requests, users of the `Switch` can subscribe to peer events (more on this later). + +##### Redial Service + +The TM2 `p2p` module has a concept of something called *persistent peers*. + +Persistent peers are specific peers whose connections must be preserved, at all costs. They are specified in the +top-level node P2P configuration, under `p2p.persistent_peers`. + +These peer connections are special, as they don’t adhere to high-level configuration limits like the maximum peer cap, +instead, they are monitored and handled actively. + +A good candidate for a persistent peer is a bootnode, that bootstraps and facilitates peer discovery for the network. + +If a persistent peer connection is lost for whatever reason (for ex, the peer disconnects), the redial service of the +`MultiplexSwitch` will create a dial request for the dial service, and attempt to re-establish the lost connection. + +```go +package p2p + +func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { + // ... + + var ( + peers = sw.Peers() + peersToDial = make([]*types.NetAddress, 0) + ) + + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) + + // Check if the peer is part of the peer set + // or is scheduled for dialing + if peers.Has(id) || sw.dialQueue.Has(addr) { + return true + } + + peersToDial = append(peersToDial, addr) + + return true + }) + + if len(peersToDial) == 0 { + // No persistent peers are missing + return + } + + // Add the peers to the dial queue + sw.DialPeers(peersToDial...) + + // ... +} +``` + +#### Events + +The `Switch` is meant to be asynchronous. + +This means that processes like dialing peers, removing peers, doing broadcasts and more, is not a synchronous blocking +process for the `Switch` user. + +To be able to tap into the outcome of these asynchronous events, the `Switch` utilizes a simple event system, based on +event filters. + +```go +package main + +func main() { + // ... + + // Subscribe to live switch events + ch, unsubFn := multiplexSwitch.Subscribe(func(event events.Event) bool { + // This subscription will only return "PeerConnected" events + return event.Type() == events.PeerConnected + }) + + defer unsubFn() // removes the subscription + + select { + // Events are sent to the channel as soon as + // they appear and pass the subscription filter + case ev <- ch: + e := ev.(*events.PeerConnectedEvent) + // use event data... + case <-ctx.Done(): + // ... + } + + // ... +} +``` + +An event setup like this is useful for example when the user of the `Switch` wants to capture successful peer dial +events, in realtime. + +#### What is “peer behavior”? + +```go +package p2p + +// PeerBehavior wraps the Reactor and MultiplexSwitch information a Transport would need when +// dialing or accepting new Peer connections. +// It is worth noting that the only reason why this information is required in the first place, +// is because Peers expose an API through which different TM modules can interact with them. +// In the future™, modules should not directly "Send" anything to Peers, but instead communicate through +// other mediums, such as the P2P module +type PeerBehavior interface { + // ReactorChDescriptors returns the Reactor channel descriptors + ReactorChDescriptors() []*conn.ChannelDescriptor + + // Reactors returns the node's active p2p Reactors (modules) + Reactors() map[byte]Reactor + + // HandlePeerError propagates a peer connection error for further processing + HandlePeerError(Peer, error) + + // IsPersistentPeer returns a flag indicating if the given peer is persistent + IsPersistentPeer(types.ID) bool + + // IsPrivatePeer returns a flag indicating if the given peer is private + IsPrivatePeer(types.ID) bool +} + +``` + +In short, the previously-mentioned crux of the `p2p` implementation (having `Peer`s be directly managed by different TM2 +modules) requires information on how to behave when interacting with other peers. + +TM2 modules on `peer A` communicate through something called *channels* to the same modules on `peer B`. For example, if +the `mempool` module on `peer A` wants to share a transaction to the mempool module on `peer B`, it will utilize a +dedicated (and unique!) channel for it (ex. `0x30`). This is a protocol that lives on top of the already-established +multiplexed connection, and metadata relating to it is passed down through *peer behavior*. + +### Transport + +As previously mentioned, the `Transport` is the infrastructure layer of the `p2p` module. + +In contrast to the `Switch`, which is concerned with higher-level application logic (like the number of peers, peer +limits, etc), the `Transport` is concerned with actually establishing and maintaining peer connections on a much lower +level. + +```go +package p2p + +// Transport handles peer dialing and connection acceptance. Additionally, +// it is also responsible for any custom connection mechanisms (like handshaking). +// Peers returned by the transport are considered to be verified and sound +type Transport interface { + // NetAddress returns the Transport's dial address + NetAddress() types.NetAddress + + // Accept returns a newly connected inbound peer + Accept(context.Context, PeerBehavior) (Peer, error) + + // Dial dials a peer, and returns it + Dial(context.Context, types.NetAddress, PeerBehavior) (Peer, error) + + // Remove drops any resources associated + // with the Peer in the transport + Remove(Peer) +} +``` + +When peers dial other peers in TM2, they are in fact dialing their `Transport`s, and the connection is being handled +here. + +- `Accept` waits for an **incoming** connection, parses it and returns it. +- `Dial` attempts to establish an **outgoing** connection, parses it and returns it. + +There are a few important steps that happen when establishing a p2p connection in TM2, between 2 different peers: + +1. The peers go through a handshaking process, and establish something called a *secret connection*. The handshaking + process is based on the [STS protocol](https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf), and + after it is completed successfully, all communication between the 2 peers is **encrypted**. +2. After establishing a secret connection, the peers exchange their respective node information. The purpose of this + step is to verify that the peers are indeed compatible with each other, and should be establishing a connection in + the first place (same network, common protocols , etc). +3. Once the secret connection is established, and the node information is exchanged, the connection to the peer is + considered valid and verified — it can now be used by the `Switch` (accepted, or rejected, based on `Switch` + high-level constraints). Note the distinction here that the `Transport` establishes and maintains the connection, but + it can ultimately be scraped by the `Switch` at any point in time. + +### Peer Discovery + +There is a final service that runs alongside the previously-mentioned `Switch` services — peer discovery. + +Every blockchain node needs an adequate amount of peers to communicate with, in order to ensure smooth functioning. For +validator nodes, they need to be *loosely connected* to at least 2/3+ of the validator set in order to participate and +not cause block misses or mis-votes (loosely connected means that there always exists a path between different peers in +the network topology, that allows them to be reachable to each other). + +The peer discovery service ensures that the given node is always learning more about the overall network topology, and +filling out any empty connection slots (outbound peers). + +This background service works in the following (albeit primitive) way: + +1. At specific intervals, `node A` checks its peer table, and picks a random peer `P`, from the active peer list. +2. When `P` is picked, `node A` initiates a discovery protocol process, in which: + - `node A` sends a request to peer `P` for his peer list (max 30 peers) + - peer `P` responds to the request + +3. Once `node A` has the peer list from `P`, it adds the entire peer list into the dial queue, to establish outbound + peer connections. + +This process repeats at specific intervals. It is worth nothing that if the limit of outbound peers is reached, the peer +dials have no effect. + +#### Bootnodes (Seeds) + +Bootnodes are specialized network nodes that play a critical role in the initial peer discovery process for new nodes +joining the network. + +When a blockchain client starts, it needs to find and connect to other nodes to synchronize data and participate in the +network. Bootnodes provide a predefined list of accessible and reliable entry points that act as a gateway for +discovering other active nodes (through peer discovery). + +These nodes are provided as part of the node’s p2p configuration. Once connected to a bootnode, the client uses peer +discovery to discover and connect to additional peers, enabling full participation and unlocking other client +protocols (consensus, mempool…). + +Bootnodes usually do not store the full blockchain or participate in consensus; their primary role is to facilitate +connectivity in the network (act as a peer relay). \ No newline at end of file diff --git a/tm2/pkg/p2p/base_reactor.go b/tm2/pkg/p2p/base_reactor.go index 91b3981d109..35b03f73be4 100644 --- a/tm2/pkg/p2p/base_reactor.go +++ b/tm2/pkg/p2p/base_reactor.go @@ -6,17 +6,17 @@ import ( ) // Reactor is responsible for handling incoming messages on one or more -// Channel. Switch calls GetChannels when reactor is added to it. When a new +// Channel. MultiplexSwitch calls GetChannels when reactor is added to it. When a new // peer joins our node, InitPeer and AddPeer are called. RemovePeer is called // when the peer is stopped. Receive is called when a message is received on a // channel associated with this reactor. // -// Peer#Send or Peer#TrySend should be used to send the message to a peer. +// PeerConn#Send or PeerConn#TrySend should be used to send the message to a peer. type Reactor interface { service.Service // Start, Stop // SetSwitch allows setting a switch. - SetSwitch(*Switch) + SetSwitch(Switch) // GetChannels returns the list of MConnection.ChannelDescriptor. Make sure // that each ID is unique across all the reactors added to the switch. @@ -28,15 +28,15 @@ type Reactor interface { // NOTE: The switch won't call AddPeer nor RemovePeer if it fails to start // the peer. Do not store any data associated with the peer in the reactor // itself unless you don't want to have a state, which is never cleaned up. - InitPeer(peer Peer) Peer + InitPeer(peer PeerConn) PeerConn // AddPeer is called by the switch after the peer is added and successfully // started. Use it to start goroutines communicating with the peer. - AddPeer(peer Peer) + AddPeer(peer PeerConn) // RemovePeer is called by the switch when the peer is stopped (due to error // or other reason). - RemovePeer(peer Peer, reason interface{}) + RemovePeer(peer PeerConn, reason interface{}) // Receive is called by the switch when msgBytes is received from the peer. // @@ -44,14 +44,14 @@ type Reactor interface { // copying. // // CONTRACT: msgBytes are not nil. - Receive(chID byte, peer Peer, msgBytes []byte) + Receive(chID byte, peer PeerConn, msgBytes []byte) } -//-------------------------------------- +// -------------------------------------- type BaseReactor struct { - service.BaseService // Provides Start, Stop, .Quit - Switch *Switch + service.BaseService // Provides Start, Stop, Quit + Switch Switch } func NewBaseReactor(name string, impl Reactor) *BaseReactor { @@ -61,11 +61,11 @@ func NewBaseReactor(name string, impl Reactor) *BaseReactor { } } -func (br *BaseReactor) SetSwitch(sw *Switch) { +func (br *BaseReactor) SetSwitch(sw Switch) { br.Switch = sw } -func (*BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } -func (*BaseReactor) AddPeer(peer Peer) {} -func (*BaseReactor) RemovePeer(peer Peer, reason interface{}) {} -func (*BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} -func (*BaseReactor) InitPeer(peer Peer) Peer { return peer } +func (*BaseReactor) GetChannels() []*conn.ChannelDescriptor { return nil } +func (*BaseReactor) AddPeer(_ PeerConn) {} +func (*BaseReactor) RemovePeer(_ PeerConn, _ any) {} +func (*BaseReactor) Receive(_ byte, _ PeerConn, _ []byte) {} +func (*BaseReactor) InitPeer(peer PeerConn) PeerConn { return peer } diff --git a/tm2/pkg/p2p/cmd/stest/main.go b/tm2/pkg/p2p/cmd/stest/main.go deleted file mode 100644 index 2835e0cc1f0..00000000000 --- a/tm2/pkg/p2p/cmd/stest/main.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "net" - "os" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - p2pconn "github.com/gnolang/gno/tm2/pkg/p2p/conn" -) - -var ( - remote string - listen string -) - -func init() { - flag.StringVar(&listen, "listen", "", "set to :port if server, eg :8080") - flag.StringVar(&remote, "remote", "", "remote ip:port") - flag.Parse() -} - -func main() { - if listen != "" { - fmt.Println("listening at", listen) - ln, err := net.Listen("tcp", listen) - if err != nil { - // handle error - } - conn, err := ln.Accept() - if err != nil { - panic(err) - } - handleConnection(conn) - } else { - // connect to remote. - if remote == "" { - panic("must specify remote ip:port unless server") - } - fmt.Println("connecting to", remote) - conn, err := net.Dial("tcp", remote) - if err != nil { - panic(err) - } - handleConnection(conn) - } -} - -func handleConnection(conn net.Conn) { - priv := ed25519.GenPrivKey() - pub := priv.PubKey() - fmt.Println("local pubkey:", pub) - fmt.Println("local pubkey addr:", pub.Address()) - - sconn, err := p2pconn.MakeSecretConnection(conn, priv) - if err != nil { - panic(err) - } - // Read line from sconn and print. - go func() { - sc := bufio.NewScanner(sconn) - for sc.Scan() { - line := sc.Text() // GET the line string - fmt.Println(">>", line) - } - if err := sc.Err(); err != nil { - panic(err) - } - }() - // Read line from stdin and write. - for { - sc := bufio.NewScanner(os.Stdin) - for sc.Scan() { - line := sc.Text() + "\n" - _, err := sconn.Write([]byte(line)) - if err != nil { - panic(err) - } - } - if err := sc.Err(); err != nil { - panic(err) - } - } -} diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 07692145fee..380dadc4f6f 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -1,19 +1,15 @@ package config import ( + "errors" "time" - - "github.com/gnolang/gno/tm2/pkg/errors" ) -// ----------------------------------------------------------------------------- -// P2PConfig - -const ( - // FuzzModeDrop is a mode in which we randomly drop reads/writes, connections or sleep - FuzzModeDrop = iota - // FuzzModeDelay is a mode in which we randomly sleep - FuzzModeDelay +var ( + ErrInvalidFlushThrottleTimeout = errors.New("invalid flush throttle timeout") + ErrInvalidMaxPayloadSize = errors.New("invalid message payload size") + ErrInvalidSendRate = errors.New("invalid packet send rate") + ErrInvalidReceiveRate = errors.New("invalid packet receive rate") ) // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer @@ -32,14 +28,11 @@ type P2PConfig struct { // Comma separated list of nodes to keep persistent connections to PersistentPeers string `json:"persistent_peers" toml:"persistent_peers" comment:"Comma separated list of nodes to keep persistent connections to"` - // UPNP port forwarding - UPNP bool `json:"upnp" toml:"upnp" comment:"UPNP port forwarding"` - // Maximum number of inbound peers - MaxNumInboundPeers int `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` + MaxNumInboundPeers uint64 `json:"max_num_inbound_peers" toml:"max_num_inbound_peers" comment:"Maximum number of inbound peers"` // Maximum number of outbound peers to connect to, excluding persistent peers - MaxNumOutboundPeers int `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` + MaxNumOutboundPeers uint64 `json:"max_num_outbound_peers" toml:"max_num_outbound_peers" comment:"Maximum number of outbound peers to connect to, excluding persistent peers"` // Time to wait before flushing messages out on the connection FlushThrottleTimeout time.Duration `json:"flush_throttle_timeout" toml:"flush_throttle_timeout" comment:"Time to wait before flushing messages out on the connection"` @@ -54,105 +47,45 @@ type P2PConfig struct { RecvRate int64 `json:"recv_rate" toml:"recv_rate" comment:"Rate at which packets can be received, in bytes/second"` // Set true to enable the peer-exchange reactor - PexReactor bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` + PeerExchange bool `json:"pex" toml:"pex" comment:"Set true to enable the peer-exchange reactor"` - // Seed mode, in which node constantly crawls the network and looks for - // peers. If another node asks it for addresses, it responds and disconnects. - // - // Does not work if the peer-exchange reactor is disabled. - SeedMode bool `json:"seed_mode" toml:"seed_mode" comment:"Seed mode, in which node constantly crawls the network and looks for\n peers. If another node asks it for addresses, it responds and disconnects.\n\n Does not work if the peer-exchange reactor is disabled."` - - // Comma separated list of peer IDs to keep private (will not be gossiped to - // other peers) + // Comma separated list of peer IDs to keep private (will not be gossiped to other peers) PrivatePeerIDs string `json:"private_peer_ids" toml:"private_peer_ids" comment:"Comma separated list of peer IDs to keep private (will not be gossiped to other peers)"` - - // Toggle to disable guard against peers connecting from the same ip. - AllowDuplicateIP bool `json:"allow_duplicate_ip" toml:"allow_duplicate_ip" comment:"Toggle to disable guard against peers connecting from the same ip."` - - // Peer connection configuration. - HandshakeTimeout time.Duration `json:"handshake_timeout" toml:"handshake_timeout" comment:"Peer connection configuration."` - DialTimeout time.Duration `json:"dial_timeout" toml:"dial_timeout"` - - // Testing params. - // Force dial to fail - TestDialFail bool `json:"test_dial_fail" toml:"test_dial_fail"` - // FUzz connection - TestFuzz bool `json:"test_fuzz" toml:"test_fuzz"` - TestFuzzConfig *FuzzConnConfig `json:"test_fuzz_config" toml:"test_fuzz_config"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:26656", - ExternalAddress: "", - UPNP: false, + ExternalAddress: "", // nothing is advertised differently MaxNumInboundPeers: 40, MaxNumOutboundPeers: 10, FlushThrottleTimeout: 100 * time.Millisecond, MaxPacketMsgPayloadSize: 1024, // 1 kB SendRate: 5120000, // 5 mB/s RecvRate: 5120000, // 5 mB/s - PexReactor: true, - SeedMode: false, - AllowDuplicateIP: false, - HandshakeTimeout: 20 * time.Second, - DialTimeout: 3 * time.Second, - TestDialFail: false, - TestFuzz: false, - TestFuzzConfig: DefaultFuzzConnConfig(), + PeerExchange: true, } } -// TestP2PConfig returns a configuration for testing the peer-to-peer layer -func TestP2PConfig() *P2PConfig { - cfg := DefaultP2PConfig() - cfg.ListenAddress = "tcp://0.0.0.0:26656" - cfg.FlushThrottleTimeout = 10 * time.Millisecond - cfg.AllowDuplicateIP = true - return cfg -} - // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *P2PConfig) ValidateBasic() error { - if cfg.MaxNumInboundPeers < 0 { - return errors.New("max_num_inbound_peers can't be negative") - } - if cfg.MaxNumOutboundPeers < 0 { - return errors.New("max_num_outbound_peers can't be negative") - } if cfg.FlushThrottleTimeout < 0 { - return errors.New("flush_throttle_timeout can't be negative") + return ErrInvalidFlushThrottleTimeout } + if cfg.MaxPacketMsgPayloadSize < 0 { - return errors.New("max_packet_msg_payload_size can't be negative") + return ErrInvalidMaxPayloadSize } + if cfg.SendRate < 0 { - return errors.New("send_rate can't be negative") + return ErrInvalidSendRate } + if cfg.RecvRate < 0 { - return errors.New("recv_rate can't be negative") + return ErrInvalidReceiveRate } - return nil -} - -// FuzzConnConfig is a FuzzedConnection configuration. -type FuzzConnConfig struct { - Mode int - MaxDelay time.Duration - ProbDropRW float64 - ProbDropConn float64 - ProbSleep float64 -} -// DefaultFuzzConnConfig returns the default config. -func DefaultFuzzConnConfig() *FuzzConnConfig { - return &FuzzConnConfig{ - Mode: FuzzModeDrop, - MaxDelay: 3 * time.Second, - ProbDropRW: 0.2, - ProbDropConn: 0.00, - ProbSleep: 0.00, - } + return nil } diff --git a/tm2/pkg/p2p/config/config_test.go b/tm2/pkg/p2p/config/config_test.go new file mode 100644 index 00000000000..f624563d984 --- /dev/null +++ b/tm2/pkg/p2p/config/config_test.go @@ -0,0 +1,59 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestP2PConfig_ValidateBasic(t *testing.T) { + t.Parallel() + + t.Run("invalid flush throttle timeout", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.FlushThrottleTimeout = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), ErrInvalidFlushThrottleTimeout) + }) + + t.Run("invalid max packet payload size", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.MaxPacketMsgPayloadSize = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), ErrInvalidMaxPayloadSize) + }) + + t.Run("invalid send rate", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.SendRate = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), ErrInvalidSendRate) + }) + + t.Run("invalid receive rate", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + cfg.RecvRate = -1 + + assert.ErrorIs(t, cfg.ValidateBasic(), ErrInvalidReceiveRate) + }) + + t.Run("valid configuration", func(t *testing.T) { + t.Parallel() + + cfg := DefaultP2PConfig() + + assert.NoError(t, cfg.ValidateBasic()) + }) +} diff --git a/tm2/pkg/p2p/conn/conn.go b/tm2/pkg/p2p/conn/conn.go new file mode 100644 index 00000000000..3215adc38ca --- /dev/null +++ b/tm2/pkg/p2p/conn/conn.go @@ -0,0 +1,22 @@ +package conn + +import ( + "net" + "time" +) + +// pipe wraps the networking conn interface +type pipe struct { + net.Conn +} + +func (p *pipe) SetDeadline(_ time.Time) error { + return nil +} + +func NetPipe() (net.Conn, net.Conn) { + p1, p2 := net.Pipe() + return &pipe{p1}, &pipe{p2} +} + +var _ net.Conn = (*pipe)(nil) diff --git a/tm2/pkg/p2p/conn/conn_go110.go b/tm2/pkg/p2p/conn/conn_go110.go deleted file mode 100644 index 37796ac791d..00000000000 --- a/tm2/pkg/p2p/conn/conn_go110.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build go1.10 - -package conn - -// Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/classic/issues/851 - -import "net" - -func NetPipe() (net.Conn, net.Conn) { - return net.Pipe() -} diff --git a/tm2/pkg/p2p/conn/conn_notgo110.go b/tm2/pkg/p2p/conn/conn_notgo110.go deleted file mode 100644 index f91b0c7ea63..00000000000 --- a/tm2/pkg/p2p/conn/conn_notgo110.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build !go1.10 - -package conn - -import ( - "net" - "time" -) - -// Only Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// -// lest we run into problems like -// -// https://github.com/tendermint/classic/issues/851 -// -// so for go versions < Go1.10 use our custom net.Conn creator -// that doesn't return an `Unimplemented error` for net.Conn. -// Before https://github.com/tendermint/classic/commit/49faa79bdce5663894b3febbf4955fb1d172df04 -// we hadn't cared about errors from SetDeadline so swallow them up anyways. -type pipe struct { - net.Conn -} - -func (p *pipe) SetDeadline(t time.Time) error { - return nil -} - -func NetPipe() (net.Conn, net.Conn) { - p1, p2 := net.Pipe() - return &pipe{p1}, &pipe{p2} -} - -var _ net.Conn = (*pipe)(nil) diff --git a/tm2/pkg/p2p/conn/connection.go b/tm2/pkg/p2p/conn/connection.go index 6b7400600d3..acbffdb3cd7 100644 --- a/tm2/pkg/p2p/conn/connection.go +++ b/tm2/pkg/p2p/conn/connection.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/flow" + "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/timer" ) @@ -31,7 +32,6 @@ const ( // some of these defaults are written in the user config // flushThrottle, sendRate, recvRate - // TODO: remove values present in config defaultFlushThrottle = 100 * time.Millisecond defaultSendQueueCapacity = 1 @@ -46,7 +46,7 @@ const ( type ( receiveCbFunc func(chID byte, msgBytes []byte) - errorCbFunc func(interface{}) + errorCbFunc func(error) ) /* @@ -147,6 +147,18 @@ func DefaultMConnConfig() MConnConfig { } } +// MConfigFromP2P returns a multiplex connection configuration +// with fields updated from the P2PConfig +func MConfigFromP2P(cfg *config.P2PConfig) MConnConfig { + mConfig := DefaultMConnConfig() + mConfig.FlushThrottle = cfg.FlushThrottleTimeout + mConfig.SendRate = cfg.SendRate + mConfig.RecvRate = cfg.RecvRate + mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize + + return mConfig +} + // NewMConnection wraps net.Conn and creates multiplex connection func NewMConnection(conn net.Conn, chDescs []*ChannelDescriptor, onReceive receiveCbFunc, onError errorCbFunc) *MConnection { return NewMConnectionWithConfig( @@ -323,7 +335,7 @@ func (c *MConnection) _recover() { } } -func (c *MConnection) stopForError(r interface{}) { +func (c *MConnection) stopForError(r error) { c.Stop() if atomic.CompareAndSwapUint32(&c.errored, 0, 1) { if c.onError != nil { diff --git a/tm2/pkg/p2p/conn/connection_test.go b/tm2/pkg/p2p/conn/connection_test.go index 7bbe88ded22..58b363b7b78 100644 --- a/tm2/pkg/p2p/conn/connection_test.go +++ b/tm2/pkg/p2p/conn/connection_test.go @@ -21,7 +21,7 @@ func createTestMConnection(t *testing.T, conn net.Conn) *MConnection { onReceive := func(chID byte, msgBytes []byte) { } - onError := func(r interface{}) { + onError := func(r error) { } c := createMConnectionWithCallbacks(t, conn, onReceive, onError) c.SetLogger(log.NewTestingLogger(t)) @@ -32,7 +32,7 @@ func createMConnectionWithCallbacks( t *testing.T, conn net.Conn, onReceive func(chID byte, msgBytes []byte), - onError func(r interface{}), + onError func(r error), ) *MConnection { t.Helper() @@ -137,7 +137,7 @@ func TestMConnectionReceive(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn1 := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -192,7 +192,7 @@ func TestMConnectionPongTimeoutResultsInError(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -233,7 +233,7 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -288,7 +288,7 @@ func TestMConnectionMultiplePings(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -331,7 +331,7 @@ func TestMConnectionPingPongs(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -384,7 +384,7 @@ func TestMConnectionStopsAndReturnsError(t *testing.T) { onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } - onError := func(r interface{}) { + onError := func(r error) { errorsCh <- r } mconn := createMConnectionWithCallbacks(t, client, onReceive, onError) @@ -413,7 +413,7 @@ func newClientAndServerConnsForReadErrors(t *testing.T, chOnErr chan struct{}) ( server, client := NetPipe() onReceive := func(chID byte, msgBytes []byte) {} - onError := func(r interface{}) {} + onError := func(r error) {} // create client conn with two channels chDescs := []*ChannelDescriptor{ @@ -428,7 +428,7 @@ func newClientAndServerConnsForReadErrors(t *testing.T, chOnErr chan struct{}) ( // create server conn with 1 channel // it fires on chOnErr when there's an error serverLogger := log.NewNoopLogger().With("module", "server") - onError = func(r interface{}) { + onError = func(_ error) { chOnErr <- struct{}{} } mconnServer := createMConnectionWithCallbacks(t, server, onReceive, onError) diff --git a/tm2/pkg/p2p/conn/secret_connection.go b/tm2/pkg/p2p/conn/secret_connection.go index a37788b947d..d45b5b3846a 100644 --- a/tm2/pkg/p2p/conn/secret_connection.go +++ b/tm2/pkg/p2p/conn/secret_connection.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/binary" + "fmt" "io" "math" "net" @@ -128,7 +129,10 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* } // Sign the challenge bytes for authentication. - locSignature := signChallenge(challenge, locPrivKey) + locSignature, err := locPrivKey.Sign(challenge[:]) + if err != nil { + return nil, fmt.Errorf("unable to sign challenge, %w", err) + } // Share (in secret) each other's pubkey & challenge signature authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature) @@ -424,15 +428,6 @@ func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) { return } -func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature []byte) { - signature, err := locPrivKey.Sign(challenge[:]) - // TODO(ismail): let signChallenge return an error instead - if err != nil { - panic(err) - } - return -} - type authSigMessage struct { Key crypto.PubKey Sig []byte diff --git a/tm2/pkg/p2p/conn_set.go b/tm2/pkg/p2p/conn_set.go deleted file mode 100644 index d646227831a..00000000000 --- a/tm2/pkg/p2p/conn_set.go +++ /dev/null @@ -1,81 +0,0 @@ -package p2p - -import ( - "net" - "sync" -) - -// ConnSet is a lookup table for connections and all their ips. -type ConnSet interface { - Has(net.Conn) bool - HasIP(net.IP) bool - Set(net.Conn, []net.IP) - Remove(net.Conn) - RemoveAddr(net.Addr) -} - -type connSetItem struct { - conn net.Conn - ips []net.IP -} - -type connSet struct { - sync.RWMutex - - conns map[string]connSetItem -} - -// NewConnSet returns a ConnSet implementation. -func NewConnSet() *connSet { - return &connSet{ - conns: map[string]connSetItem{}, - } -} - -func (cs *connSet) Has(c net.Conn) bool { - cs.RLock() - defer cs.RUnlock() - - _, ok := cs.conns[c.RemoteAddr().String()] - - return ok -} - -func (cs *connSet) HasIP(ip net.IP) bool { - cs.RLock() - defer cs.RUnlock() - - for _, c := range cs.conns { - for _, known := range c.ips { - if known.Equal(ip) { - return true - } - } - } - - return false -} - -func (cs *connSet) Remove(c net.Conn) { - cs.Lock() - defer cs.Unlock() - - delete(cs.conns, c.RemoteAddr().String()) -} - -func (cs *connSet) RemoveAddr(addr net.Addr) { - cs.Lock() - defer cs.Unlock() - - delete(cs.conns, addr.String()) -} - -func (cs *connSet) Set(c net.Conn, ips []net.IP) { - cs.Lock() - defer cs.Unlock() - - cs.conns[c.RemoteAddr().String()] = connSetItem{ - conn: c, - ips: ips, - } -} diff --git a/tm2/pkg/p2p/dial/dial.go b/tm2/pkg/p2p/dial/dial.go new file mode 100644 index 00000000000..e4a7d6fd445 --- /dev/null +++ b/tm2/pkg/p2p/dial/dial.go @@ -0,0 +1,83 @@ +package dial + +import ( + "sync" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + queue "github.com/sig-0/insertion-queue" +) + +// Item is a single dial queue item, wrapping +// the approximately appropriate dial time, and the +// peer dial address +type Item struct { + Time time.Time // appropriate dial time + Address *types.NetAddress // the dial address of the peer +} + +// Less is the comparison method for the dial queue Item (time ascending) +func (i Item) Less(item Item) bool { + return i.Time.Before(item.Time) +} + +// Queue is a time-sorted (ascending) dial queue +type Queue struct { + mux sync.RWMutex + + items queue.Queue[Item] // sorted dial queue (by time, ascending) +} + +// NewQueue creates a new dial queue +func NewQueue() *Queue { + return &Queue{ + items: queue.NewQueue[Item](), + } +} + +// Peek returns the first item in the dial queue, if any +func (q *Queue) Peek() *Item { + q.mux.RLock() + defer q.mux.RUnlock() + + if q.items.Len() == 0 { + return nil + } + + item := q.items.Index(0) + + return &item +} + +// Push adds new items to the dial queue +func (q *Queue) Push(items ...Item) { + q.mux.Lock() + defer q.mux.Unlock() + + for _, item := range items { + q.items.Push(item) + } +} + +// Pop removes an item from the dial queue, if any +func (q *Queue) Pop() *Item { + q.mux.Lock() + defer q.mux.Unlock() + + return q.items.PopFront() +} + +// Has returns a flag indicating if the given +// address is in the dial queue +func (q *Queue) Has(addr *types.NetAddress) bool { + q.mux.RLock() + defer q.mux.RUnlock() + + for _, i := range q.items { + if addr.Equals(*i.Address) { + return true + } + } + + return false +} diff --git a/tm2/pkg/p2p/dial/dial_test.go b/tm2/pkg/p2p/dial/dial_test.go new file mode 100644 index 00000000000..5e85ec1f95e --- /dev/null +++ b/tm2/pkg/p2p/dial/dial_test.go @@ -0,0 +1,147 @@ +package dial + +import ( + "crypto/rand" + "math/big" + "slices" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateRandomTimes generates random time intervals +func generateRandomTimes(t *testing.T, count int) []time.Time { + t.Helper() + + const timeRange = 94608000 // 3 years + + var ( + maxRange = big.NewInt(time.Now().Unix() - timeRange) + times = make([]time.Time, 0, count) + ) + + for range count { + n, err := rand.Int(rand.Reader, maxRange) + require.NoError(t, err) + + randTime := time.Unix(n.Int64()+timeRange, 0) + + times = append(times, randTime) + } + + return times +} + +func TestQueue_Push(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 10) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + assert.Len(t, q.items, len(timestamps)) +} + +func TestQueue_Peek(t *testing.T) { + t.Parallel() + + t.Run("empty queue", func(t *testing.T) { + t.Parallel() + + q := NewQueue() + + assert.Nil(t, q.Peek()) + }) + + t.Run("existing item", func(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 100) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + // Sort the initial list to find the best timestamp + slices.SortFunc(timestamps, func(a, b time.Time) int { + if a.Before(b) { + return -1 + } + + if a.After(b) { + return 1 + } + + return 0 + }) + + assert.Equal(t, q.Peek().Time.Unix(), timestamps[0].Unix()) + }) +} + +func TestQueue_Pop(t *testing.T) { + t.Parallel() + + t.Run("empty queue", func(t *testing.T) { + t.Parallel() + + q := NewQueue() + + assert.Nil(t, q.Pop()) + }) + + t.Run("existing item", func(t *testing.T) { + t.Parallel() + + var ( + timestamps = generateRandomTimes(t, 100) + q = NewQueue() + ) + + // Add the dial items + for _, timestamp := range timestamps { + q.Push(Item{ + Time: timestamp, + }) + } + + assert.Len(t, q.items, len(timestamps)) + + // Sort the initial list to find the best timestamp + slices.SortFunc(timestamps, func(a, b time.Time) int { + if a.Before(b) { + return -1 + } + + if a.After(b) { + return 1 + } + + return 0 + }) + + for index, timestamp := range timestamps { + item := q.Pop() + + require.Len(t, q.items, len(timestamps)-1-index) + + assert.Equal(t, item.Time.Unix(), timestamp.Unix()) + } + }) +} diff --git a/tm2/pkg/p2p/dial/doc.go b/tm2/pkg/p2p/dial/doc.go new file mode 100644 index 00000000000..069160e73e6 --- /dev/null +++ b/tm2/pkg/p2p/dial/doc.go @@ -0,0 +1,10 @@ +// Package dial contains an implementation of a thread-safe priority dial queue. The queue is sorted by +// dial items, time ascending. +// The behavior of the dial queue is the following: +// +// - Peeking the dial queue will return the most urgent dial item, or nil if the queue is empty. +// +// - Popping the dial queue will return the most urgent dial item or nil if the queue is empty. Popping removes the dial item. +// +// - Push will push a new item to the dial queue, upon which the queue will find an adequate place for it. +package dial diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go new file mode 100644 index 00000000000..d884b118c75 --- /dev/null +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -0,0 +1,242 @@ +package discovery + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "time" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "golang.org/x/exp/slices" +) + +const ( + // Channel is the unique channel for the peer discovery protocol + Channel = byte(0x50) + + // discoveryInterval is the peer discovery interval, for random peers + discoveryInterval = time.Second * 3 + + // maxPeersShared is the maximum number of peers shared in the discovery request + maxPeersShared = 30 +) + +// descriptor is the constant peer discovery protocol descriptor +var descriptor = &conn.ChannelDescriptor{ + ID: Channel, + Priority: 1, // peer discovery is high priority + SendQueueCapacity: 20, // more than enough active conns + RecvMessageCapacity: 5242880, // 5MB +} + +// Reactor wraps the logic for the peer exchange protocol +type Reactor struct { + // This embed and the usage of "services" + // like the peer discovery reactor highlight the + // flipped design of the p2p package. + // The peer exchange service needs to be instantiated _outside_ + // the p2p module, because of this flipped design. + // Peers communicate with each other through Reactor channels, + // which are instantiated outside the p2p module + p2p.BaseReactor + + ctx context.Context + cancelFn context.CancelFunc + + discoveryInterval time.Duration +} + +// NewReactor creates a new peer discovery reactor +func NewReactor(opts ...Option) *Reactor { + ctx, cancelFn := context.WithCancel(context.Background()) + + r := &Reactor{ + ctx: ctx, + cancelFn: cancelFn, + discoveryInterval: discoveryInterval, + } + + r.BaseReactor = *p2p.NewBaseReactor("Reactor", r) + + // Apply the options + for _, opt := range opts { + opt(r) + } + + return r +} + +// OnStart runs the peer discovery protocol +func (r *Reactor) OnStart() error { + go func() { + ticker := time.NewTicker(r.discoveryInterval) + defer ticker.Stop() + + for { + select { + case <-r.ctx.Done(): + r.Logger.Debug("discovery service stopped") + + return + case <-ticker.C: + // Run the discovery protocol // + + // Grab a random peer, and engage + // them for peer discovery + peers := r.Switch.Peers().List() + + if len(peers) == 0 { + // No discovery to run + continue + } + + // Generate a random peer index + randomPeer, _ := rand.Int( + rand.Reader, + big.NewInt(int64(len(peers))), + ) + + // Request peers, async + go r.requestPeers(peers[randomPeer.Int64()]) + } + } + }() + + return nil +} + +// OnStop stops the peer discovery protocol +func (r *Reactor) OnStop() { + r.cancelFn() +} + +// requestPeers requests the peer set from the given peer +func (r *Reactor) requestPeers(peer p2p.PeerConn) { + // Initiate peer discovery + r.Logger.Debug("running peer discovery", "peer", peer.ID()) + + // Prepare the request + // (empty, as it's a notification) + req := &Request{} + + reqBytes, err := amino.MarshalAny(req) + if err != nil { + r.Logger.Error("unable to marshal discovery request", "err", err) + + return + } + + // Send the request + if !peer.Send(Channel, reqBytes) { + r.Logger.Warn("unable to send discovery request", "peer", peer.ID()) + } +} + +// GetChannels returns the channels associated with peer discovery +func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { + return []*conn.ChannelDescriptor{descriptor} +} + +// Receive handles incoming messages for the peer discovery reactor +func (r *Reactor) Receive(chID byte, peer p2p.PeerConn, msgBytes []byte) { + r.Logger.Debug( + "received message", + "peerID", peer.ID(), + "chID", chID, + ) + + // Unmarshal the message + var msg Message + + if err := amino.UnmarshalAny(msgBytes, &msg); err != nil { + r.Logger.Error("unable to unmarshal discovery message", "err", err) + + return + } + + // Validate the message + if err := msg.ValidateBasic(); err != nil { + r.Logger.Error("unable to validate discovery message", "err", err) + + return + } + + switch msg := msg.(type) { + case *Request: + if err := r.handleDiscoveryRequest(peer); err != nil { + r.Logger.Error("unable to handle discovery request", "err", err) + } + case *Response: + // Make the peers available for dialing on the switch + r.Switch.DialPeers(msg.Peers...) + default: + r.Logger.Warn("invalid message received", "msg", msgBytes) + } +} + +// handleDiscoveryRequest prepares a peer list that can be shared +// with the peer requesting discovery +func (r *Reactor) handleDiscoveryRequest(peer p2p.PeerConn) error { + var ( + localPeers = r.Switch.Peers().List() + peers = make([]*types.NetAddress, 0, len(localPeers)) + ) + + // Exclude the private peers from being shared + localPeers = slices.DeleteFunc(localPeers, func(p p2p.PeerConn) bool { + return p.IsPrivate() + }) + + // Check if there is anything to share, + // to avoid useless traffic + if len(localPeers) == 0 { + r.Logger.Warn("no peers to share in discovery request") + + return nil + } + + // Shuffle and limit the peers shared + shufflePeers(localPeers) + + if len(localPeers) > maxPeersShared { + localPeers = localPeers[:maxPeersShared] + } + + for _, p := range localPeers { + peers = append(peers, p.SocketAddr()) + } + + // Create the response, and marshal + // it to Amino binary + resp := &Response{ + Peers: peers, + } + + preparedResp, err := amino.MarshalAny(resp) + if err != nil { + return fmt.Errorf("unable to marshal discovery response, %w", err) + } + + // Send the response to the peer + if !peer.Send(Channel, preparedResp) { + return fmt.Errorf("unable to send discovery response to peer %s", peer.ID()) + } + + return nil +} + +// shufflePeers shuffles the peer list in-place +func shufflePeers(peers []p2p.PeerConn) { + for i := len(peers) - 1; i > 0; i-- { + jBig, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1))) + + j := int(jBig.Int64()) + + // Swap elements + peers[i], peers[j] = peers[j], peers[i] + } +} diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go new file mode 100644 index 00000000000..17404e6039a --- /dev/null +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -0,0 +1,453 @@ +package discovery + +import ( + "slices" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReactor_DiscoveryRequest(t *testing.T) { + t.Parallel() + + var ( + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + return []p2p.PeerConn{mockPeer} + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Start the discovery service + require.NoError(t, r.Start()) + t.Cleanup(func() { + require.NoError(t, r.Stop()) + }) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + _, ok := msg.(*Request) + + require.True(t, ok) +} + +func TestReactor_DiscoveryResponse(t *testing.T) { + t.Parallel() + + t.Run("discovery request received", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 50) + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + listed := make([]p2p.PeerConn, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed + }, + numInboundFn: func() uint64 { + return uint64(len(peers)) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + resp, ok := msg.(*Response) + require.True(t, ok) + + // Make sure the peers are valid + require.Len(t, resp.Peers, maxPeersShared) + + slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { + for _, localP := range peers { + if localP.SocketAddr().Equals(*addr) { + return true + } + } + + return false + }) + }) + + t.Run("empty peers on discover", func(t *testing.T) { + t.Parallel() + + var ( + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + return make([]p2p.PeerConn, 0) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + // Make sure no message was captured + assert.Nil(t, capturedSend) + }) + + t.Run("private peers not shared", func(t *testing.T) { + t.Parallel() + + var ( + publicPeers = 1 + privatePeers = 50 + + peers = mock.GeneratePeers(t, publicPeers+privatePeers) + notifCh = make(chan struct{}, 1) + + capturedSend []byte + + mockPeer = &mock.Peer{ + SendFn: func(chID byte, data []byte) bool { + require.Equal(t, Channel, chID) + + capturedSend = data + + notifCh <- struct{}{} + + return true + }, + } + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + listed := make([]p2p.PeerConn, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed + }, + numInboundFn: func() uint64 { + return uint64(len(peers)) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + } + ) + + // Mark all except the last X peers as private + for _, peer := range peers[:privatePeers] { + peer.IsPrivateFn = func() bool { + return true + } + } + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Request{} + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, mockPeer, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the adequate message was captured + require.NotNil(t, capturedSend) + + // Parse the message + var msg Message + + require.NoError(t, amino.Unmarshal(capturedSend, &msg)) + + // Make sure the base message is valid + require.NoError(t, msg.ValidateBasic()) + + resp, ok := msg.(*Response) + require.True(t, ok) + + // Make sure the peers are valid + require.Len(t, resp.Peers, publicPeers) + + slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { + for _, localP := range peers { + if localP.SocketAddr().Equals(*addr) { + return true + } + } + + return false + }) + }) + + t.Run("peer response received", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 50) + notifCh = make(chan struct{}, 1) + + capturedDials []*types.NetAddress + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + listed := make([]p2p.PeerConn, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed + }, + numInboundFn: func() uint64 { + return uint64(len(peers)) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + dialPeersFn: func(addresses ...*types.NetAddress) { + capturedDials = append(capturedDials, addresses...) + + notifCh <- struct{}{} + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the addresses + peerAddrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + peerAddrs = append(peerAddrs, p.SocketAddr()) + } + + // Prepare the message + req := &Response{ + Peers: peerAddrs, + } + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, &mock.Peer{}, preparedReq) + + select { + case <-notifCh: + case <-time.After(5 * time.Second): + } + + // Make sure the correct peers were dialed + assert.Equal(t, capturedDials, peerAddrs) + }) + + t.Run("invalid peer response received", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 50) + + capturedDials []*types.NetAddress + + ps = &mockPeerSet{ + listFn: func() []p2p.PeerConn { + listed := make([]p2p.PeerConn, 0, len(peers)) + + for _, peer := range peers { + listed = append(listed, peer) + } + + return listed + }, + numInboundFn: func() uint64 { + return uint64(len(peers)) + }, + } + + mockSwitch = &mockSwitch{ + peersFn: func() p2p.PeerSet { + return ps + }, + dialPeersFn: func(addresses ...*types.NetAddress) { + capturedDials = append(capturedDials, addresses...) + }, + } + ) + + r := NewReactor( + WithDiscoveryInterval(10 * time.Millisecond), + ) + + // Set the mock switch + r.SetSwitch(mockSwitch) + + // Prepare the message + req := &Response{ + Peers: make([]*types.NetAddress, 0), // empty + } + + preparedReq, err := amino.MarshalAny(req) + require.NoError(t, err) + + // Receive the message + r.Receive(Channel, &mock.Peer{}, preparedReq) + + // Make sure no peers were dialed + assert.Empty(t, capturedDials) + }) +} diff --git a/tm2/pkg/p2p/discovery/doc.go b/tm2/pkg/p2p/discovery/doc.go new file mode 100644 index 00000000000..5426bb41277 --- /dev/null +++ b/tm2/pkg/p2p/discovery/doc.go @@ -0,0 +1,9 @@ +// Package discovery contains the p2p peer discovery service (Reactor). +// The purpose of the peer discovery service is to gather peer lists from known peers, +// and attempt to fill out open peer connection slots in order to build out a fuller mesh. +// +// The implementation of the peer discovery protocol is relatively simple. +// In essence, it pings a random peer at a specific interval (3s), for a list of their known peers (max 30). +// After receiving the list, and verifying it, the node attempts to establish outbound connections to the +// given peers. +package discovery diff --git a/tm2/pkg/p2p/discovery/mock_test.go b/tm2/pkg/p2p/discovery/mock_test.go new file mode 100644 index 00000000000..cd543428f86 --- /dev/null +++ b/tm2/pkg/p2p/discovery/mock_test.go @@ -0,0 +1,135 @@ +package discovery + +import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type ( + broadcastDelegate func(byte, []byte) + peersDelegate func() p2p.PeerSet + stopPeerForErrorDelegate func(p2p.PeerConn, error) + dialPeersDelegate func(...*types.NetAddress) + subscribeDelegate func(events.EventFilter) (<-chan events.Event, func()) +) + +type mockSwitch struct { + broadcastFn broadcastDelegate + peersFn peersDelegate + stopPeerForErrorFn stopPeerForErrorDelegate + dialPeersFn dialPeersDelegate + subscribeFn subscribeDelegate +} + +func (m *mockSwitch) Broadcast(chID byte, data []byte) { + if m.broadcastFn != nil { + m.broadcastFn(chID, data) + } +} + +func (m *mockSwitch) Peers() p2p.PeerSet { + if m.peersFn != nil { + return m.peersFn() + } + + return nil +} + +func (m *mockSwitch) StopPeerForError(peer p2p.PeerConn, err error) { + if m.stopPeerForErrorFn != nil { + m.stopPeerForErrorFn(peer, err) + } +} + +func (m *mockSwitch) DialPeers(peerAddrs ...*types.NetAddress) { + if m.dialPeersFn != nil { + m.dialPeersFn(peerAddrs...) + } +} + +func (m *mockSwitch) Subscribe(filter events.EventFilter) (<-chan events.Event, func()) { + if m.subscribeFn != nil { + m.subscribeFn(filter) + } + + return nil, func() {} +} + +type ( + addDelegate func(p2p.PeerConn) + removeDelegate func(types.ID) bool + hasDelegate func(types.ID) bool + hasIPDelegate func(net.IP) bool + getPeerDelegate func(types.ID) p2p.PeerConn + listDelegate func() []p2p.PeerConn + numInboundDelegate func() uint64 + numOutboundDelegate func() uint64 +) + +type mockPeerSet struct { + addFn addDelegate + removeFn removeDelegate + hasFn hasDelegate + hasIPFn hasIPDelegate + getFn getPeerDelegate + listFn listDelegate + numInboundFn numInboundDelegate + numOutboundFn numOutboundDelegate +} + +func (m *mockPeerSet) Add(peer p2p.PeerConn) { + if m.addFn != nil { + m.addFn(peer) + } +} + +func (m *mockPeerSet) Remove(key types.ID) bool { + if m.removeFn != nil { + m.removeFn(key) + } + + return false +} + +func (m *mockPeerSet) Has(key types.ID) bool { + if m.hasFn != nil { + return m.hasFn(key) + } + + return false +} + +func (m *mockPeerSet) Get(key types.ID) p2p.PeerConn { + if m.getFn != nil { + return m.getFn(key) + } + + return nil +} + +func (m *mockPeerSet) List() []p2p.PeerConn { + if m.listFn != nil { + return m.listFn() + } + + return nil +} + +func (m *mockPeerSet) NumInbound() uint64 { + if m.numInboundFn != nil { + return m.numInboundFn() + } + + return 0 +} + +func (m *mockPeerSet) NumOutbound() uint64 { + if m.numOutboundFn != nil { + return m.numOutboundFn() + } + + return 0 +} diff --git a/tm2/pkg/p2p/discovery/option.go b/tm2/pkg/p2p/discovery/option.go new file mode 100644 index 00000000000..dc0fb95b109 --- /dev/null +++ b/tm2/pkg/p2p/discovery/option.go @@ -0,0 +1,12 @@ +package discovery + +import "time" + +type Option func(*Reactor) + +// WithDiscoveryInterval sets the discovery crawl interval +func WithDiscoveryInterval(interval time.Duration) Option { + return func(r *Reactor) { + r.discoveryInterval = interval + } +} diff --git a/tm2/pkg/p2p/discovery/package.go b/tm2/pkg/p2p/discovery/package.go new file mode 100644 index 00000000000..a3865fdf5d2 --- /dev/null +++ b/tm2/pkg/p2p/discovery/package.go @@ -0,0 +1,16 @@ +package discovery + +import ( + "github.com/gnolang/gno/tm2/pkg/amino" +) + +var Package = amino.RegisterPackage(amino.NewPackage( + "github.com/gnolang/gno/tm2/pkg/p2p/discovery", + "p2p", + amino.GetCallersDirname(), +). + WithTypes( + &Request{}, + &Response{}, + ), +) diff --git a/tm2/pkg/p2p/discovery/types.go b/tm2/pkg/p2p/discovery/types.go new file mode 100644 index 00000000000..87ea936ebb5 --- /dev/null +++ b/tm2/pkg/p2p/discovery/types.go @@ -0,0 +1,44 @@ +package discovery + +import ( + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +var errNoPeers = errors.New("no peers received") + +// Message is the wrapper for the discovery message +type Message interface { + ValidateBasic() error +} + +// Request is the peer discovery request. +// It is empty by design, since it's used as +// a notification type +type Request struct{} + +func (r *Request) ValidateBasic() error { + return nil +} + +// Response is the peer discovery response +type Response struct { + Peers []*types.NetAddress // the peer set returned by the peer +} + +func (r *Response) ValidateBasic() error { + // Make sure at least some peers were received + if len(r.Peers) == 0 { + return errNoPeers + } + + // Make sure the returned peer dial + // addresses are valid + for _, peer := range r.Peers { + if err := peer.Validate(); err != nil { + return err + } + } + + return nil +} diff --git a/tm2/pkg/p2p/discovery/types_test.go b/tm2/pkg/p2p/discovery/types_test.go new file mode 100644 index 00000000000..0ac2c16f4e5 --- /dev/null +++ b/tm2/pkg/p2p/discovery/types_test.go @@ -0,0 +1,80 @@ +package discovery + +import ( + "net" + "testing" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateNetAddrs generates random net addresses +func generateNetAddrs(t *testing.T, count int) []*types.NetAddress { + t.Helper() + + addrs := make([]*types.NetAddress, count) + + for i := 0; i < count; i++ { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + addrs[i] = addr + } + + return addrs +} + +func TestRequest_ValidateBasic(t *testing.T) { + t.Parallel() + + r := &Request{} + + assert.NoError(t, r.ValidateBasic()) +} + +func TestResponse_ValidateBasic(t *testing.T) { + t.Parallel() + + t.Run("empty peer set", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: make([]*types.NetAddress, 0), + } + + assert.ErrorIs(t, r.ValidateBasic(), errNoPeers) + }) + + t.Run("invalid peer dial address", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: []*types.NetAddress{ + { + ID: "", // invalid ID + }, + }, + } + + assert.Error(t, r.ValidateBasic()) + }) + + t.Run("valid peer set", func(t *testing.T) { + t.Parallel() + + r := &Response{ + Peers: generateNetAddrs(t, 10), + } + + assert.NoError(t, r.ValidateBasic()) + }) +} diff --git a/tm2/pkg/p2p/errors.go b/tm2/pkg/p2p/errors.go deleted file mode 100644 index d4ad58e8ab5..00000000000 --- a/tm2/pkg/p2p/errors.go +++ /dev/null @@ -1,184 +0,0 @@ -package p2p - -import ( - "fmt" - "net" -) - -// FilterTimeoutError indicates that a filter operation timed out. -type FilterTimeoutError struct{} - -func (e FilterTimeoutError) Error() string { - return "filter timed out" -} - -// RejectedError indicates that a Peer was rejected carrying additional -// information as to the reason. -type RejectedError struct { - addr NetAddress - conn net.Conn - err error - id ID - isAuthFailure bool - isDuplicate bool - isFiltered bool - isIncompatible bool - isNodeInfoInvalid bool - isSelf bool -} - -// Addr returns the NetAddress for the rejected Peer. -func (e RejectedError) Addr() NetAddress { - return e.addr -} - -func (e RejectedError) Error() string { - if e.isAuthFailure { - return fmt.Sprintf("auth failure: %s", e.err) - } - - if e.isDuplicate { - if e.conn != nil { - return fmt.Sprintf( - "duplicate CONN<%s>", - e.conn.RemoteAddr().String(), - ) - } - if !e.id.IsZero() { - return fmt.Sprintf("duplicate ID<%v>", e.id) - } - } - - if e.isFiltered { - if e.conn != nil { - return fmt.Sprintf( - "filtered CONN<%s>: %s", - e.conn.RemoteAddr().String(), - e.err, - ) - } - - if !e.id.IsZero() { - return fmt.Sprintf("filtered ID<%v>: %s", e.id, e.err) - } - } - - if e.isIncompatible { - return fmt.Sprintf("incompatible: %s", e.err) - } - - if e.isNodeInfoInvalid { - return fmt.Sprintf("invalid NodeInfo: %s", e.err) - } - - if e.isSelf { - return fmt.Sprintf("self ID<%v>", e.id) - } - - return fmt.Sprintf("%s", e.err) -} - -// IsAuthFailure when Peer authentication was unsuccessful. -func (e RejectedError) IsAuthFailure() bool { return e.isAuthFailure } - -// IsDuplicate when Peer ID or IP are present already. -func (e RejectedError) IsDuplicate() bool { return e.isDuplicate } - -// IsFiltered when Peer ID or IP was filtered. -func (e RejectedError) IsFiltered() bool { return e.isFiltered } - -// IsIncompatible when Peer NodeInfo is not compatible with our own. -func (e RejectedError) IsIncompatible() bool { return e.isIncompatible } - -// IsNodeInfoInvalid when the sent NodeInfo is not valid. -func (e RejectedError) IsNodeInfoInvalid() bool { return e.isNodeInfoInvalid } - -// IsSelf when Peer is our own node. -func (e RejectedError) IsSelf() bool { return e.isSelf } - -// SwitchDuplicatePeerIDError to be raised when a peer is connecting with a known -// ID. -type SwitchDuplicatePeerIDError struct { - ID ID -} - -func (e SwitchDuplicatePeerIDError) Error() string { - return fmt.Sprintf("duplicate peer ID %v", e.ID) -} - -// SwitchDuplicatePeerIPError to be raised when a peer is connecting with a known -// IP. -type SwitchDuplicatePeerIPError struct { - IP net.IP -} - -func (e SwitchDuplicatePeerIPError) Error() string { - return fmt.Sprintf("duplicate peer IP %v", e.IP.String()) -} - -// SwitchConnectToSelfError to be raised when trying to connect to itself. -type SwitchConnectToSelfError struct { - Addr *NetAddress -} - -func (e SwitchConnectToSelfError) Error() string { - return fmt.Sprintf("connect to self: %v", e.Addr) -} - -type SwitchAuthenticationFailureError struct { - Dialed *NetAddress - Got ID -} - -func (e SwitchAuthenticationFailureError) Error() string { - return fmt.Sprintf( - "failed to authenticate peer. Dialed %v, but got peer with ID %s", - e.Dialed, - e.Got, - ) -} - -// TransportClosedError is raised when the Transport has been closed. -type TransportClosedError struct{} - -func (e TransportClosedError) Error() string { - return "transport has been closed" -} - -// ------------------------------------------------------------------- - -type NetAddressNoIDError struct { - Addr string -} - -func (e NetAddressNoIDError) Error() string { - return fmt.Sprintf("address (%s) does not contain ID", e.Addr) -} - -type NetAddressInvalidError struct { - Addr string - Err error -} - -func (e NetAddressInvalidError) Error() string { - return fmt.Sprintf("invalid address (%s): %v", e.Addr, e.Err) -} - -type NetAddressLookupError struct { - Addr string - Err error -} - -func (e NetAddressLookupError) Error() string { - return fmt.Sprintf("error looking up host (%s): %v", e.Addr, e.Err) -} - -// CurrentlyDialingOrExistingAddressError indicates that we're currently -// dialing this address or it belongs to an existing peer. -type CurrentlyDialingOrExistingAddressError struct { - Addr string -} - -func (e CurrentlyDialingOrExistingAddressError) Error() string { - return fmt.Sprintf("connection with %s has been established or dialed", e.Addr) -} diff --git a/tm2/pkg/p2p/events/doc.go b/tm2/pkg/p2p/events/doc.go new file mode 100644 index 00000000000..a624102379e --- /dev/null +++ b/tm2/pkg/p2p/events/doc.go @@ -0,0 +1,3 @@ +// Package events contains a simple p2p event system implementation, that simplifies asynchronous event flows in the +// p2p module. The event subscriptions allow for event filtering, which eases the load on the event notification flow. +package events diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go new file mode 100644 index 00000000000..bf76e27d91e --- /dev/null +++ b/tm2/pkg/p2p/events/events.go @@ -0,0 +1,104 @@ +package events + +import ( + "sync" + + "github.com/rs/xid" +) + +// EventFilter is the filter function used to +// filter incoming p2p events. A false flag will +// consider the event as irrelevant +type EventFilter func(Event) bool + +// Events is the p2p event switch +type Events struct { + subs subscriptions + subscriptionsMux sync.RWMutex +} + +// New creates a new event subscription manager +func New() *Events { + return &Events{ + subs: make(subscriptions), + } +} + +// Subscribe registers a new filtered event listener +func (es *Events) Subscribe(filterFn EventFilter) (<-chan Event, func()) { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + // Create a new subscription + id, ch := es.subs.add(filterFn) + + // Create the unsubscribe callback + unsubscribeFn := func() { + es.subscriptionsMux.Lock() + defer es.subscriptionsMux.Unlock() + + es.subs.remove(id) + } + + return ch, unsubscribeFn +} + +// Notify notifies all subscribers of an incoming event [BLOCKING] +func (es *Events) Notify(event Event) { + es.subscriptionsMux.RLock() + defer es.subscriptionsMux.RUnlock() + + es.subs.notify(event) +} + +type ( + // subscriptions holds the corresponding subscription information + subscriptions map[string]subscription // subscription ID -> subscription + + // subscription wraps the subscription notification channel, + // and the event filter + subscription struct { + ch chan Event + filterFn EventFilter + } +) + +// add adds a new subscription to the subscription map. +// Returns the subscription ID, and update channel +func (s *subscriptions) add(filterFn EventFilter) (string, chan Event) { + var ( + id = xid.New().String() + ch = make(chan Event, 1) + ) + + (*s)[id] = subscription{ + ch: ch, + filterFn: filterFn, + } + + return id, ch +} + +// remove removes the given subscription +func (s *subscriptions) remove(id string) { + if sub, exists := (*s)[id]; exists { + // Close the notification channel + close(sub.ch) + } + + // Delete the subscription + delete(*s, id) +} + +// notify notifies all subscription listeners, +// if their filters pass +func (s *subscriptions) notify(event Event) { + // Notify the listeners + for _, sub := range *s { + if !sub.filterFn(event) { + continue + } + + sub.ch <- event + } +} diff --git a/tm2/pkg/p2p/events/events_test.go b/tm2/pkg/p2p/events/events_test.go new file mode 100644 index 00000000000..a0feafceddb --- /dev/null +++ b/tm2/pkg/p2p/events/events_test.go @@ -0,0 +1,94 @@ +package events + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateEvents generates p2p events +func generateEvents(count int) []Event { + events := make([]Event, 0, count) + + for i := range count { + var event Event + + if i%2 == 0 { + event = PeerConnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } else { + event = PeerDisconnectedEvent{ + PeerID: types.ID(fmt.Sprintf("peer-%d", i)), + } + } + + events = append(events, event) + } + + return events +} + +func TestEvents_Subscribe(t *testing.T) { + t.Parallel() + + var ( + capturedEvents []Event + + events = generateEvents(10) + subFn = func(e Event) bool { + return e.Type() == PeerDisconnected + } + ) + + // Create the events manager + e := New() + + // Subscribe to events + ch, unsubFn := e.Subscribe(subFn) + defer unsubFn() + + // Listen for the events + var wg sync.WaitGroup + + wg.Add(1) + + go func() { + defer wg.Done() + + timeout := time.After(5 * time.Second) + + for { + select { + case ev := <-ch: + capturedEvents = append(capturedEvents, ev) + + if len(capturedEvents) == len(events)/2 { + return + } + case <-timeout: + return + } + } + }() + + // Send out the events + for _, ev := range events { + e.Notify(ev) + } + + wg.Wait() + + // Make sure the events were captured + // and filtered properly + require.Len(t, capturedEvents, len(events)/2) + + for _, ev := range capturedEvents { + assert.Equal(t, ev.Type(), PeerDisconnected) + } +} diff --git a/tm2/pkg/p2p/events/types.go b/tm2/pkg/p2p/events/types.go new file mode 100644 index 00000000000..cbaac1816ff --- /dev/null +++ b/tm2/pkg/p2p/events/types.go @@ -0,0 +1,39 @@ +package events + +import ( + "net" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type EventType string + +const ( + PeerConnected EventType = "PeerConnected" // emitted when a fresh peer connects + PeerDisconnected EventType = "PeerDisconnected" // emitted when a peer disconnects +) + +// Event is a generic p2p event +type Event interface { + // Type returns the type information for the event + Type() EventType +} + +type PeerConnectedEvent struct { + PeerID types.ID // the ID of the peer + Address net.Addr // the remote address of the peer +} + +func (p PeerConnectedEvent) Type() EventType { + return PeerConnected +} + +type PeerDisconnectedEvent struct { + PeerID types.ID // the ID of the peer + Address net.Addr // the remote address of the peer + Reason error // the disconnect reason, if any +} + +func (p PeerDisconnectedEvent) Type() EventType { + return PeerDisconnected +} diff --git a/tm2/pkg/p2p/fuzz.go b/tm2/pkg/p2p/fuzz.go deleted file mode 100644 index 03cf88cf750..00000000000 --- a/tm2/pkg/p2p/fuzz.go +++ /dev/null @@ -1,131 +0,0 @@ -package p2p - -import ( - "net" - "sync" - "time" - - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/random" -) - -// FuzzedConnection wraps any net.Conn and depending on the mode either delays -// reads/writes or randomly drops reads/writes/connections. -type FuzzedConnection struct { - conn net.Conn - - mtx sync.Mutex - start <-chan time.Time - active bool - - config *config.FuzzConnConfig -} - -// FuzzConnAfterFromConfig creates a new FuzzedConnection from a config. -// Fuzzing starts when the duration elapses. -func FuzzConnAfterFromConfig( - conn net.Conn, - d time.Duration, - config *config.FuzzConnConfig, -) net.Conn { - return &FuzzedConnection{ - conn: conn, - start: time.After(d), - active: false, - config: config, - } -} - -// Config returns the connection's config. -func (fc *FuzzedConnection) Config() *config.FuzzConnConfig { - return fc.config -} - -// Read implements net.Conn. -func (fc *FuzzedConnection) Read(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Read(data) -} - -// Write implements net.Conn. -func (fc *FuzzedConnection) Write(data []byte) (n int, err error) { - if fc.fuzz() { - return 0, nil - } - return fc.conn.Write(data) -} - -// Close implements net.Conn. -func (fc *FuzzedConnection) Close() error { return fc.conn.Close() } - -// LocalAddr implements net.Conn. -func (fc *FuzzedConnection) LocalAddr() net.Addr { return fc.conn.LocalAddr() } - -// RemoteAddr implements net.Conn. -func (fc *FuzzedConnection) RemoteAddr() net.Addr { return fc.conn.RemoteAddr() } - -// SetDeadline implements net.Conn. -func (fc *FuzzedConnection) SetDeadline(t time.Time) error { return fc.conn.SetDeadline(t) } - -// SetReadDeadline implements net.Conn. -func (fc *FuzzedConnection) SetReadDeadline(t time.Time) error { - return fc.conn.SetReadDeadline(t) -} - -// SetWriteDeadline implements net.Conn. -func (fc *FuzzedConnection) SetWriteDeadline(t time.Time) error { - return fc.conn.SetWriteDeadline(t) -} - -func (fc *FuzzedConnection) randomDuration() time.Duration { - maxDelayMillis := int(fc.config.MaxDelay.Nanoseconds() / 1000) - return time.Millisecond * time.Duration(random.RandInt()%maxDelayMillis) //nolint: gas -} - -// implements the fuzz (delay, kill conn) -// and returns whether or not the read/write should be ignored -func (fc *FuzzedConnection) fuzz() bool { - if !fc.shouldFuzz() { - return false - } - - switch fc.config.Mode { - case config.FuzzModeDrop: - // randomly drop the r/w, drop the conn, or sleep - r := random.RandFloat64() - switch { - case r <= fc.config.ProbDropRW: - return true - case r < fc.config.ProbDropRW+fc.config.ProbDropConn: - // XXX: can't this fail because machine precision? - // XXX: do we need an error? - fc.Close() //nolint: errcheck, gas - return true - case r < fc.config.ProbDropRW+fc.config.ProbDropConn+fc.config.ProbSleep: - time.Sleep(fc.randomDuration()) - } - case config.FuzzModeDelay: - // sleep a bit - time.Sleep(fc.randomDuration()) - } - return false -} - -func (fc *FuzzedConnection) shouldFuzz() bool { - if fc.active { - return true - } - - fc.mtx.Lock() - defer fc.mtx.Unlock() - - select { - case <-fc.start: - fc.active = true - return true - default: - return false - } -} diff --git a/tm2/pkg/p2p/key.go b/tm2/pkg/p2p/key.go deleted file mode 100644 index a41edeb07f8..00000000000 --- a/tm2/pkg/p2p/key.go +++ /dev/null @@ -1,94 +0,0 @@ -package p2p - -import ( - "bytes" - "fmt" - "os" - - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - osm "github.com/gnolang/gno/tm2/pkg/os" -) - -// ------------------------------------------------------------------------------ -// Persistent peer ID -// TODO: encrypt on disk - -// NodeKey is the persistent peer key. -// It contains the nodes private key for authentication. -// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go -type NodeKey struct { - crypto.PrivKey `json:"priv_key"` // our priv key -} - -func (nk NodeKey) ID() ID { - return nk.PubKey().Address().ID() -} - -// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. -// If the file does not exist, it generates and saves a new NodeKey. -func LoadOrGenNodeKey(filePath string) (*NodeKey, error) { - if osm.FileExists(filePath) { - nodeKey, err := LoadNodeKey(filePath) - if err != nil { - return nil, err - } - return nodeKey, nil - } - return genNodeKey(filePath) -} - -func LoadNodeKey(filePath string) (*NodeKey, error) { - jsonBytes, err := os.ReadFile(filePath) - if err != nil { - return nil, err - } - nodeKey := new(NodeKey) - err = amino.UnmarshalJSON(jsonBytes, nodeKey) - if err != nil { - return nil, fmt.Errorf("Error reading NodeKey from %v: %w", filePath, err) - } - return nodeKey, nil -} - -func genNodeKey(filePath string) (*NodeKey, error) { - privKey := ed25519.GenPrivKey() - nodeKey := &NodeKey{ - PrivKey: privKey, - } - - jsonBytes, err := amino.MarshalJSON(nodeKey) - if err != nil { - return nil, err - } - err = os.WriteFile(filePath, jsonBytes, 0o600) - if err != nil { - return nil, err - } - return nodeKey, nil -} - -// ------------------------------------------------------------------------------ - -// MakePoWTarget returns the big-endian encoding of 2^(targetBits - difficulty) - 1. -// It can be used as a Proof of Work target. -// NOTE: targetBits must be a multiple of 8 and difficulty must be less than targetBits. -func MakePoWTarget(difficulty, targetBits uint) []byte { - if targetBits%8 != 0 { - panic(fmt.Sprintf("targetBits (%d) not a multiple of 8", targetBits)) - } - if difficulty >= targetBits { - panic(fmt.Sprintf("difficulty (%d) >= targetBits (%d)", difficulty, targetBits)) - } - targetBytes := targetBits / 8 - zeroPrefixLen := (int(difficulty) / 8) - prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) - mod := (difficulty % 8) - if mod > 0 { - nonZeroPrefix := byte(1<<(8-mod) - 1) - prefix = append(prefix, nonZeroPrefix) - } - tailLen := int(targetBytes) - len(prefix) - return append(prefix, bytes.Repeat([]byte{0xFF}, tailLen)...) -} diff --git a/tm2/pkg/p2p/key_test.go b/tm2/pkg/p2p/key_test.go deleted file mode 100644 index 4f67cc0a5da..00000000000 --- a/tm2/pkg/p2p/key_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package p2p - -import ( - "bytes" - "os" - "path/filepath" - "testing" - - "github.com/gnolang/gno/tm2/pkg/random" - "github.com/stretchr/testify/assert" -) - -func TestLoadOrGenNodeKey(t *testing.T) { - t.Parallel() - - filePath := filepath.Join(os.TempDir(), random.RandStr(12)+"_peer_id.json") - - nodeKey, err := LoadOrGenNodeKey(filePath) - assert.Nil(t, err) - - nodeKey2, err := LoadOrGenNodeKey(filePath) - assert.Nil(t, err) - - assert.Equal(t, nodeKey, nodeKey2) -} - -// ---------------------------------------------------------- - -func padBytes(bz []byte, targetBytes int) []byte { - return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...) -} - -func TestPoWTarget(t *testing.T) { - t.Parallel() - - targetBytes := 20 - cases := []struct { - difficulty uint - target []byte - }{ - {0, padBytes([]byte{}, targetBytes)}, - {1, padBytes([]byte{127}, targetBytes)}, - {8, padBytes([]byte{0}, targetBytes)}, - {9, padBytes([]byte{0, 127}, targetBytes)}, - {10, padBytes([]byte{0, 63}, targetBytes)}, - {16, padBytes([]byte{0, 0}, targetBytes)}, - {17, padBytes([]byte{0, 0, 127}, targetBytes)}, - } - - for _, c := range cases { - assert.Equal(t, MakePoWTarget(c.difficulty, 20*8), c.target) - } -} diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index 906c168c3a8..e5a01952831 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -1,68 +1,377 @@ package mock import ( + "fmt" + "io" + "log/slog" "net" + "testing" + "time" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" + "github.com/stretchr/testify/require" ) +type ( + flushStopDelegate func() + idDelegate func() types.ID + remoteIPDelegate func() net.IP + remoteAddrDelegate func() net.Addr + isOutboundDelegate func() bool + isPersistentDelegate func() bool + isPrivateDelegate func() bool + closeConnDelegate func() error + nodeInfoDelegate func() types.NodeInfo + statusDelegate func() conn.ConnectionStatus + socketAddrDelegate func() *types.NetAddress + sendDelegate func(byte, []byte) bool + trySendDelegate func(byte, []byte) bool + setDelegate func(string, any) + getDelegate func(string) any + stopDelegate func() error +) + +// GeneratePeers generates random peers +func GeneratePeers(t *testing.T, count int) []*Peer { + t.Helper() + + peers := make([]*Peer, count) + + for i := range count { + var ( + key = types.GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := types.NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + p := &Peer{ + IDFn: func() types.ID { + return key.ID() + }, + NodeInfoFn: func() types.NodeInfo { + return types.NodeInfo{ + PeerID: key.ID(), + } + }, + SocketAddrFn: func() *types.NetAddress { + return addr + }, + } + + p.BaseService = *service.NewBaseService( + slog.New(slog.NewTextHandler(io.Discard, nil)), + fmt.Sprintf("peer-%d", i), + p, + ) + + peers[i] = p + } + + return peers +} + type Peer struct { - *service.BaseService - ip net.IP - id p2p.ID - addr *p2p.NetAddress - kv map[string]interface{} - Outbound, Persistent bool -} - -// NewPeer creates and starts a new mock peer. If the ip -// is nil, random routable address is used. -func NewPeer(ip net.IP) *Peer { - var netAddr *p2p.NetAddress - if ip == nil { - _, netAddr = p2p.CreateRoutableAddr() - } else { - netAddr = p2p.NewNetAddressFromIPPort("", ip, 26656) - } - nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} - netAddr.ID = nodeKey.ID() - mp := &Peer{ - ip: ip, - id: nodeKey.ID(), - addr: netAddr, - kv: make(map[string]interface{}), - } - mp.BaseService = service.NewBaseService(nil, "MockPeer", mp) - mp.Start() - return mp -} - -func (mp *Peer) FlushStop() { mp.Stop() } -func (mp *Peer) TrySend(chID byte, msgBytes []byte) bool { return true } -func (mp *Peer) Send(chID byte, msgBytes []byte) bool { return true } -func (mp *Peer) NodeInfo() p2p.NodeInfo { - return p2p.NodeInfo{ - NetAddress: mp.addr, - } -} -func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } -func (mp *Peer) ID() p2p.ID { return mp.id } -func (mp *Peer) IsOutbound() bool { return mp.Outbound } -func (mp *Peer) IsPersistent() bool { return mp.Persistent } -func (mp *Peer) Get(key string) interface{} { - if value, ok := mp.kv[key]; ok { - return value + service.BaseService + + FlushStopFn flushStopDelegate + IDFn idDelegate + RemoteIPFn remoteIPDelegate + RemoteAddrFn remoteAddrDelegate + IsOutboundFn isOutboundDelegate + IsPersistentFn isPersistentDelegate + IsPrivateFn isPrivateDelegate + CloseConnFn closeConnDelegate + NodeInfoFn nodeInfoDelegate + StopFn stopDelegate + StatusFn statusDelegate + SocketAddrFn socketAddrDelegate + SendFn sendDelegate + TrySendFn trySendDelegate + SetFn setDelegate + GetFn getDelegate +} + +func (m *Peer) FlushStop() { + if m.FlushStopFn != nil { + m.FlushStopFn() + } +} + +func (m *Peer) ID() types.ID { + if m.IDFn != nil { + return m.IDFn() + } + + return "" +} + +func (m *Peer) RemoteIP() net.IP { + if m.RemoteIPFn != nil { + return m.RemoteIPFn() + } + + return nil +} + +func (m *Peer) RemoteAddr() net.Addr { + if m.RemoteAddrFn != nil { + return m.RemoteAddrFn() + } + + return nil +} + +func (m *Peer) Stop() error { + if m.StopFn != nil { + return m.StopFn() + } + + return nil +} + +func (m *Peer) IsOutbound() bool { + if m.IsOutboundFn != nil { + return m.IsOutboundFn() + } + + return false +} + +func (m *Peer) IsPersistent() bool { + if m.IsPersistentFn != nil { + return m.IsPersistentFn() + } + + return false +} + +func (m *Peer) IsPrivate() bool { + if m.IsPrivateFn != nil { + return m.IsPrivateFn() + } + + return false +} + +func (m *Peer) CloseConn() error { + if m.CloseConnFn != nil { + return m.CloseConnFn() + } + + return nil +} + +func (m *Peer) NodeInfo() types.NodeInfo { + if m.NodeInfoFn != nil { + return m.NodeInfoFn() + } + + return types.NodeInfo{} +} + +func (m *Peer) Status() conn.ConnectionStatus { + if m.StatusFn != nil { + return m.StatusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *Peer) SocketAddr() *types.NetAddress { + if m.SocketAddrFn != nil { + return m.SocketAddrFn() + } + + return nil +} + +func (m *Peer) Send(classifier byte, data []byte) bool { + if m.SendFn != nil { + return m.SendFn(classifier, data) + } + + return false +} + +func (m *Peer) TrySend(classifier byte, data []byte) bool { + if m.TrySendFn != nil { + return m.TrySendFn(classifier, data) + } + + return false +} + +func (m *Peer) Set(key string, data any) { + if m.SetFn != nil { + m.SetFn(key, data) + } +} + +func (m *Peer) Get(key string) any { + if m.GetFn != nil { + return m.GetFn(key) + } + + return nil +} + +type ( + readDelegate func([]byte) (int, error) + writeDelegate func([]byte) (int, error) + closeDelegate func() error + localAddrDelegate func() net.Addr + setDeadlineDelegate func(time.Time) error +) + +type Conn struct { + ReadFn readDelegate + WriteFn writeDelegate + CloseFn closeDelegate + LocalAddrFn localAddrDelegate + RemoteAddrFn remoteAddrDelegate + SetDeadlineFn setDeadlineDelegate + SetReadDeadlineFn setDeadlineDelegate + SetWriteDeadlineFn setDeadlineDelegate +} + +func (m *Conn) Read(b []byte) (int, error) { + if m.ReadFn != nil { + return m.ReadFn(b) + } + + return 0, nil +} + +func (m *Conn) Write(b []byte) (int, error) { + if m.WriteFn != nil { + return m.WriteFn(b) } + + return 0, nil +} + +func (m *Conn) Close() error { + if m.CloseFn != nil { + return m.CloseFn() + } + return nil } -func (mp *Peer) Set(key string, value interface{}) { - mp.kv[key] = value +func (m *Conn) LocalAddr() net.Addr { + if m.LocalAddrFn != nil { + return m.LocalAddrFn() + } + + return nil +} + +func (m *Conn) RemoteAddr() net.Addr { + if m.RemoteAddrFn != nil { + return m.RemoteAddrFn() + } + + return nil +} + +func (m *Conn) SetDeadline(t time.Time) error { + if m.SetDeadlineFn != nil { + return m.SetDeadlineFn(t) + } + + return nil +} + +func (m *Conn) SetReadDeadline(t time.Time) error { + if m.SetReadDeadlineFn != nil { + return m.SetReadDeadlineFn(t) + } + + return nil +} + +func (m *Conn) SetWriteDeadline(t time.Time) error { + if m.SetWriteDeadlineFn != nil { + return m.SetWriteDeadlineFn(t) + } + + return nil +} + +type ( + startDelegate func() error + stringDelegate func() string +) + +type MConn struct { + FlushFn flushStopDelegate + StartFn startDelegate + StopFn stopDelegate + SendFn sendDelegate + TrySendFn trySendDelegate + StatusFn statusDelegate + StringFn stringDelegate +} + +func (m *MConn) FlushStop() { + if m.FlushFn != nil { + m.FlushFn() + } +} + +func (m *MConn) Start() error { + if m.StartFn != nil { + return m.StartFn() + } + + return nil +} + +func (m *MConn) Stop() error { + if m.StopFn != nil { + return m.StopFn() + } + + return nil +} + +func (m *MConn) Send(ch byte, data []byte) bool { + if m.SendFn != nil { + return m.SendFn(ch, data) + } + + return false +} + +func (m *MConn) TrySend(ch byte, data []byte) bool { + if m.TrySendFn != nil { + return m.TrySendFn(ch, data) + } + + return false +} + +func (m *MConn) SetLogger(_ *slog.Logger) {} + +func (m *MConn) Status() conn.ConnectionStatus { + if m.StatusFn != nil { + return m.StatusFn() + } + + return conn.ConnectionStatus{} +} + +func (m *MConn) String() string { + if m.StringFn != nil { + return m.StringFn() + } + + return "" } -func (mp *Peer) RemoteIP() net.IP { return mp.ip } -func (mp *Peer) SocketAddr() *p2p.NetAddress { return mp.addr } -func (mp *Peer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } -func (mp *Peer) CloseConn() error { return nil } diff --git a/tm2/pkg/p2p/mock/reactor.go b/tm2/pkg/p2p/mock/reactor.go deleted file mode 100644 index fe123fdc0b2..00000000000 --- a/tm2/pkg/p2p/mock/reactor.go +++ /dev/null @@ -1,23 +0,0 @@ -package mock - -import ( - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" -) - -type Reactor struct { - p2p.BaseReactor -} - -func NewReactor() *Reactor { - r := &Reactor{} - r.BaseReactor = *p2p.NewBaseReactor("Reactor", r) - r.SetLogger(log.NewNoopLogger()) - return r -} - -func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { return []*conn.ChannelDescriptor{} } -func (r *Reactor) AddPeer(peer p2p.Peer) {} -func (r *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) {} -func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {} diff --git a/tm2/pkg/p2p/mock_test.go b/tm2/pkg/p2p/mock_test.go new file mode 100644 index 00000000000..5fd7f947b71 --- /dev/null +++ b/tm2/pkg/p2p/mock_test.go @@ -0,0 +1,404 @@ +package p2p + +import ( + "context" + "log/slog" + "net" + "time" + + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type ( + netAddressDelegate func() types.NetAddress + acceptDelegate func(context.Context, PeerBehavior) (PeerConn, error) + dialDelegate func(context.Context, types.NetAddress, PeerBehavior) (PeerConn, error) + removeDelegate func(PeerConn) +) + +type mockTransport struct { + netAddressFn netAddressDelegate + acceptFn acceptDelegate + dialFn dialDelegate + removeFn removeDelegate +} + +func (m *mockTransport) NetAddress() types.NetAddress { + if m.netAddressFn != nil { + return m.netAddressFn() + } + + return types.NetAddress{} +} + +func (m *mockTransport) Accept(ctx context.Context, behavior PeerBehavior) (PeerConn, error) { + if m.acceptFn != nil { + return m.acceptFn(ctx, behavior) + } + + return nil, nil +} + +func (m *mockTransport) Dial(ctx context.Context, address types.NetAddress, behavior PeerBehavior) (PeerConn, error) { + if m.dialFn != nil { + return m.dialFn(ctx, address, behavior) + } + + return nil, nil +} + +func (m *mockTransport) Remove(p PeerConn) { + if m.removeFn != nil { + m.removeFn(p) + } +} + +type ( + addDelegate func(PeerConn) + removePeerDelegate func(types.ID) bool + hasDelegate func(types.ID) bool + hasIPDelegate func(net.IP) bool + getDelegate func(types.ID) PeerConn + listDelegate func() []PeerConn + numInboundDelegate func() uint64 + numOutboundDelegate func() uint64 +) + +type mockSet struct { + addFn addDelegate + removeFn removePeerDelegate + hasFn hasDelegate + hasIPFn hasIPDelegate + listFn listDelegate + getFn getDelegate + numInboundFn numInboundDelegate + numOutboundFn numOutboundDelegate +} + +func (m *mockSet) Add(peer PeerConn) { + if m.addFn != nil { + m.addFn(peer) + } +} + +func (m *mockSet) Remove(key types.ID) bool { + if m.removeFn != nil { + m.removeFn(key) + } + + return false +} + +func (m *mockSet) Has(key types.ID) bool { + if m.hasFn != nil { + return m.hasFn(key) + } + + return false +} + +func (m *mockSet) Get(key types.ID) PeerConn { + if m.getFn != nil { + return m.getFn(key) + } + + return nil +} + +func (m *mockSet) List() []PeerConn { + if m.listFn != nil { + return m.listFn() + } + + return nil +} + +func (m *mockSet) NumInbound() uint64 { + if m.numInboundFn != nil { + return m.numInboundFn() + } + + return 0 +} + +func (m *mockSet) NumOutbound() uint64 { + if m.numOutboundFn != nil { + return m.numOutboundFn() + } + + return 0 +} + +type ( + listenerAcceptDelegate func() (net.Conn, error) + closeDelegate func() error + addrDelegate func() net.Addr +) + +type mockListener struct { + acceptFn listenerAcceptDelegate + closeFn closeDelegate + addrFn addrDelegate +} + +func (m *mockListener) Accept() (net.Conn, error) { + if m.acceptFn != nil { + return m.acceptFn() + } + + return nil, nil +} + +func (m *mockListener) Close() error { + if m.closeFn != nil { + return m.closeFn() + } + + return nil +} + +func (m *mockListener) Addr() net.Addr { + if m.addrFn != nil { + return m.addrFn() + } + + return nil +} + +type ( + readDelegate func([]byte) (int, error) + writeDelegate func([]byte) (int, error) + localAddrDelegate func() net.Addr + remoteAddrDelegate func() net.Addr + setDeadlineDelegate func(time.Time) error +) + +type mockConn struct { + readFn readDelegate + writeFn writeDelegate + closeFn closeDelegate + localAddrFn localAddrDelegate + remoteAddrFn remoteAddrDelegate + setDeadlineFn setDeadlineDelegate + setReadDeadlineFn setDeadlineDelegate + setWriteDeadlineFn setDeadlineDelegate +} + +func (m *mockConn) Read(buff []byte) (int, error) { + if m.readFn != nil { + return m.readFn(buff) + } + + return 0, nil +} + +func (m *mockConn) Write(buff []byte) (int, error) { + if m.writeFn != nil { + return m.writeFn(buff) + } + + return 0, nil +} + +func (m *mockConn) Close() error { + if m.closeFn != nil { + return m.closeFn() + } + + return nil +} + +func (m *mockConn) LocalAddr() net.Addr { + if m.localAddrFn != nil { + return m.localAddrFn() + } + + return nil +} + +func (m *mockConn) RemoteAddr() net.Addr { + if m.remoteAddrFn != nil { + return m.remoteAddrFn() + } + + return nil +} + +func (m *mockConn) SetDeadline(t time.Time) error { + if m.setDeadlineFn != nil { + return m.setDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetReadDeadline(t time.Time) error { + if m.setReadDeadlineFn != nil { + return m.setReadDeadlineFn(t) + } + + return nil +} + +func (m *mockConn) SetWriteDeadline(t time.Time) error { + if m.setWriteDeadlineFn != nil { + return m.setWriteDeadlineFn(t) + } + + return nil +} + +type ( + startDelegate func() error + onStartDelegate func() error + stopDelegate func() error + onStopDelegate func() + resetDelegate func() error + onResetDelegate func() error + isRunningDelegate func() bool + quitDelegate func() <-chan struct{} + stringDelegate func() string + setLoggerDelegate func(*slog.Logger) + setSwitchDelegate func(Switch) + getChannelsDelegate func() []*conn.ChannelDescriptor + initPeerDelegate func(PeerConn) + addPeerDelegate func(PeerConn) + removeSwitchPeerDelegate func(PeerConn, any) + receiveDelegate func(byte, PeerConn, []byte) +) + +type mockReactor struct { + startFn startDelegate + onStartFn onStartDelegate + stopFn stopDelegate + onStopFn onStopDelegate + resetFn resetDelegate + onResetFn onResetDelegate + isRunningFn isRunningDelegate + quitFn quitDelegate + stringFn stringDelegate + setLoggerFn setLoggerDelegate + setSwitchFn setSwitchDelegate + getChannelsFn getChannelsDelegate + initPeerFn initPeerDelegate + addPeerFn addPeerDelegate + removePeerFn removeSwitchPeerDelegate + receiveFn receiveDelegate +} + +func (m *mockReactor) Start() error { + if m.startFn != nil { + return m.startFn() + } + + return nil +} + +func (m *mockReactor) OnStart() error { + if m.onStartFn != nil { + return m.onStartFn() + } + + return nil +} + +func (m *mockReactor) Stop() error { + if m.stopFn != nil { + return m.stopFn() + } + + return nil +} + +func (m *mockReactor) OnStop() { + if m.onStopFn != nil { + m.onStopFn() + } +} + +func (m *mockReactor) Reset() error { + if m.resetFn != nil { + return m.resetFn() + } + + return nil +} + +func (m *mockReactor) OnReset() error { + if m.onResetFn != nil { + return m.onResetFn() + } + + return nil +} + +func (m *mockReactor) IsRunning() bool { + if m.isRunningFn != nil { + return m.isRunningFn() + } + + return false +} + +func (m *mockReactor) Quit() <-chan struct{} { + if m.quitFn != nil { + return m.quitFn() + } + + return nil +} + +func (m *mockReactor) String() string { + if m.stringFn != nil { + return m.stringFn() + } + + return "" +} + +func (m *mockReactor) SetLogger(logger *slog.Logger) { + if m.setLoggerFn != nil { + m.setLoggerFn(logger) + } +} + +func (m *mockReactor) SetSwitch(s Switch) { + if m.setSwitchFn != nil { + m.setSwitchFn(s) + } +} + +func (m *mockReactor) GetChannels() []*conn.ChannelDescriptor { + if m.getChannelsFn != nil { + return m.getChannelsFn() + } + + return nil +} + +func (m *mockReactor) InitPeer(peer PeerConn) PeerConn { + if m.initPeerFn != nil { + m.initPeerFn(peer) + } + + return nil +} + +func (m *mockReactor) AddPeer(peer PeerConn) { + if m.addPeerFn != nil { + m.addPeerFn(peer) + } +} + +func (m *mockReactor) RemovePeer(peer PeerConn, reason any) { + if m.removePeerFn != nil { + m.removePeerFn(peer, reason) + } +} + +func (m *mockReactor) Receive(chID byte, peer PeerConn, msgBytes []byte) { + if m.receiveFn != nil { + m.receiveFn(chID, peer, msgBytes) + } +} diff --git a/tm2/pkg/p2p/netaddress_test.go b/tm2/pkg/p2p/netaddress_test.go deleted file mode 100644 index 413d020c153..00000000000 --- a/tm2/pkg/p2p/netaddress_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package p2p - -import ( - "encoding/hex" - "net" - "testing" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAddress2ID(t *testing.T) { - t.Parallel() - - idbz, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - id := crypto.AddressFromBytes(idbz).ID() - assert.Equal(t, crypto.ID("g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6"), id) - - idbz, _ = hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdead0000") - id = crypto.AddressFromBytes(idbz).ID() - assert.Equal(t, crypto.ID("g1m6kmam774klwlh4dhmhaatd7al026qqqq9c22r"), id) -} - -func TestNewNetAddress(t *testing.T) { - t.Parallel() - - tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") - require.Nil(t, err) - - assert.Panics(t, func() { - NewNetAddress("", tcpAddr) - }) - - idbz, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - id := crypto.AddressFromBytes(idbz).ID() - // ^-- is "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6" - - addr := NewNetAddress(id, tcpAddr) - assert.Equal(t, "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", addr.String()) - - assert.NotPanics(t, func() { - NewNetAddress("", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) - }, "Calling NewNetAddress with UDPAddr should not panic in testing") -} - -func TestNewNetAddressFromString(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - addr string - expected string - correct bool - }{ - {"no node id and no protocol", "127.0.0.1:8080", "", false}, - {"no node id w/ tcp input", "tcp://127.0.0.1:8080", "", false}, - {"no node id w/ udp input", "udp://127.0.0.1:8080", "", false}, - - {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - {"malformed tcp input", "tcp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "", false}, - {"malformed udp input", "udp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "", false}, - - // {"127.0.0:8080", false}, - {"invalid host", "notahost", "", false}, - {"invalid port", "127.0.0.1:notapath", "", false}, - {"invalid host w/ port", "notahost:8080", "", false}, - {"just a port", "8082", "", false}, - {"non-existent port", "127.0.0:8080000", "", false}, - - {"too short nodeId", "deadbeef@127.0.0.1:8080", "", false}, - {"too short, not hex nodeId", "this-isnot-hex@127.0.0.1:8080", "", false}, - {"not bech32 nodeId", "xxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080", "", false}, - - {"too short nodeId w/tcp", "tcp://deadbeef@127.0.0.1:8080", "", false}, - {"too short notHex nodeId w/tcp", "tcp://this-isnot-hex@127.0.0.1:8080", "", false}, - {"not bech32 nodeId w/tcp", "tcp://xxxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080", "", false}, - {"correct nodeId w/tcp", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true}, - - {"no node id", "tcp://@127.0.0.1:8080", "", false}, - {"no node id or IP", "tcp://@", "", false}, - {"tcp no host, w/ port", "tcp://:26656", "", false}, - {"empty", "", "", false}, - {"node id delimiter 1", "@", "", false}, - {"node id delimiter 2", " @", "", false}, - {"node id delimiter 3", " @ ", "", false}, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - addr, err := NewNetAddressFromString(tc.addr) - if tc.correct { - if assert.Nil(t, err, tc.addr) { - assert.Equal(t, tc.expected, addr.String()) - } - } else { - assert.NotNil(t, err, tc.addr) - } - }) - } -} - -func TestNewNetAddressFromStrings(t *testing.T) { - t.Parallel() - - addrs, errs := NewNetAddressFromStrings([]string{ - "127.0.0.1:8080", - "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", - "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.2:8080", - }) - assert.Len(t, errs, 1) - assert.Equal(t, 2, len(addrs)) -} - -func TestNewNetAddressFromIPPort(t *testing.T) { - t.Parallel() - - addr := NewNetAddressFromIPPort("", net.ParseIP("127.0.0.1"), 8080) - assert.Equal(t, "127.0.0.1:8080", addr.String()) -} - -func TestNetAddressProperties(t *testing.T) { - t.Parallel() - - // TODO add more test cases - testCases := []struct { - addr string - valid bool - local bool - routable bool - }{ - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", true, true, false}, - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@ya.ru:80", true, false, true}, - } - - for _, tc := range testCases { - addr, err := NewNetAddressFromString(tc.addr) - require.Nil(t, err) - - err = addr.Validate() - if tc.valid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - assert.Equal(t, tc.local, addr.Local()) - assert.Equal(t, tc.routable, addr.Routable()) - } -} - -func TestNetAddressReachabilityTo(t *testing.T) { - t.Parallel() - - // TODO add more test cases - testCases := []struct { - addr string - other string - reachability int - }{ - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8081", 0}, - {"g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@ya.ru:80", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", 1}, - } - - for _, tc := range testCases { - addr, err := NewNetAddressFromString(tc.addr) - require.Nil(t, err) - - other, err := NewNetAddressFromString(tc.other) - require.Nil(t, err) - - assert.Equal(t, tc.reachability, addr.ReachabilityTo(other)) - } -} diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go deleted file mode 100644 index 48ba8f7776b..00000000000 --- a/tm2/pkg/p2p/node_info.go +++ /dev/null @@ -1,156 +0,0 @@ -package p2p - -import ( - "fmt" - - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" - "github.com/gnolang/gno/tm2/pkg/strings" - "github.com/gnolang/gno/tm2/pkg/versionset" -) - -const ( - maxNodeInfoSize = 10240 // 10KB - maxNumChannels = 16 // plenty of room for upgrades, for now -) - -// Max size of the NodeInfo struct -func MaxNodeInfoSize() int { - return maxNodeInfoSize -} - -// ------------------------------------------------------------- - -// NodeInfo is the basic node information exchanged -// between two peers during the Tendermint P2P handshake. -type NodeInfo struct { - // Set of protocol versions - VersionSet versionset.VersionSet `json:"version_set"` - - // Authenticate - NetAddress *NetAddress `json:"net_address"` - - // Check compatibility. - // Channels are HexBytes so easier to read as JSON - Network string `json:"network"` // network/chain ID - Software string `json:"software"` // name of immediate software - Version string `json:"version"` // software major.minor.revision - Channels []byte `json:"channels"` // channels this node knows about - - // ASCIIText fields - Moniker string `json:"moniker"` // arbitrary moniker - Other NodeInfoOther `json:"other"` // other application specific data -} - -// NodeInfoOther is the misc. application specific data -type NodeInfoOther struct { - TxIndex string `json:"tx_index"` - RPCAddress string `json:"rpc_address"` -} - -// Validate checks the self-reported NodeInfo is safe. -// It returns an error if there -// are too many Channels, if there are any duplicate Channels, -// if the ListenAddr is malformed, or if the ListenAddr is a host name -// that can not be resolved to some IP. -// TODO: constraints for Moniker/Other? Or is that for the UI ? -// JAE: It needs to be done on the client, but to prevent ambiguous -// unicode characters, maybe it's worth sanitizing it here. -// In the future we might want to validate these, once we have a -// name-resolution system up. -// International clients could then use punycode (or we could use -// url-encoding), and we just need to be careful with how we handle that in our -// clients. (e.g. off by default). -func (info NodeInfo) Validate() error { - // ID is already validated. TODO validate - - // Validate ListenAddr. - if info.NetAddress == nil { - return fmt.Errorf("info.NetAddress cannot be nil") - } - if err := info.NetAddress.ValidateLocal(); err != nil { - return err - } - - // Network is validated in CompatibleWith. - - // Validate Version - if len(info.Version) > 0 && - (!strings.IsASCIIText(info.Version) || strings.ASCIITrim(info.Version) == "") { - return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version) - } - - // Validate Channels - ensure max and check for duplicates. - if len(info.Channels) > maxNumChannels { - return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) - } - channels := make(map[byte]struct{}) - for _, ch := range info.Channels { - _, ok := channels[ch] - if ok { - return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) - } - channels[ch] = struct{}{} - } - - // Validate Moniker. - if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" { - return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker) - } - - // Validate Other. - other := info.Other - txIndex := other.TxIndex - switch txIndex { - case "", eventstore.StatusOn, eventstore.StatusOff: - default: - return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) - } - // XXX: Should we be more strict about address formats? - rpcAddr := other.RPCAddress - if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") { - return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr) - } - - return nil -} - -func (info NodeInfo) ID() ID { - return info.NetAddress.ID -} - -// CompatibleWith checks if two NodeInfo are compatible with eachother. -// CONTRACT: two nodes are compatible if the Block version and network match -// and they have at least one channel in common. -func (info NodeInfo) CompatibleWith(other NodeInfo) error { - // check protocol versions - _, err := info.VersionSet.CompatibleWith(other.VersionSet) - if err != nil { - return err - } - - // nodes must be on the same network - if info.Network != other.Network { - return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network) - } - - // if we have no channels, we're just testing - if len(info.Channels) == 0 { - return nil - } - - // for each of our channels, check if they have it - found := false -OUTER_LOOP: - for _, ch1 := range info.Channels { - for _, ch2 := range other.Channels { - if ch1 == ch2 { - found = true - break OUTER_LOOP // only need one - } - } - } - if !found { - return fmt.Errorf("Peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels) - } - return nil -} diff --git a/tm2/pkg/p2p/node_info_test.go b/tm2/pkg/p2p/node_info_test.go deleted file mode 100644 index 58f1dab8854..00000000000 --- a/tm2/pkg/p2p/node_info_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package p2p - -import ( - "fmt" - "net" - "testing" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/versionset" - "github.com/stretchr/testify/assert" -) - -func TestNodeInfoValidate(t *testing.T) { - t.Parallel() - - // empty fails - ni := NodeInfo{} - assert.Error(t, ni.Validate()) - - channels := make([]byte, maxNumChannels) - for i := 0; i < maxNumChannels; i++ { - channels[i] = byte(i) - } - dupChannels := make([]byte, 5) - copy(dupChannels, channels[:5]) - dupChannels = append(dupChannels, testCh) - - nonAscii := "¢§µ" - emptyTab := fmt.Sprintf("\t") - emptySpace := fmt.Sprintf(" ") - - testCases := []struct { - testName string - malleateNodeInfo func(*NodeInfo) - expectErr bool - }{ - {"Too Many Channels", func(ni *NodeInfo) { ni.Channels = append(channels, byte(maxNumChannels)) }, true}, //nolint: gocritic - {"Duplicate Channel", func(ni *NodeInfo) { ni.Channels = dupChannels }, true}, - {"Good Channels", func(ni *NodeInfo) { ni.Channels = ni.Channels[:5] }, false}, - - {"Nil NetAddress", func(ni *NodeInfo) { ni.NetAddress = nil }, true}, - {"Zero NetAddress ID", func(ni *NodeInfo) { ni.NetAddress.ID = "" }, true}, - {"Invalid NetAddress IP", func(ni *NodeInfo) { ni.NetAddress.IP = net.IP([]byte{0x00}) }, true}, - - {"Non-ASCII Version", func(ni *NodeInfo) { ni.Version = nonAscii }, true}, - {"Empty tab Version", func(ni *NodeInfo) { ni.Version = emptyTab }, true}, - {"Empty space Version", func(ni *NodeInfo) { ni.Version = emptySpace }, true}, - {"Empty Version", func(ni *NodeInfo) { ni.Version = "" }, false}, - - {"Non-ASCII Moniker", func(ni *NodeInfo) { ni.Moniker = nonAscii }, true}, - {"Empty tab Moniker", func(ni *NodeInfo) { ni.Moniker = emptyTab }, true}, - {"Empty space Moniker", func(ni *NodeInfo) { ni.Moniker = emptySpace }, true}, - {"Empty Moniker", func(ni *NodeInfo) { ni.Moniker = "" }, true}, - {"Good Moniker", func(ni *NodeInfo) { ni.Moniker = "hey its me" }, false}, - - {"Non-ASCII TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = nonAscii }, true}, - {"Empty tab TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = emptyTab }, true}, - {"Empty space TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = emptySpace }, true}, - {"Empty TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = "" }, false}, - {"Off TxIndex", func(ni *NodeInfo) { ni.Other.TxIndex = "off" }, false}, - - {"Non-ASCII RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = nonAscii }, true}, - {"Empty tab RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptyTab }, true}, - {"Empty space RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = emptySpace }, true}, - {"Empty RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "" }, false}, - {"Good RPCAddress", func(ni *NodeInfo) { ni.Other.RPCAddress = "0.0.0.0:26657" }, false}, - } - - nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} - name := "testing" - - // test case passes - ni = testNodeInfo(nodeKey.ID(), name) - ni.Channels = channels - assert.NoError(t, ni.Validate()) - - for _, tc := range testCases { - ni := testNodeInfo(nodeKey.ID(), name) - ni.Channels = channels - tc.malleateNodeInfo(&ni) - err := ni.Validate() - if tc.expectErr { - assert.Error(t, err, tc.testName) - } else { - assert.NoError(t, err, tc.testName) - } - } -} - -func TestNodeInfoCompatible(t *testing.T) { - t.Parallel() - - nodeKey1 := NodeKey{PrivKey: ed25519.GenPrivKey()} - nodeKey2 := NodeKey{PrivKey: ed25519.GenPrivKey()} - name := "testing" - - var newTestChannel byte = 0x2 - - // test NodeInfo is compatible - ni1 := testNodeInfo(nodeKey1.ID(), name) - ni2 := testNodeInfo(nodeKey2.ID(), name) - assert.NoError(t, ni1.CompatibleWith(ni2)) - - // add another channel; still compatible - ni2.Channels = []byte{newTestChannel, testCh} - assert.NoError(t, ni1.CompatibleWith(ni2)) - - // wrong NodeInfo type is not compatible - _, netAddr := CreateRoutableAddr() - ni3 := NodeInfo{NetAddress: netAddr} - assert.Error(t, ni1.CompatibleWith(ni3)) - - testCases := []struct { - testName string - malleateNodeInfo func(*NodeInfo) - }{ - {"Bad block version", func(ni *NodeInfo) { - ni.VersionSet.Set(versionset.VersionInfo{Name: "Block", Version: "badversion"}) - }}, - {"Wrong block version", func(ni *NodeInfo) { - ni.VersionSet.Set(versionset.VersionInfo{Name: "Block", Version: "v999.999.999-wrong"}) - }}, - {"Wrong network", func(ni *NodeInfo) { ni.Network += "-wrong" }}, - {"No common channels", func(ni *NodeInfo) { ni.Channels = []byte{newTestChannel} }}, - } - - for i, tc := range testCases { - t.Logf("case #%v", i) - ni := testNodeInfo(nodeKey2.ID(), name) - tc.malleateNodeInfo(&ni) - fmt.Printf("case #%v\n", i) - assert.Error(t, ni1.CompatibleWith(ni)) - } -} diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index ef2ddcf2c25..135bf4b250c 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -6,169 +6,132 @@ import ( "net" "github.com/gnolang/gno/tm2/pkg/cmap" - connm "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" ) -// Peer is an interface representing a peer connected on a reactor. -type Peer interface { - service.Service - FlushStop() - - ID() ID // peer's cryptographic ID - RemoteIP() net.IP // remote IP of the connection - RemoteAddr() net.Addr // remote address of the connection - - IsOutbound() bool // did we dial the peer - IsPersistent() bool // do we redial this peer when we disconnect - - CloseConn() error // close original connection - - NodeInfo() NodeInfo // peer's info - Status() connm.ConnectionStatus - SocketAddr() *NetAddress // actual address of the socket - - Send(byte, []byte) bool - TrySend(byte, []byte) bool - - Set(string, interface{}) - Get(string) interface{} +type ConnConfig struct { + MConfig conn.MConnConfig + ReactorsByCh map[byte]Reactor + ChDescs []*conn.ChannelDescriptor + OnPeerError func(PeerConn, error) } -// ---------------------------------------------------------- - -// peerConn contains the raw connection and its config. -type peerConn struct { - outbound bool - persistent bool - conn net.Conn // source connection - - socketAddr *NetAddress - - // cached RemoteIP() - ip net.IP +// ConnInfo wraps the remote peer connection +type ConnInfo struct { + Outbound bool // flag indicating if the connection is dialed + Persistent bool // flag indicating if the connection is persistent + Private bool // flag indicating if the peer is private (not shared) + Conn net.Conn // the source connection + RemoteIP net.IP // the remote IP of the peer + SocketAddr *types.NetAddress } -func newPeerConn( - outbound, persistent bool, - conn net.Conn, - socketAddr *NetAddress, -) peerConn { - return peerConn{ - outbound: outbound, - persistent: persistent, - conn: conn, - socketAddr: socketAddr, - } -} - -// ID only exists for SecretConnection. -// NOTE: Will panic if conn is not *SecretConnection. -func (pc peerConn) ID() ID { - return (pc.conn.(*connm.SecretConnection).RemotePubKey()).Address().ID() -} - -// Return the IP from the connection RemoteAddr -func (pc peerConn) RemoteIP() net.IP { - if pc.ip != nil { - return pc.ip - } - - host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) - if err != nil { - panic(err) - } - - ips, err := net.LookupIP(host) - if err != nil { - panic(err) - } - - pc.ip = ips[0] - - return pc.ip +type multiplexConn interface { + FlushStop() + Start() error + Stop() error + Send(byte, []byte) bool + TrySend(byte, []byte) bool + SetLogger(*slog.Logger) + Status() conn.ConnectionStatus + String() string } -// peer implements Peer. -// +// peer is a wrapper for a remote peer // Before using a peer, you will need to perform a handshake on connection. type peer struct { service.BaseService - // raw peerConn and the multiplex connection - peerConn - mconn *connm.MConnection - - // peer's node info and the channel it knows about - // channels = nodeInfo.Channels - // cached to avoid copying nodeInfo in hasChannel - nodeInfo NodeInfo - channels []byte + connInfo *ConnInfo // Metadata about the connection + nodeInfo types.NodeInfo // Information about the peer's node + mConn multiplexConn // The multiplexed connection - // User data - Data *cmap.CMap + data *cmap.CMap // Arbitrary data store associated with the peer } -type PeerOption func(*peer) - +// newPeer creates an uninitialized peer instance func newPeer( - pc peerConn, - mConfig connm.MConnConfig, - nodeInfo NodeInfo, - reactorsByCh map[byte]Reactor, - chDescs []*connm.ChannelDescriptor, - onPeerError func(Peer, interface{}), - options ...PeerOption, -) *peer { + connInfo *ConnInfo, + nodeInfo types.NodeInfo, + mConfig *ConnConfig, +) PeerConn { p := &peer{ - peerConn: pc, + connInfo: connInfo, nodeInfo: nodeInfo, - channels: nodeInfo.Channels, // TODO - Data: cmap.NewCMap(), + data: cmap.NewCMap(), } - p.mconn = createMConnection( - pc.conn, - p, - reactorsByCh, - chDescs, - onPeerError, + p.mConn = p.createMConnection( + connInfo.Conn, mConfig, ) + p.BaseService = *service.NewBaseService(nil, "Peer", p) - for _, option := range options { - option(p) - } return p } -// String representation. +// RemoteIP returns the IP from the remote connection +func (p *peer) RemoteIP() net.IP { + return p.connInfo.RemoteIP +} + +// RemoteAddr returns the address from the remote connection +func (p *peer) RemoteAddr() net.Addr { + return p.connInfo.Conn.RemoteAddr() +} + func (p *peer) String() string { - if p.outbound { - return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) + if p.connInfo.Outbound { + return fmt.Sprintf("Peer{%s %s out}", p.mConn, p.ID()) } - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) + return fmt.Sprintf("Peer{%s %s in}", p.mConn, p.ID()) } -// --------------------------------------------------- -// Implements service.Service +// IsOutbound returns true if the connection is outbound, false otherwise. +func (p *peer) IsOutbound() bool { + return p.connInfo.Outbound +} + +// IsPersistent returns true if the peer is persistent, false otherwise. +func (p *peer) IsPersistent() bool { + return p.connInfo.Persistent +} + +// IsPrivate returns true if the peer is private, false otherwise. +func (p *peer) IsPrivate() bool { + return p.connInfo.Private +} + +// SocketAddr returns the address of the socket. +// For outbound peers, it's the address dialed (after DNS resolution). +// For inbound peers, it's the address returned by the underlying connection +// (not what's reported in the peer's NodeInfo). +func (p *peer) SocketAddr() *types.NetAddress { + return p.connInfo.SocketAddr +} + +// CloseConn closes original connection. +// Used for cleaning up in cases where the peer had not been started at all. +func (p *peer) CloseConn() error { + return p.connInfo.Conn.Close() +} -// SetLogger implements BaseService. func (p *peer) SetLogger(l *slog.Logger) { p.Logger = l - p.mconn.SetLogger(l) + p.mConn.SetLogger(l) } -// OnStart implements BaseService. func (p *peer) OnStart() error { if err := p.BaseService.OnStart(); err != nil { - return err + return fmt.Errorf("unable to start base service, %w", err) } - if err := p.mconn.Start(); err != nil { - return err + if err := p.mConn.Start(); err != nil { + return fmt.Errorf("unable to start multiplex connection, %w", err) } return nil @@ -179,164 +142,105 @@ func (p *peer) OnStart() error { // NOTE: it is not safe to call this method more than once. func (p *peer) FlushStop() { p.BaseService.OnStop() - p.mconn.FlushStop() // stop everything and close the conn + p.mConn.FlushStop() // stop everything and close the conn } // OnStop implements BaseService. func (p *peer) OnStop() { p.BaseService.OnStop() - p.mconn.Stop() // stop everything and close the conn -} -// --------------------------------------------------- -// Implements Peer - -// ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() ID { - return p.nodeInfo.NetAddress.ID -} - -// IsOutbound returns true if the connection is outbound, false otherwise. -func (p *peer) IsOutbound() bool { - return p.peerConn.outbound + if err := p.mConn.Stop(); err != nil { + p.Logger.Error( + "unable to gracefully close mConn", + "err", + err, + ) + } } -// IsPersistent returns true if the peer is persistent, false otherwise. -func (p *peer) IsPersistent() bool { - return p.peerConn.persistent +// ID returns the peer's ID - the hex encoded hash of its pubkey. +func (p *peer) ID() types.ID { + return p.nodeInfo.PeerID } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() NodeInfo { +func (p *peer) NodeInfo() types.NodeInfo { return p.nodeInfo } -// SocketAddr returns the address of the socket. -// For outbound peers, it's the address dialed (after DNS resolution). -// For inbound peers, it's the address returned by the underlying connection -// (not what's reported in the peer's NodeInfo). -func (p *peer) SocketAddr() *NetAddress { - return p.peerConn.socketAddr -} - // Status returns the peer's ConnectionStatus. -func (p *peer) Status() connm.ConnectionStatus { - return p.mconn.Status() +func (p *peer) Status() conn.ConnectionStatus { + return p.mConn.Status() } // Send msg bytes to the channel identified by chID byte. Returns false if the // send queue is full after timeout, specified by MConnection. func (p *peer) Send(chID byte, msgBytes []byte) bool { - if !p.IsRunning() { - // see Switch#Broadcast, where we fetch the list of peers and loop over + if !p.IsRunning() || !p.hasChannel(chID) { + // see MultiplexSwitch#Broadcast, where we fetch the list of peers and loop over // them - while we're looping, one peer may be removed and stopped. return false - } else if !p.hasChannel(chID) { - return false } - res := p.mconn.Send(chID, msgBytes) - return res + + return p.mConn.Send(chID, msgBytes) } // TrySend msg bytes to the channel identified by chID byte. Immediately returns // false if the send queue is full. func (p *peer) TrySend(chID byte, msgBytes []byte) bool { - if !p.IsRunning() { - return false - } else if !p.hasChannel(chID) { + if !p.IsRunning() || !p.hasChannel(chID) { return false } - res := p.mconn.TrySend(chID, msgBytes) - return res + + return p.mConn.TrySend(chID, msgBytes) } // Get the data for a given key. -func (p *peer) Get(key string) interface{} { - return p.Data.Get(key) +func (p *peer) Get(key string) any { + return p.data.Get(key) } // Set sets the data for the given key. -func (p *peer) Set(key string, data interface{}) { - p.Data.Set(key, data) +func (p *peer) Set(key string, data any) { + p.data.Set(key, data) } // hasChannel returns true if the peer reported // knowing about the given chID. func (p *peer) hasChannel(chID byte) bool { - for _, ch := range p.channels { + for _, ch := range p.nodeInfo.Channels { if ch == chID { return true } } - // NOTE: probably will want to remove this - // but could be helpful while the feature is new - p.Logger.Debug( - "Unknown channel for peer", - "channel", - chID, - "channels", - p.channels, - ) - return false -} - -// CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all. -func (p *peer) CloseConn() error { - return p.peerConn.conn.Close() -} - -// --------------------------------------------------- -// methods only used for testing -// TODO: can we remove these? - -// CloseConn closes the underlying connection -func (pc *peerConn) CloseConn() { - pc.conn.Close() //nolint: errcheck -} -// RemoteAddr returns peer's remote network address. -func (p *peer) RemoteAddr() net.Addr { - return p.peerConn.conn.RemoteAddr() -} - -// CanSend returns true if the send queue is not full, false otherwise. -func (p *peer) CanSend(chID byte) bool { - if !p.IsRunning() { - return false - } - return p.mconn.CanSend(chID) + return false } -// ------------------------------------------------------------------ -// helper funcs - -func createMConnection( - conn net.Conn, - p *peer, - reactorsByCh map[byte]Reactor, - chDescs []*connm.ChannelDescriptor, - onPeerError func(Peer, interface{}), - config connm.MConnConfig, -) *connm.MConnection { +func (p *peer) createMConnection( + c net.Conn, + config *ConnConfig, +) *conn.MConnection { onReceive := func(chID byte, msgBytes []byte) { - reactor := reactorsByCh[chID] + reactor := config.ReactorsByCh[chID] if reactor == nil { // Note that its ok to panic here as it's caught in the connm._recover, // which does onPeerError. panic(fmt.Sprintf("Unknown channel %X", chID)) } + reactor.Receive(chID, p, msgBytes) } - onError := func(r interface{}) { - onPeerError(p, r) + onError := func(r error) { + config.OnPeerError(p, r) } - return connm.NewMConnectionWithConfig( - conn, - chDescs, + return conn.NewMConnectionWithConfig( + c, + config.ChDescs, onReceive, onError, - config, + config.MConfig, ) } diff --git a/tm2/pkg/p2p/peer_set.go b/tm2/pkg/p2p/peer_set.go deleted file mode 100644 index 396ba56da11..00000000000 --- a/tm2/pkg/p2p/peer_set.go +++ /dev/null @@ -1,147 +0,0 @@ -package p2p - -import ( - "net" - "sync" -) - -// IPeerSet has a (immutable) subset of the methods of PeerSet. -type IPeerSet interface { - Has(key ID) bool - HasIP(ip net.IP) bool - Get(key ID) Peer - List() []Peer - Size() int -} - -// ----------------------------------------------------------------------------- - -// PeerSet is a special structure for keeping a table of peers. -// Iteration over the peers is super fast and thread-safe. -type PeerSet struct { - mtx sync.Mutex - lookup map[ID]*peerSetItem - list []Peer -} - -type peerSetItem struct { - peer Peer - index int -} - -// NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. -func NewPeerSet() *PeerSet { - return &PeerSet{ - lookup: make(map[ID]*peerSetItem), - list: make([]Peer, 0, 256), - } -} - -// Add adds the peer to the PeerSet. -// It returns an error carrying the reason, if the peer is already present. -func (ps *PeerSet) Add(peer Peer) error { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - if ps.lookup[peer.ID()] != nil { - return SwitchDuplicatePeerIDError{peer.ID()} - } - - index := len(ps.list) - // Appending is safe even with other goroutines - // iterating over the ps.list slice. - ps.list = append(ps.list, peer) - ps.lookup[peer.ID()] = &peerSetItem{peer, index} - return nil -} - -// Has returns true if the set contains the peer referred to by this -// peerKey, otherwise false. -func (ps *PeerSet) Has(peerKey ID) bool { - ps.mtx.Lock() - _, ok := ps.lookup[peerKey] - ps.mtx.Unlock() - return ok -} - -// HasIP returns true if the set contains the peer referred to by this IP -// address, otherwise false. -func (ps *PeerSet) HasIP(peerIP net.IP) bool { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - return ps.hasIP(peerIP) -} - -// hasIP does not acquire a lock so it can be used in public methods which -// already lock. -func (ps *PeerSet) hasIP(peerIP net.IP) bool { - for _, item := range ps.lookup { - if item.peer.RemoteIP().Equal(peerIP) { - return true - } - } - - return false -} - -// Get looks up a peer by the provided peerKey. Returns nil if peer is not -// found. -func (ps *PeerSet) Get(peerKey ID) Peer { - ps.mtx.Lock() - defer ps.mtx.Unlock() - item, ok := ps.lookup[peerKey] - if ok { - return item.peer - } - return nil -} - -// Remove discards peer by its Key, if the peer was previously memoized. -// Returns true if the peer was removed, and false if it was not found. -// in the set. -func (ps *PeerSet) Remove(peer Peer) bool { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - item := ps.lookup[peer.ID()] - if item == nil { - return false - } - - index := item.index - // Create a new copy of the list but with one less item. - // (we must copy because we'll be mutating the list). - newList := make([]Peer, len(ps.list)-1) - copy(newList, ps.list) - // If it's the last peer, that's an easy special case. - if index == len(ps.list)-1 { - ps.list = newList - delete(ps.lookup, peer.ID()) - return true - } - - // Replace the popped item with the last item in the old list. - lastPeer := ps.list[len(ps.list)-1] - lastPeerKey := lastPeer.ID() - lastPeerItem := ps.lookup[lastPeerKey] - newList[index] = lastPeer - lastPeerItem.index = index - ps.list = newList - delete(ps.lookup, peer.ID()) - return true -} - -// Size returns the number of unique items in the peerSet. -func (ps *PeerSet) Size() int { - ps.mtx.Lock() - defer ps.mtx.Unlock() - return len(ps.list) -} - -// List returns the threadsafe list of peers. -func (ps *PeerSet) List() []Peer { - ps.mtx.Lock() - defer ps.mtx.Unlock() - return ps.list -} diff --git a/tm2/pkg/p2p/peer_set_test.go b/tm2/pkg/p2p/peer_set_test.go deleted file mode 100644 index 7aca84d59b0..00000000000 --- a/tm2/pkg/p2p/peer_set_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package p2p - -import ( - "net" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/service" -) - -// mockPeer for testing the PeerSet -type mockPeer struct { - service.BaseService - ip net.IP - id ID -} - -func (mp *mockPeer) FlushStop() { mp.Stop() } -func (mp *mockPeer) TrySend(chID byte, msgBytes []byte) bool { return true } -func (mp *mockPeer) Send(chID byte, msgBytes []byte) bool { return true } -func (mp *mockPeer) NodeInfo() NodeInfo { return NodeInfo{} } -func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } -func (mp *mockPeer) ID() ID { return mp.id } -func (mp *mockPeer) IsOutbound() bool { return false } -func (mp *mockPeer) IsPersistent() bool { return true } -func (mp *mockPeer) Get(s string) interface{} { return s } -func (mp *mockPeer) Set(string, interface{}) {} -func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } -func (mp *mockPeer) SocketAddr() *NetAddress { return nil } -func (mp *mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } -func (mp *mockPeer) CloseConn() error { return nil } - -// Returns a mock peer -func newMockPeer(ip net.IP) *mockPeer { - if ip == nil { - ip = net.IP{127, 0, 0, 1} - } - nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} - return &mockPeer{ - ip: ip, - id: nodeKey.ID(), - } -} - -func TestPeerSetAddRemoveOne(t *testing.T) { - t.Parallel() - - peerSet := NewPeerSet() - - var peerList []Peer - for i := 0; i < 5; i++ { - p := newMockPeer(net.IP{127, 0, 0, byte(i)}) - if err := peerSet.Add(p); err != nil { - t.Error(err) - } - peerList = append(peerList, p) - } - - n := len(peerList) - // 1. Test removing from the front - for i, peerAtFront := range peerList { - removed := peerSet.Remove(peerAtFront) - assert.True(t, removed) - wantSize := n - i - 1 - for j := 0; j < 2; j++ { - assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j) - assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j) - // Test the route of removing the now non-existent element - removed := peerSet.Remove(peerAtFront) - assert.False(t, removed) - } - } - - // 2. Next we are testing removing the peer at the end - // a) Replenish the peerSet - for _, peer := range peerList { - if err := peerSet.Add(peer); err != nil { - t.Error(err) - } - } - - // b) In reverse, remove each element - for i := n - 1; i >= 0; i-- { - peerAtEnd := peerList[i] - removed := peerSet.Remove(peerAtEnd) - assert.True(t, removed) - assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i) - assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i) - } -} - -func TestPeerSetAddRemoveMany(t *testing.T) { - t.Parallel() - peerSet := NewPeerSet() - - peers := []Peer{} - N := 100 - for i := 0; i < N; i++ { - peer := newMockPeer(net.IP{127, 0, 0, byte(i)}) - if err := peerSet.Add(peer); err != nil { - t.Errorf("Failed to add new peer") - } - if peerSet.Size() != i+1 { - t.Errorf("Failed to add new peer and increment size") - } - peers = append(peers, peer) - } - - for i, peer := range peers { - removed := peerSet.Remove(peer) - assert.True(t, removed) - if peerSet.Has(peer.ID()) { - t.Errorf("Failed to remove peer") - } - if peerSet.Size() != len(peers)-i-1 { - t.Errorf("Failed to remove peer and decrement size") - } - } -} - -func TestPeerSetAddDuplicate(t *testing.T) { - t.Parallel() - peerSet := NewPeerSet() - peer := newMockPeer(nil) - - n := 20 - errsChan := make(chan error) - // Add the same asynchronously to test the - // concurrent guarantees of our APIs, and - // our expectation in the end is that only - // one addition succeeded, but the rest are - // instances of ErrSwitchDuplicatePeer. - for i := 0; i < n; i++ { - go func() { - errsChan <- peerSet.Add(peer) - }() - } - - // Now collect and tally the results - errsTally := make(map[string]int) - for i := 0; i < n; i++ { - err := <-errsChan - - switch err.(type) { - case SwitchDuplicatePeerIDError: - errsTally["duplicateID"]++ - default: - errsTally["other"]++ - } - } - - // Our next procedure is to ensure that only one addition - // succeeded and that the rest are each ErrSwitchDuplicatePeer. - wantErrCount, gotErrCount := n-1, errsTally["duplicateID"] - assert.Equal(t, wantErrCount, gotErrCount, "invalid ErrSwitchDuplicatePeer count") - - wantNilErrCount, gotNilErrCount := 1, errsTally["other"] - assert.Equal(t, wantNilErrCount, gotNilErrCount, "invalid nil errCount") -} - -func TestPeerSetGet(t *testing.T) { - t.Parallel() - - var ( - peerSet = NewPeerSet() - peer = newMockPeer(nil) - ) - - assert.Nil(t, peerSet.Get(peer.ID()), "expecting a nil lookup, before .Add") - - if err := peerSet.Add(peer); err != nil { - t.Fatalf("Failed to add new peer: %v", err) - } - - var wg sync.WaitGroup - for i := 0; i < 10; i++ { - // Add them asynchronously to test the - // concurrent guarantees of our APIs. - wg.Add(1) - go func(i int) { - defer wg.Done() - have, want := peerSet.Get(peer.ID()), peer - assert.Equal(t, have, want, "%d: have %v, want %v", i, have, want) - }(i) - } - wg.Wait() -} diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 28217c4486e..a74ea9e96a4 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -1,233 +1,630 @@ package p2p import ( + "errors" "fmt" - golog "log" + "io" + "log/slog" "net" "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/cmap" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/service" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestPeerBasic(t *testing.T) { +func TestPeer_Properties(t *testing.T) { t.Parallel() - assert, require := assert.New(t), require.New(t) + t.Run("connection info", func(t *testing.T) { + t.Parallel() - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() + t.Run("remote IP", func(t *testing.T) { + t.Parallel() - p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), cfg, conn.DefaultMConnConfig()) - require.Nil(err) + var ( + info = &ConnInfo{ + RemoteIP: net.IP{127, 0, 0, 1}, + } - err = p.Start() - require.Nil(err) - defer p.Stop() + p = &peer{ + connInfo: info, + } + ) - assert.True(p.IsRunning()) - assert.True(p.IsOutbound()) - assert.False(p.IsPersistent()) - p.persistent = true - assert.True(p.IsPersistent()) - assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String()) - assert.Equal(rp.ID(), p.ID()) -} + assert.Equal(t, info.RemoteIP, p.RemoteIP()) + }) -func TestPeerSend(t *testing.T) { - t.Parallel() + t.Run("remote address", func(t *testing.T) { + t.Parallel() - assert, require := assert.New(t), require.New(t) + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) - config := cfg + var ( + info = &ConnInfo{ + Conn: &mock.Conn{ + RemoteAddrFn: func() net.Addr { + return tcpAddr + }, + }, + } - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: config} - rp.Start() - defer rp.Stop() + p = &peer{ + connInfo: info, + } + ) - p, err := createOutboundPeerAndPerformHandshake(t, rp.Addr(), config, conn.DefaultMConnConfig()) - require.Nil(err) + assert.Equal(t, tcpAddr.String(), p.RemoteAddr().String()) + }) - err = p.Start() - require.Nil(err) + t.Run("socket address", func(t *testing.T) { + t.Parallel() - defer p.Stop() + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) - assert.True(p.CanSend(testCh)) - assert.True(p.Send(testCh, []byte("Asylum"))) -} + netAddr, err := types.NewNetAddress(types.GenerateNodeKey().ID(), tcpAddr) + require.NoError(t, err) -func createOutboundPeerAndPerformHandshake( - t *testing.T, - addr *NetAddress, - config *config.P2PConfig, - mConfig conn.MConnConfig, -) (*peer, error) { - t.Helper() - - chDescs := []*conn.ChannelDescriptor{ - {ID: testCh, Priority: 1}, - } - reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)} - pk := ed25519.GenPrivKey() - pc, err := testOutboundPeerConn(addr, config, false, pk) - if err != nil { - return nil, err - } - timeout := 1 * time.Second - ourNodeInfo := testNodeInfo(addr.ID, "host_peer") - peerNodeInfo, err := handshake(pc.conn, timeout, ourNodeInfo) - if err != nil { - return nil, err - } - - p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {}) - p.SetLogger(log.NewTestingLogger(t).With("peer", addr)) - return p, nil -} + var ( + info = &ConnInfo{ + SocketAddr: netAddr, + } + + p = &peer{ + connInfo: info, + } + ) + + assert.Equal(t, netAddr.String(), p.SocketAddr().String()) + }) + + t.Run("set logger", func(t *testing.T) { + t.Parallel() + + var ( + l = slog.New(slog.NewTextHandler(io.Discard, nil)) + + p = &peer{ + mConn: &mock.MConn{}, + } + ) + + p.SetLogger(l) + + assert.Equal(t, l, p.Logger) + }) + + t.Run("peer start", func(t *testing.T) { + t.Parallel() + + var ( + expectedErr = errors.New("some error") + + mConn = &mock.MConn{ + StartFn: func() error { + return expectedErr + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + assert.ErrorIs(t, p.OnStart(), expectedErr) + }) + + t.Run("peer stop", func(t *testing.T) { + t.Parallel() + + var ( + stopCalled = false + expectedErr = errors.New("some error") + + mConn = &mock.MConn{ + StopFn: func() error { + stopCalled = true + + return expectedErr + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + p.OnStop() + + assert.True(t, stopCalled) + }) + + t.Run("flush stop", func(t *testing.T) { + t.Parallel() + + var ( + stopCalled = false + + mConn = &mock.MConn{ + FlushFn: func() { + stopCalled = true + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + p.FlushStop() + + assert.True(t, stopCalled) + }) -func testDial(addr *NetAddress, cfg *config.P2PConfig) (net.Conn, error) { - if cfg.TestDialFail { - return nil, fmt.Errorf("dial err (peerConfig.DialFail == true)") - } + t.Run("node info fetch", func(t *testing.T) { + t.Parallel() - conn, err := addr.DialTimeout(cfg.DialTimeout) - if err != nil { - return nil, err - } - return conn, nil + var ( + info = types.NodeInfo{ + Network: "gnoland", + } + + p = &peer{ + nodeInfo: info, + } + ) + + assert.Equal(t, info, p.NodeInfo()) + }) + + t.Run("node status fetch", func(t *testing.T) { + t.Parallel() + + var ( + status = conn.ConnectionStatus{ + Duration: 5 * time.Second, + } + + mConn = &mock.MConn{ + StatusFn: func() conn.ConnectionStatus { + return status + }, + } + + p = &peer{ + mConn: mConn, + } + ) + + assert.Equal(t, status, p.Status()) + }) + + t.Run("string representation", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + outbound bool + }{ + { + "outbound", + true, + }, + { + "inbound", + false, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + var ( + id = types.GenerateNodeKey().ID() + mConnStr = "description" + + p = &peer{ + mConn: &mock.MConn{ + StringFn: func() string { + return mConnStr + }, + }, + nodeInfo: types.NodeInfo{ + PeerID: id, + }, + connInfo: &ConnInfo{ + Outbound: testCase.outbound, + }, + } + + direction = "in" + ) + + if testCase.outbound { + direction = "out" + } + + assert.Contains( + t, + p.String(), + fmt.Sprintf( + "Peer{%s %s %s}", + mConnStr, + id, + direction, + ), + ) + }) + } + }) + + t.Run("outbound information", func(t *testing.T) { + t.Parallel() + + p := &peer{ + connInfo: &ConnInfo{ + Outbound: true, + }, + } + + assert.True( + t, + p.IsOutbound(), + ) + }) + + t.Run("persistent information", func(t *testing.T) { + t.Parallel() + + p := &peer{ + connInfo: &ConnInfo{ + Persistent: true, + }, + } + + assert.True(t, p.IsPersistent()) + }) + + t.Run("initial conn close", func(t *testing.T) { + t.Parallel() + + var ( + closeErr = errors.New("close error") + + mockConn = &mock.Conn{ + CloseFn: func() error { + return closeErr + }, + } + + p = &peer{ + connInfo: &ConnInfo{ + Conn: mockConn, + }, + } + ) + + assert.ErrorIs(t, p.CloseConn(), closeErr) + }) + }) } -func testOutboundPeerConn( - addr *NetAddress, - config *config.P2PConfig, - persistent bool, - ourNodePrivKey crypto.PrivKey, -) (peerConn, error) { - var pc peerConn - conn, err := testDial(addr, config) - if err != nil { - return pc, errors.Wrap(err, "Error creating peer") - } - - pc, err = testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) - if err != nil { - if cerr := conn.Close(); cerr != nil { - return pc, errors.Wrap(err, cerr.Error()) - } - return pc, err - } +func TestPeer_GetSet(t *testing.T) { + t.Parallel() - // ensure dialed ID matches connection ID - if addr.ID != pc.ID() { - if cerr := conn.Close(); cerr != nil { - return pc, errors.Wrap(err, cerr.Error()) + var ( + key = "key" + data = []byte("random") + + p = &peer{ + data: cmap.NewCMap(), } - return pc, SwitchAuthenticationFailureError{addr, pc.ID()} - } + ) - return pc, nil -} + assert.Nil(t, p.Get(key)) -type remotePeer struct { - PrivKey crypto.PrivKey - Config *config.P2PConfig - addr *NetAddress - channels []byte - listenAddr string - listener net.Listener -} + // Set the key + p.Set(key, data) -func (rp *remotePeer) Addr() *NetAddress { - return rp.addr + assert.Equal(t, data, p.Get(key)) } -func (rp *remotePeer) ID() ID { - return rp.PrivKey.PubKey().Address().ID() -} +func TestPeer_Send(t *testing.T) { + t.Parallel() -func (rp *remotePeer) Start() { - if rp.listenAddr == "" { - rp.listenAddr = "127.0.0.1:0" - } - - l, e := net.Listen("tcp", rp.listenAddr) // any available address - if e != nil { - golog.Fatalf("net.Listen tcp :0: %+v", e) - } - rp.listener = l - rp.addr = NewNetAddress(rp.PrivKey.PubKey().Address().ID(), l.Addr()) - if rp.channels == nil { - rp.channels = []byte{testCh} - } - go rp.accept() -} + t.Run("peer not running", func(t *testing.T) { + t.Parallel() -func (rp *remotePeer) Stop() { - rp.listener.Close() -} + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + SendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Make sure the send fails + require.False(t, p.Send(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("peer doesn't have channel", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + SendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{}, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send fails + require.False(t, p.Send(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) -func (rp *remotePeer) Dial(addr *NetAddress) (net.Conn, error) { - conn, err := addr.DialTimeout(1 * time.Second) - if err != nil { - return nil, err - } - pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) - if err != nil { - return nil, err - } - _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) - if err != nil { - return nil, err - } - return conn, err + t.Run("valid peer data send", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + SendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send is valid + require.True(t, p.Send(chID, data)) + + assert.Equal(t, chID, capturedSendID) + assert.Equal(t, data, capturedSendData) + }) } -func (rp *remotePeer) accept() { - conns := []net.Conn{} +func TestPeer_TrySend(t *testing.T) { + t.Parallel() + + t.Run("peer not running", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") - for { - conn, err := rp.listener.Accept() - if err != nil { - golog.Printf("Failed to accept conn: %+v", err) - for _, conn := range conns { - _ = conn.Close() + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + TrySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, } - return - } - pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) - if err != nil { - golog.Fatalf("Failed to create a peer: %+v", err) - } + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) - _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) - if err != nil { - golog.Fatalf("Failed to perform handshake: %+v", err) - } + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Make sure the send fails + require.False(t, p.TrySend(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("peer doesn't have channel", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") + + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + TrySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{}, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send fails + require.False(t, p.TrySend(chID, data)) + + assert.Empty(t, capturedSendID) + assert.Nil(t, capturedSendData) + }) + + t.Run("valid peer data send", func(t *testing.T) { + t.Parallel() + + var ( + chID = byte(10) + data = []byte("random") - conns = append(conns, conn) - } + capturedSendID byte + capturedSendData []byte + + mockConn = &mock.MConn{ + TrySendFn: func(c byte, d []byte) bool { + capturedSendID = c + capturedSendData = d + + return true + }, + } + + p = &peer{ + nodeInfo: types.NodeInfo{ + Channels: []byte{ + chID, + }, + }, + mConn: mockConn, + } + ) + + p.BaseService = *service.NewBaseService(nil, "Peer", p) + + // Start the peer "multiplexing" + require.NoError(t, p.Start()) + t.Cleanup(func() { + require.NoError(t, p.Stop()) + }) + + // Make sure the send is valid + require.True(t, p.TrySend(chID, data)) + + assert.Equal(t, chID, capturedSendID) + assert.Equal(t, data, capturedSendData) + }) } -func (rp *remotePeer) nodeInfo() NodeInfo { - return NodeInfo{ - VersionSet: testVersionSet(), - NetAddress: rp.Addr(), - Network: "testing", - Version: "1.2.3-rc0-deadbeef", - Channels: rp.channels, - Moniker: "remote_peer", - } +func TestPeer_NewPeer(t *testing.T) { + t.Parallel() + + tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:8080") + require.NoError(t, err) + + netAddr, err := types.NewNetAddress(types.GenerateNodeKey().ID(), tcpAddr) + require.NoError(t, err) + + var ( + connInfo = &ConnInfo{ + Outbound: false, + Persistent: true, + Conn: &mock.Conn{}, + RemoteIP: tcpAddr.IP, + SocketAddr: netAddr, + } + + mConfig = &ConnConfig{ + MConfig: conn.MConfigFromP2P(config.DefaultP2PConfig()), + ReactorsByCh: make(map[byte]Reactor), + ChDescs: make([]*conn.ChannelDescriptor, 0), + OnPeerError: nil, + } + ) + + assert.NotPanics(t, func() { + _ = newPeer(connInfo, types.NodeInfo{}, mConfig) + }) } diff --git a/tm2/pkg/p2p/set.go b/tm2/pkg/p2p/set.go new file mode 100644 index 00000000000..b347c480b7e --- /dev/null +++ b/tm2/pkg/p2p/set.go @@ -0,0 +1,121 @@ +package p2p + +import ( + "sync" + + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +type set struct { + mux sync.RWMutex + + peers map[types.ID]PeerConn + outbound uint64 + inbound uint64 +} + +// newSet creates an empty peer set +func newSet() *set { + return &set{ + peers: make(map[types.ID]PeerConn), + outbound: 0, + inbound: 0, + } +} + +// Add adds the peer to the set +func (s *set) Add(peer PeerConn) { + s.mux.Lock() + defer s.mux.Unlock() + + s.peers[peer.ID()] = peer + + if peer.IsOutbound() { + s.outbound += 1 + + return + } + + s.inbound += 1 +} + +// Has returns true if the set contains the peer referred to by this +// peerKey, otherwise false. +func (s *set) Has(peerKey types.ID) bool { + s.mux.RLock() + defer s.mux.RUnlock() + + _, exists := s.peers[peerKey] + + return exists +} + +// Get looks up a peer by the peer ID. Returns nil if peer is not +// found. +func (s *set) Get(key types.ID) PeerConn { + s.mux.RLock() + defer s.mux.RUnlock() + + p, found := s.peers[key] + if !found { + // TODO change this to an error, it doesn't make + // sense to propagate an implementation detail like this + return nil + } + + return p.(PeerConn) +} + +// Remove discards peer by its Key, if the peer was previously memoized. +// Returns true if the peer was removed, and false if it was not found. +// in the set. +func (s *set) Remove(key types.ID) bool { + s.mux.Lock() + defer s.mux.Unlock() + + p, found := s.peers[key] + if !found { + return false + } + + delete(s.peers, key) + + if p.(PeerConn).IsOutbound() { + s.outbound -= 1 + + return true + } + + s.inbound -= 1 + + return true +} + +// NumInbound returns the number of inbound peers +func (s *set) NumInbound() uint64 { + s.mux.RLock() + defer s.mux.RUnlock() + + return s.inbound +} + +// NumOutbound returns the number of outbound peers +func (s *set) NumOutbound() uint64 { + s.mux.RLock() + defer s.mux.RUnlock() + + return s.outbound +} + +// List returns the list of peers +func (s *set) List() []PeerConn { + s.mux.RLock() + defer s.mux.RUnlock() + + peers := make([]PeerConn, 0) + for _, p := range s.peers { + peers = append(peers, p.(PeerConn)) + } + + return peers +} diff --git a/tm2/pkg/p2p/set_test.go b/tm2/pkg/p2p/set_test.go new file mode 100644 index 00000000000..ced35538a9b --- /dev/null +++ b/tm2/pkg/p2p/set_test.go @@ -0,0 +1,146 @@ +package p2p + +import ( + "sort" + "testing" + + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSet_Add(t *testing.T) { + t.Parallel() + + var ( + numPeers = 100 + peers = mock.GeneratePeers(t, numPeers) + + s = newSet() + ) + + for _, peer := range peers { + // Add the peer + s.Add(peer) + + // Make sure the peer is present + assert.True(t, s.Has(peer.ID())) + } + + assert.EqualValues(t, numPeers, s.NumInbound()+s.NumOutbound()) +} + +func TestSet_Remove(t *testing.T) { + t.Parallel() + + var ( + numPeers = 100 + peers = mock.GeneratePeers(t, numPeers) + + s = newSet() + ) + + // Add the initial peers + for _, peer := range peers { + // Add the peer + s.Add(peer) + + // Make sure the peer is present + require.True(t, s.Has(peer.ID())) + } + + require.EqualValues(t, numPeers, s.NumInbound()+s.NumOutbound()) + + // Remove the peers + // Add the initial peers + for _, peer := range peers { + // Add the peer + s.Remove(peer.ID()) + + // Make sure the peer is present + assert.False(t, s.Has(peer.ID())) + } +} + +func TestSet_Get(t *testing.T) { + t.Parallel() + + t.Run("existing peer", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 100) + s = newSet() + ) + + for _, peer := range peers { + id := peer.ID() + s.Add(peer) + + assert.True(t, s.Get(id).ID() == id) + } + }) + + t.Run("missing peer", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 100) + s = newSet() + ) + + for _, peer := range peers { + s.Add(peer) + } + + p := s.Get("random ID") + assert.Nil(t, p) + }) +} + +func TestSet_List(t *testing.T) { + t.Parallel() + + t.Run("empty peer set", func(t *testing.T) { + t.Parallel() + + // Empty set + s := newSet() + + // Linearize the set + assert.Len(t, s.List(), 0) + }) + + t.Run("existing peer set", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 100) + s = newSet() + ) + + for _, peer := range peers { + s.Add(peer) + } + + // Linearize the set + listedPeers := s.List() + + require.Len(t, listedPeers, len(peers)) + + // Make sure the lists are sorted + // for easier comparison + sort.Slice(listedPeers, func(i, j int) bool { + return listedPeers[i].ID() < listedPeers[j].ID() + }) + + sort.Slice(peers, func(i, j int) bool { + return peers[i].ID() < peers[j].ID() + }) + + // Compare the lists + for index, listedPeer := range listedPeers { + assert.Equal(t, listedPeer.ID(), peers[index].ID()) + } + }) +} diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 317f34e496b..5c1c37f7729 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -2,226 +2,164 @@ package p2p import ( "context" + "crypto/rand" "fmt" "math" + "math/big" "sync" "time" - "github.com/gnolang/gno/tm2/pkg/cmap" - "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/p2p/config" "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/random" + "github.com/gnolang/gno/tm2/pkg/p2p/dial" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/gnolang/gno/tm2/pkg/service" "github.com/gnolang/gno/tm2/pkg/telemetry" "github.com/gnolang/gno/tm2/pkg/telemetry/metrics" ) -const ( - // wait a random amount of time from this interval - // before dialing peers or reconnecting to help prevent DoS - dialRandomizerIntervalMilliseconds = 3000 +// defaultDialTimeout is the default wait time for a dial to succeed +var defaultDialTimeout = 3 * time.Second - // repeatedly try to reconnect for a few minutes - // ie. 5 * 20 = 100s - reconnectAttempts = 20 - reconnectInterval = 5 * time.Second +type reactorPeerBehavior struct { + chDescs []*conn.ChannelDescriptor + reactorsByCh map[byte]Reactor - // then move into exponential backoff mode for ~1day - // ie. 3**10 = 16hrs - reconnectBackOffAttempts = 10 - reconnectBackOffBaseSeconds = 3 -) + handlePeerErrFn func(PeerConn, error) + isPersistentPeerFn func(types.ID) bool + isPrivatePeerFn func(types.ID) bool +} + +func (r *reactorPeerBehavior) ReactorChDescriptors() []*conn.ChannelDescriptor { + return r.chDescs +} + +func (r *reactorPeerBehavior) Reactors() map[byte]Reactor { + return r.reactorsByCh +} -// MConnConfig returns an MConnConfig with fields updated -// from the P2PConfig. -func MConnConfig(cfg *config.P2PConfig) conn.MConnConfig { - mConfig := conn.DefaultMConnConfig() - mConfig.FlushThrottle = cfg.FlushThrottleTimeout - mConfig.SendRate = cfg.SendRate - mConfig.RecvRate = cfg.RecvRate - mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize - return mConfig +func (r *reactorPeerBehavior) HandlePeerError(p PeerConn, err error) { + r.handlePeerErrFn(p, err) } -// PeerFilterFunc to be implemented by filter hooks after a new Peer has been -// fully setup. -type PeerFilterFunc func(IPeerSet, Peer) error +func (r *reactorPeerBehavior) IsPersistentPeer(id types.ID) bool { + return r.isPersistentPeerFn(id) +} -// ----------------------------------------------------------------------------- +func (r *reactorPeerBehavior) IsPrivatePeer(id types.ID) bool { + return r.isPrivatePeerFn(id) +} -// Switch handles peer connections and exposes an API to receive incoming messages +// MultiplexSwitch handles peer connections and exposes an API to receive incoming messages // on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one // or more `Channels`. So while sending outgoing messages is typically performed on the peer, // incoming messages are received on the reactor. -type Switch struct { +type MultiplexSwitch struct { service.BaseService - config *config.P2PConfig - reactors map[string]Reactor - chDescs []*conn.ChannelDescriptor - reactorsByCh map[byte]Reactor - peers *PeerSet - dialing *cmap.CMap - reconnecting *cmap.CMap - nodeInfo NodeInfo // our node info - nodeKey *NodeKey // our node privkey - // peers addresses with whom we'll maintain constant connection - persistentPeersAddrs []*NetAddress + ctx context.Context + cancelFn context.CancelFunc - transport Transport + maxInboundPeers uint64 + maxOutboundPeers uint64 - filterTimeout time.Duration - peerFilters []PeerFilterFunc + reactors map[string]Reactor + peerBehavior *reactorPeerBehavior - rng *random.Rand // seed for randomizing dial times and orders -} + peers PeerSet // currently active peer set (live connections) + persistentPeers sync.Map // ID -> *NetAddress; peers whose connections are constant + privatePeers sync.Map // ID -> nothing; lookup table of peers who are not shared + transport Transport -// NetAddress returns the address the switch is listening on. -func (sw *Switch) NetAddress() *NetAddress { - addr := sw.transport.NetAddress() - return &addr + dialQueue *dial.Queue + events *events.Events } -// SwitchOption sets an optional parameter on the Switch. -type SwitchOption func(*Switch) - -// NewSwitch creates a new Switch with the given config. -func NewSwitch( - cfg *config.P2PConfig, +// NewMultiplexSwitch creates a new MultiplexSwitch with the given config. +func NewMultiplexSwitch( transport Transport, - options ...SwitchOption, -) *Switch { - sw := &Switch{ - config: cfg, - reactors: make(map[string]Reactor), - chDescs: make([]*conn.ChannelDescriptor, 0), - reactorsByCh: make(map[byte]Reactor), - peers: NewPeerSet(), - dialing: cmap.NewCMap(), - reconnecting: cmap.NewCMap(), - transport: transport, - filterTimeout: defaultFilterTimeout, - persistentPeersAddrs: make([]*NetAddress, 0), - } - - // Ensure we have a completely undeterministic PRNG. - sw.rng = random.NewRand() - - sw.BaseService = *service.NewBaseService(nil, "P2P Switch", sw) - - for _, option := range options { - option(sw) - } - - return sw -} + opts ...SwitchOption, +) *MultiplexSwitch { + defaultCfg := config.DefaultP2PConfig() -// SwitchFilterTimeout sets the timeout used for peer filters. -func SwitchFilterTimeout(timeout time.Duration) SwitchOption { - return func(sw *Switch) { sw.filterTimeout = timeout } -} - -// SwitchPeerFilters sets the filters for rejection of new peers. -func SwitchPeerFilters(filters ...PeerFilterFunc) SwitchOption { - return func(sw *Switch) { sw.peerFilters = filters } -} - -// --------------------------------------------------------------------- -// Switch setup - -// AddReactor adds the given reactor to the switch. -// NOTE: Not goroutine safe. -func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { - for _, chDesc := range reactor.GetChannels() { - chID := chDesc.ID - // No two reactors can share the same channel. - if sw.reactorsByCh[chID] != nil { - panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chID, sw.reactorsByCh[chID], reactor)) - } - sw.chDescs = append(sw.chDescs, chDesc) - sw.reactorsByCh[chID] = reactor + sw := &MultiplexSwitch{ + reactors: make(map[string]Reactor), + peers: newSet(), + transport: transport, + dialQueue: dial.NewQueue(), + events: events.New(), + maxInboundPeers: defaultCfg.MaxNumInboundPeers, + maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, } - sw.reactors[name] = reactor - reactor.SetSwitch(sw) - return reactor -} -// RemoveReactor removes the given Reactor from the Switch. -// NOTE: Not goroutine safe. -func (sw *Switch) RemoveReactor(name string, reactor Reactor) { - for _, chDesc := range reactor.GetChannels() { - // remove channel description - for i := 0; i < len(sw.chDescs); i++ { - if chDesc.ID == sw.chDescs[i].ID { - sw.chDescs = append(sw.chDescs[:i], sw.chDescs[i+1:]...) - break - } - } - delete(sw.reactorsByCh, chDesc.ID) + // Set up the peer dial behavior + sw.peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: sw.StopPeerForError, + isPersistentPeerFn: func(id types.ID) bool { + return sw.isPersistentPeer(id) + }, + isPrivatePeerFn: func(id types.ID) bool { + return sw.isPrivatePeer(id) + }, } - delete(sw.reactors, name) - reactor.SetSwitch(nil) -} -// Reactors returns a map of reactors registered on the switch. -// NOTE: Not goroutine safe. -func (sw *Switch) Reactors() map[string]Reactor { - return sw.reactors -} + sw.BaseService = *service.NewBaseService(nil, "P2P MultiplexSwitch", sw) -// Reactor returns the reactor with the given name. -// NOTE: Not goroutine safe. -func (sw *Switch) Reactor(name string) Reactor { - return sw.reactors[name] -} + // Set up the context + sw.ctx, sw.cancelFn = context.WithCancel(context.Background()) -// SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. -// NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { - sw.nodeInfo = nodeInfo -} + // Apply the options + for _, opt := range opts { + opt(sw) + } -// NodeInfo returns the switch's NodeInfo. -// NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() NodeInfo { - return sw.nodeInfo + return sw } -// SetNodeKey sets the switch's private key for authenticated encryption. -// NOTE: Not goroutine safe. -func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { - sw.nodeKey = nodeKey +// Subscribe registers to live events happening on the p2p Switch. +// Returns the notification channel, along with an unsubscribe method +func (sw *MultiplexSwitch) Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) { + return sw.events.Subscribe(filterFn) } // --------------------------------------------------------------------- // Service start/stop // OnStart implements BaseService. It starts all the reactors and peers. -func (sw *Switch) OnStart() error { +func (sw *MultiplexSwitch) OnStart() error { // Start reactors for _, reactor := range sw.reactors { - err := reactor.Start() - if err != nil { - return errors.Wrapf(err, "failed to start %v", reactor) + if err := reactor.Start(); err != nil { + return fmt.Errorf("unable to start reactor %w", err) } } - // Start accepting Peers. - go sw.acceptRoutine() + // Run the peer accept routine. + // The accept routine asynchronously accepts + // and processes incoming peer connections + go sw.runAcceptLoop(sw.ctx) + + // Run the dial routine. + // The dial routine parses items in the dial queue + // and initiates outbound peer connections + go sw.runDialLoop(sw.ctx) + + // Run the redial routine. + // The redial routine monitors for important + // peer disconnects, and attempts to reconnect + // to them + go sw.runRedialLoop(sw.ctx) return nil } // OnStop implements BaseService. It stops all peers and reactors. -func (sw *Switch) OnStop() { - // Stop transport - if t, ok := sw.transport.(TransportLifecycle); ok { - err := t.Close() - if err != nil { - sw.Logger.Error("Error stopping transport on stop: ", "error", err) - } - } +func (sw *MultiplexSwitch) OnStop() { + // Close all hanging threads + sw.cancelFn() // Stop peers for _, p := range sw.peers.List() { @@ -229,465 +167,504 @@ func (sw *Switch) OnStop() { } // Stop reactors - sw.Logger.Debug("Switch: Stopping reactors") for _, reactor := range sw.reactors { - reactor.Stop() - } -} - -// --------------------------------------------------------------------- -// Peers - -// Broadcast runs a go routine for each attempted send, which will block trying -// to send for defaultSendTimeoutSeconds. Returns a channel which receives -// success values for each attempted send (false if times out). Channel will be -// closed once msg bytes are sent to all peers (or time out). -// -// NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved. -func (sw *Switch) Broadcast(chID byte, msgBytes []byte) chan bool { - startTime := time.Now() - - sw.Logger.Debug( - "Broadcast", - "channel", chID, - "value", fmt.Sprintf("%X", msgBytes), - ) - - peers := sw.peers.List() - var wg sync.WaitGroup - wg.Add(len(peers)) - successChan := make(chan bool, len(peers)) - - for _, peer := range peers { - go func(p Peer) { - defer wg.Done() - success := p.Send(chID, msgBytes) - successChan <- success - }(peer) - } - - go func() { - wg.Wait() - close(successChan) - if telemetry.MetricsEnabled() { - metrics.BroadcastTxTimer.Record(context.Background(), time.Since(startTime).Milliseconds()) - } - }() - - return successChan -} - -// NumPeers returns the count of outbound/inbound and outbound-dialing peers. -func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { - peers := sw.peers.List() - for _, peer := range peers { - if peer.IsOutbound() { - outbound++ - } else { - inbound++ + if err := reactor.Stop(); err != nil { + sw.Logger.Error("unable to gracefully stop reactor", "err", err) } } - dialing = sw.dialing.Size() - return } -// MaxNumOutboundPeers returns a maximum number of outbound peers. -func (sw *Switch) MaxNumOutboundPeers() int { - return sw.config.MaxNumOutboundPeers +// Broadcast broadcasts the given data to the given channel, +// across the entire switch peer set, without blocking +func (sw *MultiplexSwitch) Broadcast(chID byte, data []byte) { + for _, p := range sw.peers.List() { + go func() { + // This send context is managed internally + // by the Peer's underlying connection implementation + if !p.Send(chID, data) { + sw.Logger.Error( + "unable to perform broadcast", + "chID", chID, + "peerID", p.ID(), + ) + } + }() + } } // Peers returns the set of peers that are connected to the switch. -func (sw *Switch) Peers() IPeerSet { +func (sw *MultiplexSwitch) Peers() PeerSet { return sw.peers } // StopPeerForError disconnects from a peer due to external error. -// If the peer is persistent, it will attempt to reconnect. -// TODO: make record depending on reason. -func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { - sw.Logger.Error("Stopping peer for error", "peer", peer, "err", reason) - sw.stopAndRemovePeer(peer, reason) - - if peer.IsPersistent() { - var addr *NetAddress - if peer.IsOutbound() { // socket address for outbound peers - addr = peer.SocketAddr() - } else { // self-reported address for inbound peers - addr = peer.NodeInfo().NetAddress - } - go sw.reconnectToPeer(addr) +// If the peer is persistent, it will attempt to reconnect +func (sw *MultiplexSwitch) StopPeerForError(peer PeerConn, err error) { + sw.Logger.Error("Stopping peer for error", "peer", peer, "err", err) + + sw.stopAndRemovePeer(peer, err) + + if !peer.IsPersistent() { + // Peer is not a persistent peer, + // no need to initiate a redial + return } -} -// StopPeerGracefully disconnects from a peer gracefully. -// TODO: handle graceful disconnects. -func (sw *Switch) StopPeerGracefully(peer Peer) { - sw.Logger.Info("Stopping peer gracefully") - sw.stopAndRemovePeer(peer, nil) + // Add the peer to the dial queue + sw.DialPeers(peer.SocketAddr()) } -func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { - sw.transport.Cleanup(peer) - peer.Stop() +func (sw *MultiplexSwitch) stopAndRemovePeer(peer PeerConn, err error) { + // Remove the peer from the transport + sw.transport.Remove(peer) + + // Close the (original) peer connection + if closeErr := peer.CloseConn(); closeErr != nil { + sw.Logger.Error( + "unable to gracefully close peer connection", + "peer", peer, + "err", closeErr, + ) + } + + // Stop the peer connection multiplexing + if stopErr := peer.Stop(); stopErr != nil { + sw.Logger.Error( + "unable to gracefully stop peer", + "peer", peer, + "err", stopErr, + ) + } + // Alert the reactors of a peer removal for _, reactor := range sw.reactors { - reactor.RemovePeer(peer, reason) + reactor.RemovePeer(peer, err) } // Removing a peer should go last to avoid a situation where a peer // reconnect to our node and the switch calls InitPeer before // RemovePeer is finished. // https://github.com/tendermint/classic/issues/3338 - sw.peers.Remove(peer) + sw.peers.Remove(peer.ID()) + + sw.events.Notify(events.PeerDisconnectedEvent{ + Address: peer.RemoteAddr(), + PeerID: peer.ID(), + Reason: err, + }) } -// reconnectToPeer tries to reconnect to the addr, first repeatedly -// with a fixed interval, then with exponential backoff. -// If no success after all that, it stops trying. -// NOTE: this will keep trying even if the handshake or auth fails. -// TODO: be more explicit with error types so we only retry on certain failures -// - ie. if we're getting ErrDuplicatePeer we can stop -func (sw *Switch) reconnectToPeer(addr *NetAddress) { - if sw.reconnecting.Has(addr.ID.String()) { - return - } - sw.reconnecting.Set(addr.ID.String(), addr) - defer sw.reconnecting.Delete(addr.ID.String()) +// --------------------------------------------------------------------- +// Dialing - start := time.Now() - sw.Logger.Info("Reconnecting to peer", "addr", addr) - for i := 0; i < reconnectAttempts; i++ { - if !sw.IsRunning() { - return - } +func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("dial context canceled") - err := sw.DialPeerWithAddress(addr) - if err == nil { - return // success - } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { return - } + default: + // Grab a dial item + item := sw.dialQueue.Peek() + if item == nil { + // Nothing to dial + continue + } - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) - // sleep a set amount - sw.randomSleep(reconnectInterval) - continue - } + // Check if the dial time is right + // for the item + if time.Now().Before(item.Time) { + // Nothing to dial + continue + } - sw.Logger.Error("Failed to reconnect to peer. Beginning exponential backoff", - "addr", addr, "elapsed", time.Since(start)) - for i := 0; i < reconnectBackOffAttempts; i++ { - if !sw.IsRunning() { - return - } + // Pop the item from the dial queue + item = sw.dialQueue.Pop() - // sleep an exponentially increasing amount - sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) - sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) + // Dial the peer + sw.Logger.Info( + "dialing peer", + "address", item.Address.String(), + ) - err := sw.DialPeerWithAddress(addr) - if err == nil { - return // success - } else if _, ok := err.(CurrentlyDialingOrExistingAddressError); ok { - return - } - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "addr", addr) - } - sw.Logger.Error("Failed to reconnect to peer. Giving up", "addr", addr, "elapsed", time.Since(start)) -} + peerAddr := item.Address -// --------------------------------------------------------------------- -// Dialing + // Check if the peer is already connected + ps := sw.Peers() + if ps.Has(peerAddr.ID) { + sw.Logger.Warn( + "ignoring dial request for existing peer", + "id", peerAddr.ID, + ) -// DialPeersAsync dials a list of peers asynchronously in random order. -// Used to dial peers from config on startup or from unsafe-RPC (trusted sources). -// It ignores NetAddressLookupError. However, if there are other errors, first -// encounter is returned. -// Nop if there are no peers. -func (sw *Switch) DialPeersAsync(peers []string) error { - netAddrs, errs := NewNetAddressFromStrings(peers) - // report all the errors - for _, err := range errs { - sw.Logger.Error("Error in peer's address", "err", err) - } - // return first non-NetAddressLookupError error - for _, err := range errs { - if _, ok := err.(NetAddressLookupError); ok { - continue - } - return err - } - sw.dialPeersAsync(netAddrs) - return nil -} + continue + } -func (sw *Switch) dialPeersAsync(netAddrs []*NetAddress) { - ourAddr := sw.NetAddress() + // Create a dial context + dialCtx, cancelFn := context.WithTimeout(ctx, defaultDialTimeout) + defer cancelFn() - // permute the list, dial them in random order. - perm := sw.rng.Perm(len(netAddrs)) - for i := 0; i < len(perm); i++ { - go func(i int) { - j := perm[i] - addr := netAddrs[j] + p, err := sw.transport.Dial(dialCtx, *peerAddr, sw.peerBehavior) + if err != nil { + sw.Logger.Error( + "unable to dial peer", + "peer", peerAddr, + "err", err, + ) - if addr.Same(ourAddr) { - sw.Logger.Debug("Ignore attempt to connect to ourselves", "addr", addr, "ourAddr", ourAddr) - return + continue } - sw.randomSleep(0) + // Register the peer with the switch + if err = sw.addPeer(p); err != nil { + sw.Logger.Error( + "unable to add peer", + "peer", p, + "err", err, + ) - err := sw.DialPeerWithAddress(addr) - if err != nil { - switch err.(type) { - case SwitchConnectToSelfError, SwitchDuplicatePeerIDError, CurrentlyDialingOrExistingAddressError: - sw.Logger.Debug("Error dialing peer", "err", err) - default: - sw.Logger.Error("Error dialing peer", "err", err) + sw.transport.Remove(p) + + if !p.IsRunning() { + continue + } + + if stopErr := p.Stop(); stopErr != nil { + sw.Logger.Error( + "unable to gracefully stop peer", + "peer", p, + "err", stopErr, + ) } } - }(i) + + // Log the telemetry + sw.logTelemetry() + } } } -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects -// and authenticates successfully. -// If we're currently dialing this address or it belongs to an existing peer, -// CurrentlyDialingOrExistingAddressError is returned. -func (sw *Switch) DialPeerWithAddress(addr *NetAddress) error { - if sw.IsDialingOrExistingAddress(addr) { - return CurrentlyDialingOrExistingAddressError{addr.String()} +// runRedialLoop starts the persistent peer redial loop +func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + + type backoffItem struct { + lastDialTime time.Time + attempts int } - sw.dialing.Set(addr.ID.String(), addr) - defer sw.dialing.Delete(addr.ID.String()) + var ( + backoffMap = make(map[types.ID]*backoffItem) - return sw.addOutboundPeerWithConfig(addr, sw.config) -} + mux sync.RWMutex + ) -// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] -func (sw *Switch) randomSleep(interval time.Duration) { - r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond - time.Sleep(r + interval) -} + setBackoffItem := func(id types.ID, item *backoffItem) { + mux.Lock() + defer mux.Unlock() -// IsDialingOrExistingAddress returns true if switch has a peer with the given -// address or dialing it at the moment. -func (sw *Switch) IsDialingOrExistingAddress(addr *NetAddress) bool { - return sw.dialing.Has(addr.ID.String()) || - sw.peers.Has(addr.ID) || - (!sw.config.AllowDuplicateIP && sw.peers.HasIP(addr.IP)) -} + backoffMap[id] = item + } -// AddPersistentPeers allows you to set persistent peers. It ignores -// NetAddressLookupError. However, if there are other errors, first encounter is -// returned. -func (sw *Switch) AddPersistentPeers(addrs []string) error { - sw.Logger.Info("Adding persistent peers", "addrs", addrs) - netAddrs, errs := NewNetAddressFromStrings(addrs) - // report all the errors - for _, err := range errs { - sw.Logger.Error("Error in peer's address", "err", err) - } - // return first non-NetAddressLookupError error - for _, err := range errs { - if _, ok := err.(NetAddressLookupError); ok { - continue - } - return err + getBackoffItem := func(id types.ID) *backoffItem { + mux.RLock() + defer mux.RUnlock() + + return backoffMap[id] } - sw.persistentPeersAddrs = netAddrs - return nil -} -func (sw *Switch) isPeerPersistentFn() func(*NetAddress) bool { - return func(na *NetAddress) bool { - for _, pa := range sw.persistentPeersAddrs { - if pa.Equals(na) { + clearBackoffItem := func(id types.ID) { + mux.Lock() + defer mux.Unlock() + + delete(backoffMap, id) + } + + subCh, unsubFn := sw.Subscribe(func(event events.Event) bool { + if event.Type() != events.PeerConnected { + return false + } + + ev := event.(events.PeerConnectedEvent) + + return sw.isPersistentPeer(ev.PeerID) + }) + defer unsubFn() + + // redialFn goes through the persistent peer list + // and dials missing peers + redialFn := func() { + var ( + peers = sw.Peers() + peersToDial = make([]*types.NetAddress, 0) + ) + + sw.persistentPeers.Range(func(key, value any) bool { + var ( + id = key.(types.ID) + addr = value.(*types.NetAddress) + ) + + // Check if the peer is part of the peer set + // or is scheduled for dialing + if peers.Has(id) || sw.dialQueue.Has(addr) { return true } - } - return false - } -} -func (sw *Switch) acceptRoutine() { - for { - p, err := sw.transport.Accept(peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - reactorsByCh: sw.reactorsByCh, - isPersistent: sw.isPeerPersistentFn(), + peersToDial = append(peersToDial, addr) + + return true }) - if err != nil { - switch err := err.(type) { - case RejectedError: - if err.IsSelf() { - // TODO: warn? - } - sw.Logger.Info( - "Inbound Peer rejected", - "err", err, - "numPeers", sw.peers.Size(), - ) + if len(peersToDial) == 0 { + // No persistent peers are missing + return + } - continue - case FilterTimeoutError: - sw.Logger.Error( - "Peer filter timed out", - "err", err, - ) + // Calculate the dial items + dialItems := make([]dial.Item, 0, len(peersToDial)) + for _, p := range peersToDial { + item := getBackoffItem(p.ID) + if item == nil { + dialItem := dial.Item{ + Time: time.Now(), + Address: p, + } + + dialItems = append(dialItems, dialItem) + setBackoffItem(p.ID, &backoffItem{dialItem.Time, 0}) continue - case TransportClosedError: - sw.Logger.Error( - "Stopped accept routine, as transport is closed", - "numPeers", sw.peers.Size(), - ) - default: - sw.Logger.Error( - "Accept on transport errored", - "err", err, - "numPeers", sw.peers.Size(), - ) - // We could instead have a retry loop around the acceptRoutine, - // but that would need to stop and let the node shutdown eventually. - // So might as well panic and let process managers restart the node. - // There's no point in letting the node run without the acceptRoutine, - // since it won't be able to accept new connections. - panic(fmt.Errorf("accept routine exited: %w", err)) } - break + setBackoffItem(p.ID, &backoffItem{ + lastDialTime: time.Now().Add( + calculateBackoff( + item.attempts, + time.Second, + 10*time.Minute, + ), + ), + attempts: item.attempts + 1, + }) } - // Ignore connection if we already have enough peers. - _, in, _ := sw.NumPeers() - if in >= sw.config.MaxNumInboundPeers { - sw.Logger.Info( - "Ignoring inbound connection: already have enough inbound peers", - "address", p.SocketAddr(), - "have", in, - "max", sw.config.MaxNumInboundPeers, - ) + // Add the peers to the dial queue + sw.dialItems(dialItems...) + } + + // Run the initial redial loop on start, + // in case persistent peer connections are not + // active + redialFn() + + for { + select { + case <-ctx.Done(): + sw.Logger.Debug("redial crawl context canceled") + + return + case <-ticker.C: + redialFn() + case event := <-subCh: + // A persistent peer reconnected, + // clear their redial queue + ev := event.(events.PeerConnectedEvent) + + clearBackoffItem(ev.PeerID) + } + } +} + +// calculateBackoff calculates a backoff time, +// based on the number of attempts and range limits +func calculateBackoff( + attempts int, + minTimeout time.Duration, + maxTimeout time.Duration, +) time.Duration { + var ( + minTime = time.Second * 1 + maxTime = time.Second * 60 + multiplier = float64(2) // exponential + ) + + // Check the min limit + if minTimeout > 0 { + minTime = minTimeout + } + + // Check the max limit + if maxTimeout > 0 { + maxTime = maxTimeout + } + + // Sanity check the range + if minTime >= maxTime { + return maxTime + } + + // Calculate the backoff duration + var ( + base = float64(minTime) + calculated = base * math.Pow(multiplier, float64(attempts)) + ) + + // Attempt to calculate the jitter factor + n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) + if err == nil { + jitterFactor := float64(n.Int64()) / float64(math.MaxInt64) // range [0, 1] + + calculated = jitterFactor*(calculated-base) + base + } - sw.transport.Cleanup(p) + // Prevent overflow for int64 (duration) cast + if calculated > float64(math.MaxInt64) { + return maxTime + } + + duration := time.Duration(calculated) + + // Clamp the duration within bounds + if duration < minTime { + return minTime + } + if duration > maxTime { + return maxTime + } + + return duration +} + +// DialPeers adds the peers to the dial queue for async dialing. +// To monitor dial progress, subscribe to adequate p2p MultiplexSwitch events +func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { + for _, peerAddr := range peerAddrs { + // Check if this is our address + if peerAddr.Same(sw.transport.NetAddress()) { continue } - if err := sw.addPeer(p); err != nil { - sw.transport.Cleanup(p) - if p.IsRunning() { - _ = p.Stop() - } - sw.Logger.Info( - "Ignoring inbound connection: error while adding peer", - "err", err, - "id", p.ID(), + // Ignore dial if the limit is reached + if out := sw.Peers().NumOutbound(); out >= sw.maxOutboundPeers { + sw.Logger.Warn( + "ignoring dial request: already have max outbound peers", + "have", out, + "max", sw.maxOutboundPeers, ) + + continue } + + item := dial.Item{ + Time: time.Now(), + Address: peerAddr, + } + + sw.dialQueue.Push(item) } } -// dial the peer; make secret connection; authenticate against the dialed ID; -// add the peer. -// if dialing fails, start the reconnect loop. If handshake fails, it's over. -// If peer is started successfully, reconnectLoop will start when -// StopPeerForError is called. -func (sw *Switch) addOutboundPeerWithConfig( - addr *NetAddress, - cfg *config.P2PConfig, -) error { - sw.Logger.Info("Dialing peer", "address", addr) - - // XXX(xla): Remove the leakage of test concerns in implementation. - if cfg.TestDialFail { - go sw.reconnectToPeer(addr) - return fmt.Errorf("dial err (peerConfig.DialFail == true)") - } - - p, err := sw.transport.Dial(*addr, peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - if e, ok := err.(RejectedError); ok { - if e.IsSelf() { - // TODO: warn? - return err - } +// dialItems adds custom dial items for the multiplex switch +func (sw *MultiplexSwitch) dialItems(dialItems ...dial.Item) { + for _, dialItem := range dialItems { + // Check if this is our address + if dialItem.Address.Same(sw.transport.NetAddress()) { + continue } - // retry persistent peers after - // any dial error besides IsSelf() - if sw.isPeerPersistentFn()(addr) { - go sw.reconnectToPeer(addr) + // Ignore dial if the limit is reached + if out := sw.Peers().NumOutbound(); out >= sw.maxOutboundPeers { + sw.Logger.Warn( + "ignoring dial request: already have max outbound peers", + "have", out, + "max", sw.maxOutboundPeers, + ) + + continue } - return err + sw.dialQueue.Push(dialItem) } +} - if err := sw.addPeer(p); err != nil { - sw.transport.Cleanup(p) - if p.IsRunning() { - _ = p.Stop() - } - return err - } +// isPersistentPeer returns a flag indicating if a peer +// is present in the persistent peer set +func (sw *MultiplexSwitch) isPersistentPeer(id types.ID) bool { + _, persistent := sw.persistentPeers.Load(id) - return nil + return persistent } -func (sw *Switch) filterPeer(p Peer) error { - // Avoid duplicate - if sw.peers.Has(p.ID()) { - return RejectedError{id: p.ID(), isDuplicate: true} - } - - errc := make(chan error, len(sw.peerFilters)) +// isPrivatePeer returns a flag indicating if a peer +// is present in the private peer set +func (sw *MultiplexSwitch) isPrivatePeer(id types.ID) bool { + _, persistent := sw.privatePeers.Load(id) - for _, f := range sw.peerFilters { - go func(f PeerFilterFunc, p Peer, errc chan<- error) { - errc <- f(sw.peers, p) - }(f, p, errc) - } + return persistent +} - for i := 0; i < cap(errc); i++ { +// runAcceptLoop is the main powerhouse method +// for accepting incoming peer connections, filtering them, +// and persisting them +func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { + for { select { - case err := <-errc: + case <-ctx.Done(): + sw.Logger.Debug("switch context close received") + + return + default: + p, err := sw.transport.Accept(ctx, sw.peerBehavior) if err != nil { - return RejectedError{id: p.ID(), err: err, isFiltered: true} + sw.Logger.Error( + "error encountered during peer connection accept", + "err", err, + ) + + continue + } + + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.maxInboundPeers, + ) + + sw.transport.Remove(p) + + continue + } + + // There are open peer slots, add peers + if err := sw.addPeer(p); err != nil { + sw.transport.Remove(p) + + if p.IsRunning() { + _ = p.Stop() + } + + sw.Logger.Info( + "Ignoring inbound connection: error while adding peer", + "err", err, + "id", p.ID(), + ) } - case <-time.After(sw.filterTimeout): - return FilterTimeoutError{} } } - - return nil } -// addPeer starts up the Peer and adds it to the Switch. Error is returned if +// addPeer starts up the Peer and adds it to the MultiplexSwitch. Error is returned if // the peer is filtered out or failed to start or can't be added. -func (sw *Switch) addPeer(p Peer) error { - if err := sw.filterPeer(p); err != nil { - return err - } - +func (sw *MultiplexSwitch) addPeer(p PeerConn) error { p.SetLogger(sw.Logger.With("peer", p.SocketAddr())) - // Handle the shut down case where the switch has stopped but we're - // concurrently trying to add a peer. - if !sw.IsRunning() { - // XXX should this return an error or just log and terminate? - sw.Logger.Error("Won't start a peer - switch is not running", "peer", p) - return nil - } - // Add some data to the peer, which is required by reactors. for _, reactor := range sw.reactors { p = reactor.InitPeer(p) @@ -696,19 +673,15 @@ func (sw *Switch) addPeer(p Peer) error { // Start the peer's send/recv routines. // Must start it before adding it to the peer set // to prevent Start and Stop from being called concurrently. - err := p.Start() - if err != nil { - // Should never happen + if err := p.Start(); err != nil { sw.Logger.Error("Error starting peer", "err", err, "peer", p) + return err } - // Add the peer to PeerSet. Do this before starting the reactors + // Add the peer to the peer set. Do this before starting the reactors // so that if Receive errors, we will find the peer and remove it. - // Add should not err since we already checked peers.Has(). - if err := sw.peers.Add(p); err != nil { - return err - } + sw.peers.Add(p) // Start all the reactor protocols on the peer. for _, reactor := range sw.reactors { @@ -717,29 +690,28 @@ func (sw *Switch) addPeer(p Peer) error { sw.Logger.Info("Added peer", "peer", p) - // Update the telemetry data - sw.logTelemetry() + sw.events.Notify(events.PeerConnectedEvent{ + Address: p.RemoteAddr(), + PeerID: p.ID(), + }) return nil } // logTelemetry logs the switch telemetry data // to global metrics funnels -func (sw *Switch) logTelemetry() { +func (sw *MultiplexSwitch) logTelemetry() { // Update the telemetry data if !telemetry.MetricsEnabled() { return } // Fetch the number of peers - outbound, inbound, dialing := sw.NumPeers() + outbound, inbound := sw.peers.NumOutbound(), sw.peers.NumInbound() // Log the outbound peer count metrics.OutboundPeers.Record(context.Background(), int64(outbound)) // Log the inbound peer count metrics.InboundPeers.Record(context.Background(), int64(inbound)) - - // Log the dialing peer count - metrics.DialingPeers.Record(context.Background(), int64(dialing)) } diff --git a/tm2/pkg/p2p/switch_option.go b/tm2/pkg/p2p/switch_option.go new file mode 100644 index 00000000000..83a6920f2cd --- /dev/null +++ b/tm2/pkg/p2p/switch_option.go @@ -0,0 +1,61 @@ +package p2p + +import ( + "github.com/gnolang/gno/tm2/pkg/p2p/types" +) + +// SwitchOption is a callback used for configuring the p2p MultiplexSwitch +type SwitchOption func(*MultiplexSwitch) + +// WithReactor sets the p2p switch reactors +func WithReactor(name string, reactor Reactor) SwitchOption { + return func(sw *MultiplexSwitch) { + for _, chDesc := range reactor.GetChannels() { + chID := chDesc.ID + + // No two reactors can share the same channel + if sw.peerBehavior.reactorsByCh[chID] != nil { + continue + } + + sw.peerBehavior.chDescs = append(sw.peerBehavior.chDescs, chDesc) + sw.peerBehavior.reactorsByCh[chID] = reactor + } + + sw.reactors[name] = reactor + + reactor.SetSwitch(sw) + } +} + +// WithPersistentPeers sets the p2p switch's persistent peer set +func WithPersistentPeers(peerAddrs []*types.NetAddress) SwitchOption { + return func(sw *MultiplexSwitch) { + for _, addr := range peerAddrs { + sw.persistentPeers.Store(addr.ID, addr) + } + } +} + +// WithPrivatePeers sets the p2p switch's private peer set +func WithPrivatePeers(peerIDs []types.ID) SwitchOption { + return func(sw *MultiplexSwitch) { + for _, id := range peerIDs { + sw.privatePeers.Store(id, struct{}{}) + } + } +} + +// WithMaxInboundPeers sets the p2p switch's maximum inbound peer limit +func WithMaxInboundPeers(maxInbound uint64) SwitchOption { + return func(sw *MultiplexSwitch) { + sw.maxInboundPeers = maxInbound + } +} + +// WithMaxOutboundPeers sets the p2p switch's maximum outbound peer limit +func WithMaxOutboundPeers(maxOutbound uint64) SwitchOption { + return func(sw *MultiplexSwitch) { + sw.maxOutboundPeers = maxOutbound + } +} diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index a7033b466fe..19a5db2efa5 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -1,704 +1,825 @@ package p2p import ( - "bytes" - "errors" - "fmt" - "io" + "context" "net" "sync" - "sync/atomic" "testing" "time" + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/p2p/dial" + "github.com/gnolang/gno/tm2/pkg/p2p/mock" + "github.com/gnolang/gno/tm2/pkg/p2p/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/testutils" ) -var cfg *config.P2PConfig +func TestMultiplexSwitch_Options(t *testing.T) { + t.Parallel() -func init() { - cfg = config.DefaultP2PConfig() - cfg.PexReactor = true - cfg.AllowDuplicateIP = true -} + t.Run("custom reactors", func(t *testing.T) { + t.Parallel() -type PeerMessage struct { - PeerID ID - Bytes []byte - Counter int -} + var ( + name = "custom reactor" + mockReactor = &mockReactor{ + setSwitchFn: func(s Switch) { + require.NotNil(t, s) + }, + } + ) -type TestReactor struct { - BaseReactor + sw := NewMultiplexSwitch(nil, WithReactor(name, mockReactor)) - mtx sync.Mutex - channels []*conn.ChannelDescriptor - logMessages bool - msgsCounter int - msgsReceived map[byte][]PeerMessage -} + assert.Equal(t, mockReactor, sw.reactors[name]) + }) -func NewTestReactor(channels []*conn.ChannelDescriptor, logMessages bool) *TestReactor { - tr := &TestReactor{ - channels: channels, - logMessages: logMessages, - msgsReceived: make(map[byte][]PeerMessage), - } - tr.BaseReactor = *NewBaseReactor("TestReactor", tr) - tr.SetLogger(log.NewNoopLogger()) - return tr -} + t.Run("persistent peers", func(t *testing.T) { + t.Parallel() -func (tr *TestReactor) GetChannels() []*conn.ChannelDescriptor { - return tr.channels -} + peers := generateNetAddr(t, 10) -func (tr *TestReactor) AddPeer(peer Peer) {} + sw := NewMultiplexSwitch(nil, WithPersistentPeers(peers)) -func (tr *TestReactor) RemovePeer(peer Peer, reason interface{}) {} + for _, p := range peers { + assert.True(t, sw.isPersistentPeer(p.ID)) + } + }) -func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { - if tr.logMessages { - tr.mtx.Lock() - defer tr.mtx.Unlock() - // fmt.Printf("Received: %X, %X\n", chID, msgBytes) - tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) - tr.msgsCounter++ - } -} + t.Run("private peers", func(t *testing.T) { + t.Parallel() -func (tr *TestReactor) getMsgs(chID byte) []PeerMessage { - tr.mtx.Lock() - defer tr.mtx.Unlock() - return tr.msgsReceived[chID] -} + var ( + peers = generateNetAddr(t, 10) + ids = make([]types.ID, 0, len(peers)) + ) -// ----------------------------------------------------------------------------- + for _, p := range peers { + ids = append(ids, p.ID) + } -// convenience method for creating two switches connected to each other. -// XXX: note this uses net.Pipe and not a proper TCP conn -func MakeSwitchPair(_ testing.TB, initSwitch func(int, *Switch) *Switch) (*Switch, *Switch) { - // Create two switches that will be interconnected. - switches := MakeConnectedSwitches(cfg, 2, initSwitch, Connect2Switches) - return switches[0], switches[1] -} + sw := NewMultiplexSwitch(nil, WithPrivatePeers(ids)) -func initSwitchFunc(i int, sw *Switch) *Switch { - // Make two reactors of two channels each - sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x00), Priority: 10}, - {ID: byte(0x01), Priority: 10}, - }, true)) - sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x02), Priority: 10}, - {ID: byte(0x03), Priority: 10}, - }, true)) - - return sw -} + for _, p := range peers { + assert.True(t, sw.isPrivatePeer(p.ID)) + } + }) -func TestSwitches(t *testing.T) { - t.Parallel() + t.Run("max inbound peers", func(t *testing.T) { + t.Parallel() - s1, s2 := MakeSwitchPair(t, initSwitchFunc) - defer s1.Stop() - defer s2.Stop() + maxInbound := uint64(500) - if s1.Peers().Size() != 1 { - t.Errorf("Expected exactly 1 peer in s1, got %v", s1.Peers().Size()) - } - if s2.Peers().Size() != 1 { - t.Errorf("Expected exactly 1 peer in s2, got %v", s2.Peers().Size()) - } + sw := NewMultiplexSwitch(nil, WithMaxInboundPeers(maxInbound)) - // Lets send some messages - ch0Msg := []byte("channel zero") - ch1Msg := []byte("channel foo") - ch2Msg := []byte("channel bar") + assert.Equal(t, maxInbound, sw.maxInboundPeers) + }) + + t.Run("max outbound peers", func(t *testing.T) { + t.Parallel() - s1.Broadcast(byte(0x00), ch0Msg) - s1.Broadcast(byte(0x01), ch1Msg) - s1.Broadcast(byte(0x02), ch2Msg) + maxOutbound := uint64(500) - assertMsgReceivedWithTimeout(t, ch0Msg, byte(0x00), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) - assertMsgReceivedWithTimeout(t, ch1Msg, byte(0x01), s2.Reactor("foo").(*TestReactor), 10*time.Millisecond, 5*time.Second) - assertMsgReceivedWithTimeout(t, ch2Msg, byte(0x02), s2.Reactor("bar").(*TestReactor), 10*time.Millisecond, 5*time.Second) + sw := NewMultiplexSwitch(nil, WithMaxOutboundPeers(maxOutbound)) + + assert.Equal(t, maxOutbound, sw.maxOutboundPeers) + }) } -func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, reactor *TestReactor, checkPeriod, timeout time.Duration) { - t.Helper() +func TestMultiplexSwitch_Broadcast(t *testing.T) { + t.Parallel() - ticker := time.NewTicker(checkPeriod) - for { - select { - case <-ticker.C: - msgs := reactor.getMsgs(channel) - if len(msgs) > 0 { - if !bytes.Equal(msgs[0].Bytes, msgBytes) { - t.Fatalf("Unexpected message bytes. Wanted: %X, Got: %X", msgBytes, msgs[0].Bytes) - } - return - } + var ( + wg sync.WaitGroup + + expectedChID = byte(10) + expectedData = []byte("broadcast data") - case <-time.After(timeout): - t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (PeerConn, error) { + return nil, errors.New("constant error") + }, } - } -} -func TestSwitchFiltersOutItself(t *testing.T) { - t.Parallel() + peers = mock.GeneratePeers(t, 10) + sw = NewMultiplexSwitch(mockTransport) + ) - s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) + require.NoError(t, sw.OnStart()) + t.Cleanup(sw.OnStop) - // simulate s1 having a public IP by creating a remote peer with the same ID - rp := &remotePeer{PrivKey: s1.nodeKey.PrivKey, Config: cfg} - rp.Start() + // Create a new peer set + sw.peers = newSet() - // addr should be rejected in addPeer based on the same ID - err := s1.DialPeerWithAddress(rp.Addr()) - if assert.Error(t, err) { - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected self to be rejected") - } - } else { - t.Errorf("expected RejectedError") + for _, p := range peers { + wg.Add(1) + + p.SendFn = func(chID byte, data []byte) bool { + wg.Done() + + require.Equal(t, expectedChID, chID) + assert.Equal(t, expectedData, data) + + return false } + + // Load it up with peers + sw.peers.Add(p) } - rp.Stop() + // Broadcast the data + sw.Broadcast(expectedChID, expectedData) - assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond) + wg.Wait() } -func TestSwitchPeerFilter(t *testing.T) { +func TestMultiplexSwitch_Peers(t *testing.T) { t.Parallel() var ( - filters = []PeerFilterFunc{ - func(_ IPeerSet, _ Peer) error { return nil }, - func(_ IPeerSet, _ Peer) error { return fmt.Errorf("denied!") }, - func(_ IPeerSet, _ Peer) error { return nil }, - } - sw = MakeSwitch( - cfg, - 1, - "testing", - "123.123.123", - initSwitchFunc, - SwitchPeerFilters(filters...), - ) + peers = mock.GeneratePeers(t, 10) + sw = NewMultiplexSwitch(nil) ) - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - t.Fatal(err) - } - err = sw.addPeer(p) - if err, ok := err.(RejectedError); ok { - if !err.IsFiltered() { - t.Errorf("expected peer to be filtered") - } - } else { - t.Errorf("expected RejectedError") + // Create a new peer set + sw.peers = newSet() + + for _, p := range peers { + // Load it up with peers + sw.peers.Add(p) } -} -func TestSwitchPeerFilterTimeout(t *testing.T) { - t.Parallel() + // Broadcast the data + ps := sw.Peers() - var ( - filters = []PeerFilterFunc{ - func(_ IPeerSet, _ Peer) error { - time.Sleep(10 * time.Millisecond) - return nil - }, - } - sw = MakeSwitch( - cfg, - 1, - "testing", - "123.123.123", - initSwitchFunc, - SwitchFilterTimeout(5*time.Millisecond), - SwitchPeerFilters(filters...), - ) + require.EqualValues( + t, + len(peers), + ps.NumInbound()+ps.NumOutbound(), ) - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, - }) - if err != nil { - t.Fatal(err) - } - err = sw.addPeer(p) - if _, ok := err.(FilterTimeoutError); !ok { - t.Errorf("expected FilterTimeoutError") + for _, p := range peers { + assert.True(t, ps.Has(p.ID())) } } -func TestSwitchPeerFilterDuplicate(t *testing.T) { +func TestMultiplexSwitch_StopPeer(t *testing.T) { t.Parallel() - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - sw.Start() - defer sw.Stop() + t.Run("peer not persistent", func(t *testing.T) { + t.Parallel() + + var ( + p = mock.GeneratePeers(t, 1)[0] + mockTransport = &mockTransport{ + removeFn: func(removedPeer PeerConn) { + assert.Equal(t, p.ID(), removedPeer.ID()) + }, + } + + sw = NewMultiplexSwitch(mockTransport) + ) + + // Create a new peer set + sw.peers = newSet() - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() + // Save the single peer + sw.peers.Add(p) - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, + // Stop and remove the peer + sw.StopPeerForError(p, nil) + + // Make sure the peer is removed + assert.False(t, sw.peers.Has(p.ID())) }) - if err != nil { - t.Fatal(err) - } - if err := sw.addPeer(p); err != nil { - t.Fatal(err) - } + t.Run("persistent peer", func(t *testing.T) { + t.Parallel() + + var ( + p = mock.GeneratePeers(t, 1)[0] + mockTransport = &mockTransport{ + removeFn: func(removedPeer PeerConn) { + assert.Equal(t, p.ID(), removedPeer.ID()) + }, + netAddressFn: func() types.NetAddress { + return types.NetAddress{} + }, + } - err = sw.addPeer(p) - if errRej, ok := err.(RejectedError); ok { - if !errRej.IsDuplicate() { - t.Errorf("expected peer to be duplicate. got %v", errRej) + sw = NewMultiplexSwitch(mockTransport) + ) + + // Make sure the peer is persistent + p.IsPersistentFn = func() bool { + return true + } + + p.IsOutboundFn = func() bool { + return false } - } else { - t.Errorf("expected RejectedError, got %v", err) - } -} -func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) { - t.Helper() + // Create a new peer set + sw.peers = newSet() - time.Sleep(timeout) - if sw.Peers().Size() != 0 { - t.Fatalf("Expected %v to not connect to some peers, got %d", sw, sw.Peers().Size()) - } + // Save the single peer + sw.peers.Add(p) + + // Stop and remove the peer + sw.StopPeerForError(p, nil) + + // Make sure the peer is removed + assert.False(t, sw.peers.Has(p.ID())) + + // Make sure the peer is in the dial queue + sw.dialQueue.Has(p.SocketAddr()) + }) } -func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { +func TestMultiplexSwitch_DialLoop(t *testing.T) { t.Parallel() - assert, require := assert.New(t), require.New(t) + t.Run("peer already connected", func(t *testing.T) { + t.Parallel() - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - if err != nil { - t.Error(err) - } - defer sw.Stop() - - // simulate remote peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - p, err := sw.transport.Dial(*rp.Addr(), peerConfig{ - chDescs: sw.chDescs, - onPeerError: sw.StopPeerForError, - isPersistent: sw.isPeerPersistentFn(), - reactorsByCh: sw.reactorsByCh, + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + + peerDialed bool + + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past + + mockSet = &mockSet{ + hasFn: func(id types.ID) bool { + require.Equal(t, p.ID(), id) + + cancelFn() + + ch <- struct{}{} + + return true + }, + } + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (PeerConn, error) { + peerDialed = true + + return nil, nil + }, + } + + sw = NewMultiplexSwitch(mockTransport) + ) + + sw.peers = mockSet + + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.SocketAddr(), + }) + + // Run the dial loop + go sw.runDialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.False(t, peerDialed) }) - require.Nil(err) - err = sw.addPeer(p) - require.Nil(err) + t.Run("peer undialable", func(t *testing.T) { + t.Parallel() - require.NotNil(sw.Peers().Get(rp.ID())) + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() - // simulate failure by closing connection - p.(*peer).CloseConn() + var ( + ch = make(chan struct{}, 1) - assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond) - assert.False(p.IsRunning()) -} + peerDialed bool -func TestSwitchStopPeerForError(t *testing.T) { - t.Parallel() + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past + + mockSet = &mockSet{ + hasFn: func(id types.ID) bool { + require.Equal(t, p.ID(), id) + + return false + }, + } + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (PeerConn, error) { + peerDialed = true + + cancelFn() + + ch <- struct{}{} + + return nil, errors.New("invalid dial") + }, + } + + sw = NewMultiplexSwitch(mockTransport) + ) + + sw.peers = mockSet - // make two connected switches - sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { - return initSwitchFunc(i, sw) + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.SocketAddr(), + }) + + // Run the dial loop + go sw.runDialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.True(t, peerDialed) }) - assert.Equal(t, len(sw1.Peers().List()), 1) + t.Run("peer dialed and added", func(t *testing.T) { + t.Parallel() - // send messages to the peer from sw1 - p := sw1.Peers().List()[0] - p.Send(0x1, []byte("here's a message to send")) + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() - // stop sw2. this should cause the p to fail, - // which results in calling StopPeerForError internally - sw2.Stop() + var ( + ch = make(chan struct{}, 1) - // now call StopPeerForError explicitly, eg. from a reactor - sw1.StopPeerForError(p, fmt.Errorf("some err")) + peerDialed bool - assert.Equal(t, len(sw1.Peers().List()), 0) -} + p = mock.GeneratePeers(t, 1)[0] + dialTime = time.Now().Add(-5 * time.Second) // in the past -func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { - t.Parallel() + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + _ types.NetAddress, + _ PeerBehavior, + ) (PeerConn, error) { + peerDialed = true - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - // 1. simulate failure by closing connection - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - - err = sw.AddPersistentPeers([]string{rp.Addr().String()}) - require.NoError(t, err) - - err = sw.DialPeerWithAddress(rp.Addr()) - require.Nil(t, err) - require.NotNil(t, sw.Peers().Get(rp.ID())) - - p := sw.Peers().List()[0] - p.(*peer).CloseConn() - - waitUntilSwitchHasAtLeastNPeers(sw, 1) - assert.False(t, p.IsRunning()) // old peer instance - assert.Equal(t, 1, sw.Peers().Size()) // new peer instance - - // 2. simulate first time dial failure - rp = &remotePeer{ - PrivKey: ed25519.GenPrivKey(), - Config: cfg, - // Use different interface to prevent duplicate IP filter, this will break - // beyond two peers. - listenAddr: "127.0.0.1:0", - } - rp.Start() - defer rp.Stop() - - conf := config.DefaultP2PConfig() - conf.TestDialFail = true // will trigger a reconnect - err = sw.addOutboundPeerWithConfig(rp.Addr(), conf) - require.NotNil(t, err) - // DialPeerWithAddres - sw.peerConfig resets the dialer - waitUntilSwitchHasAtLeastNPeers(sw, 2) - assert.Equal(t, 2, sw.Peers().Size()) -} + cancelFn() -func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { - t.Parallel() + ch <- struct{}{} + + return p, nil + }, + } - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() + sw = NewMultiplexSwitch(mockTransport) + ) - // 1. simulate failure by closing the connection - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() + // Prepare the dial queue + sw.dialQueue.Push(dial.Item{ + Time: dialTime, + Address: p.SocketAddr(), + }) - err = sw.AddPersistentPeers([]string{rp.Addr().String()}) - require.NoError(t, err) + // Run the dial loop + go sw.runDialLoop(ctx) - conn, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - time.Sleep(100 * time.Millisecond) - require.NotNil(t, sw.Peers().Get(rp.ID())) + select { + case <-ch: + case <-time.After(5 * time.Second): + } - conn.Close() + require.True(t, sw.Peers().Has(p.ID())) - waitUntilSwitchHasAtLeastNPeers(sw, 1) - assert.Equal(t, 1, sw.Peers().Size()) + assert.True(t, peerDialed) + }) } -func TestSwitchDialPeersAsync(t *testing.T) { +func TestMultiplexSwitch_AcceptLoop(t *testing.T) { t.Parallel() - if testing.Short() { - return - } + t.Run("inbound limit reached", func(t *testing.T) { + t.Parallel() - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() + var ( + ch = make(chan struct{}, 1) + maxInbound = uint64(10) - err = sw.DialPeersAsync([]string{rp.Addr().String()}) - require.NoError(t, err) - time.Sleep(dialRandomizerIntervalMilliseconds * time.Millisecond) - require.NotNil(t, sw.Peers().Get(rp.ID())) -} + peerRemoved bool + + p = mock.GeneratePeers(t, 1)[0] + + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (PeerConn, error) { + return p, nil + }, + removeFn: func(removedPeer PeerConn) { + require.Equal(t, p.ID(), removedPeer.ID()) + + peerRemoved = true + + ch <- struct{}{} + }, + } -func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { - for i := 0; i < 20; i++ { - time.Sleep(250 * time.Millisecond) - has := sw.Peers().Size() - if has >= n { - break + ps = &mockSet{ + numInboundFn: func() uint64 { + return maxInbound + }, + } + + sw = NewMultiplexSwitch( + mockTransport, + WithMaxInboundPeers(maxInbound), + ) + ) + + // Set the peer set + sw.peers = ps + + // Run the accept loop + go sw.runAcceptLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): } - } + + assert.True(t, peerRemoved) + }) + + t.Run("peer accepted", func(t *testing.T) { + t.Parallel() + + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var ( + ch = make(chan struct{}, 1) + maxInbound = uint64(10) + + peerAdded bool + + p = mock.GeneratePeers(t, 1)[0] + + mockTransport = &mockTransport{ + acceptFn: func(_ context.Context, _ PeerBehavior) (PeerConn, error) { + return p, nil + }, + } + + ps = &mockSet{ + numInboundFn: func() uint64 { + return maxInbound - 1 // available slot + }, + addFn: func(peer PeerConn) { + require.Equal(t, p.ID(), peer.ID()) + + peerAdded = true + + ch <- struct{}{} + }, + } + + sw = NewMultiplexSwitch( + mockTransport, + WithMaxInboundPeers(maxInbound), + ) + ) + + // Set the peer set + sw.peers = ps + + // Run the accept loop + go sw.runAcceptLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): + } + + assert.True(t, peerAdded) + }) } -func TestSwitchFullConnectivity(t *testing.T) { +func TestMultiplexSwitch_RedialLoop(t *testing.T) { t.Parallel() - switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) - defer func() { - for _, sw := range switches { - sw.Stop() + t.Run("no peers to dial", func(t *testing.T) { + t.Parallel() + + var ( + ch = make(chan struct{}, 1) + + peersChecked = 0 + peers = mock.GeneratePeers(t, 10) + + ps = &mockSet{ + hasFn: func(id types.ID) bool { + exists := false + for _, p := range peers { + if p.ID() == id { + exists = true + + break + } + } + + require.True(t, exists) + + peersChecked++ + + if peersChecked == len(peers) { + ch <- struct{}{} + } + + return true + }, + } + ) + + // Make sure the peers are the + // switch persistent peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.SocketAddr()) } - }() - for i, sw := range switches { - if sw.Peers().Size() != 2 { - t.Fatalf("Expected each switch to be connected to 2 other, but %d switch only connected to %d", sw.Peers().Size(), i) + // Create the switch + sw := NewMultiplexSwitch( + nil, + WithPersistentPeers(addrs), + ) + + // Set the peer set + sw.peers = ps + + // Run the redial loop + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + go sw.runRedialLoop(ctx) + + select { + case <-ch: + case <-time.After(5 * time.Second): } - } -} -func TestSwitchAcceptRoutine(t *testing.T) { - t.Parallel() + assert.Equal(t, len(peers), peersChecked) + }) + + t.Run("missing peers dialed", func(t *testing.T) { + t.Parallel() + + var ( + peers = mock.GeneratePeers(t, 10) + missingPeer = peers[0] + missingAddr = missingPeer.SocketAddr() + + peersDialed []types.NetAddress + + mockTransport = &mockTransport{ + dialFn: func( + _ context.Context, + address types.NetAddress, + _ PeerBehavior, + ) (PeerConn, error) { + peersDialed = append(peersDialed, address) + + if address.Equals(*missingPeer.SocketAddr()) { + return missingPeer, nil + } + + return nil, errors.New("invalid dial") + }, + } + ps = &mockSet{ + hasFn: func(id types.ID) bool { + return id != missingPeer.ID() + }, + } + ) + + // Make sure the peers are the + // switch persistent peers + addrs := make([]*types.NetAddress, 0, len(peers)) + + for _, p := range peers { + addrs = append(addrs, p.SocketAddr()) + } + + // Create the switch + sw := NewMultiplexSwitch( + mockTransport, + WithPersistentPeers(addrs), + ) + + // Set the peer set + sw.peers = ps + + // Run the redial loop + ctx, cancelFn := context.WithTimeout( + context.Background(), + 5*time.Second, + ) + defer cancelFn() + + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + defer wg.Done() + + sw.runRedialLoop(ctx) + }() + + go func() { + defer wg.Done() + + deadline := time.After(5 * time.Second) - cfg.MaxNumInboundPeers = 5 - - // make switch - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - remotePeers := make([]*remotePeer, 0) - assert.Equal(t, 0, sw.Peers().Size()) - - // 1. check we connect up to MaxNumInboundPeers - for i := 0; i < cfg.MaxNumInboundPeers; i++ { - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - remotePeers = append(remotePeers, rp) - rp.Start() - c, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // spawn a reading routine to prevent connection from closing - go func(c net.Conn) { for { - one := make([]byte, 1) - _, err := c.Read(one) - if err != nil { + select { + case <-deadline: + return + default: + if !sw.dialQueue.Has(missingAddr) { + continue + } + + cancelFn() + return } } - }(c) - } - time.Sleep(100 * time.Millisecond) - assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) - - // 2. check we close new connections if we already have MaxNumInboundPeers peers - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - conn, err := rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // check conn is closed - one := make([]byte, 1) - conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - _, err = conn.Read(one) - assert.Equal(t, io.EOF, err) - assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) - rp.Stop() - - // stop remote peers - for _, rp := range remotePeers { - rp.Stop() - } -} + }() -type errorTransport struct { - acceptErr error -} + wg.Wait() -func (et errorTransport) NetAddress() NetAddress { - panic("not implemented") + require.True(t, sw.dialQueue.Has(missingAddr)) + assert.Equal(t, missingAddr, sw.dialQueue.Peek().Address) + }) } -func (et errorTransport) Accept(c peerConfig) (Peer, error) { - return nil, et.acceptErr -} +func TestMultiplexSwitch_DialPeers(t *testing.T) { + t.Parallel() -func (errorTransport) Dial(NetAddress, peerConfig) (Peer, error) { - panic("not implemented") -} + t.Run("self dial request", func(t *testing.T) { + t.Parallel() -func (errorTransport) Cleanup(Peer) { - panic("not implemented") -} + var ( + p = mock.GeneratePeers(t, 1)[0] + addr = types.NetAddress{ + ID: "id", + IP: p.SocketAddr().IP, + Port: p.SocketAddr().Port, + } -func TestSwitchAcceptRoutineErrorCases(t *testing.T) { - t.Parallel() + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return addr + }, + } + ) - sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() - }) + // Make sure the "peer" has the same address + // as the transport (node) + p.NodeInfoFn = func() types.NodeInfo { + return types.NodeInfo{ + PeerID: addr.ID, + } + } - sw = NewSwitch(cfg, errorTransport{RejectedError{conn: nil, err: errors.New("filtered"), isFiltered: true}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() - }) + sw := NewMultiplexSwitch(mockTransport) + + // Dial the peers + sw.DialPeers(p.SocketAddr()) - sw = NewSwitch(cfg, errorTransport{TransportClosedError{}}) - assert.NotPanics(t, func() { - err := sw.Start() - assert.NoError(t, err) - sw.Stop() + // Make sure the peer wasn't actually dialed + assert.False(t, sw.dialQueue.Has(p.SocketAddr())) }) -} -// mockReactor checks that InitPeer never called before RemovePeer. If that's -// not true, InitCalledBeforeRemoveFinished will return true. -type mockReactor struct { - *BaseReactor + t.Run("outbound peer limit reached", func(t *testing.T) { + t.Parallel() - // atomic - removePeerInProgress uint32 - initCalledBeforeRemoveFinished uint32 -} + var ( + maxOutbound = uint64(10) + peers = mock.GeneratePeers(t, 10) -func (r *mockReactor) RemovePeer(peer Peer, reason interface{}) { - atomic.StoreUint32(&r.removePeerInProgress, 1) - defer atomic.StoreUint32(&r.removePeerInProgress, 0) - time.Sleep(100 * time.Millisecond) -} + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return types.NetAddress{ + ID: "id", + IP: net.IP{}, + } + }, + } -func (r *mockReactor) InitPeer(peer Peer) Peer { - if atomic.LoadUint32(&r.removePeerInProgress) == 1 { - atomic.StoreUint32(&r.initCalledBeforeRemoveFinished, 1) - } + ps = &mockSet{ + numOutboundFn: func() uint64 { + return maxOutbound + }, + } + ) - return peer -} + sw := NewMultiplexSwitch( + mockTransport, + WithMaxOutboundPeers(maxOutbound), + ) -func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { - return atomic.LoadUint32(&r.initCalledBeforeRemoveFinished) == 1 -} + // Set the peer set + sw.peers = ps -// see stopAndRemovePeer -func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { - t.Parallel() + // Dial the peers + addrs := make([]*types.NetAddress, 0, len(peers)) - testutils.FilterStability(t, testutils.Flappy) + for _, p := range peers { + addrs = append(addrs, p.SocketAddr()) + } - // make reactor - reactor := &mockReactor{} - reactor.BaseReactor = NewBaseReactor("mockReactor", reactor) + sw.DialPeers(addrs...) - // make switch - sw := MakeSwitch(cfg, 1, "testing", "123.123.123", func(i int, sw *Switch) *Switch { - sw.AddReactor("mock", reactor) - return sw + // Make sure no peers were dialed + for _, p := range peers { + assert.False(t, sw.dialQueue.Has(p.SocketAddr())) + } }) - err := sw.Start() - require.NoError(t, err) - defer sw.Stop() - - // add peer - rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} - rp.Start() - defer rp.Stop() - _, err = rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // wait till the switch adds rp to the peer set - time.Sleep(100 * time.Millisecond) - - // stop peer asynchronously - go sw.StopPeerForError(sw.Peers().Get(rp.ID()), "test") - - // simulate peer reconnecting to us - _, err = rp.Dial(sw.NetAddress()) - require.NoError(t, err) - // wait till the switch adds rp to the peer set - time.Sleep(100 * time.Millisecond) - - // make sure reactor.RemovePeer is finished before InitPeer is called - assert.False(t, reactor.InitCalledBeforeRemoveFinished()) -} -func BenchmarkSwitchBroadcast(b *testing.B) { - s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { - // Make bar reactors of bar channels each - sw.AddReactor("foo", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x00), Priority: 10}, - {ID: byte(0x01), Priority: 10}, - }, false)) - sw.AddReactor("bar", NewTestReactor([]*conn.ChannelDescriptor{ - {ID: byte(0x02), Priority: 10}, - {ID: byte(0x03), Priority: 10}, - }, false)) - return sw - }) - defer s1.Stop() - defer s2.Stop() + t.Run("peers dialed", func(t *testing.T) { + t.Parallel() + + var ( + maxOutbound = uint64(1000) + peers = mock.GeneratePeers(t, int(maxOutbound/2)) - // Allow time for goroutines to boot up - time.Sleep(1 * time.Second) + mockTransport = &mockTransport{ + netAddressFn: func() types.NetAddress { + return types.NetAddress{ + ID: "id", + IP: net.IP{}, + } + }, + } + ) - b.ResetTimer() + sw := NewMultiplexSwitch( + mockTransport, + WithMaxOutboundPeers(10), + ) - numSuccess, numFailure := 0, 0 + // Dial the peers + addrs := make([]*types.NetAddress, 0, len(peers)) - // Send random message from foo channel to another - for i := 0; i < b.N; i++ { - chID := byte(i % 4) - successChan := s1.Broadcast(chID, []byte("test data")) - for s := range successChan { - if s { - numSuccess++ - } else { - numFailure++ - } + for _, p := range peers { + addrs = append(addrs, p.SocketAddr()) } - } - b.Logf("success: %v, failure: %v", numSuccess, numFailure) + sw.DialPeers(addrs...) + + // Make sure peers were dialed + for _, p := range peers { + assert.True(t, sw.dialQueue.Has(p.SocketAddr())) + } + }) } diff --git a/tm2/pkg/p2p/test_util.go b/tm2/pkg/p2p/test_util.go deleted file mode 100644 index dd0d9cd6bc7..00000000000 --- a/tm2/pkg/p2p/test_util.go +++ /dev/null @@ -1,238 +0,0 @@ -package p2p - -import ( - "fmt" - "net" - "time" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" - "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gnolang/gno/tm2/pkg/p2p/config" - "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/random" - "github.com/gnolang/gno/tm2/pkg/versionset" -) - -const testCh = 0x01 - -// ------------------------------------------------ - -func CreateRoutableAddr() (addr string, netAddr *NetAddress) { - for { - id := ed25519.GenPrivKey().PubKey().Address().ID() - var err error - addr = fmt.Sprintf("%s@%v.%v.%v.%v:26656", id, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256, random.RandInt()%256) - netAddr, err = NewNetAddressFromString(addr) - if err != nil { - panic(err) - } - if netAddr.Routable() { - break - } - } - return -} - -// ------------------------------------------------------------------ -// Connects switches via arbitrary net.Conn. Used for testing. - -const TEST_HOST = "localhost" - -// MakeConnectedSwitches returns n switches, connected according to the connect func. -// If connect==Connect2Switches, the switches will be fully connected. -// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). -// NOTE: panics if any switch fails to start. -func MakeConnectedSwitches(cfg *config.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { - switches := make([]*Switch, n) - for i := 0; i < n; i++ { - switches[i] = MakeSwitch(cfg, i, TEST_HOST, "123.123.123", initSwitch) - } - - if err := StartSwitches(switches); err != nil { - panic(err) - } - - for i := 0; i < n; i++ { - for j := i + 1; j < n; j++ { - connect(switches, i, j) - } - } - - return switches -} - -// Connect2Switches will connect switches i and j via net.Pipe(). -// Blocks until a connection is established. -// NOTE: caller ensures i and j are within bounds. -func Connect2Switches(switches []*Switch, i, j int) { - switchI := switches[i] - switchJ := switches[j] - - c1, c2 := conn.NetPipe() - - doneCh := make(chan struct{}) - go func() { - err := switchI.addPeerWithConnection(c1) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - go func() { - err := switchJ.addPeerWithConnection(c2) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - <-doneCh - <-doneCh -} - -func (sw *Switch) addPeerWithConnection(conn net.Conn) error { - pc, err := testInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - - ni, err := handshake(conn, time.Second, sw.nodeInfo) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - - p := newPeer( - pc, - MConnConfig(sw.config), - ni, - sw.reactorsByCh, - sw.chDescs, - sw.StopPeerForError, - ) - - if err = sw.addPeer(p); err != nil { - pc.CloseConn() - return err - } - - return nil -} - -// StartSwitches calls sw.Start() for each given switch. -// It returns the first encountered error. -func StartSwitches(switches []*Switch) error { - for _, s := range switches { - err := s.Start() // start switch and reactors - if err != nil { - return err - } - } - return nil -} - -func MakeSwitch( - cfg *config.P2PConfig, - i int, - network, version string, - initSwitch func(int, *Switch) *Switch, - opts ...SwitchOption, -) *Switch { - nodeKey := NodeKey{ - PrivKey: ed25519.GenPrivKey(), - } - nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) - - t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) - - if err := t.Listen(*nodeInfo.NetAddress); err != nil { - panic(err) - } - - // TODO: let the config be passed in? - sw := initSwitch(i, NewSwitch(cfg, t, opts...)) - sw.SetLogger(log.NewNoopLogger().With("switch", i)) - sw.SetNodeKey(&nodeKey) - - for ch := range sw.reactorsByCh { - nodeInfo.Channels = append(nodeInfo.Channels, ch) - } - - // TODO: We need to setup reactors ahead of time so the NodeInfo is properly - // populated and we don't have to do those awkward overrides and setters. - t.nodeInfo = nodeInfo - sw.SetNodeInfo(nodeInfo) - - return sw -} - -func testInboundPeerConn( - conn net.Conn, - config *config.P2PConfig, - ourNodePrivKey crypto.PrivKey, -) (peerConn, error) { - return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) -} - -func testPeerConn( - rawConn net.Conn, - cfg *config.P2PConfig, - outbound, persistent bool, - ourNodePrivKey crypto.PrivKey, - socketAddr *NetAddress, -) (pc peerConn, err error) { - conn := rawConn - - // Fuzz connection - if cfg.TestFuzz { - // so we have time to do peer handshakes and get set up - conn = FuzzConnAfterFromConfig(conn, 10*time.Second, cfg.TestFuzzConfig) - } - - // Encrypt connection - conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey) - if err != nil { - return pc, errors.Wrap(err, "Error creating peer") - } - - // Only the information we already have - return newPeerConn(outbound, persistent, conn, socketAddr), nil -} - -// ---------------------------------------------------------------- -// rand node info - -func testNodeInfo(id ID, name string) NodeInfo { - return testNodeInfoWithNetwork(id, name, "testing") -} - -func testVersionSet() versionset.VersionSet { - return versionset.VersionSet{ - versionset.VersionInfo{ - Name: "p2p", - Version: "v0.0.0", // dontcare - }, - } -} - -func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { - return NodeInfo{ - VersionSet: testVersionSet(), - NetAddress: NewNetAddressFromIPPort(id, net.ParseIP("127.0.0.1"), 0), - Network: network, - Software: "p2ptest", - Version: "v1.2.3-rc.0-deadbeef", - Channels: []byte{testCh}, - Moniker: name, - Other: NodeInfoOther{ - TxIndex: "on", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), - }, - } -} diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 5bfae9e52b8..9edef9a15e5 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -3,144 +3,64 @@ package p2p import ( "context" "fmt" + "io" + "log/slog" "net" - "strconv" + "sync" "time" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "golang.org/x/sync/errgroup" ) -const ( - defaultDialTimeout = time.Second - defaultFilterTimeout = 5 * time.Second - defaultHandshakeTimeout = 3 * time.Second -) - -// IPResolver is a behaviour subset of net.Resolver. -type IPResolver interface { - LookupIPAddr(context.Context, string) ([]net.IPAddr, error) -} - -// accept is the container to carry the upgraded connection and NodeInfo from an -// asynchronously running routine to the Accept method. -type accept struct { - netAddr *NetAddress - conn net.Conn - nodeInfo NodeInfo - err error -} - -// peerConfig is used to bundle data we need to fully setup a Peer with an -// MConn, provided by the caller of Accept and Dial (currently the Switch). This -// a temporary measure until reactor setup is less dynamic and we introduce the -// concept of PeerBehaviour to communicate about significant Peer lifecycle -// events. -// TODO(xla): Refactor out with more static Reactor setup and PeerBehaviour. -type peerConfig struct { - chDescs []*conn.ChannelDescriptor - onPeerError func(Peer, interface{}) - outbound bool - // isPersistent allows you to set a function, which, given socket address - // (for outbound peers) OR self-reported address (for inbound peers), tells - // if the peer is persistent or not. - isPersistent func(*NetAddress) bool - reactorsByCh map[byte]Reactor -} - -// Transport emits and connects to Peers. The implementation of Peer is left to -// the transport. Each transport is also responsible to filter establishing -// peers specific to its domain. -type Transport interface { - // Listening address. - NetAddress() NetAddress - - // Accept returns a newly connected Peer. - Accept(peerConfig) (Peer, error) - - // Dial connects to the Peer for the address. - Dial(NetAddress, peerConfig) (Peer, error) - - // Cleanup any resources associated with Peer. - Cleanup(Peer) -} - -// TransportLifecycle bundles the methods for callers to control start and stop -// behaviour. -type TransportLifecycle interface { - Close() error - Listen(NetAddress) error -} - -// ConnFilterFunc to be implemented by filter hooks after a new connection has -// been established. The set of existing connections is passed along together -// with all resolved IPs for the new connection. -type ConnFilterFunc func(ConnSet, net.Conn, []net.IP) error - -// ConnDuplicateIPFilter resolves and keeps all ips for an incoming connection -// and refuses new ones if they come from a known ip. -func ConnDuplicateIPFilter() ConnFilterFunc { - return func(cs ConnSet, c net.Conn, ips []net.IP) error { - for _, ip := range ips { - if cs.HasIP(ip) { - return RejectedError{ - conn: c, - err: fmt.Errorf("IP<%v> already connected", ip), - isDuplicate: true, - } - } - } +// defaultHandshakeTimeout is the timeout for the STS handshaking protocol +const defaultHandshakeTimeout = 3 * time.Second - return nil - } -} +var ( + errTransportClosed = errors.New("transport is closed") + errTransportInactive = errors.New("transport is inactive") + errDuplicateConnection = errors.New("duplicate peer connection") + errPeerIDNodeInfoMismatch = errors.New("connection ID does not match node info ID") + errPeerIDDialMismatch = errors.New("connection ID does not match dialed ID") + errIncompatibleNodeInfo = errors.New("incompatible node info") +) -// MultiplexTransportOption sets an optional parameter on the -// MultiplexTransport. -type MultiplexTransportOption func(*MultiplexTransport) +type connUpgradeFn func(io.ReadWriteCloser, crypto.PrivKey) (*conn.SecretConnection, error) -// MultiplexTransportConnFilters sets the filters for rejection new connections. -func MultiplexTransportConnFilters( - filters ...ConnFilterFunc, -) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.connFilters = filters } -} +type secretConn interface { + net.Conn -// MultiplexTransportFilterTimeout sets the timeout waited for filter calls to -// return. -func MultiplexTransportFilterTimeout( - timeout time.Duration, -) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.filterTimeout = timeout } + RemotePubKey() crypto.PubKey } -// MultiplexTransportResolver sets the Resolver used for ip lookups, defaults to -// net.DefaultResolver. -func MultiplexTransportResolver(resolver IPResolver) MultiplexTransportOption { - return func(mt *MultiplexTransport) { mt.resolver = resolver } +// peerInfo is a wrapper for an unverified peer connection +type peerInfo struct { + addr *types.NetAddress // the dial address of the peer + conn net.Conn // the connection associated with the peer + nodeInfo types.NodeInfo // the relevant peer node info } // MultiplexTransport accepts and dials tcp connections and upgrades them to // multiplexed peers. type MultiplexTransport struct { - netAddr NetAddress - listener net.Listener + ctx context.Context + cancelFn context.CancelFunc - acceptc chan accept - closec chan struct{} + logger *slog.Logger - // Lookup table for duplicate ip and id checks. - conns ConnSet - connFilters []ConnFilterFunc + netAddr types.NetAddress // the node's P2P dial address, used for handshaking + nodeInfo types.NodeInfo // the node's P2P info, used for handshaking + nodeKey types.NodeKey // the node's private P2P key, used for handshaking - dialTimeout time.Duration - filterTimeout time.Duration - handshakeTimeout time.Duration - nodeInfo NodeInfo - nodeKey NodeKey - resolver IPResolver + listener net.Listener // listener for inbound peer connections + peerCh chan peerInfo // pipe for inbound peer connections + activeConns sync.Map // active peer connections (remote address -> nothing) + + connUpgradeFn connUpgradeFn // Upgrades the connection to a secret connection // TODO(xla): This config is still needed as we parameterize peerConn and // peer currently. All relevant configuration should be refactored into options @@ -148,439 +68,376 @@ type MultiplexTransport struct { mConfig conn.MConnConfig } -// Test multiplexTransport for interface completeness. -var ( - _ Transport = (*MultiplexTransport)(nil) - _ TransportLifecycle = (*MultiplexTransport)(nil) -) - // NewMultiplexTransport returns a tcp connected multiplexed peer. func NewMultiplexTransport( - nodeInfo NodeInfo, - nodeKey NodeKey, + nodeInfo types.NodeInfo, + nodeKey types.NodeKey, mConfig conn.MConnConfig, + logger *slog.Logger, ) *MultiplexTransport { return &MultiplexTransport{ - acceptc: make(chan accept), - closec: make(chan struct{}), - dialTimeout: defaultDialTimeout, - filterTimeout: defaultFilterTimeout, - handshakeTimeout: defaultHandshakeTimeout, - mConfig: mConfig, - nodeInfo: nodeInfo, - nodeKey: nodeKey, - conns: NewConnSet(), - resolver: net.DefaultResolver, + peerCh: make(chan peerInfo, 1), + mConfig: mConfig, + nodeInfo: nodeInfo, + nodeKey: nodeKey, + logger: logger, + connUpgradeFn: conn.MakeSecretConnection, } } -// NetAddress implements Transport. -func (mt *MultiplexTransport) NetAddress() NetAddress { +// NetAddress returns the transport's listen address (for p2p connections) +func (mt *MultiplexTransport) NetAddress() types.NetAddress { return mt.netAddr } -// Accept implements Transport. -func (mt *MultiplexTransport) Accept(cfg peerConfig) (Peer, error) { +// Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] +func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) (PeerConn, error) { + // Sanity check, no need to wait + // on an inactive transport + if mt.listener == nil { + return nil, errTransportInactive + } + select { - // This case should never have any side-effectful/blocking operations to - // ensure that quality peers are ready to be used. - case a := <-mt.acceptc: - if a.err != nil { - return nil, a.err + case <-ctx.Done(): + return nil, ctx.Err() + case info, ok := <-mt.peerCh: + if !ok { + return nil, errTransportClosed } - cfg.outbound = false - - return mt.wrapPeer(a.conn, a.nodeInfo, cfg, a.netAddr), nil - case <-mt.closec: - return nil, TransportClosedError{} + return mt.newMultiplexPeer(info, behavior, false) } } -// Dial implements Transport. +// Dial creates an outbound Peer connection, and +// verifies it (performs handshaking) [BLOCKING] func (mt *MultiplexTransport) Dial( - addr NetAddress, - cfg peerConfig, -) (Peer, error) { - c, err := addr.DialTimeout(mt.dialTimeout) + ctx context.Context, + addr types.NetAddress, + behavior PeerBehavior, +) (PeerConn, error) { + // Set a dial timeout for the connection + c, err := addr.DialContext(ctx) if err != nil { return nil, err } - // TODO(xla): Evaluate if we should apply filters if we explicitly dial. - if err := mt.filterConn(c); err != nil { - return nil, err - } - - secretConn, nodeInfo, err := mt.upgrade(c, &addr) + // Process the connection with expected ID + info, err := mt.processConn(c, addr.ID) if err != nil { - return nil, err - } - - cfg.outbound = true + // Close the net peer connection + _ = c.Close() - p := mt.wrapPeer(secretConn, nodeInfo, cfg, &addr) + return nil, fmt.Errorf("unable to process connection, %w", err) + } - return p, nil + return mt.newMultiplexPeer(info, behavior, true) } -// Close implements TransportLifecycle. +// Close stops the multiplex transport func (mt *MultiplexTransport) Close() error { - close(mt.closec) - - if mt.listener != nil { - return mt.listener.Close() + if mt.listener == nil { + return nil } - return nil + mt.cancelFn() + + return mt.listener.Close() } -// Listen implements TransportLifecycle. -func (mt *MultiplexTransport) Listen(addr NetAddress) error { +// Listen starts an active process of listening for incoming connections [NON-BLOCKING] +func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { + // Reserve a port, and start listening ln, err := net.Listen("tcp", addr.DialString()) if err != nil { - return err + return fmt.Errorf("unable to listen on address, %w", err) } if addr.Port == 0 { // net.Listen on port 0 means the kernel will auto-allocate a port // - find out which one has been given to us. - _, p, err := net.SplitHostPort(ln.Addr().String()) - if err != nil { + tcpAddr, ok := ln.Addr().(*net.TCPAddr) + if !ok { return fmt.Errorf("error finding port (after listening on port 0): %w", err) } - pInt, _ := strconv.Atoi(p) - addr.Port = uint16(pInt) + + addr.Port = uint16(tcpAddr.Port) } + // Set up the context + mt.ctx, mt.cancelFn = context.WithCancel(context.Background()) + mt.netAddr = addr mt.listener = ln - go mt.acceptPeers() + // Run the routine for accepting + // incoming peer connections + go mt.runAcceptLoop() return nil } -func (mt *MultiplexTransport) acceptPeers() { +// runAcceptLoop runs the loop where incoming peers are: +// +// 1. accepted by the transport +// 2. filtered +// 3. upgraded (handshaked + verified) +func (mt *MultiplexTransport) runAcceptLoop() { + var wg sync.WaitGroup + + defer func() { + wg.Wait() // Wait for all process routines + + close(mt.peerCh) + }() + for { - c, err := mt.listener.Accept() - if err != nil { - // If Close() has been called, silently exit. - select { - case _, ok := <-mt.closec: - if !ok { - return - } - default: - // Transport is not closed - } + select { + case <-mt.ctx.Done(): + mt.logger.Debug("transport accept context closed") - mt.acceptc <- accept{err: err} return - } + default: + // Accept an incoming peer connection + c, err := mt.listener.Accept() + if err != nil { + mt.logger.Error( + "unable to accept p2p connection", + "err", err, + ) - // Connection upgrade and filtering should be asynchronous to avoid - // Head-of-line blocking[0]. - // Reference: https://github.com/tendermint/classic/issues/2047 - // - // [0] https://en.wikipedia.org/wiki/Head-of-line_blocking - go func(c net.Conn) { - defer func() { - if r := recover(); r != nil { - err := RejectedError{ - conn: c, - err: errors.New("recovered from panic: %v", r), - isAuthFailure: true, - } - select { - case mt.acceptc <- accept{err: err}: - case <-mt.closec: - // Give up if the transport was closed. - _ = c.Close() - return - } - } - }() - - var ( - nodeInfo NodeInfo - secretConn *conn.SecretConnection - netAddr *NetAddress - ) - - err := mt.filterConn(c) - if err == nil { - secretConn, nodeInfo, err = mt.upgrade(c, nil) - if err == nil { - addr := c.RemoteAddr() - id := secretConn.RemotePubKey().Address().ID() - netAddr = NewNetAddress(id, addr) - } + continue } - select { - case mt.acceptc <- accept{netAddr, secretConn, nodeInfo, err}: - // Make the upgraded peer available. - case <-mt.closec: - // Give up if the transport was closed. - _ = c.Close() - return - } - }(c) - } -} + // Process the new connection asynchronously + wg.Add(1) -// Cleanup removes the given address from the connections set and -// closes the connection. -func (mt *MultiplexTransport) Cleanup(p Peer) { - mt.conns.RemoveAddr(p.RemoteAddr()) - _ = p.CloseConn() -} + go func(c net.Conn) { + defer wg.Done() -func (mt *MultiplexTransport) cleanup(c net.Conn) error { - mt.conns.Remove(c) + info, err := mt.processConn(c, "") + if err != nil { + mt.logger.Error( + "unable to process p2p connection", + "err", err, + ) - return c.Close() -} + // Close the connection + _ = c.Close() -func (mt *MultiplexTransport) filterConn(c net.Conn) (err error) { - defer func() { - if err != nil { - _ = c.Close() - } - }() + return + } - // Reject if connection is already present. - if mt.conns.Has(c) { - return RejectedError{conn: c, isDuplicate: true} + select { + case mt.peerCh <- info: + case <-mt.ctx.Done(): + // Give up if the transport was closed. + _ = c.Close() + } + }(c) + } } +} - // Resolve ips for incoming conn. - ips, err := resolveIPs(mt.resolver, c) - if err != nil { - return err +// processConn handles the raw connection by upgrading it and verifying it +func (mt *MultiplexTransport) processConn(c net.Conn, expectedID types.ID) (peerInfo, error) { + dialAddr := c.RemoteAddr().String() + + // Check if the connection is a duplicate one + if _, exists := mt.activeConns.LoadOrStore(dialAddr, struct{}{}); exists { + return peerInfo{}, errDuplicateConnection } - errc := make(chan error, len(mt.connFilters)) + // Handshake with the peer, through STS + secretConn, nodeInfo, err := mt.upgradeAndVerifyConn(c) + if err != nil { + mt.activeConns.Delete(dialAddr) - for _, f := range mt.connFilters { - go func(f ConnFilterFunc, c net.Conn, ips []net.IP, errc chan<- error) { - errc <- f(mt.conns, c, ips) - }(f, c, ips, errc) + return peerInfo{}, fmt.Errorf("unable to upgrade connection, %w", err) } - for i := 0; i < cap(errc); i++ { - select { - case err := <-errc: - if err != nil { - return RejectedError{conn: c, err: err, isFiltered: true} - } - case <-time.After(mt.filterTimeout): - return FilterTimeoutError{} - } + // Grab the connection ID. + // At this point, the connection and information shared + // with the peer is considered valid, since full handshaking + // and verification took place + id := secretConn.RemotePubKey().Address().ID() + + // The reason the dial ID needs to be verified is because + // for outbound peers (peers the node dials), there is an expected peer ID + // when initializing the outbound connection, that can differ from the exchanged one. + // For inbound peers, the ID is whatever the peer exchanges during the + // handshaking process, and is verified separately + if !expectedID.IsZero() && id.String() != expectedID.String() { + mt.activeConns.Delete(dialAddr) + + return peerInfo{}, fmt.Errorf( + "%w (expected %q got %q)", + errPeerIDDialMismatch, + expectedID, + id, + ) } - mt.conns.Set(c, ips) + netAddr, _ := types.NewNetAddress(id, c.RemoteAddr()) - return nil + return peerInfo{ + addr: netAddr, + conn: secretConn, + nodeInfo: nodeInfo, + }, nil } -func (mt *MultiplexTransport) upgrade( - c net.Conn, - dialedAddr *NetAddress, -) (secretConn *conn.SecretConnection, nodeInfo NodeInfo, err error) { - defer func() { - if err != nil { - _ = mt.cleanup(c) - } - }() +// Remove removes the peer resources from the transport +func (mt *MultiplexTransport) Remove(p PeerConn) { + mt.activeConns.Delete(p.RemoteAddr().String()) +} - secretConn, err = upgradeSecretConn(c, mt.handshakeTimeout, mt.nodeKey.PrivKey) +// upgradeAndVerifyConn upgrades the connections (performs the handshaking process) +// and verifies that the connecting peer is valid +func (mt *MultiplexTransport) upgradeAndVerifyConn(c net.Conn) (secretConn, types.NodeInfo, error) { + // Upgrade to a secret connection. + // A secret connection is a connection that has passed + // an initial handshaking process, as defined by the STS + // protocol, and is considered to be secure and authentic + sc, err := mt.upgradeToSecretConn( + c, + defaultHandshakeTimeout, + mt.nodeKey.PrivKey, + ) if err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: fmt.Errorf("secret conn failed: %w", err), - isAuthFailure: true, - } + return nil, types.NodeInfo{}, fmt.Errorf("unable to upgrade p2p connection, %w", err) } - // For outgoing conns, ensure connection key matches dialed key. - connID := secretConn.RemotePubKey().Address().ID() - if dialedAddr != nil { - if dialedID := dialedAddr.ID; connID.String() != dialedID.String() { - return nil, NodeInfo{}, RejectedError{ - conn: c, - id: connID, - err: fmt.Errorf( - "conn.ID (%v) dialed ID (%v) mismatch", - connID, - dialedID, - ), - isAuthFailure: true, - } - } - } - - nodeInfo, err = handshake(secretConn, mt.handshakeTimeout, mt.nodeInfo) + // Exchange node information + nodeInfo, err := exchangeNodeInfo(sc, defaultHandshakeTimeout, mt.nodeInfo) if err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: fmt.Errorf("handshake failed: %w", err), - isAuthFailure: true, - } + return nil, types.NodeInfo{}, fmt.Errorf("unable to exchange node information, %w", err) } - if err := nodeInfo.Validate(); err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: err, - isNodeInfoInvalid: true, - } - } + // Ensure the connection ID matches the node's reported ID + connID := sc.RemotePubKey().Address().ID() - // Ensure connection key matches self reported key. if connID != nodeInfo.ID() { - return nil, NodeInfo{}, RejectedError{ - conn: c, - id: connID, - err: fmt.Errorf( - "conn.ID (%v) NodeInfo.ID (%v) mismatch", - connID, - nodeInfo.ID(), - ), - isAuthFailure: true, - } - } - - // Reject self. - if mt.nodeInfo.ID() == nodeInfo.ID() { - return nil, NodeInfo{}, RejectedError{ - addr: *NewNetAddress(nodeInfo.ID(), c.RemoteAddr()), - conn: c, - id: nodeInfo.ID(), - isSelf: true, - } + return nil, types.NodeInfo{}, fmt.Errorf( + "%w (expected %q got %q)", + errPeerIDNodeInfoMismatch, + connID.String(), + nodeInfo.ID().String(), + ) } - if err := mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { - return nil, NodeInfo{}, RejectedError{ - conn: c, - err: err, - id: nodeInfo.ID(), - isIncompatible: true, - } + // Check compatibility with the node + if err = mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { + return nil, types.NodeInfo{}, fmt.Errorf("%w, %w", errIncompatibleNodeInfo, err) } - return secretConn, nodeInfo, nil + return sc, nodeInfo, nil } -func (mt *MultiplexTransport) wrapPeer( - c net.Conn, - ni NodeInfo, - cfg peerConfig, - socketAddr *NetAddress, -) Peer { - persistent := false - if cfg.isPersistent != nil { - if cfg.outbound { - persistent = cfg.isPersistent(socketAddr) - } else { - selfReportedAddr := ni.NetAddress - persistent = cfg.isPersistent(selfReportedAddr) - } +// newMultiplexPeer creates a new multiplex Peer, using +// the provided Peer behavior and info +func (mt *MultiplexTransport) newMultiplexPeer( + info peerInfo, + behavior PeerBehavior, + isOutbound bool, +) (PeerConn, error) { + // Extract the host + host, _, err := net.SplitHostPort(info.conn.RemoteAddr().String()) + if err != nil { + return nil, fmt.Errorf("unable to extract peer host, %w", err) } - peerConn := newPeerConn( - cfg.outbound, - persistent, - c, - socketAddr, - ) + // Look up the IPs + ips, err := net.LookupIP(host) + if err != nil { + return nil, fmt.Errorf("unable to lookup peer IPs, %w", err) + } - p := newPeer( - peerConn, - mt.mConfig, - ni, - cfg.reactorsByCh, - cfg.chDescs, - cfg.onPeerError, - ) + // Wrap the info related to the connection + peerConn := &ConnInfo{ + Outbound: isOutbound, + Persistent: behavior.IsPersistentPeer(info.addr.ID), + Private: behavior.IsPrivatePeer(info.nodeInfo.ID()), + Conn: info.conn, + RemoteIP: ips[0], // IPv4 + SocketAddr: info.addr, + } + + // Create the info related to the multiplex connection + mConfig := &ConnConfig{ + MConfig: mt.mConfig, + ReactorsByCh: behavior.Reactors(), + ChDescs: behavior.ReactorChDescriptors(), + OnPeerError: behavior.HandlePeerError, + } - return p + return newPeer(peerConn, info.nodeInfo, mConfig), nil } -func handshake( - c net.Conn, +// exchangeNodeInfo performs a data swap, where node +// info is exchanged between the current node and a peer async +func exchangeNodeInfo( + c secretConn, timeout time.Duration, - nodeInfo NodeInfo, -) (NodeInfo, error) { + nodeInfo types.NodeInfo, +) (types.NodeInfo, error) { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { - return NodeInfo{}, err + return types.NodeInfo{}, err } var ( - errc = make(chan error, 2) - - peerNodeInfo NodeInfo + peerNodeInfo types.NodeInfo ourNodeInfo = nodeInfo ) - go func(errc chan<- error, c net.Conn) { + g, _ := errgroup.WithContext(context.Background()) + + g.Go(func() error { _, err := amino.MarshalSizedWriter(c, ourNodeInfo) - errc <- err - }(errc, c) - go func(errc chan<- error, c net.Conn) { + + return err + }) + + g.Go(func() error { _, err := amino.UnmarshalSizedReader( c, &peerNodeInfo, - int64(MaxNodeInfoSize()), + types.MaxNodeInfoSize, ) - errc <- err - }(errc, c) - for i := 0; i < cap(errc); i++ { - err := <-errc - if err != nil { - return NodeInfo{}, err - } + return err + }) + + if err := g.Wait(); err != nil { + return types.NodeInfo{}, err + } + + // Validate the received node information + if err := nodeInfo.Validate(); err != nil { + return types.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } return peerNodeInfo, c.SetDeadline(time.Time{}) } -func upgradeSecretConn( +// upgradeToSecretConn takes an active TCP connection, +// and upgrades it to a verified, handshaked connection through +// the STS protocol +func (mt *MultiplexTransport) upgradeToSecretConn( c net.Conn, timeout time.Duration, privKey crypto.PrivKey, -) (*conn.SecretConnection, error) { +) (secretConn, error) { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { return nil, err } - sc, err := conn.MakeSecretConnection(c, privKey) + // Handshake (STS) + sc, err := mt.connUpgradeFn(c, privKey) if err != nil { return nil, err } return sc, sc.SetDeadline(time.Time{}) } - -func resolveIPs(resolver IPResolver, c net.Conn) ([]net.IP, error) { - host, _, err := net.SplitHostPort(c.RemoteAddr().String()) - if err != nil { - return nil, err - } - - addrs, err := resolver.LookupIPAddr(context.Background(), host) - if err != nil { - return nil, err - } - - ips := []net.IP{} - - for _, addr := range addrs { - ips = append(ips, addr.IP) - } - - return ips, nil -} diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 63b1c26e666..3eb3264ec2b 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -1,650 +1,519 @@ package p2p import ( + "context" "fmt" - "math/rand" "net" - "reflect" "testing" "time" - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p/conn" - "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/versionset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var defaultNodeName = "host_peer" - -func emptyNodeInfo() NodeInfo { - return NodeInfo{} -} - -// newMultiplexTransport returns a tcp connected multiplexed peer -// using the default MConnConfig. It's a convenience function used -// for testing. -func newMultiplexTransport( - nodeInfo NodeInfo, - nodeKey NodeKey, -) *MultiplexTransport { - return NewMultiplexTransport( - nodeInfo, nodeKey, conn.DefaultMConnConfig(), - ) -} - -func TestTransportMultiplexConnFilter(t *testing.T) { - t.Parallel() - - mt := newMultiplexTransport( - emptyNodeInfo(), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, - ) - id := mt.nodeKey.ID() - - MultiplexTransportConnFilters( - func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, - func(_ ConnSet, _ net.Conn, _ []net.IP) error { return nil }, - func(_ ConnSet, _ net.Conn, _ []net.IP) error { - return fmt.Errorf("rejected") - }, - )(mt) - - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } - - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } +// generateNetAddr generates dummy net addresses +func generateNetAddr(t *testing.T, count int) []*types.NetAddress { + t.Helper() - errc := make(chan error) + addrs := make([]*types.NetAddress, 0, count) - go func() { - addr := NewNetAddress(id, mt.listener.Addr()) + for i := 0; i < count; i++ { + key := types.GenerateNodeKey() - _, err := addr.Dial() - if err != nil { - errc <- err - return - } + // Grab a random port + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) - close(errc) - }() + addr, err := types.NewNetAddress(key.ID(), ln.Addr()) + require.NoError(t, err) - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) + addrs = append(addrs, addr) } - _, err = mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsFiltered() { - t.Errorf("expected peer to be filtered") - } - } else { - t.Errorf("expected RejectedError") - } + return addrs } -func TestTransportMultiplexConnFilterTimeout(t *testing.T) { +func TestMultiplexTransport_NetAddress(t *testing.T) { t.Parallel() - mt := newMultiplexTransport( - emptyNodeInfo(), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, - ) - id := mt.nodeKey.ID() - - MultiplexTransportFilterTimeout(5 * time.Millisecond)(mt) - MultiplexTransportConnFilters( - func(_ ConnSet, _ net.Conn, _ []net.IP) error { - time.Sleep(100 * time.Millisecond) - return nil - }, - )(mt) - - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } - - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } - - errc := make(chan error) + t.Run("transport not active", func(t *testing.T) { + t.Parallel() - go func() { - addr := NewNetAddress(id, mt.listener.Addr()) - - _, err := addr.Dial() - if err != nil { - errc <- err - return - } - - close(errc) - }() - - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } - - _, err = mt.Accept(peerConfig{}) - if _, ok := err.(FilterTimeoutError); !ok { - t.Errorf("expected FilterTimeoutError") - } -} - -func TestTransportMultiplexAcceptMultiple(t *testing.T) { - t.Parallel() + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + ) - mt := testSetupMultiplexTransport(t) - laddr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + transport := NewMultiplexTransport(ni, nk, mCfg, logger) + addr := transport.NetAddress() - var ( - seed = rand.New(rand.NewSource(time.Now().UnixNano())) - nDialers = seed.Intn(64) + 64 - errc = make(chan error, nDialers) - ) + assert.Error(t, addr.Validate()) + }) - // Setup dialers. - for i := 0; i < nDialers; i++ { - go testDialer(*laddr, errc) - } + t.Run("active transport on random port", func(t *testing.T) { + t.Parallel() - // Catch connection errors. - for i := 0; i < nDialers; i++ { - if err := <-errc; err != nil { - t.Fatal(err) - } - } + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) - ps := []Peer{} + addr.Port = 0 // random port - // Accept all peers. - for i := 0; i < cap(errc); i++ { - p, err := mt.Accept(peerConfig{}) - if err != nil { - t.Fatal(err) - } + transport := NewMultiplexTransport(ni, nk, mCfg, logger) - if err := p.Start(); err != nil { - t.Fatal(err) - } + require.NoError(t, transport.Listen(*addr)) + defer func() { + require.NoError(t, transport.Close()) + }() - ps = append(ps, p) - } + netAddr := transport.NetAddress() + assert.False(t, netAddr.Equals(*addr)) + assert.NoError(t, netAddr.Validate()) + }) - if have, want := len(ps), cap(errc); have != want { - t.Errorf("have %v, want %v", have, want) - } + t.Run("active transport on specific port", func(t *testing.T) { + t.Parallel() - // Stop all peers. - for _, p := range ps { - if err := p.Stop(); err != nil { - t.Fatal(err) - } - } + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] + ) - if err := mt.Close(); err != nil { - t.Errorf("close errored: %v", err) - } -} + addr.Port = 4123 // specific port -func testDialer(dialAddr NetAddress, errc chan error) { - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), defaultNodeName), - NodeKey{ - PrivKey: pv, - }, - ) - ) + transport := NewMultiplexTransport(ni, nk, mCfg, logger) - _, err := dialer.Dial(dialAddr, peerConfig{}) - if err != nil { - errc <- err - return - } + require.NoError(t, transport.Listen(*addr)) + defer func() { + require.NoError(t, transport.Close()) + }() - // Signal that the connection was established. - errc <- nil + netAddr := transport.NetAddress() + assert.True(t, netAddr.Equals(*addr)) + assert.NoError(t, netAddr.Validate()) + }) } -func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { +func TestMultiplexTransport_Accept(t *testing.T) { t.Parallel() - testutils.FilterStability(t, testutils.Flappy) + t.Run("inactive transport", func(t *testing.T) { + t.Parallel() - mt := testSetupMultiplexTransport(t) - - var ( - fastNodePV = ed25519.GenPrivKey() - fastNodeInfo = testNodeInfo(fastNodePV.PubKey().Address().ID(), "fastnode") - errc = make(chan error) - fastc = make(chan struct{}) - slowc = make(chan struct{}) - ) - - // Simulate slow Peer. - go func() { - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - - c, err := addr.Dial() - if err != nil { - errc <- err - return - } - - close(slowc) + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + ) - select { - case <-fastc: - // Fast peer connected. - case <-time.After(100 * time.Millisecond): - // We error if the fast peer didn't succeed. - errc <- fmt.Errorf("Fast peer timed out") - } + transport := NewMultiplexTransport(ni, nk, mCfg, logger) - sc, err := upgradeSecretConn(c, 100*time.Millisecond, ed25519.GenPrivKey()) - if err != nil { - errc <- err - return - } + p, err := transport.Accept(context.Background(), nil) - _, err = handshake(sc, 100*time.Millisecond, - testNodeInfo( - ed25519.GenPrivKey().PubKey().Address().ID(), - "slow_peer", - )) - if err != nil { - errc <- err - return - } - }() + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + errTransportInactive, + ) + }) - // Simulate fast Peer. - go func() { - <-slowc + t.Run("transport closed", func(t *testing.T) { + t.Parallel() - dialer := newMultiplexTransport( - fastNodeInfo, - NodeKey{ - PrivKey: fastNodePV, - }, + var ( + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - _, err := dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } + addr.Port = 0 - close(errc) - close(fastc) - }() + transport := NewMultiplexTransport(ni, nk, mCfg, logger) - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } + // Start the transport + require.NoError(t, transport.Listen(*addr)) - p, err := mt.Accept(peerConfig{}) - if err != nil { - t.Fatal(err) - } + // Stop the transport + require.NoError(t, transport.Close()) - if have, want := p.NodeInfo(), fastNodeInfo; !reflect.DeepEqual(have, want) { - t.Errorf("have %v, want %v", have, want) - } -} - -func TestTransportMultiplexValidateNodeInfo(t *testing.T) { - t.Parallel() + p, err := transport.Accept(context.Background(), nil) - mt := testSetupMultiplexTransport(t) + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + errTransportClosed, + ) + }) - errc := make(chan error) + t.Run("context canceled", func(t *testing.T) { + t.Parallel() - go func() { var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty - NodeKey{ - PrivKey: pv, - }, - ) + ni = types.NodeInfo{} + nk = types.NodeKey{} + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + addr = generateNetAddr(t, 1)[0] ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + addr.Port = 0 - _, err := dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } + transport := NewMultiplexTransport(ni, nk, mCfg, logger) - close(errc) - }() + // Start the transport + require.NoError(t, transport.Listen(*addr)) - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } + ctx, cancelFn := context.WithCancel(context.Background()) + cancelFn() - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsNodeInfoInvalid() { - t.Errorf("expected NodeInfo to be invalid") - } - } else { - t.Errorf("expected RejectedError") - } -} + p, err := transport.Accept(ctx, nil) -func TestTransportMultiplexRejectMismatchID(t *testing.T) { - t.Parallel() + assert.Nil(t, p) + assert.ErrorIs( + t, + err, + context.Canceled, + ) + }) - mt := testSetupMultiplexTransport(t) + t.Run("peer ID mismatch", func(t *testing.T) { + t.Parallel() - errc := make(chan error) + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } - go func() { - dialer := newMultiplexTransport( - testNodeInfo( - ed25519.GenPrivKey().PubKey().Address().ID(), "dialer", - ), - NodeKey{ - PrivKey: ed25519.GenPrivKey(), - }, + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ PeerConn, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - _, err := dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } + peers := make([]*MultiplexTransport, 0, len(keys)) - close(errc) - }() + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) - if err := <-errc; err != nil { - t.Errorf("connection failed: %v", err) - } + id := key.ID() - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsAuthFailure() { - t.Errorf("expected auth failure") - } - } else { - t.Errorf("expected RejectedError") - } -} + if index%1 == 0 { + // Hijack the key value + id = types.GenerateNodeKey().ID() + } -func TestTransportMultiplexDialRejectWrongID(t *testing.T) { - t.Parallel() + na, err := types.NewNetAddress(id, addr) + require.NoError(t, err) - mt := testSetupMultiplexTransport(t) + ni := types.NodeInfo{ + Network: network, // common network + PeerID: id, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } - var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfo(pv.PubKey().Address().ID(), ""), // Should not be empty - NodeKey{ - PrivKey: pv, - }, - ) - ) + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) - wrongID := ed25519.GenPrivKey().PubKey().Address().ID() - addr := NewNetAddress(wrongID, mt.listener.Addr()) + // Start the transport + require.NoError(t, tr.Listen(*na)) - _, err := dialer.Dial(*addr, peerConfig{}) - if err != nil { - t.Logf("connection failed: %v", err) - if err, ok := err.(RejectedError); ok { - if !err.IsAuthFailure() { - t.Errorf("expected auth failure") - } - } else { - t.Errorf("expected RejectedError") + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, + ) } - } -} -func TestTransportMultiplexRejectIncompatible(t *testing.T) { - t.Parallel() + // Make peer 1 --dial--> peer 2, and handshake. + // This "upgrade" should fail because the peer shared a different + // peer ID than what they actually used for the secret connection + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() - mt := testSetupMultiplexTransport(t) + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + assert.ErrorIs(t, err, errPeerIDNodeInfoMismatch) + require.Nil(t, p) + }) - errc := make(chan error) + t.Run("incompatible peers", func(t *testing.T) { + t.Parallel() - go func() { var ( - pv = ed25519.GenPrivKey() - dialer = newMultiplexTransport( - testNodeInfoWithNetwork(pv.PubKey().Address().ID(), "dialer", "incompatible-network"), - NodeKey{ - PrivKey: pv, + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } + + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ PeerConn, err error) { + require.NoError(t, err) }, - ) + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } ) - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) - _, err := dialer.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } + peers := make([]*MultiplexTransport, 0, len(keys)) - close(errc) - }() + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsIncompatible() { - t.Errorf("expected to reject incompatible") - } - } else { - t.Errorf("expected RejectedError") - } -} + id := key.ID() -func TestTransportMultiplexRejectSelf(t *testing.T) { - t.Parallel() + na, err := types.NewNetAddress(id, addr) + require.NoError(t, err) - mt := testSetupMultiplexTransport(t) + chainID := network - errc := make(chan error) + if index%2 == 0 { + chainID = "totally-random-network" + } - go func() { - addr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) + ni := types.NodeInfo{ + Network: chainID, + PeerID: id, + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } - _, err := mt.Dial(*addr, peerConfig{}) - if err != nil { - errc <- err - return - } + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) - close(errc) - }() + // Start the transport + require.NoError(t, tr.Listen(*na)) - if err := <-errc; err != nil { - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected to reject self, got: %v", err) - } - } else { - t.Errorf("expected RejectedError") - } - } else { - t.Errorf("expected connection failure") - } + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) - _, err := mt.Accept(peerConfig{}) - if err, ok := err.(RejectedError); ok { - if !err.IsSelf() { - t.Errorf("expected to reject self, got: %v", err) + peers = append( + peers, + tr, + ) } - } else { - t.Errorf("expected RejectedError") - } -} -func TestTransportConnDuplicateIPFilter(t *testing.T) { - t.Parallel() + // Make peer 1 --dial--> peer 2, and handshake. + // This "upgrade" should fail because the peer shared a different + // peer ID than what they actually used for the secret connection + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() - filter := ConnDuplicateIPFilter() + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + assert.ErrorIs(t, err, errIncompatibleNodeInfo) + require.Nil(t, p) + }) - if err := filter(nil, &testTransportConn{}, nil); err != nil { - t.Fatal(err) - } + t.Run("dialed peer ID mismatch", func(t *testing.T) { + t.Parallel() - var ( - c = &testTransportConn{} - cs = NewConnSet() - ) + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } - cs.Set(c, []net.IP{ - {10, 0, 10, 1}, - {10, 0, 10, 2}, - {10, 0, 10, 3}, - }) + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ PeerConn, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } + ) - if err := filter(cs, c, []net.IP{ - {10, 0, 10, 2}, - }); err == nil { - t.Errorf("expected Peer to be rejected as duplicate") - } -} + peers := make([]*MultiplexTransport, 0, len(keys)) -func TestTransportHandshake(t *testing.T) { - t.Parallel() + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } + na, err := types.NewNetAddress(key.ID(), addr) + require.NoError(t, err) - var ( - peerPV = ed25519.GenPrivKey() - peerNodeInfo = testNodeInfo(peerPV.PubKey().Address().ID(), defaultNodeName) - ) + ni := types.NodeInfo{ + Network: network, // common network + PeerID: key.ID(), + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } - go func() { - c, err := net.Dial(ln.Addr().Network(), ln.Addr().String()) - if err != nil { - t.Error(err) - return - } + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) - go func(c net.Conn) { - _, err := amino.MarshalSizedWriter(c, peerNodeInfo) - if err != nil { - t.Error(err) - } - }(c) - go func(c net.Conn) { - var ni NodeInfo - - _, err := amino.UnmarshalSizedReader( - c, - &ni, - int64(MaxNodeInfoSize()), + // Start the transport + require.NoError(t, tr.Listen(*na)) + + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) + + peers = append( + peers, + tr, ) - if err != nil { - t.Error(err) - } - }(c) - }() + } - c, err := ln.Accept() - if err != nil { - t.Fatal(err) - } + // Make peer 1 --dial--> peer 2, and handshake + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() - ni, err := handshake(c, 100*time.Millisecond, emptyNodeInfo()) - if err != nil { - t.Fatal(err) - } + p, err := peers[0].Dial( + ctx, + types.NetAddress{ + ID: types.GenerateNodeKey().ID(), // mismatched ID + IP: peers[1].netAddr.IP, + Port: peers[1].netAddr.Port, + }, + peerBehavior, + ) + assert.ErrorIs(t, err, errPeerIDDialMismatch) + assert.Nil(t, p) + }) - if have, want := ni, peerNodeInfo; !reflect.DeepEqual(have, want) { - t.Errorf("have %v, want %v", have, want) - } -} + t.Run("valid peer accepted", func(t *testing.T) { + t.Parallel() -// create listener -func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { - t.Helper() + var ( + network = "dev" + mCfg = conn.DefaultMConnConfig() + logger = log.NewNoopLogger() + keys = []*types.NodeKey{ + types.GenerateNodeKey(), + types.GenerateNodeKey(), + } - var ( - pv = ed25519.GenPrivKey() - id = pv.PubKey().Address().ID() - mt = newMultiplexTransport( - testNodeInfo( - id, "transport", - ), - NodeKey{ - PrivKey: pv, - }, + peerBehavior = &reactorPeerBehavior{ + chDescs: make([]*conn.ChannelDescriptor, 0), + reactorsByCh: make(map[byte]Reactor), + handlePeerErrFn: func(_ PeerConn, err error) { + require.NoError(t, err) + }, + isPersistentPeerFn: func(_ types.ID) bool { + return false + }, + isPrivatePeerFn: func(_ types.ID) bool { + return false + }, + } ) - ) - addr, err := NewNetAddressFromString(NetAddressString(id, "127.0.0.1:0")) - if err != nil { - t.Fatal(err) - } + peers := make([]*MultiplexTransport, 0, len(keys)) - if err := mt.Listen(*addr); err != nil { - t.Fatal(err) - } + for index, key := range keys { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) - return mt -} + na, err := types.NewNetAddress(key.ID(), addr) + require.NoError(t, err) -type testTransportAddr struct{} + ni := types.NodeInfo{ + Network: network, // common network + PeerID: key.ID(), + Version: "v1.0.0-rc.0", + Moniker: fmt.Sprintf("node-%d", index), + VersionSet: make(versionset.VersionSet, 0), // compatible version set + Channels: []byte{42}, // common channel + } -func (a *testTransportAddr) Network() string { return "tcp" } -func (a *testTransportAddr) String() string { return "test.local:1234" } + // Create a fresh transport + tr := NewMultiplexTransport(ni, *key, mCfg, logger) -type testTransportConn struct{} + // Start the transport + require.NoError(t, tr.Listen(*na)) -func (c *testTransportConn) Close() error { - return fmt.Errorf("Close() not implemented") -} + t.Cleanup(func() { + assert.NoError(t, tr.Close()) + }) -func (c *testTransportConn) LocalAddr() net.Addr { - return &testTransportAddr{} -} + peers = append( + peers, + tr, + ) + } -func (c *testTransportConn) RemoteAddr() net.Addr { - return &testTransportAddr{} -} + // Make peer 1 --dial--> peer 2, and handshake + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() -func (c *testTransportConn) Read(_ []byte) (int, error) { - return -1, fmt.Errorf("Read() not implemented") -} + p, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + require.NoError(t, err) + require.NotNil(t, p) -func (c *testTransportConn) SetDeadline(_ time.Time) error { - return fmt.Errorf("SetDeadline() not implemented") -} + // Make sure the new peer info is valid + assert.Equal(t, peers[1].netAddr.ID, p.ID()) -func (c *testTransportConn) SetReadDeadline(_ time.Time) error { - return fmt.Errorf("SetReadDeadline() not implemented") -} + assert.Equal(t, peers[1].nodeInfo.Channels, p.NodeInfo().Channels) + assert.Equal(t, peers[1].nodeInfo.Moniker, p.NodeInfo().Moniker) + assert.Equal(t, peers[1].nodeInfo.Network, p.NodeInfo().Network) -func (c *testTransportConn) SetWriteDeadline(_ time.Time) error { - return fmt.Errorf("SetWriteDeadline() not implemented") -} + // Attempt to dial again, expect the dial to fail + // because the connection is already active + dialedPeer, err := peers[0].Dial(ctx, peers[1].netAddr, peerBehavior) + require.ErrorIs(t, err, errDuplicateConnection) + assert.Nil(t, dialedPeer) -func (c *testTransportConn) Write(_ []byte) (int, error) { - return -1, fmt.Errorf("Write() not implemented") + // Remove the peer + peers[0].Remove(p) + }) } diff --git a/tm2/pkg/p2p/types.go b/tm2/pkg/p2p/types.go index 150325f52bb..d206a5af662 100644 --- a/tm2/pkg/p2p/types.go +++ b/tm2/pkg/p2p/types.go @@ -1,10 +1,115 @@ package p2p import ( + "context" + "net" + "github.com/gnolang/gno/tm2/pkg/p2p/conn" + "github.com/gnolang/gno/tm2/pkg/p2p/events" + "github.com/gnolang/gno/tm2/pkg/p2p/types" + "github.com/gnolang/gno/tm2/pkg/service" ) type ( ChannelDescriptor = conn.ChannelDescriptor ConnectionStatus = conn.ConnectionStatus ) + +// PeerConn is a wrapper for a connected peer +type PeerConn interface { + service.Service + + FlushStop() + + ID() types.ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection + + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + IsPrivate() bool // do we share the peer + + CloseConn() error // close original connection + + NodeInfo() types.NodeInfo // peer's info + Status() ConnectionStatus + SocketAddr() *types.NetAddress // actual address of the socket + + Send(byte, []byte) bool + TrySend(byte, []byte) bool + + Set(string, any) + Get(string) any +} + +// PeerSet has a (immutable) subset of the methods of PeerSet. +type PeerSet interface { + Add(peer PeerConn) + Remove(key types.ID) bool + Has(key types.ID) bool + Get(key types.ID) PeerConn + List() []PeerConn + + NumInbound() uint64 // returns the number of connected inbound nodes + NumOutbound() uint64 // returns the number of connected outbound nodes +} + +// Transport handles peer dialing and connection acceptance. Additionally, +// it is also responsible for any custom connection mechanisms (like handshaking). +// Peers returned by the transport are considered to be verified and sound +type Transport interface { + // NetAddress returns the Transport's dial address + NetAddress() types.NetAddress + + // Accept returns a newly connected inbound peer + Accept(context.Context, PeerBehavior) (PeerConn, error) + + // Dial dials a peer, and returns it + Dial(context.Context, types.NetAddress, PeerBehavior) (PeerConn, error) + + // Remove drops any resources associated + // with the PeerConn in the transport + Remove(PeerConn) +} + +// Switch is the abstraction in the p2p module that handles +// and manages peer connections thorough a Transport +type Switch interface { + // Broadcast publishes data on the given channel, to all peers + Broadcast(chID byte, data []byte) + + // Peers returns the latest peer set + Peers() PeerSet + + // Subscribe subscribes to active switch events + Subscribe(filterFn events.EventFilter) (<-chan events.Event, func()) + + // StopPeerForError stops the peer with the given reason + StopPeerForError(peer PeerConn, err error) + + // DialPeers marks the given peers as ready for async dialing + DialPeers(peerAddrs ...*types.NetAddress) +} + +// PeerBehavior wraps the Reactor and MultiplexSwitch information a Transport would need when +// dialing or accepting new Peer connections. +// It is worth noting that the only reason why this information is required in the first place, +// is because Peers expose an API through which different TM modules can interact with them. +// In the future™, modules should not directly "Send" anything to Peers, but instead communicate through +// other mediums, such as the P2P module +type PeerBehavior interface { + // ReactorChDescriptors returns the Reactor channel descriptors + ReactorChDescriptors() []*conn.ChannelDescriptor + + // Reactors returns the node's active p2p Reactors (modules) + Reactors() map[byte]Reactor + + // HandlePeerError propagates a peer connection error for further processing + HandlePeerError(PeerConn, error) + + // IsPersistentPeer returns a flag indicating if the given peer is persistent + IsPersistentPeer(types.ID) bool + + // IsPrivatePeer returns a flag indicating if the given peer is private + IsPrivatePeer(types.ID) bool +} diff --git a/tm2/pkg/p2p/types/key.go b/tm2/pkg/p2p/types/key.go new file mode 100644 index 00000000000..bc45de709d8 --- /dev/null +++ b/tm2/pkg/p2p/types/key.go @@ -0,0 +1,113 @@ +package types + +import ( + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + osm "github.com/gnolang/gno/tm2/pkg/os" +) + +// ID represents the cryptographically unique Peer ID +type ID = crypto.ID + +// NewIDFromStrings returns an array of ID's build using +// the provided strings +func NewIDFromStrings(idStrs []string) ([]ID, []error) { + var ( + ids = make([]ID, 0, len(idStrs)) + errs = make([]error, 0, len(idStrs)) + ) + + for _, idStr := range idStrs { + id := ID(idStr) + if err := id.Validate(); err != nil { + errs = append(errs, err) + + continue + } + + ids = append(ids, id) + } + + return ids, errs +} + +// NodeKey is the persistent peer key. +// It contains the nodes private key for authentication. +// NOTE: keep in sync with gno.land/cmd/gnoland/secrets.go +type NodeKey struct { + crypto.PrivKey `json:"priv_key"` // our priv key +} + +// ID returns the bech32 representation +// of the node's public p2p key, with +// the bech32 prefix +func (k NodeKey) ID() ID { + return k.PubKey().Address().ID() +} + +// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. +// If the file does not exist, it generates and saves a new NodeKey. +func LoadOrGenNodeKey(path string) (*NodeKey, error) { + // Check if the key exists + if osm.FileExists(path) { + // Load the node key + return LoadNodeKey(path) + } + + // Key is not present on path, + // generate a fresh one + nodeKey := GenerateNodeKey() + if err := saveNodeKey(path, nodeKey); err != nil { + return nil, fmt.Errorf("unable to save node key, %w", err) + } + + return nodeKey, nil +} + +// LoadNodeKey loads the node key from the given path +func LoadNodeKey(path string) (*NodeKey, error) { + // Load the key + jsonBytes, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("unable to read key, %w", err) + } + + var nodeKey NodeKey + + // Parse the key + if err = amino.UnmarshalJSON(jsonBytes, &nodeKey); err != nil { + return nil, fmt.Errorf("unable to JSON unmarshal node key, %w", err) + } + + return &nodeKey, nil +} + +// GenerateNodeKey generates a random +// node P2P key, based on ed25519 +func GenerateNodeKey() *NodeKey { + privKey := ed25519.GenPrivKey() + + return &NodeKey{ + PrivKey: privKey, + } +} + +// saveNodeKey saves the node key +func saveNodeKey(path string, nodeKey *NodeKey) error { + // Get Amino JSON + marshalledData, err := amino.MarshalJSONIndent(nodeKey, "", "\t") + if err != nil { + return fmt.Errorf("unable to marshal node key into JSON, %w", err) + } + + // Save the data to disk + if err := os.WriteFile(path, marshalledData, 0o644); err != nil { + return fmt.Errorf("unable to save node key to path, %w", err) + } + + return nil +} diff --git a/tm2/pkg/p2p/types/key_test.go b/tm2/pkg/p2p/types/key_test.go new file mode 100644 index 00000000000..5dc153b08c0 --- /dev/null +++ b/tm2/pkg/p2p/types/key_test.go @@ -0,0 +1,158 @@ +package types + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateKeys generates random node p2p keys +func generateKeys(t *testing.T, count int) []*NodeKey { + t.Helper() + + keys := make([]*NodeKey, count) + + for i := 0; i < count; i++ { + keys[i] = GenerateNodeKey() + } + + return keys +} + +func TestNodeKey_Generate(t *testing.T) { + t.Parallel() + + keys := generateKeys(t, 10) + + for _, key := range keys { + require.NotNil(t, key) + assert.NotNil(t, key.PrivKey) + + // Make sure all keys are unique + for _, keyInner := range keys { + if key.ID() == keyInner.ID() { + continue + } + + assert.False(t, key.Equals(keyInner)) + } + } +} + +func TestNodeKey_Load(t *testing.T) { + t.Parallel() + + t.Run("non-existing key", func(t *testing.T) { + t.Parallel() + + key, err := LoadNodeKey("definitely valid path") + + require.Nil(t, key) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("invalid key format", func(t *testing.T) { + t.Parallel() + + // Generate a random path + path := fmt.Sprintf("%s/key.json", t.TempDir()) + + type random struct { + field string + } + + data, err := json.Marshal(&random{ + field: "random data", + }) + require.NoError(t, err) + + // Save the invalid data format + require.NoError(t, os.WriteFile(path, data, 0o644)) + + // Load the key, that's invalid + key, err := LoadNodeKey(path) + + require.NoError(t, err) + assert.Nil(t, key.PrivKey) + }) + + t.Run("valid key loaded", func(t *testing.T) { + t.Parallel() + + var ( + path = fmt.Sprintf("%s/key.json", t.TempDir()) + key = GenerateNodeKey() + ) + + // Save the key + require.NoError(t, saveNodeKey(path, key)) + + // Load the key, that's valid + loadedKey, err := LoadNodeKey(path) + require.NoError(t, err) + + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + assert.Equal(t, key.ID(), loadedKey.ID()) + }) +} + +func TestNodeKey_ID(t *testing.T) { + t.Parallel() + + keys := generateKeys(t, 10) + + for _, key := range keys { + // Make sure the ID is valid + id := key.ID() + require.NotNil(t, id) + + assert.NoError(t, id.Validate()) + } +} + +func TestNodeKey_LoadOrGenNodeKey(t *testing.T) { + t.Parallel() + + t.Run("existing key loaded", func(t *testing.T) { + t.Parallel() + + var ( + path = fmt.Sprintf("%s/key.json", t.TempDir()) + key = GenerateNodeKey() + ) + + // Save the key + require.NoError(t, saveNodeKey(path, key)) + + loadedKey, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Make sure the key was not generated + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + }) + + t.Run("fresh key generated", func(t *testing.T) { + t.Parallel() + + path := fmt.Sprintf("%s/key.json", t.TempDir()) + + // Make sure there is no key at the path + _, err := os.Stat(path) + require.ErrorIs(t, err, os.ErrNotExist) + + // Generate the fresh key + key, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Load the saved key + loadedKey, err := LoadOrGenNodeKey(path) + require.NoError(t, err) + + // Make sure the keys are the same + assert.True(t, key.PrivKey.Equals(loadedKey.PrivKey)) + }) +} diff --git a/tm2/pkg/p2p/netaddress.go b/tm2/pkg/p2p/types/netaddress.go similarity index 52% rename from tm2/pkg/p2p/netaddress.go rename to tm2/pkg/p2p/types/netaddress.go index 77f89b2a4b3..a43f90454ea 100644 --- a/tm2/pkg/p2p/netaddress.go +++ b/tm2/pkg/p2p/types/netaddress.go @@ -2,143 +2,156 @@ // Originally Copyright (c) 2013-2014 Conformal Systems LLC. // https://github.com/conformal/btcd/blob/master/LICENSE -package p2p +package types import ( - "flag" + "context" "fmt" "net" "strconv" "strings" - "time" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" ) -type ID = crypto.ID +const ( + nilNetAddress = "" + badNetAddress = "" +) + +var ( + ErrInvalidTCPAddress = errors.New("invalid TCP address") + ErrUnsetIPAddress = errors.New("unset IP address") + ErrInvalidIP = errors.New("invalid IP address") + ErrUnspecifiedIP = errors.New("unspecified IP address") + ErrInvalidNetAddress = errors.New("invalid net address") + ErrEmptyHost = errors.New("empty host address") +) // NetAddress defines information about a peer on the network -// including its Address, IP address, and port. -// NOTE: NetAddress is not meant to be mutated due to memoization. -// @amino2: immutable XXX +// including its ID, IP address, and port type NetAddress struct { - ID ID `json:"id"` // authenticated identifier (TODO) - IP net.IP `json:"ip"` // part of "addr" - Port uint16 `json:"port"` // part of "addr" - - // TODO: - // Name string `json:"name"` // optional DNS name - - // memoize .String() - str string + ID ID `json:"id"` // unique peer identifier (public key address) + IP net.IP `json:"ip"` // the IP part of the dial address + Port uint16 `json:"port"` // the port part of the dial address } // NetAddressString returns id@addr. It strips the leading // protocol from protocolHostPort if it exists. func NetAddressString(id ID, protocolHostPort string) string { - addr := removeProtocolIfDefined(protocolHostPort) - return fmt.Sprintf("%s@%s", id, addr) + return fmt.Sprintf( + "%s@%s", + id, + removeProtocolIfDefined(protocolHostPort), + ) } // NewNetAddress returns a new NetAddress using the provided TCP -// address. When testing, other net.Addr (except TCP) will result in -// using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will -// panic. Panics if ID is invalid. -// TODO: socks proxies? -func NewNetAddress(id ID, addr net.Addr) *NetAddress { +// address +func NewNetAddress(id ID, addr net.Addr) (*NetAddress, error) { + // Make sure the address is valid tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - if flag.Lookup("test.v") == nil { // normal run - panic(fmt.Sprintf("Only TCPAddrs are supported. Got: %v", addr)) - } else { // in testing - netAddr := NewNetAddressFromIPPort("", net.IP("0.0.0.0"), 0) - netAddr.ID = id - return netAddr - } + return nil, ErrInvalidTCPAddress } + // Validate the ID if err := id.Validate(); err != nil { - panic(fmt.Sprintf("Invalid ID %v: %v (addr: %v)", id, err, addr)) + return nil, fmt.Errorf("unable to verify ID, %w", err) } - ip := tcpAddr.IP - port := uint16(tcpAddr.Port) - na := NewNetAddressFromIPPort("", ip, port) + na := NewNetAddressFromIPPort( + tcpAddr.IP, + uint16(tcpAddr.Port), + ) + + // Set the ID na.ID = id - return na + + return na, nil } // NewNetAddressFromString returns a new NetAddress using the provided address in // the form of "ID@IP:Port". // Also resolves the host if host is not an IP. -// Errors are of type ErrNetAddressXxx where Xxx is in (NoID, Invalid, Lookup) func NewNetAddressFromString(idaddr string) (*NetAddress, error) { - idaddr = removeProtocolIfDefined(idaddr) - spl := strings.Split(idaddr, "@") + var ( + prunedAddr = removeProtocolIfDefined(idaddr) + spl = strings.Split(prunedAddr, "@") + ) + if len(spl) != 2 { - return nil, NetAddressNoIDError{idaddr} + return nil, ErrInvalidNetAddress } - // get ID - id := crypto.ID(spl[0]) + var ( + id = crypto.ID(spl[0]) + addr = spl[1] + ) + + // Validate the ID if err := id.Validate(); err != nil { - return nil, NetAddressInvalidError{idaddr, err} + return nil, fmt.Errorf("unable to verify address ID, %w", err) } - addr := spl[1] - // get host and port + // Extract the host and port host, portStr, err := net.SplitHostPort(addr) if err != nil { - return nil, NetAddressInvalidError{addr, err} + return nil, fmt.Errorf("unable to split host and port, %w", err) } - if len(host) == 0 { - return nil, NetAddressInvalidError{ - addr, - errors.New("host is empty"), - } + + if host == "" { + return nil, ErrEmptyHost } ip := net.ParseIP(host) if ip == nil { ips, err := net.LookupIP(host) if err != nil { - return nil, NetAddressLookupError{host, err} + return nil, fmt.Errorf("unable to look up IP, %w", err) } + ip = ips[0] } port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - return nil, NetAddressInvalidError{portStr, err} + return nil, fmt.Errorf("unable to parse port %s, %w", portStr, err) } - na := NewNetAddressFromIPPort("", ip, uint16(port)) + na := NewNetAddressFromIPPort(ip, uint16(port)) na.ID = id + return na, nil } // NewNetAddressFromStrings returns an array of NetAddress'es build using // the provided strings. func NewNetAddressFromStrings(idaddrs []string) ([]*NetAddress, []error) { - netAddrs := make([]*NetAddress, 0) - errs := make([]error, 0) + var ( + netAddrs = make([]*NetAddress, 0, len(idaddrs)) + errs = make([]error, 0, len(idaddrs)) + ) + for _, addr := range idaddrs { netAddr, err := NewNetAddressFromString(addr) if err != nil { errs = append(errs, err) - } else { - netAddrs = append(netAddrs, netAddr) + + continue } + + netAddrs = append(netAddrs, netAddr) } + return netAddrs, errs } // NewNetAddressFromIPPort returns a new NetAddress using the provided IP // and port number. -func NewNetAddressFromIPPort(id ID, ip net.IP, port uint16) *NetAddress { +func NewNetAddressFromIPPort(ip net.IP, port uint16) *NetAddress { return &NetAddress{ - ID: id, IP: ip, Port: port, } @@ -146,88 +159,78 @@ func NewNetAddressFromIPPort(id ID, ip net.IP, port uint16) *NetAddress { // Equals reports whether na and other are the same addresses, // including their ID, IP, and Port. -func (na *NetAddress) Equals(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - return na.String() == o.String() - } - return false +func (na *NetAddress) Equals(other NetAddress) bool { + return na.String() == other.String() } // Same returns true is na has the same non-empty ID or DialString as other. -func (na *NetAddress) Same(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - if na.DialString() == o.DialString() { - return true - } - if na.ID != "" && na.ID == o.ID { - return true - } - } - return false +func (na *NetAddress) Same(other NetAddress) bool { + var ( + dialsSame = na.DialString() == other.DialString() + IDsSame = na.ID != "" && na.ID == other.ID + ) + + return dialsSame || IDsSame } // String representation: @: func (na *NetAddress) String() string { if na == nil { - return "" - } - if na.str != "" { - return na.str + return nilNetAddress } + str, err := na.MarshalAmino() if err != nil { - return "" + return badNetAddress } + return str } +// MarshalAmino stringifies a NetAddress. // Needed because (a) IP doesn't encode, and (b) the intend of this type is to // serialize to a string anyways. func (na NetAddress) MarshalAmino() (string, error) { - if na.str == "" { - addrStr := na.DialString() - if na.ID != "" { - addrStr = NetAddressString(na.ID, addrStr) - } - na.str = addrStr + addrStr := na.DialString() + + if na.ID != "" { + return NetAddressString(na.ID, addrStr), nil } - return na.str, nil + + return addrStr, nil } -func (na *NetAddress) UnmarshalAmino(str string) (err error) { - na2, err := NewNetAddressFromString(str) +func (na *NetAddress) UnmarshalAmino(raw string) (err error) { + netAddress, err := NewNetAddressFromString(raw) if err != nil { return err } - *na = *na2 + + *na = *netAddress + return nil } func (na *NetAddress) DialString() string { if na == nil { - return "" + return nilNetAddress } + return net.JoinHostPort( na.IP.String(), strconv.FormatUint(uint64(na.Port), 10), ) } -// Dial calls net.Dial on the address. -func (na *NetAddress) Dial() (net.Conn, error) { - conn, err := net.Dial("tcp", na.DialString()) - if err != nil { - return nil, err - } - return conn, nil -} +// DialContext dials the given NetAddress with a context +func (na *NetAddress) DialContext(ctx context.Context) (net.Conn, error) { + var d net.Dialer -// DialTimeout calls net.DialTimeout on the address. -func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { - conn, err := net.DialTimeout("tcp", na.DialString(), timeout) + conn, err := d.DialContext(ctx, "tcp", na.DialString()) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to dial address, %w", err) } + return conn, nil } @@ -236,47 +239,45 @@ func (na *NetAddress) Routable() bool { if err := na.Validate(); err != nil { return false } + // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? - return !(na.RFC1918() || na.RFC3927() || na.RFC4862() || - na.RFC4193() || na.RFC4843() || na.Local()) + return !(na.RFC1918() || + na.RFC3927() || + na.RFC4862() || + na.RFC4193() || + na.RFC4843() || + na.Local()) } -func (na *NetAddress) ValidateLocal() error { +// Validate validates the NetAddress params +func (na *NetAddress) Validate() error { + // Validate the ID if err := na.ID.Validate(); err != nil { - return err + return fmt.Errorf("unable to validate ID, %w", err) } + + // Make sure the IP is set if na.IP == nil { - return errors.New("no IP") - } - if len(na.IP) != 4 && len(na.IP) != 16 { - return fmt.Errorf("invalid IP bytes: %v", len(na.IP)) - } - if na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errors.New("invalid IP", na.IP.IsUnspecified()) + return ErrUnsetIPAddress } - return nil -} -func (na *NetAddress) Validate() error { - if err := na.ID.Validate(); err != nil { - return err - } - if na.IP == nil { - return errors.New("no IP") + // Make sure the IP is valid + ipLen := len(na.IP) + if ipLen != 4 && ipLen != 16 { + return ErrInvalidIP } - if len(na.IP) != 4 && len(na.IP) != 16 { - return fmt.Errorf("invalid IP bytes: %v", len(na.IP)) + + // Check if the IP is unspecified + if na.IP.IsUnspecified() { + return ErrUnspecifiedIP } - if na.IP.IsUnspecified() || na.RFC3849() || na.IP.Equal(net.IPv4bcast) { - return errors.New("invalid IP", na.IP.IsUnspecified()) + + // Check if the IP conforms to standards, or is a broadcast + if na.RFC3849() || na.IP.Equal(net.IPv4bcast) { + return ErrInvalidIP } - return nil -} -// HasID returns true if the address has an ID. -// NOTE: It does not check whether the ID is valid or not. -func (na *NetAddress) HasID() bool { - return !na.ID.IsZero() + return nil } // Local returns true if it is a local address. @@ -284,56 +285,6 @@ func (na *NetAddress) Local() bool { return na.IP.IsLoopback() || zero4.Contains(na.IP) } -// ReachabilityTo checks whenever o can be reached from na. -func (na *NetAddress) ReachabilityTo(o *NetAddress) int { - const ( - Unreachable = 0 - Default = iota - Teredo - Ipv6_weak - Ipv4 - Ipv6_strong - ) - switch { - case !na.Routable(): - return Unreachable - case na.RFC4380(): - switch { - case !o.Routable(): - return Default - case o.RFC4380(): - return Teredo - case o.IP.To4() != nil: - return Ipv4 - default: // ipv6 - return Ipv6_weak - } - case na.IP.To4() != nil: - if o.Routable() && o.IP.To4() != nil { - return Ipv4 - } - return Default - default: /* ipv6 */ - var tunnelled bool - // Is our v6 is tunnelled? - if o.RFC3964() || o.RFC6052() || o.RFC6145() { - tunnelled = true - } - switch { - case !o.Routable(): - return Default - case o.RFC4380(): - return Teredo - case o.IP.To4() != nil: - return Ipv4 - case tunnelled: - // only prioritise ipv6 if we aren't tunnelling it. - return Ipv6_weak - } - return Ipv6_strong - } -} - // RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) // RFC3849: IPv6 Documentation address (2001:0DB8::/32) // RFC3927: IPv4 Autoconfig (169.254.0.0/16) @@ -376,9 +327,12 @@ func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) } func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) } func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) } +// removeProtocolIfDefined removes the protocol part of the given address func removeProtocolIfDefined(addr string) string { - if strings.Contains(addr, "://") { - return strings.Split(addr, "://")[1] + if !strings.Contains(addr, "://") { + // No protocol part + return addr } - return addr + + return strings.Split(addr, "://")[1] } diff --git a/tm2/pkg/p2p/types/netaddress_test.go b/tm2/pkg/p2p/types/netaddress_test.go new file mode 100644 index 00000000000..1f8f0229b99 --- /dev/null +++ b/tm2/pkg/p2p/types/netaddress_test.go @@ -0,0 +1,323 @@ +package types + +import ( + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkNetAddress_String(b *testing.B) { + key := GenerateNodeKey() + + na, err := NewNetAddressFromString(NetAddressString(key.ID(), "127.0.0.1:0")) + require.NoError(b, err) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = na.String() + } +} + +func TestNewNetAddress(t *testing.T) { + t.Parallel() + + t.Run("invalid TCP address", func(t *testing.T) { + t.Parallel() + + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + udpAddr, err := net.ResolveUDPAddr("udp", address) + require.NoError(t, err) + + _, err = NewNetAddress(key.ID(), udpAddr) + require.Error(t, err) + }) + + t.Run("invalid ID", func(t *testing.T) { + t.Parallel() + + var ( + id = "" // zero ID + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + _, err = NewNetAddress(ID(id), tcpAddr) + require.Error(t, err) + }) + + t.Run("valid net address", func(t *testing.T) { + t.Parallel() + + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + assert.Equal(t, fmt.Sprintf("%s@%s", key.ID(), address), addr.String()) + }) +} + +func TestNewNetAddressFromString(t *testing.T) { + t.Parallel() + + t.Run("valid net address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + expected string + }{ + {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"no protocol", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"tcp input", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"udp input", "udp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"correct nodeId w/tcp", "tcp://g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + addr, err := NewNetAddressFromString(testCase.addr) + require.NoError(t, err) + + assert.Equal(t, testCase.expected, addr.String()) + }) + } + }) + + t.Run("invalid net address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + }{ + {"no node id and no protocol", "127.0.0.1:8080"}, + {"no node id w/ tcp input", "tcp://127.0.0.1:8080"}, + {"no node id w/ udp input", "udp://127.0.0.1:8080"}, + + {"malformed tcp input", "tcp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + {"malformed udp input", "udp//g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080"}, + + {"invalid host", "notahost"}, + {"invalid port", "127.0.0.1:notapath"}, + {"invalid host w/ port", "notahost:8080"}, + {"just a port", "8082"}, + {"non-existent port", "127.0.0:8080000"}, + + {"too short nodeId", "deadbeef@127.0.0.1:8080"}, + {"too short, not hex nodeId", "this-isnot-hex@127.0.0.1:8080"}, + {"not bech32 nodeId", "xxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080"}, + + {"too short nodeId w/tcp", "tcp://deadbeef@127.0.0.1:8080"}, + {"too short notHex nodeId w/tcp", "tcp://this-isnot-hex@127.0.0.1:8080"}, + {"not bech32 nodeId w/tcp", "tcp://xxxxm6kmam774klwlh4dhmhaatd7al02m0h0hdap9l@127.0.0.1:8080"}, + + {"no node id", "tcp://@127.0.0.1:8080"}, + {"no node id or IP", "tcp://@"}, + {"tcp no host, w/ port", "tcp://:26656"}, + {"empty", ""}, + {"node id delimiter 1", "@"}, + {"node id delimiter 2", " @"}, + {"node id delimiter 3", " @ "}, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + addr, err := NewNetAddressFromString(testCase.addr) + + assert.Nil(t, addr) + assert.Error(t, err) + }) + } + }) +} + +func TestNewNetAddressFromStrings(t *testing.T) { + t.Parallel() + + t.Run("invalid addresses", func(t *testing.T) { + t.Parallel() + + var ( + keys = generateKeys(t, 10) + strs = make([]string, 0, len(keys)) + ) + + for index, key := range keys { + if index%2 != 0 { + strs = append( + strs, + fmt.Sprintf("%s@:8080", key.ID()), // missing host + ) + + continue + } + + strs = append( + strs, + fmt.Sprintf("%s@127.0.0.1:8080", key.ID()), + ) + } + + // Convert the strings + addrs, errs := NewNetAddressFromStrings(strs) + + assert.Len(t, errs, len(keys)/2) + assert.Equal(t, len(keys)/2, len(addrs)) + + for index, addr := range addrs { + assert.Contains(t, addr.String(), keys[index*2].ID()) + } + }) + + t.Run("valid addresses", func(t *testing.T) { + t.Parallel() + + var ( + keys = generateKeys(t, 10) + strs = make([]string, 0, len(keys)) + ) + + for _, key := range keys { + strs = append( + strs, + fmt.Sprintf("%s@127.0.0.1:8080", key.ID()), + ) + } + + // Convert the strings + addrs, errs := NewNetAddressFromStrings(strs) + + assert.Len(t, errs, 0) + assert.Equal(t, len(keys), len(addrs)) + + for index, addr := range addrs { + assert.Contains(t, addr.String(), keys[index].ID()) + } + }) +} + +func TestNewNetAddressFromIPPort(t *testing.T) { + t.Parallel() + + var ( + host = "127.0.0.1" + port = uint16(8080) + ) + + addr := NewNetAddressFromIPPort(net.ParseIP(host), port) + + assert.Equal( + t, + fmt.Sprintf("%s:%d", host, port), + addr.String(), + ) +} + +func TestNetAddress_Local(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + isLocal bool + }{ + { + "local loopback", + "127.0.0.1:8080", + true, + }, + { + "local loopback, zero", + "0.0.0.0:8080", + true, + }, + { + "non-local address", + "200.100.200.100:8080", + false, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + key := GenerateNodeKey() + + addr, err := NewNetAddressFromString( + fmt.Sprintf( + "%s@%s", + key.ID(), + testCase.addr, + ), + ) + require.NoError(t, err) + + assert.Equal(t, testCase.isLocal, addr.Local()) + }) + } +} + +func TestNetAddress_Routable(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + addr string + isRoutable bool + }{ + { + "local loopback", + "127.0.0.1:8080", + false, + }, + { + "routable address", + "gno.land:80", + true, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + key := GenerateNodeKey() + + addr, err := NewNetAddressFromString( + fmt.Sprintf( + "%s@%s", + key.ID(), + testCase.addr, + ), + ) + require.NoError(t, err) + + assert.Equal(t, testCase.isRoutable, addr.Routable()) + }) + } +} diff --git a/tm2/pkg/p2p/types/node_info.go b/tm2/pkg/p2p/types/node_info.go new file mode 100644 index 00000000000..8452cb43cb8 --- /dev/null +++ b/tm2/pkg/p2p/types/node_info.go @@ -0,0 +1,141 @@ +package types + +import ( + "errors" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/strings" + "github.com/gnolang/gno/tm2/pkg/versionset" +) + +const ( + MaxNodeInfoSize = int64(10240) // 10KB + maxNumChannels = 16 // plenty of room for upgrades, for now +) + +var ( + ErrInvalidPeerID = errors.New("invalid peer ID") + ErrInvalidVersion = errors.New("invalid node version") + ErrInvalidMoniker = errors.New("invalid node moniker") + ErrInvalidRPCAddress = errors.New("invalid node RPC address") + ErrExcessiveChannels = errors.New("excessive node channels") + ErrDuplicateChannels = errors.New("duplicate node channels") + ErrIncompatibleNetworks = errors.New("incompatible networks") + ErrNoCommonChannels = errors.New("no common channels") +) + +// NodeInfo is the basic node information exchanged +// between two peers during the Tendermint P2P handshake. +type NodeInfo struct { + // Set of protocol versions + VersionSet versionset.VersionSet `json:"version_set"` + + // Unique peer identifier + PeerID ID `json:"id"` + + // Check compatibility. + // Channels are HexBytes so easier to read as JSON + Network string `json:"network"` // network/chain ID + Software string `json:"software"` // name of immediate software + Version string `json:"version"` // software major.minor.revision + Channels []byte `json:"channels"` // channels this node knows about + + // ASCIIText fields + Moniker string `json:"moniker"` // arbitrary moniker + Other NodeInfoOther `json:"other"` // other application specific data +} + +// NodeInfoOther is the misc. application specific data +type NodeInfoOther struct { + TxIndex string `json:"tx_index"` + RPCAddress string `json:"rpc_address"` +} + +// Validate checks the self-reported NodeInfo is safe. +// It returns an error if there +// are too many Channels, if there are any duplicate Channels, +// if the ListenAddr is malformed, or if the ListenAddr is a host name +// that can not be resolved to some IP +func (info NodeInfo) Validate() error { + // Validate the ID + if err := info.PeerID.Validate(); err != nil { + return fmt.Errorf("%w, %w", ErrInvalidPeerID, err) + } + + // Validate Version + if len(info.Version) > 0 && + (!strings.IsASCIIText(info.Version) || + strings.ASCIITrim(info.Version) == "") { + return ErrInvalidVersion + } + + // Validate Channels - ensure max and check for duplicates. + if len(info.Channels) > maxNumChannels { + return ErrExcessiveChannels + } + + channelMap := make(map[byte]struct{}, len(info.Channels)) + for _, ch := range info.Channels { + if _, ok := channelMap[ch]; ok { + return ErrDuplicateChannels + } + + // Mark the channel as present + channelMap[ch] = struct{}{} + } + + // Validate Moniker. + if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" { + return ErrInvalidMoniker + } + + // XXX: Should we be more strict about address formats? + rpcAddr := info.Other.RPCAddress + if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") { + return ErrInvalidRPCAddress + } + + return nil +} + +// ID returns the local node ID +func (info NodeInfo) ID() ID { + return info.PeerID +} + +// CompatibleWith checks if two NodeInfo are compatible with each other. +// CONTRACT: two nodes are compatible if the Block version and networks match, +// and they have at least one channel in common +func (info NodeInfo) CompatibleWith(other NodeInfo) error { + // Validate the protocol versions + if _, err := info.VersionSet.CompatibleWith(other.VersionSet); err != nil { + return fmt.Errorf("incompatible version sets, %w", err) + } + + // Make sure nodes are on the same network + if info.Network != other.Network { + return ErrIncompatibleNetworks + } + + // Make sure there is at least 1 channel in common + commonFound := false + for _, ch1 := range info.Channels { + for _, ch2 := range other.Channels { + if ch1 == ch2 { + commonFound = true + + break + } + } + + if commonFound { + break + } + } + + if !commonFound { + return ErrNoCommonChannels + } + + return nil +} diff --git a/tm2/pkg/p2p/types/node_info_test.go b/tm2/pkg/p2p/types/node_info_test.go new file mode 100644 index 00000000000..d03d77e608f --- /dev/null +++ b/tm2/pkg/p2p/types/node_info_test.go @@ -0,0 +1,321 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/gnolang/gno/tm2/pkg/versionset" + "github.com/stretchr/testify/assert" +) + +func TestNodeInfo_Validate(t *testing.T) { + t.Parallel() + + t.Run("invalid peer ID", func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: "", // zero + } + + assert.ErrorIs(t, info.Validate(), ErrInvalidPeerID) + }) + + t.Run("invalid version", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + version string + }{ + { + "non-ascii version", + "¢§µ", + }, + { + "empty tab version", + fmt.Sprintf("\t"), + }, + { + "empty space version", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: GenerateNodeKey().ID(), + Version: testCase.version, + } + + assert.ErrorIs(t, info.Validate(), ErrInvalidVersion) + }) + } + }) + + t.Run("invalid moniker", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + moniker string + }{ + { + "empty moniker", + "", + }, + { + "non-ascii moniker", + "¢§µ", + }, + { + "empty tab moniker", + fmt.Sprintf("\t"), + }, + { + "empty space moniker", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: GenerateNodeKey().ID(), + Moniker: testCase.moniker, + } + + assert.ErrorIs(t, info.Validate(), ErrInvalidMoniker) + }) + } + }) + + t.Run("invalid RPC Address", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + rpcAddress string + }{ + { + "non-ascii moniker", + "¢§µ", + }, + { + "empty tab RPC address", + fmt.Sprintf("\t"), + }, + { + "empty space RPC address", + fmt.Sprintf(" "), + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: GenerateNodeKey().ID(), + Moniker: "valid moniker", + Other: NodeInfoOther{ + RPCAddress: testCase.rpcAddress, + }, + } + + assert.ErrorIs(t, info.Validate(), ErrInvalidRPCAddress) + }) + } + }) + + t.Run("invalid channels", func(t *testing.T) { + t.Parallel() + + testTable := []struct { + name string + channels []byte + expectedErr error + }{ + { + "too many channels", + make([]byte, maxNumChannels+1), + ErrExcessiveChannels, + }, + { + "duplicate channels", + []byte{ + byte(10), + byte(20), + byte(10), + }, + ErrDuplicateChannels, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: GenerateNodeKey().ID(), + Moniker: "valid moniker", + Channels: testCase.channels, + } + + assert.ErrorIs(t, info.Validate(), testCase.expectedErr) + }) + } + }) + + t.Run("valid node info", func(t *testing.T) { + t.Parallel() + + info := &NodeInfo{ + PeerID: GenerateNodeKey().ID(), + Moniker: "valid moniker", + Channels: []byte{10, 20, 30}, + Other: NodeInfoOther{ + RPCAddress: "0.0.0.0:26657", + }, + } + + assert.NoError(t, info.Validate()) + }) +} + +func TestNodeInfo_CompatibleWith(t *testing.T) { + t.Parallel() + + t.Run("incompatible version sets", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + + infoOne = &NodeInfo{ + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: "badversion", + }, + }, + } + + infoTwo = &NodeInfo{ + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: "v0.0.0", + }, + }, + } + ) + + assert.Error(t, infoTwo.CompatibleWith(*infoOne)) + }) + + t.Run("incompatible networks", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + + infoOne = &NodeInfo{ + Network: "+wrong", + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + } + + infoTwo = &NodeInfo{ + Network: "gno", + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + } + ) + + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), ErrIncompatibleNetworks) + }) + + t.Run("no common channels", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + network = "gno" + + infoOne = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: []byte{10}, + } + + infoTwo = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: []byte{20}, + } + ) + + assert.ErrorIs(t, infoTwo.CompatibleWith(*infoOne), ErrNoCommonChannels) + }) + + t.Run("fully compatible node infos", func(t *testing.T) { + t.Parallel() + + var ( + name = "Block" + version = "v0.0.0" + network = "gno" + channels = []byte{10, 20, 30} + + infoOne = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: channels, + } + + infoTwo = &NodeInfo{ + Network: network, + VersionSet: []versionset.VersionInfo{ + { + Name: name, + Version: version, + }, + }, + Channels: channels[1:], + } + ) + + assert.NoError(t, infoTwo.CompatibleWith(*infoOne)) + }) +} diff --git a/tm2/pkg/p2p/upnp/probe.go b/tm2/pkg/p2p/upnp/probe.go deleted file mode 100644 index 29480e7cecc..00000000000 --- a/tm2/pkg/p2p/upnp/probe.go +++ /dev/null @@ -1,110 +0,0 @@ -package upnp - -import ( - "fmt" - "log/slog" - "net" - "time" -) - -type UPNPCapabilities struct { - PortMapping bool - Hairpin bool -} - -func makeUPNPListener(intPort int, extPort int, logger *slog.Logger) (NAT, net.Listener, net.IP, error) { - nat, err := Discover() - if err != nil { - return nil, nil, nil, fmt.Errorf("NAT upnp could not be discovered: %w", err) - } - logger.Info(fmt.Sprintf("ourIP: %v", nat.(*upnpNAT).ourIP)) - - ext, err := nat.GetExternalAddress() - if err != nil { - return nat, nil, nil, fmt.Errorf("external address error: %w", err) - } - logger.Info(fmt.Sprintf("External address: %v", ext)) - - port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0) - if err != nil { - return nat, nil, ext, fmt.Errorf("port mapping error: %w", err) - } - logger.Info(fmt.Sprintf("Port mapping mapped: %v", port)) - - // also run the listener, open for all remote addresses. - listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort)) - if err != nil { - return nat, nil, ext, fmt.Errorf("error establishing listener: %w", err) - } - return nat, listener, ext, nil -} - -func testHairpin(listener net.Listener, extAddr string, logger *slog.Logger) (supportsHairpin bool) { - // Listener - go func() { - inConn, err := listener.Accept() - if err != nil { - logger.Info(fmt.Sprintf("Listener.Accept() error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr())) - buf := make([]byte, 1024) - n, err := inConn.Read(buf) - if err != nil { - logger.Info(fmt.Sprintf("Incoming connection read error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Incoming connection read %v bytes: %X", n, buf)) - if string(buf) == "test data" { - supportsHairpin = true - return - } - }() - - // Establish outgoing - outConn, err := net.Dial("tcp", extAddr) - if err != nil { - logger.Info(fmt.Sprintf("Outgoing connection dial error: %v", err)) - return - } - - n, err := outConn.Write([]byte("test data")) - if err != nil { - logger.Info(fmt.Sprintf("Outgoing connection write error: %v", err)) - return - } - logger.Info(fmt.Sprintf("Outgoing connection wrote %v bytes", n)) - - // Wait for data receipt - time.Sleep(1 * time.Second) - return supportsHairpin -} - -func Probe(logger *slog.Logger) (caps UPNPCapabilities, err error) { - logger.Info("Probing for UPnP!") - - intPort, extPort := 8001, 8001 - - nat, listener, ext, err := makeUPNPListener(intPort, extPort, logger) - if err != nil { - return - } - caps.PortMapping = true - - // Deferred cleanup - defer func() { - if err := nat.DeletePortMapping("tcp", intPort, extPort); err != nil { - logger.Error(fmt.Sprintf("Port mapping delete error: %v", err)) - } - if err := listener.Close(); err != nil { - logger.Error(fmt.Sprintf("Listener closing error: %v", err)) - } - }() - - supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort), logger) - if supportsHairpin { - caps.Hairpin = true - } - - return -} diff --git a/tm2/pkg/p2p/upnp/upnp.go b/tm2/pkg/p2p/upnp/upnp.go deleted file mode 100644 index cd47ac35553..00000000000 --- a/tm2/pkg/p2p/upnp/upnp.go +++ /dev/null @@ -1,392 +0,0 @@ -// Taken from taipei-torrent. -// Just enough UPnP to be able to forward ports -// For more information, see: http://www.upnp-hacks.org/upnp.html -package upnp - -// TODO: use syscalls to get actual ourIP, see issue #712 - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "time" -) - -type upnpNAT struct { - serviceURL string - ourIP string - urnDomain string -} - -// protocol is either "udp" or "tcp" -type NAT interface { - GetExternalAddress() (addr net.IP, err error) - AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) - DeletePortMapping(protocol string, externalPort, internalPort int) (err error) -} - -func Discover() (nat NAT, err error) { - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return - } - conn, err := net.ListenPacket("udp4", ":0") - if err != nil { - return - } - socket := conn.(*net.UDPConn) - defer socket.Close() //nolint: errcheck - - if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil { - return nil, err - } - - st := "InternetGatewayDevice:1" - - buf := bytes.NewBufferString( - "M-SEARCH * HTTP/1.1\r\n" + - "HOST: 239.255.255.250:1900\r\n" + - "ST: ssdp:all\r\n" + - "MAN: \"ssdp:discover\"\r\n" + - "MX: 2\r\n\r\n") - message := buf.Bytes() - answerBytes := make([]byte, 1024) - for i := 0; i < 3; i++ { - _, err = socket.WriteToUDP(message, ssdp) - if err != nil { - return - } - var n int - _, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - return - } - for { - n, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - break - } - answer := string(answerBytes[0:n]) - if !strings.Contains(answer, st) { - continue - } - // HTTP header field names are case-insensitive. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - locString := "\r\nlocation:" - answer = strings.ToLower(answer) - locIndex := strings.Index(answer, locString) - if locIndex < 0 { - continue - } - loc := answer[locIndex+len(locString):] - endIndex := strings.Index(loc, "\r\n") - if endIndex < 0 { - continue - } - locURL := strings.TrimSpace(loc[0:endIndex]) - var serviceURL, urnDomain string - serviceURL, urnDomain, err = getServiceURL(locURL) - if err != nil { - return - } - var ourIP net.IP - ourIP, err = localIPv4() - if err != nil { - return - } - nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain} - return - } - } - err = errors.New("UPnP port discovery failed") - return nat, err -} - -type Envelope struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` - Soap *SoapBody -} - -type SoapBody struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` - ExternalIP *ExternalIPAddressResponse -} - -type ExternalIPAddressResponse struct { - XMLName xml.Name `xml:"GetExternalIPAddressResponse"` - IPAddress string `xml:"NewExternalIPAddress"` -} - -type ExternalIPAddress struct { - XMLName xml.Name `xml:"NewExternalIPAddress"` - IP string -} - -type UPNPService struct { - ServiceType string `xml:"serviceType"` - ControlURL string `xml:"controlURL"` -} - -type DeviceList struct { - Device []Device `xml:"device"` -} - -type ServiceList struct { - Service []UPNPService `xml:"service"` -} - -type Device struct { - XMLName xml.Name `xml:"device"` - DeviceType string `xml:"deviceType"` - DeviceList DeviceList `xml:"deviceList"` - ServiceList ServiceList `xml:"serviceList"` -} - -type Root struct { - Device Device -} - -func getChildDevice(d *Device, deviceType string) *Device { - dl := d.DeviceList.Device - for i := 0; i < len(dl); i++ { - if strings.Contains(dl[i].DeviceType, deviceType) { - return &dl[i] - } - } - return nil -} - -func getChildService(d *Device, serviceType string) *UPNPService { - sl := d.ServiceList.Service - for i := 0; i < len(sl); i++ { - if strings.Contains(sl[i].ServiceType, serviceType) { - return &sl[i] - } - } - return nil -} - -func localIPv4() (net.IP, error) { - tt, err := net.Interfaces() - if err != nil { - return nil, err - } - for _, t := range tt { - aa, err := t.Addrs() - if err != nil { - return nil, err - } - for _, a := range aa { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - v4 := ipnet.IP.To4() - if v4 == nil || v4[0] == 127 { // loopback address - continue - } - return v4, nil - } - } - return nil, errors.New("cannot find local IP address") -} - -func getServiceURL(rootURL string) (url, urnDomain string, err error) { - r, err := http.Get(rootURL) //nolint: gosec - if err != nil { - return - } - defer r.Body.Close() //nolint: errcheck - - if r.StatusCode >= 400 { - err = errors.New(fmt.Sprint(r.StatusCode)) - return - } - var root Root - err = xml.NewDecoder(r.Body).Decode(&root) - if err != nil { - return - } - a := &root.Device - if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") { - err = errors.New("no InternetGatewayDevice") - return - } - b := getChildDevice(a, "WANDevice:1") - if b == nil { - err = errors.New("no WANDevice") - return - } - c := getChildDevice(b, "WANConnectionDevice:1") - if c == nil { - err = errors.New("no WANConnectionDevice") - return - } - d := getChildService(c, "WANIPConnection:1") - if d == nil { - // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice, - // instead of under WanConnectionDevice - d = getChildService(b, "WANIPConnection:1") - - if d == nil { - err = errors.New("no WANIPConnection") - return - } - } - // Extract the domain name, which isn't always 'schemas-upnp-org' - urnDomain = strings.Split(d.ServiceType, ":")[1] - url = combineURL(rootURL, d.ControlURL) - return url, urnDomain, err -} - -func combineURL(rootURL, subURL string) string { - protocolEnd := "://" - protoEndIndex := strings.Index(rootURL, protocolEnd) - a := rootURL[protoEndIndex+len(protocolEnd):] - rootIndex := strings.Index(a, "/") - return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL -} - -func soapRequest(url, function, message, domain string) (r *http.Response, err error) { - fullMessage := "" + - "\r\n" + - "" + message + "" - - req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") - req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - // req.Header.Set("Transfer-Encoding", "chunked") - req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"") - req.Header.Set("Connection", "Close") - req.Header.Set("Cache-Control", "no-cache") - req.Header.Set("Pragma", "no-cache") - - // log.Stderr("soapRequest ", req) - - r, err = http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - /*if r.Body != nil { - defer r.Body.Close() - }*/ - - if r.StatusCode >= 400 { - // log.Stderr(function, r.StatusCode) - err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) - r = nil - return - } - return r, err -} - -type statusInfo struct { - externalIPAddress string -} - -func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) { - message := "\r\n" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - var envelope Envelope - data, err := io.ReadAll(response.Body) - if err != nil { - return - } - reader := bytes.NewReader(data) - err = xml.NewDecoder(reader).Decode(&envelope) - if err != nil { - return - } - - info = statusInfo{envelope.Soap.ExternalIP.IPAddress} - - if err != nil { - return - } - - return info, err -} - -// GetExternalAddress returns an external IP. If GetExternalIPAddress action -// fails or IP returned is invalid, GetExternalAddress returns an error. -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - info, err := n.getExternalIPAddress() - if err != nil { - return - } - addr = net.ParseIP(info.externalIPAddress) - if addr == nil { - err = fmt.Errorf("failed to parse IP: %v", info.externalIPAddress) - } - return -} - -func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) { - // A single concatenation would break ARM compilation. - message := "\r\n" + - "" + strconv.Itoa(externalPort) - message += "" + protocol + "" - message += "" + strconv.Itoa(internalPort) + "" + - "" + n.ourIP + "" + - "1" - message += description + - "" + strconv.Itoa(timeout) + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - - // TODO: check response to see if the port was forwarded - // log.Println(message, response) - // JAE: - // body, err := io.ReadAll(response.Body) - // fmt.Println(string(body), err) - mappedExternalPort = externalPort - _ = response - return -} - -func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { - message := "\r\n" + - "" + strconv.Itoa(externalPort) + - "" + protocol + "" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() //nolint: errcheck - } - if err != nil { - return - } - - // TODO: check response to see if the port was deleted - // log.Println(message, response) - _ = response - return -} diff --git a/tm2/pkg/telemetry/metrics/metrics.go b/tm2/pkg/telemetry/metrics/metrics.go index e3ae932612f..5eeb664ec8f 100644 --- a/tm2/pkg/telemetry/metrics/metrics.go +++ b/tm2/pkg/telemetry/metrics/metrics.go @@ -16,12 +16,10 @@ import ( ) const ( - broadcastTxTimerKey = "broadcast_tx_hist" - buildBlockTimerKey = "build_block_hist" + buildBlockTimerKey = "build_block_hist" inboundPeersKey = "inbound_peers_gauge" outboundPeersKey = "outbound_peers_gauge" - dialingPeersKey = "dialing_peers_gauge" numMempoolTxsKey = "num_mempool_txs_hist" numCachedTxsKey = "num_cached_txs_hist" @@ -42,11 +40,6 @@ const ( ) var ( - // Misc // - - // BroadcastTxTimer measures the transaction broadcast duration - BroadcastTxTimer metric.Int64Histogram - // Networking // // InboundPeers measures the active number of inbound peers @@ -55,9 +48,6 @@ var ( // OutboundPeers measures the active number of outbound peers OutboundPeers metric.Int64Gauge - // DialingPeers measures the active number of peers in the dialing state - DialingPeers metric.Int64Gauge - // Mempool // // NumMempoolTxs measures the number of transaction inside the mempool @@ -156,14 +146,6 @@ func Init(config config.Config) error { otel.SetMeterProvider(provider) meter := provider.Meter(config.MeterName) - if BroadcastTxTimer, err = meter.Int64Histogram( - broadcastTxTimerKey, - metric.WithDescription("broadcast tx duration"), - metric.WithUnit("ms"), - ); err != nil { - return fmt.Errorf("unable to create histogram, %w", err) - } - if BuildBlockTimer, err = meter.Int64Histogram( buildBlockTimerKey, metric.WithDescription("block build duration"), @@ -188,18 +170,10 @@ func Init(config config.Config) error { ); err != nil { return fmt.Errorf("unable to create histogram, %w", err) } + // Initialize OutboundPeers Gauge OutboundPeers.Record(ctx, 0) - if DialingPeers, err = meter.Int64Gauge( - dialingPeersKey, - metric.WithDescription("dialing peer count"), - ); err != nil { - return fmt.Errorf("unable to create histogram, %w", err) - } - // Initialize DialingPeers Gauge - DialingPeers.Record(ctx, 0) - // Mempool // if NumMempoolTxs, err = meter.Int64Histogram( numMempoolTxsKey, From faf70cb7fb4858504b1e125f40f5614412373509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 7 Jan 2025 17:14:12 +0100 Subject: [PATCH 02/75] fix: handle p2p events properly in cluster tests (#3452) ## Description This PR fixes an issue with the p2p event stream where events can be missed due to not being actively scooped from the channel, causing tests to hang. This non-blocking nature is by design, the p2p event stream should be non-blocking for subscribers at all times when new events are added. The PR increases the buffer size for events (from 1 to a reasonable value). For larger event loads, subscribers can handle channel draining caller side without any issues --- tm2/pkg/internal/p2p/p2p.go | 9 ++++++--- tm2/pkg/p2p/events/events.go | 12 ++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go index 2a16646a159..1e650e0cd25 100644 --- a/tm2/pkg/internal/p2p/p2p.go +++ b/tm2/pkg/internal/p2p/p2p.go @@ -131,10 +131,13 @@ func MakeConnectedPeers( multiplexSwitch.DialPeers(addrs...) // Set up an exit timer - timer := time.NewTimer(5 * time.Second) + timer := time.NewTimer(1 * time.Minute) defer timer.Stop() - connectedPeers := make(map[p2pTypes.ID]struct{}) + var ( + connectedPeers = make(map[p2pTypes.ID]struct{}) + targetPeers = cfg.Count - 1 + ) for { select { @@ -143,7 +146,7 @@ func MakeConnectedPeers( connectedPeers[ev.PeerID] = struct{}{} - if len(connectedPeers) == cfg.Count-1 { + if len(connectedPeers) == targetPeers { return nil } case <-timer.C: diff --git a/tm2/pkg/p2p/events/events.go b/tm2/pkg/p2p/events/events.go index bf76e27d91e..1eb4699fb45 100644 --- a/tm2/pkg/p2p/events/events.go +++ b/tm2/pkg/p2p/events/events.go @@ -68,7 +68,12 @@ type ( func (s *subscriptions) add(filterFn EventFilter) (string, chan Event) { var ( id = xid.New().String() - ch = make(chan Event, 1) + // Since the event stream is non-blocking, + // the event buffer should be sufficiently + // large for most use-cases. Subscribers can + // handle large event load caller-side to mitigate + // events potentially being missed + ch = make(chan Event, 100) ) (*s)[id] = subscription{ @@ -99,6 +104,9 @@ func (s *subscriptions) notify(event Event) { continue } - sub.ch <- event + select { + case sub.ch <- event: + default: // non-blocking + } } } From 27073ecb721239358c55e5aad68866472ce1355e Mon Sep 17 00:00:00 2001 From: piux2 <90544084+piux2@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:23:32 -0800 Subject: [PATCH 03/75] feat: min gas prices config (#3340) The minimum gas price configuration is a mechanism to prevent spam and ensure that only transactions with sufficient fees are processed. It achieves this by setting a minimum cost per unit of gas that a transaction must meet to be included in the mempool. The following condition is checked by the validator: the gas and fees provided by the user must meet this condition before the transaction can be included in the mempool and subsequently processed by the VM. Gas Fee => Gas Wanted x Min Gas Prices A node operator can set the min gas prices as following `gnoland config set application.min_gas_prices "1000ugnot/1gas" ` Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- gno.land/cmd/gnoland/start.go | 4 +++- gno.land/pkg/gnoland/app.go | 11 ++++++++-- gno.land/pkg/gnoland/app_test.go | 2 +- tm2/pkg/bft/config/config.go | 7 ++++++ tm2/pkg/sdk/auth/ante.go | 3 ++- tm2/pkg/sdk/config/config.go | 35 ++++++++++++++++++++++++++++++ tm2/pkg/sdk/config/config_test.go | 36 +++++++++++++++++++++++++++++++ tm2/pkg/std/gasprice.go | 6 ++++-- 8 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 tm2/pkg/sdk/config/config.go create mode 100644 tm2/pkg/sdk/config/config_test.go diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index a420e652810..eaaf7293986 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -26,6 +26,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/telemetry" "go.uber.org/zap" @@ -234,9 +235,10 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { // Create a top-level shared event switch evsw := events.NewEventSwitch() + minGasPrices := cfg.Application.MinGasPrices // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger) + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger, minGasPrices) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 9e8f2163441..0de26defad6 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -39,6 +39,7 @@ type AppOptions struct { EventSwitch events.EventSwitch // required VMOutput io.Writer // optional InitChainerConfig // options related to InitChainer + MinGasPrices string // optional } // TestAppOptions provides a "ready" default [AppOptions] for use with @@ -79,9 +80,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { mainKey := store.NewStoreKey("main") baseKey := store.NewStoreKey("base") + // set sdk app options + var appOpts []func(*sdk.BaseApp) + if cfg.MinGasPrices != "" { + appOpts = append(appOpts, sdk.SetMinGasPrices(cfg.MinGasPrices)) + } // Create BaseApp. - // TODO: Add a consensus based min gas prices for the node, by default it does not check - baseApp := sdk.NewBaseApp("gnoland", cfg.Logger, cfg.DB, baseKey, mainKey) + baseApp := sdk.NewBaseApp("gnoland", cfg.Logger, cfg.DB, baseKey, mainKey, appOpts...) baseApp.SetAppVersion("dev") // Set mounts for BaseApp's MultiStore. @@ -181,6 +186,7 @@ func NewApp( skipFailingGenesisTxs bool, evsw events.EventSwitch, logger *slog.Logger, + minGasPrices string, ) (abci.Application, error) { var err error @@ -191,6 +197,7 @@ func NewApp( GenesisTxResultHandler: PanicOnFailingTxResultHandler, StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), }, + MinGasPrices: minGasPrices, } if skipFailingGenesisTxs { cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 375602cfa4a..56a15fed5a9 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -134,7 +134,7 @@ func TestNewApp(t *testing.T) { // NewApp should have good defaults and manage to run InitChain. td := t.TempDir() - app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger()) + app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger(), "") require.NoError(t, err, "NewApp should be successful") resp := app.InitChain(abci.RequestInitChain{ diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index b745c815886..1a01686f4bd 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -18,6 +18,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" osm "github.com/gnolang/gno/tm2/pkg/os" p2p "github.com/gnolang/gno/tm2/pkg/p2p/config" + sdk "github.com/gnolang/gno/tm2/pkg/sdk/config" telemetry "github.com/gnolang/gno/tm2/pkg/telemetry/config" ) @@ -55,6 +56,7 @@ type Config struct { Consensus *cns.ConsensusConfig `json:"consensus" toml:"consensus" comment:"##### consensus configuration options #####"` TxEventStore *eventstore.Config `json:"tx_event_store" toml:"tx_event_store" comment:"##### event store #####"` Telemetry *telemetry.Config `json:"telemetry" toml:"telemetry" comment:"##### node telemetry #####"` + Application *sdk.AppConfig `json:"application" toml:"application" comment:"##### app settings #####"` } // DefaultConfig returns a default configuration for a Tendermint node @@ -67,6 +69,7 @@ func DefaultConfig() *Config { Consensus: cns.DefaultConsensusConfig(), TxEventStore: eventstore.DefaultEventStoreConfig(), Telemetry: telemetry.DefaultTelemetryConfig(), + Application: sdk.DefaultAppConfig(), } } @@ -183,6 +186,7 @@ func TestConfig() *Config { Consensus: cns.TestConsensusConfig(), TxEventStore: eventstore.DefaultEventStoreConfig(), Telemetry: telemetry.DefaultTelemetryConfig(), + Application: sdk.DefaultAppConfig(), } } @@ -238,6 +242,9 @@ func (cfg *Config) ValidateBasic() error { if err := cfg.Consensus.ValidateBasic(); err != nil { return errors.Wrap(err, "Error in [consensus] section") } + if err := cfg.Application.ValidateBasic(); err != nil { + return errors.Wrap(err, "Error in [application] section") + } return nil } diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 997478fe4b5..bec2c501f61 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -387,9 +387,10 @@ func EnsureSufficientMempoolFees(ctx sdk.Context, fee std.Fee) sdk.Result { if prod1.Cmp(prod2) >= 0 { return sdk.Result{} } else { + fee := new(big.Int).Quo(prod2, gpg) return abciResult(std.ErrInsufficientFee( fmt.Sprintf( - "insufficient fees; got: {Gas-Wanted: %d, Gas-Fee %s}, fee required: %+v as minimum gas price set by the node", feeGasPrice.Gas, feeGasPrice.Price, gp, + "insufficient fees; got: {Gas-Wanted: %d, Gas-Fee %s}, fee required: %d with %+v as minimum gas price set by the node", feeGasPrice.Gas, feeGasPrice.Price, fee, gp, ), )) } diff --git a/tm2/pkg/sdk/config/config.go b/tm2/pkg/sdk/config/config.go new file mode 100644 index 00000000000..6e5ededf9a4 --- /dev/null +++ b/tm2/pkg/sdk/config/config.go @@ -0,0 +1,35 @@ +package config + +import ( + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" +) + +// ----------------------------------------------------------------------------- +// Application Config + +// AppConfig defines the configuration options for the Application +type AppConfig struct { + // Lowest gas prices accepted by a validator in the form of "100tokenA/3gas;10tokenB/5gas" separated by semicolons + MinGasPrices string `json:"min_gas_prices" toml:"min_gas_prices" comment:"Lowest gas prices accepted by a validator"` +} + +// DefaultAppConfig returns a default configuration for the application +func DefaultAppConfig() *AppConfig { + return &AppConfig{ + MinGasPrices: "", + } +} + +// ValidateBasic performs basic validation, checking format and param bounds, etc., and +// returns an error if any check fails. +func (cfg *AppConfig) ValidateBasic() error { + if cfg.MinGasPrices == "" { + return nil + } + if _, err := std.ParseGasPrices(cfg.MinGasPrices); err != nil { + return errors.Wrap(err, "invalid min gas prices") + } + + return nil +} diff --git a/tm2/pkg/sdk/config/config_test.go b/tm2/pkg/sdk/config/config_test.go new file mode 100644 index 00000000000..dd0c391849b --- /dev/null +++ b/tm2/pkg/sdk/config/config_test.go @@ -0,0 +1,36 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateAppConfig(t *testing.T) { + c := DefaultAppConfig() + c.MinGasPrices = "" // empty + + testCases := []struct { + testName string + minGasPrices string + expectErr bool + }{ + {"invalid min gas prices invalid gas", "10token/1", true}, + {"invalid min gas prices invalid gas denom", "9token/0gs", true}, + {"invalid min gas prices zero gas", "10token/0gas", true}, + {"invalid min gas prices no gas", "10token/gas", true}, + {"invalid min gas prices negtive gas", "10token/-1gas", true}, + {"invalid min gas prices invalid denom", "10$token/2gas", true}, + {"invalid min gas prices invalid second denom", "10token/2gas;10/3gas", true}, + {"valid min gas prices", "10foo/3gas;5bar/3gas", false}, + } + + cfg := DefaultAppConfig() + for _, tc := range testCases { + tc := tc + t.Run(tc.testName, func(t *testing.T) { + cfg.MinGasPrices = tc.minGasPrices + assert.Equal(t, tc.expectErr, cfg.ValidateBasic() != nil) + }) + } +} diff --git a/tm2/pkg/std/gasprice.go b/tm2/pkg/std/gasprice.go index fd082a93371..82d236c1d04 100644 --- a/tm2/pkg/std/gasprice.go +++ b/tm2/pkg/std/gasprice.go @@ -29,9 +29,11 @@ func ParseGasPrice(gasprice string) (GasPrice, error) { if gas.Denom != "gas" { return GasPrice{}, errors.New("invalid gas price: %s (invalid gas denom)", gasprice) } - if gas.Amount == 0 { - return GasPrice{}, errors.New("invalid gas price: %s (gas can not be zero)", gasprice) + + if gas.Amount <= 0 { + return GasPrice{}, errors.New("invalid gas price: %s (invalid gas amount)", gasprice) } + return GasPrice{ Gas: gas.Amount, Price: price, From fa037e7ae473e40db9108e8efa8aceee51cd8b3f Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:32:50 +0100 Subject: [PATCH 04/75] feat(examples): add moul/xmath (#3403) Experimenting with Go-based code generation in the `examples/` folder. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- .gitattributes | 4 + .github/workflows/examples.yml | 8 + examples/gno.land/p/moul/xmath/generate.go | 3 + examples/gno.land/p/moul/xmath/generator.go | 184 +++++++ examples/gno.land/p/moul/xmath/gno.mod | 1 + examples/gno.land/p/moul/xmath/xmath.gen.gno | 421 ++++++++++++++++ .../gno.land/p/moul/xmath/xmath.gen_test.gno | 466 ++++++++++++++++++ 7 files changed, 1087 insertions(+) create mode 100644 examples/gno.land/p/moul/xmath/generate.go create mode 100644 examples/gno.land/p/moul/xmath/generator.go create mode 100644 examples/gno.land/p/moul/xmath/gno.mod create mode 100644 examples/gno.land/p/moul/xmath/xmath.gen.gno create mode 100644 examples/gno.land/p/moul/xmath/xmath.gen_test.gno diff --git a/.gitattributes b/.gitattributes index adc4144ffa3..13825940056 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,7 @@ go.sum linguist-generated text gnovm/stdlibs/generated.go linguist-generated gnovm/tests/stdlibs/generated.go linguist-generated +*.gen.gno linguist-generated +*.gen_test.gno linguist-generated +*.gen.go linguist-generated +*.gen_test.go linguist-generated \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 6a6d6e02653..5d606a2a663 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -71,12 +71,20 @@ jobs: - run: make lint -C ./examples # TODO: consider running lint on every other directories, maybe in "warning" mode? # TODO: track coverage + fmt: name: Run gno fmt on examples uses: ./.github/workflows/gnofmt_template.yml with: path: "examples/..." + generate: + name: Check generated files are up to date + uses: ./.github/workflows/build_template.yml + with: + modulepath: "examples" + go-version: "1.22.x" + mod-tidy: strategy: fail-fast: false diff --git a/examples/gno.land/p/moul/xmath/generate.go b/examples/gno.land/p/moul/xmath/generate.go new file mode 100644 index 00000000000..ad70adb06bd --- /dev/null +++ b/examples/gno.land/p/moul/xmath/generate.go @@ -0,0 +1,3 @@ +package xmath + +//go:generate go run generator.go diff --git a/examples/gno.land/p/moul/xmath/generator.go b/examples/gno.land/p/moul/xmath/generator.go new file mode 100644 index 00000000000..afe5a4341fa --- /dev/null +++ b/examples/gno.land/p/moul/xmath/generator.go @@ -0,0 +1,184 @@ +//go:build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "strings" + "text/template" +) + +type Type struct { + Name string + ZeroValue string + Signed bool + Float bool +} + +var types = []Type{ + {"Int8", "0", true, false}, + {"Int16", "0", true, false}, + {"Int32", "0", true, false}, + {"Int64", "0", true, false}, + {"Int", "0", true, false}, + {"Uint8", "0", false, false}, + {"Uint16", "0", false, false}, + {"Uint32", "0", false, false}, + {"Uint64", "0", false, false}, + {"Uint", "0", false, false}, + {"Float32", "0.0", true, true}, + {"Float64", "0.0", true, true}, +} + +const sourceTpl = `// Code generated by generator.go; DO NOT EDIT. +package xmath + +{{ range .Types }} +// {{.Name}} helpers +func Max{{.Name}}(a, b {{.Name | lower}}) {{.Name | lower}} { + if a > b { + return a + } + return b +} + +func Min{{.Name}}(a, b {{.Name | lower}}) {{.Name | lower}} { + if a < b { + return a + } + return b +} + +func Clamp{{.Name}}(value, min, max {{.Name | lower}}) {{.Name | lower}} { + if value < min { + return min + } + if value > max { + return max + } + return value +} +{{if .Signed}} +func Abs{{.Name}}(x {{.Name | lower}}) {{.Name | lower}} { + if x < 0 { + return -x + } + return x +} + +func Sign{{.Name}}(x {{.Name | lower}}) {{.Name | lower}} { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} +{{end}} +{{end}} +` + +const testTpl = `package xmath + +import "testing" + +{{range .Types}} +func Test{{.Name}}Helpers(t *testing.T) { + // Test Max{{.Name}} + if Max{{.Name}}(1, 2) != 2 { + t.Error("Max{{.Name}}(1, 2) should be 2") + } + {{if .Signed}}if Max{{.Name}}(-1, -2) != -1 { + t.Error("Max{{.Name}}(-1, -2) should be -1") + }{{end}} + + // Test Min{{.Name}} + if Min{{.Name}}(1, 2) != 1 { + t.Error("Min{{.Name}}(1, 2) should be 1") + } + {{if .Signed}}if Min{{.Name}}(-1, -2) != -2 { + t.Error("Min{{.Name}}(-1, -2) should be -2") + }{{end}} + + // Test Clamp{{.Name}} + if Clamp{{.Name}}(5, 1, 3) != 3 { + t.Error("Clamp{{.Name}}(5, 1, 3) should be 3") + } + if Clamp{{.Name}}(0, 1, 3) != 1 { + t.Error("Clamp{{.Name}}(0, 1, 3) should be 1") + } + if Clamp{{.Name}}(2, 1, 3) != 2 { + t.Error("Clamp{{.Name}}(2, 1, 3) should be 2") + } + {{if .Signed}} + // Test Abs{{.Name}} + if Abs{{.Name}}(-5) != 5 { + t.Error("Abs{{.Name}}(-5) should be 5") + } + if Abs{{.Name}}(5) != 5 { + t.Error("Abs{{.Name}}(5) should be 5") + } + + // Test Sign{{.Name}} + if Sign{{.Name}}(-5) != -1 { + t.Error("Sign{{.Name}}(-5) should be -1") + } + if Sign{{.Name}}(5) != 1 { + t.Error("Sign{{.Name}}(5) should be 1") + } + if Sign{{.Name}}({{.ZeroValue}}) != 0 { + t.Error("Sign{{.Name}}({{.ZeroValue}}) should be 0") + } + {{end}} +} +{{end}} +` + +func main() { + funcMap := template.FuncMap{ + "lower": strings.ToLower, + } + + // Generate source file + sourceTmpl := template.Must(template.New("source").Funcs(funcMap).Parse(sourceTpl)) + var sourceOut bytes.Buffer + if err := sourceTmpl.Execute(&sourceOut, struct{ Types []Type }{types}); err != nil { + log.Fatal(err) + } + + // Format the generated code + formattedSource, err := format.Source(sourceOut.Bytes()) + if err != nil { + log.Fatal(err) + } + + // Write source file + if err := os.WriteFile("xmath.gen.gno", formattedSource, 0644); err != nil { + log.Fatal(err) + } + + // Generate test file + testTmpl := template.Must(template.New("test").Parse(testTpl)) + var testOut bytes.Buffer + if err := testTmpl.Execute(&testOut, struct{ Types []Type }{types}); err != nil { + log.Fatal(err) + } + + // Format the generated test code + formattedTest, err := format.Source(testOut.Bytes()) + if err != nil { + log.Fatal(err) + } + + // Write test file + if err := os.WriteFile("xmath.gen_test.gno", formattedTest, 0644); err != nil { + log.Fatal(err) + } + + fmt.Println("Generated xmath.gen.gno and xmath.gen_test.gno") +} diff --git a/examples/gno.land/p/moul/xmath/gno.mod b/examples/gno.land/p/moul/xmath/gno.mod new file mode 100644 index 00000000000..63b782c88f2 --- /dev/null +++ b/examples/gno.land/p/moul/xmath/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/xmath diff --git a/examples/gno.land/p/moul/xmath/xmath.gen.gno b/examples/gno.land/p/moul/xmath/xmath.gen.gno new file mode 100644 index 00000000000..266c77e1e84 --- /dev/null +++ b/examples/gno.land/p/moul/xmath/xmath.gen.gno @@ -0,0 +1,421 @@ +// Code generated by generator.go; DO NOT EDIT. +package xmath + +// Int8 helpers +func MaxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +func MinInt8(a, b int8) int8 { + if a < b { + return a + } + return b +} + +func ClampInt8(value, min, max int8) int8 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsInt8(x int8) int8 { + if x < 0 { + return -x + } + return x +} + +func SignInt8(x int8) int8 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Int16 helpers +func MaxInt16(a, b int16) int16 { + if a > b { + return a + } + return b +} + +func MinInt16(a, b int16) int16 { + if a < b { + return a + } + return b +} + +func ClampInt16(value, min, max int16) int16 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsInt16(x int16) int16 { + if x < 0 { + return -x + } + return x +} + +func SignInt16(x int16) int16 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Int32 helpers +func MaxInt32(a, b int32) int32 { + if a > b { + return a + } + return b +} + +func MinInt32(a, b int32) int32 { + if a < b { + return a + } + return b +} + +func ClampInt32(value, min, max int32) int32 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsInt32(x int32) int32 { + if x < 0 { + return -x + } + return x +} + +func SignInt32(x int32) int32 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Int64 helpers +func MaxInt64(a, b int64) int64 { + if a > b { + return a + } + return b +} + +func MinInt64(a, b int64) int64 { + if a < b { + return a + } + return b +} + +func ClampInt64(value, min, max int64) int64 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsInt64(x int64) int64 { + if x < 0 { + return -x + } + return x +} + +func SignInt64(x int64) int64 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Int helpers +func MaxInt(a, b int) int { + if a > b { + return a + } + return b +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} + +func ClampInt(value, min, max int) int { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsInt(x int) int { + if x < 0 { + return -x + } + return x +} + +func SignInt(x int) int { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Uint8 helpers +func MaxUint8(a, b uint8) uint8 { + if a > b { + return a + } + return b +} + +func MinUint8(a, b uint8) uint8 { + if a < b { + return a + } + return b +} + +func ClampUint8(value, min, max uint8) uint8 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +// Uint16 helpers +func MaxUint16(a, b uint16) uint16 { + if a > b { + return a + } + return b +} + +func MinUint16(a, b uint16) uint16 { + if a < b { + return a + } + return b +} + +func ClampUint16(value, min, max uint16) uint16 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +// Uint32 helpers +func MaxUint32(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +func MinUint32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + +func ClampUint32(value, min, max uint32) uint32 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +// Uint64 helpers +func MaxUint64(a, b uint64) uint64 { + if a > b { + return a + } + return b +} + +func MinUint64(a, b uint64) uint64 { + if a < b { + return a + } + return b +} + +func ClampUint64(value, min, max uint64) uint64 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +// Uint helpers +func MaxUint(a, b uint) uint { + if a > b { + return a + } + return b +} + +func MinUint(a, b uint) uint { + if a < b { + return a + } + return b +} + +func ClampUint(value, min, max uint) uint { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +// Float32 helpers +func MaxFloat32(a, b float32) float32 { + if a > b { + return a + } + return b +} + +func MinFloat32(a, b float32) float32 { + if a < b { + return a + } + return b +} + +func ClampFloat32(value, min, max float32) float32 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsFloat32(x float32) float32 { + if x < 0 { + return -x + } + return x +} + +func SignFloat32(x float32) float32 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} + +// Float64 helpers +func MaxFloat64(a, b float64) float64 { + if a > b { + return a + } + return b +} + +func MinFloat64(a, b float64) float64 { + if a < b { + return a + } + return b +} + +func ClampFloat64(value, min, max float64) float64 { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +func AbsFloat64(x float64) float64 { + if x < 0 { + return -x + } + return x +} + +func SignFloat64(x float64) float64 { + if x < 0 { + return -1 + } + if x > 0 { + return 1 + } + return 0 +} diff --git a/examples/gno.land/p/moul/xmath/xmath.gen_test.gno b/examples/gno.land/p/moul/xmath/xmath.gen_test.gno new file mode 100644 index 00000000000..16c80fc983d --- /dev/null +++ b/examples/gno.land/p/moul/xmath/xmath.gen_test.gno @@ -0,0 +1,466 @@ +package xmath + +import "testing" + +func TestInt8Helpers(t *testing.T) { + // Test MaxInt8 + if MaxInt8(1, 2) != 2 { + t.Error("MaxInt8(1, 2) should be 2") + } + if MaxInt8(-1, -2) != -1 { + t.Error("MaxInt8(-1, -2) should be -1") + } + + // Test MinInt8 + if MinInt8(1, 2) != 1 { + t.Error("MinInt8(1, 2) should be 1") + } + if MinInt8(-1, -2) != -2 { + t.Error("MinInt8(-1, -2) should be -2") + } + + // Test ClampInt8 + if ClampInt8(5, 1, 3) != 3 { + t.Error("ClampInt8(5, 1, 3) should be 3") + } + if ClampInt8(0, 1, 3) != 1 { + t.Error("ClampInt8(0, 1, 3) should be 1") + } + if ClampInt8(2, 1, 3) != 2 { + t.Error("ClampInt8(2, 1, 3) should be 2") + } + + // Test AbsInt8 + if AbsInt8(-5) != 5 { + t.Error("AbsInt8(-5) should be 5") + } + if AbsInt8(5) != 5 { + t.Error("AbsInt8(5) should be 5") + } + + // Test SignInt8 + if SignInt8(-5) != -1 { + t.Error("SignInt8(-5) should be -1") + } + if SignInt8(5) != 1 { + t.Error("SignInt8(5) should be 1") + } + if SignInt8(0) != 0 { + t.Error("SignInt8(0) should be 0") + } + +} + +func TestInt16Helpers(t *testing.T) { + // Test MaxInt16 + if MaxInt16(1, 2) != 2 { + t.Error("MaxInt16(1, 2) should be 2") + } + if MaxInt16(-1, -2) != -1 { + t.Error("MaxInt16(-1, -2) should be -1") + } + + // Test MinInt16 + if MinInt16(1, 2) != 1 { + t.Error("MinInt16(1, 2) should be 1") + } + if MinInt16(-1, -2) != -2 { + t.Error("MinInt16(-1, -2) should be -2") + } + + // Test ClampInt16 + if ClampInt16(5, 1, 3) != 3 { + t.Error("ClampInt16(5, 1, 3) should be 3") + } + if ClampInt16(0, 1, 3) != 1 { + t.Error("ClampInt16(0, 1, 3) should be 1") + } + if ClampInt16(2, 1, 3) != 2 { + t.Error("ClampInt16(2, 1, 3) should be 2") + } + + // Test AbsInt16 + if AbsInt16(-5) != 5 { + t.Error("AbsInt16(-5) should be 5") + } + if AbsInt16(5) != 5 { + t.Error("AbsInt16(5) should be 5") + } + + // Test SignInt16 + if SignInt16(-5) != -1 { + t.Error("SignInt16(-5) should be -1") + } + if SignInt16(5) != 1 { + t.Error("SignInt16(5) should be 1") + } + if SignInt16(0) != 0 { + t.Error("SignInt16(0) should be 0") + } + +} + +func TestInt32Helpers(t *testing.T) { + // Test MaxInt32 + if MaxInt32(1, 2) != 2 { + t.Error("MaxInt32(1, 2) should be 2") + } + if MaxInt32(-1, -2) != -1 { + t.Error("MaxInt32(-1, -2) should be -1") + } + + // Test MinInt32 + if MinInt32(1, 2) != 1 { + t.Error("MinInt32(1, 2) should be 1") + } + if MinInt32(-1, -2) != -2 { + t.Error("MinInt32(-1, -2) should be -2") + } + + // Test ClampInt32 + if ClampInt32(5, 1, 3) != 3 { + t.Error("ClampInt32(5, 1, 3) should be 3") + } + if ClampInt32(0, 1, 3) != 1 { + t.Error("ClampInt32(0, 1, 3) should be 1") + } + if ClampInt32(2, 1, 3) != 2 { + t.Error("ClampInt32(2, 1, 3) should be 2") + } + + // Test AbsInt32 + if AbsInt32(-5) != 5 { + t.Error("AbsInt32(-5) should be 5") + } + if AbsInt32(5) != 5 { + t.Error("AbsInt32(5) should be 5") + } + + // Test SignInt32 + if SignInt32(-5) != -1 { + t.Error("SignInt32(-5) should be -1") + } + if SignInt32(5) != 1 { + t.Error("SignInt32(5) should be 1") + } + if SignInt32(0) != 0 { + t.Error("SignInt32(0) should be 0") + } + +} + +func TestInt64Helpers(t *testing.T) { + // Test MaxInt64 + if MaxInt64(1, 2) != 2 { + t.Error("MaxInt64(1, 2) should be 2") + } + if MaxInt64(-1, -2) != -1 { + t.Error("MaxInt64(-1, -2) should be -1") + } + + // Test MinInt64 + if MinInt64(1, 2) != 1 { + t.Error("MinInt64(1, 2) should be 1") + } + if MinInt64(-1, -2) != -2 { + t.Error("MinInt64(-1, -2) should be -2") + } + + // Test ClampInt64 + if ClampInt64(5, 1, 3) != 3 { + t.Error("ClampInt64(5, 1, 3) should be 3") + } + if ClampInt64(0, 1, 3) != 1 { + t.Error("ClampInt64(0, 1, 3) should be 1") + } + if ClampInt64(2, 1, 3) != 2 { + t.Error("ClampInt64(2, 1, 3) should be 2") + } + + // Test AbsInt64 + if AbsInt64(-5) != 5 { + t.Error("AbsInt64(-5) should be 5") + } + if AbsInt64(5) != 5 { + t.Error("AbsInt64(5) should be 5") + } + + // Test SignInt64 + if SignInt64(-5) != -1 { + t.Error("SignInt64(-5) should be -1") + } + if SignInt64(5) != 1 { + t.Error("SignInt64(5) should be 1") + } + if SignInt64(0) != 0 { + t.Error("SignInt64(0) should be 0") + } + +} + +func TestIntHelpers(t *testing.T) { + // Test MaxInt + if MaxInt(1, 2) != 2 { + t.Error("MaxInt(1, 2) should be 2") + } + if MaxInt(-1, -2) != -1 { + t.Error("MaxInt(-1, -2) should be -1") + } + + // Test MinInt + if MinInt(1, 2) != 1 { + t.Error("MinInt(1, 2) should be 1") + } + if MinInt(-1, -2) != -2 { + t.Error("MinInt(-1, -2) should be -2") + } + + // Test ClampInt + if ClampInt(5, 1, 3) != 3 { + t.Error("ClampInt(5, 1, 3) should be 3") + } + if ClampInt(0, 1, 3) != 1 { + t.Error("ClampInt(0, 1, 3) should be 1") + } + if ClampInt(2, 1, 3) != 2 { + t.Error("ClampInt(2, 1, 3) should be 2") + } + + // Test AbsInt + if AbsInt(-5) != 5 { + t.Error("AbsInt(-5) should be 5") + } + if AbsInt(5) != 5 { + t.Error("AbsInt(5) should be 5") + } + + // Test SignInt + if SignInt(-5) != -1 { + t.Error("SignInt(-5) should be -1") + } + if SignInt(5) != 1 { + t.Error("SignInt(5) should be 1") + } + if SignInt(0) != 0 { + t.Error("SignInt(0) should be 0") + } + +} + +func TestUint8Helpers(t *testing.T) { + // Test MaxUint8 + if MaxUint8(1, 2) != 2 { + t.Error("MaxUint8(1, 2) should be 2") + } + + // Test MinUint8 + if MinUint8(1, 2) != 1 { + t.Error("MinUint8(1, 2) should be 1") + } + + // Test ClampUint8 + if ClampUint8(5, 1, 3) != 3 { + t.Error("ClampUint8(5, 1, 3) should be 3") + } + if ClampUint8(0, 1, 3) != 1 { + t.Error("ClampUint8(0, 1, 3) should be 1") + } + if ClampUint8(2, 1, 3) != 2 { + t.Error("ClampUint8(2, 1, 3) should be 2") + } + +} + +func TestUint16Helpers(t *testing.T) { + // Test MaxUint16 + if MaxUint16(1, 2) != 2 { + t.Error("MaxUint16(1, 2) should be 2") + } + + // Test MinUint16 + if MinUint16(1, 2) != 1 { + t.Error("MinUint16(1, 2) should be 1") + } + + // Test ClampUint16 + if ClampUint16(5, 1, 3) != 3 { + t.Error("ClampUint16(5, 1, 3) should be 3") + } + if ClampUint16(0, 1, 3) != 1 { + t.Error("ClampUint16(0, 1, 3) should be 1") + } + if ClampUint16(2, 1, 3) != 2 { + t.Error("ClampUint16(2, 1, 3) should be 2") + } + +} + +func TestUint32Helpers(t *testing.T) { + // Test MaxUint32 + if MaxUint32(1, 2) != 2 { + t.Error("MaxUint32(1, 2) should be 2") + } + + // Test MinUint32 + if MinUint32(1, 2) != 1 { + t.Error("MinUint32(1, 2) should be 1") + } + + // Test ClampUint32 + if ClampUint32(5, 1, 3) != 3 { + t.Error("ClampUint32(5, 1, 3) should be 3") + } + if ClampUint32(0, 1, 3) != 1 { + t.Error("ClampUint32(0, 1, 3) should be 1") + } + if ClampUint32(2, 1, 3) != 2 { + t.Error("ClampUint32(2, 1, 3) should be 2") + } + +} + +func TestUint64Helpers(t *testing.T) { + // Test MaxUint64 + if MaxUint64(1, 2) != 2 { + t.Error("MaxUint64(1, 2) should be 2") + } + + // Test MinUint64 + if MinUint64(1, 2) != 1 { + t.Error("MinUint64(1, 2) should be 1") + } + + // Test ClampUint64 + if ClampUint64(5, 1, 3) != 3 { + t.Error("ClampUint64(5, 1, 3) should be 3") + } + if ClampUint64(0, 1, 3) != 1 { + t.Error("ClampUint64(0, 1, 3) should be 1") + } + if ClampUint64(2, 1, 3) != 2 { + t.Error("ClampUint64(2, 1, 3) should be 2") + } + +} + +func TestUintHelpers(t *testing.T) { + // Test MaxUint + if MaxUint(1, 2) != 2 { + t.Error("MaxUint(1, 2) should be 2") + } + + // Test MinUint + if MinUint(1, 2) != 1 { + t.Error("MinUint(1, 2) should be 1") + } + + // Test ClampUint + if ClampUint(5, 1, 3) != 3 { + t.Error("ClampUint(5, 1, 3) should be 3") + } + if ClampUint(0, 1, 3) != 1 { + t.Error("ClampUint(0, 1, 3) should be 1") + } + if ClampUint(2, 1, 3) != 2 { + t.Error("ClampUint(2, 1, 3) should be 2") + } + +} + +func TestFloat32Helpers(t *testing.T) { + // Test MaxFloat32 + if MaxFloat32(1, 2) != 2 { + t.Error("MaxFloat32(1, 2) should be 2") + } + if MaxFloat32(-1, -2) != -1 { + t.Error("MaxFloat32(-1, -2) should be -1") + } + + // Test MinFloat32 + if MinFloat32(1, 2) != 1 { + t.Error("MinFloat32(1, 2) should be 1") + } + if MinFloat32(-1, -2) != -2 { + t.Error("MinFloat32(-1, -2) should be -2") + } + + // Test ClampFloat32 + if ClampFloat32(5, 1, 3) != 3 { + t.Error("ClampFloat32(5, 1, 3) should be 3") + } + if ClampFloat32(0, 1, 3) != 1 { + t.Error("ClampFloat32(0, 1, 3) should be 1") + } + if ClampFloat32(2, 1, 3) != 2 { + t.Error("ClampFloat32(2, 1, 3) should be 2") + } + + // Test AbsFloat32 + if AbsFloat32(-5) != 5 { + t.Error("AbsFloat32(-5) should be 5") + } + if AbsFloat32(5) != 5 { + t.Error("AbsFloat32(5) should be 5") + } + + // Test SignFloat32 + if SignFloat32(-5) != -1 { + t.Error("SignFloat32(-5) should be -1") + } + if SignFloat32(5) != 1 { + t.Error("SignFloat32(5) should be 1") + } + if SignFloat32(0.0) != 0 { + t.Error("SignFloat32(0.0) should be 0") + } + +} + +func TestFloat64Helpers(t *testing.T) { + // Test MaxFloat64 + if MaxFloat64(1, 2) != 2 { + t.Error("MaxFloat64(1, 2) should be 2") + } + if MaxFloat64(-1, -2) != -1 { + t.Error("MaxFloat64(-1, -2) should be -1") + } + + // Test MinFloat64 + if MinFloat64(1, 2) != 1 { + t.Error("MinFloat64(1, 2) should be 1") + } + if MinFloat64(-1, -2) != -2 { + t.Error("MinFloat64(-1, -2) should be -2") + } + + // Test ClampFloat64 + if ClampFloat64(5, 1, 3) != 3 { + t.Error("ClampFloat64(5, 1, 3) should be 3") + } + if ClampFloat64(0, 1, 3) != 1 { + t.Error("ClampFloat64(0, 1, 3) should be 1") + } + if ClampFloat64(2, 1, 3) != 2 { + t.Error("ClampFloat64(2, 1, 3) should be 2") + } + + // Test AbsFloat64 + if AbsFloat64(-5) != 5 { + t.Error("AbsFloat64(-5) should be 5") + } + if AbsFloat64(5) != 5 { + t.Error("AbsFloat64(5) should be 5") + } + + // Test SignFloat64 + if SignFloat64(-5) != -1 { + t.Error("SignFloat64(-5) should be -1") + } + if SignFloat64(5) != 1 { + t.Error("SignFloat64(5) should be 1") + } + if SignFloat64(0.0) != 0 { + t.Error("SignFloat64(0.0) should be 0") + } + +} From 36d6e7c3b357efec8bd7d85a92530850a213e070 Mon Sep 17 00:00:00 2001 From: Stefan Nikolic Date: Wed, 8 Jan 2025 16:34:35 +0100 Subject: [PATCH 05/75] feat(stefann/home): Convert HTML to Markdown and enhance features (#3456) Changes in the home realm: - Convert `HTML` to pure `.md` - Register the realm in `HoF` - Add namespace resolution functionality - Implement `ugnot` to `gnot` conversion for Leaderboard - Update tests --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/r/stefann/home/home.gno | 111 ++++++++---------- .../gno.land/r/stefann/home/home_test.gno | 13 -- 2 files changed, 48 insertions(+), 76 deletions(-) diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index 9586f377311..f54721ce37c 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -8,6 +8,8 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" + "gno.land/r/leon/hof" "gno.land/r/stefann/registry" ) @@ -23,7 +25,6 @@ type Sponsor struct { } type Profile struct { - pfp string aboutMe []string } @@ -49,15 +50,15 @@ var ( func init() { owner = ownable.NewWithAddress(registry.MainAddr()) + hof.Register() profile = Profile{ - pfp: "https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg", aboutMe: []string{ - `### About Me`, - `Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`, + `## About Me`, + `### Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`, - `### Contributions`, - `I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`, + `## Contributions`, + `### I'm just getting started, but you can follow my journey through gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`, }, } @@ -83,7 +84,7 @@ func init() { } sponsorship = Sponsorship{ - maxSponsors: 5, + maxSponsors: 3, sponsors: avl.NewTree(), DonationsCount: 0, sponsorsCount: 0, @@ -106,11 +107,6 @@ func UpdateJarLink(newLink string) { travel.jarLink = newLink } -func UpdatePFP(url string) { - owner.AssertCallerIsOwner() - profile.pfp = url -} - func UpdateAboutMe(aboutMeStr string) { owner.AssertCallerIsOwner() profile.aboutMe = strings.Split(aboutMeStr, "|") @@ -203,46 +199,27 @@ func Render(path string) string { } func renderAboutMe() string { - out := "
" - - out += "
\n\n" + out := "" - out += ufmt.Sprintf("
\n\n", travel.cities[travel.currentCityIndex%len(travel.cities)].URL) - - out += ufmt.Sprintf("my profile pic\n\n", profile.pfp) - - out += "
\n\n" + out += ufmt.Sprintf("![Current Location](%s)\n\n", travel.cities[travel.currentCityIndex%len(travel.cities)].URL) for _, rows := range profile.aboutMe { - out += "
\n\n" out += rows + "\n\n" - out += "
\n\n" } - out += "
\n\n" - return out } func renderTips() string { - out := `
` + "\n\n" + out := "# Help Me Travel The World\n\n" - out += `
` + "\n" + out += ufmt.Sprintf("## I am currently in %s, tip the jar to send me somewhere else!\n\n", travel.cities[travel.currentCityIndex].Name) + out += "### **Click** the jar, **tip** in GNOT coins, and **watch** my background change as I head to a new adventure!\n\n" - out += `

Help Me Travel The World

` + "\n\n" - - out += renderTipsJar() + "\n" - - out += ufmt.Sprintf(`I am currently in %s,
tip the jar to send me somewhere else!
`, travel.cities[travel.currentCityIndex].Name) - - out += `
Click the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!

` + "\n\n" + out += renderTipsJar() + "\n\n" out += renderSponsors() - out += `
` + "\n\n" - - out += `
` + "\n" - return out } @@ -253,11 +230,27 @@ func formatAddress(address string) string { return address[:4] + "..." + address[len(address)-4:] } +func getDisplayName(addr std.Address) string { + if user := users.GetUserByAddress(addr); user != nil { + return user.Name + } + return formatAddress(addr.String()) +} + +func formatAmount(amount std.Coins) string { + ugnot := amount.AmountOf("ugnot") + if ugnot >= 1000000 { + gnot := float64(ugnot) / 1000000 + return ufmt.Sprintf("`%v`*GNOT*", gnot) + } + return ufmt.Sprintf("`%d`*ugnot*", ugnot) +} + func renderSponsors() string { - out := `

Sponsor Leaderboard

` + "\n" + out := "## Sponsor Leaderboard\n\n" if sponsorship.sponsorsCount == 0 { - return out + `

No sponsors yet. Be the first to tip the jar!

` + "\n" + return out + "No sponsors yet. Be the first to tip the jar!\n" } topSponsors := GetTopSponsors() @@ -266,38 +259,30 @@ func renderSponsors() string { numSponsors = sponsorship.maxSponsors } - out += `
    ` + "\n" - for i := 0; i < numSponsors; i++ { sponsor := topSponsors[i] - isLastItem := (i == numSponsors-1) - - padding := "10px 5px" - border := "border-bottom: 1px solid #ddd;" - - if isLastItem { - padding = "8px 5px" - border = "" + position := "" + switch i { + case 0: + position = "🥇" + case 1: + position = "🥈" + case 2: + position = "🥉" + default: + position = ufmt.Sprintf("%d.", i+1) } - out += ufmt.Sprintf( - `
  • - %d. %s - %s -
  • `, - padding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(), + out += ufmt.Sprintf("%s **%s** - %s\n\n", + position, + getDisplayName(sponsor.Address), + formatAmount(sponsor.Amount), ) } - return out + return out + "\n" } func renderTipsJar() string { - out := ufmt.Sprintf(``, travel.jarLink) + "\n" - - out += `Tips Jar` + "\n" - - out += `` + "\n" - - return out + return ufmt.Sprintf("[![Tips Jar](https://i.ibb.co/4TH9zbw/tips-jar.png)](%s)", travel.jarLink) } diff --git a/examples/gno.land/r/stefann/home/home_test.gno b/examples/gno.land/r/stefann/home/home_test.gno index ca146b9eb13..b8ea88670a6 100644 --- a/examples/gno.land/r/stefann/home/home_test.gno +++ b/examples/gno.land/r/stefann/home/home_test.gno @@ -9,19 +9,6 @@ import ( "gno.land/p/demo/testutils" ) -func TestUpdatePFP(t *testing.T) { - var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) - - profile.pfp = "" - - UpdatePFP("https://example.com/pic.png") - - if profile.pfp != "https://example.com/pic.png" { - t.Fatalf("expected pfp to be https://example.com/pic.png, got %s", profile.pfp) - } -} - func TestUpdateAboutMe(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") std.TestSetOrigCaller(owner) From c6c0e6eba99fdde97a3ab8e66c7ab22f058d6333 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 8 Jan 2025 16:53:47 +0100 Subject: [PATCH 06/75] chore: Remove markdown/highlighting.go and directly use goldmark-highlighting (#3461) Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnodev/go.mod | 1 + contribs/gnodev/go.sum | 7 + gno.land/pkg/gnoweb/app.go | 3 +- gno.land/pkg/gnoweb/markdown/highlighting.go | 588 ------------------ .../pkg/gnoweb/markdown/highlighting_test.go | 568 ----------------- go.mod | 1 + go.sum | 7 + 7 files changed, 18 insertions(+), 1157 deletions(-) delete mode 100644 gno.land/pkg/gnoweb/markdown/highlighting.go delete mode 100644 gno.land/pkg/gnoweb/markdown/highlighting_test.go diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 52e1e23f27b..92d8494fa40 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -82,6 +82,7 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.2 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index e1792734b95..3f22e4f2f00 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -3,8 +3,10 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -91,6 +93,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -225,10 +229,13 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index dc13253468e..1deea86644b 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -7,11 +7,12 @@ import ( "path" "strings" + markdown "github.com/yuin/goldmark-highlighting/v2" + "github.com/alecthomas/chroma/v2" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/styles" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" - "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" mdhtml "github.com/yuin/goldmark/renderer/html" diff --git a/gno.land/pkg/gnoweb/markdown/highlighting.go b/gno.land/pkg/gnoweb/markdown/highlighting.go deleted file mode 100644 index 51c66674df1..00000000000 --- a/gno.land/pkg/gnoweb/markdown/highlighting.go +++ /dev/null @@ -1,588 +0,0 @@ -// This file was copied from https://github.com/yuin/goldmark-highlighting -// -// package highlighting is an extension for the goldmark(http://github.com/yuin/goldmark). -// -// This extension adds syntax-highlighting to the fenced code blocks using -// chroma(https://github.com/alecthomas/chroma). -package markdown - -import ( - "bytes" - "io" - "strconv" - "strings" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/renderer/html" - "github.com/yuin/goldmark/text" - "github.com/yuin/goldmark/util" - - "github.com/alecthomas/chroma/v2" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/lexers" - "github.com/alecthomas/chroma/v2/styles" -) - -// ImmutableAttributes is a read-only interface for ast.Attributes. -type ImmutableAttributes interface { - // Get returns (value, true) if an attribute associated with given - // name exists, otherwise (nil, false) - Get(name []byte) (interface{}, bool) - - // GetString returns (value, true) if an attribute associated with given - // name exists, otherwise (nil, false) - GetString(name string) (interface{}, bool) - - // All returns all attributes. - All() []ast.Attribute -} - -type immutableAttributes struct { - n ast.Node -} - -func (a *immutableAttributes) Get(name []byte) (interface{}, bool) { - return a.n.Attribute(name) -} - -func (a *immutableAttributes) GetString(name string) (interface{}, bool) { - return a.n.AttributeString(name) -} - -func (a *immutableAttributes) All() []ast.Attribute { - if a.n.Attributes() == nil { - return []ast.Attribute{} - } - return a.n.Attributes() -} - -// CodeBlockContext holds contextual information of code highlighting. -type CodeBlockContext interface { - // Language returns (language, true) if specified, otherwise (nil, false). - Language() ([]byte, bool) - - // Highlighted returns true if this code block can be highlighted, otherwise false. - Highlighted() bool - - // Attributes return attributes of the code block. - Attributes() ImmutableAttributes -} - -type codeBlockContext struct { - language []byte - highlighted bool - attributes ImmutableAttributes -} - -func newCodeBlockContext(language []byte, highlighted bool, attrs ImmutableAttributes) CodeBlockContext { - return &codeBlockContext{ - language: language, - highlighted: highlighted, - attributes: attrs, - } -} - -func (c *codeBlockContext) Language() ([]byte, bool) { - if c.language != nil { - return c.language, true - } - return nil, false -} - -func (c *codeBlockContext) Highlighted() bool { - return c.highlighted -} - -func (c *codeBlockContext) Attributes() ImmutableAttributes { - return c.attributes -} - -// WrapperRenderer renders wrapper elements like div, pre, etc. -type WrapperRenderer func(w util.BufWriter, context CodeBlockContext, entering bool) - -// CodeBlockOptions creates Chroma options per code block. -type CodeBlockOptions func(ctx CodeBlockContext) []chromahtml.Option - -// Config struct holds options for the extension. -type Config struct { - html.Config - - // Style is a highlighting style. - // Supported styles are defined under https://github.com/alecthomas/chroma/tree/master/formatters. - Style string - - // Pass in a custom Chroma style. If this is not nil, the Style string will be ignored - CustomStyle *chroma.Style - - // If set, will try to guess language if none provided. - // If the guessing fails, we will fall back to a text lexer. - // Note that while Chroma's API supports language guessing, the implementation - // is not there yet, so you will currently always get the basic text lexer. - GuessLanguage bool - - // FormatOptions is a option related to output formats. - // See https://github.com/alecthomas/chroma#the-html-formatter for details. - FormatOptions []chromahtml.Option - - // CSSWriter is an io.Writer that will be used as CSS data output buffer. - // If WithClasses() is enabled, you can get CSS data corresponds to the style. - CSSWriter io.Writer - - // CodeBlockOptions allows set Chroma options per code block. - CodeBlockOptions CodeBlockOptions - - // WrapperRenderer allows you to change wrapper elements. - WrapperRenderer WrapperRenderer -} - -// NewConfig returns a new Config with defaults. -func NewConfig() Config { - return Config{ - Config: html.NewConfig(), - Style: "github", - FormatOptions: []chromahtml.Option{}, - CSSWriter: nil, - WrapperRenderer: nil, - CodeBlockOptions: nil, - } -} - -// SetOption implements renderer.SetOptioner. -func (c *Config) SetOption(name renderer.OptionName, value interface{}) { - switch name { - case optStyle: - c.Style = value.(string) - case optCustomStyle: - c.CustomStyle = value.(*chroma.Style) - case optFormatOptions: - if value != nil { - c.FormatOptions = value.([]chromahtml.Option) - } - case optCSSWriter: - c.CSSWriter = value.(io.Writer) - case optWrapperRenderer: - c.WrapperRenderer = value.(WrapperRenderer) - case optCodeBlockOptions: - c.CodeBlockOptions = value.(CodeBlockOptions) - case optGuessLanguage: - c.GuessLanguage = value.(bool) - default: - c.Config.SetOption(name, value) - } -} - -// Option interface is a functional option interface for the extension. -type Option interface { - renderer.Option - // SetHighlightingOption sets given option to the extension. - SetHighlightingOption(*Config) -} - -type withHTMLOptions struct { - value []html.Option -} - -func (o *withHTMLOptions) SetConfig(c *renderer.Config) { - if o.value != nil { - for _, v := range o.value { - v.(renderer.Option).SetConfig(c) - } - } -} - -func (o *withHTMLOptions) SetHighlightingOption(c *Config) { - if o.value != nil { - for _, v := range o.value { - v.SetHTMLOption(&c.Config) - } - } -} - -// WithHTMLOptions is functional option that wraps goldmark HTMLRenderer options. -func WithHTMLOptions(opts ...html.Option) Option { - return &withHTMLOptions{opts} -} - -const ( - optStyle renderer.OptionName = "HighlightingStyle" - optCustomStyle renderer.OptionName = "HighlightingCustomStyle" -) - -var highlightLinesAttrName = []byte("hl_lines") - -var ( - styleAttrName = []byte("hl_style") - nohlAttrName = []byte("nohl") - linenosAttrName = []byte("linenos") - linenosTableAttrValue = []byte("table") - linenosInlineAttrValue = []byte("inline") - linenostartAttrName = []byte("linenostart") -) - -type withStyle struct { - value string -} - -func (o *withStyle) SetConfig(c *renderer.Config) { - c.Options[optStyle] = o.value -} - -func (o *withStyle) SetHighlightingOption(c *Config) { - c.Style = o.value -} - -// WithStyle is a functional option that changes highlighting style. -func WithStyle(style string) Option { - return &withStyle{style} -} - -type withCustomStyle struct { - value *chroma.Style -} - -func (o *withCustomStyle) SetConfig(c *renderer.Config) { - c.Options[optCustomStyle] = o.value -} - -func (o *withCustomStyle) SetHighlightingOption(c *Config) { - c.CustomStyle = o.value -} - -// WithStyle is a functional option that changes highlighting style. -func WithCustomStyle(style *chroma.Style) Option { - return &withCustomStyle{style} -} - -const optCSSWriter renderer.OptionName = "HighlightingCSSWriter" - -type withCSSWriter struct { - value io.Writer -} - -func (o *withCSSWriter) SetConfig(c *renderer.Config) { - c.Options[optCSSWriter] = o.value -} - -func (o *withCSSWriter) SetHighlightingOption(c *Config) { - c.CSSWriter = o.value -} - -// WithCSSWriter is a functional option that sets io.Writer for CSS data. -func WithCSSWriter(w io.Writer) Option { - return &withCSSWriter{w} -} - -const optGuessLanguage renderer.OptionName = "HighlightingGuessLanguage" - -type withGuessLanguage struct { - value bool -} - -func (o *withGuessLanguage) SetConfig(c *renderer.Config) { - c.Options[optGuessLanguage] = o.value -} - -func (o *withGuessLanguage) SetHighlightingOption(c *Config) { - c.GuessLanguage = o.value -} - -// WithGuessLanguage is a functional option that toggles language guessing -// if none provided. -func WithGuessLanguage(b bool) Option { - return &withGuessLanguage{value: b} -} - -const optWrapperRenderer renderer.OptionName = "HighlightingWrapperRenderer" - -type withWrapperRenderer struct { - value WrapperRenderer -} - -func (o *withWrapperRenderer) SetConfig(c *renderer.Config) { - c.Options[optWrapperRenderer] = o.value -} - -func (o *withWrapperRenderer) SetHighlightingOption(c *Config) { - c.WrapperRenderer = o.value -} - -// WithWrapperRenderer is a functional option that sets WrapperRenderer that -// renders wrapper elements like div, pre, etc. -func WithWrapperRenderer(w WrapperRenderer) Option { - return &withWrapperRenderer{w} -} - -const optCodeBlockOptions renderer.OptionName = "HighlightingCodeBlockOptions" - -type withCodeBlockOptions struct { - value CodeBlockOptions -} - -func (o *withCodeBlockOptions) SetConfig(c *renderer.Config) { - c.Options[optCodeBlockOptions] = o.value -} - -func (o *withCodeBlockOptions) SetHighlightingOption(c *Config) { - c.CodeBlockOptions = o.value -} - -// WithCodeBlockOptions is a functional option that sets CodeBlockOptions that -// allows setting Chroma options per code block. -func WithCodeBlockOptions(c CodeBlockOptions) Option { - return &withCodeBlockOptions{value: c} -} - -const optFormatOptions renderer.OptionName = "HighlightingFormatOptions" - -type withFormatOptions struct { - value []chromahtml.Option -} - -func (o *withFormatOptions) SetConfig(c *renderer.Config) { - if _, ok := c.Options[optFormatOptions]; !ok { - c.Options[optFormatOptions] = []chromahtml.Option{} - } - c.Options[optFormatOptions] = append(c.Options[optFormatOptions].([]chromahtml.Option), o.value...) -} - -func (o *withFormatOptions) SetHighlightingOption(c *Config) { - c.FormatOptions = append(c.FormatOptions, o.value...) -} - -// WithFormatOptions is a functional option that wraps chroma HTML formatter options. -func WithFormatOptions(opts ...chromahtml.Option) Option { - return &withFormatOptions{opts} -} - -// HTMLRenderer struct is a renderer.NodeRenderer implementation for the extension. -type HTMLRenderer struct { - Config -} - -// NewHTMLRenderer builds a new HTMLRenderer with given options and returns it. -func NewHTMLRenderer(opts ...Option) renderer.NodeRenderer { - r := &HTMLRenderer{ - Config: NewConfig(), - } - for _, opt := range opts { - opt.SetHighlightingOption(&r.Config) - } - return r -} - -// RegisterFuncs implements NodeRenderer.RegisterFuncs. -func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { - reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock) -} - -func getAttributes(node *ast.FencedCodeBlock, infostr []byte) ImmutableAttributes { - if node.Attributes() != nil { - return &immutableAttributes{node} - } - if infostr != nil { - attrStartIdx := -1 - - for idx, char := range infostr { - if char == '{' { - attrStartIdx = idx - break - } - } - if attrStartIdx > 0 { - n := ast.NewTextBlock() // dummy node for storing attributes - attrStr := infostr[attrStartIdx:] - if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr { - for _, attr := range attrs { - n.SetAttribute(attr.Name, attr.Value) - } - return &immutableAttributes{n} - } - } - } - return nil -} - -func (r *HTMLRenderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - n := node.(*ast.FencedCodeBlock) - if !entering { - return ast.WalkContinue, nil - } - language := n.Language(source) - - chromaFormatterOptions := make([]chromahtml.Option, 0, len(r.FormatOptions)) - for _, opt := range r.FormatOptions { - chromaFormatterOptions = append(chromaFormatterOptions, opt) - } - - style := r.CustomStyle - if style == nil { - style = styles.Get(r.Style) - } - nohl := false - - var info []byte - if n.Info != nil { - info = n.Info.Segment.Value(source) - } - attrs := getAttributes(n, info) - if attrs != nil { - baseLineNumber := 1 - if linenostartAttr, ok := attrs.Get(linenostartAttrName); ok { - if linenostart, ok := linenostartAttr.(float64); ok { - baseLineNumber = int(linenostart) - chromaFormatterOptions = append( - chromaFormatterOptions, chromahtml.BaseLineNumber(baseLineNumber), - ) - } - } - if linesAttr, hasLinesAttr := attrs.Get(highlightLinesAttrName); hasLinesAttr { - if lines, ok := linesAttr.([]interface{}); ok { - var hlRanges [][2]int - for _, l := range lines { - if ln, ok := l.(float64); ok { - hlRanges = append(hlRanges, [2]int{int(ln) + baseLineNumber - 1, int(ln) + baseLineNumber - 1}) - } - if rng, ok := l.([]uint8); ok { - slices := strings.Split(string(rng), "-") - lhs, err := strconv.Atoi(slices[0]) - if err != nil { - continue - } - rhs := lhs - if len(slices) > 1 { - rhs, err = strconv.Atoi(slices[1]) - if err != nil { - continue - } - } - hlRanges = append(hlRanges, [2]int{lhs + baseLineNumber - 1, rhs + baseLineNumber - 1}) - } - } - chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.HighlightLines(hlRanges)) - } - } - if styleAttr, hasStyleAttr := attrs.Get(styleAttrName); hasStyleAttr { - if st, ok := styleAttr.([]uint8); ok { - styleStr := string(st) - style = styles.Get(styleStr) - } - } - if _, hasNohlAttr := attrs.Get(nohlAttrName); hasNohlAttr { - nohl = true - } - - if linenosAttr, ok := attrs.Get(linenosAttrName); ok { - switch v := linenosAttr.(type) { - case bool: - chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.WithLineNumbers(v)) - case []uint8: - if v != nil { - chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.WithLineNumbers(true)) - } - if bytes.Equal(v, linenosTableAttrValue) { - chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.LineNumbersInTable(true)) - } else if bytes.Equal(v, linenosInlineAttrValue) { - chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.LineNumbersInTable(false)) - } - } - } - } - - var lexer chroma.Lexer - if language != nil { - lexer = lexers.Get(string(language)) - } - if !nohl && (lexer != nil || r.GuessLanguage) { - if style == nil { - style = styles.Fallback - } - var buffer bytes.Buffer - l := n.Lines().Len() - for i := 0; i < l; i++ { - line := n.Lines().At(i) - buffer.Write(line.Value(source)) - } - - if lexer == nil { - lexer = lexers.Analyse(buffer.String()) - if lexer == nil { - lexer = lexers.Fallback - } - language = []byte(strings.ToLower(lexer.Config().Name)) - } - lexer = chroma.Coalesce(lexer) - - iterator, err := lexer.Tokenise(nil, buffer.String()) - if err == nil { - c := newCodeBlockContext(language, true, attrs) - - if r.CodeBlockOptions != nil { - chromaFormatterOptions = append(chromaFormatterOptions, r.CodeBlockOptions(c)...) - } - formatter := chromahtml.New(chromaFormatterOptions...) - if r.WrapperRenderer != nil { - r.WrapperRenderer(w, c, true) - } - _ = formatter.Format(w, style, iterator) == nil - if r.WrapperRenderer != nil { - r.WrapperRenderer(w, c, false) - } - if r.CSSWriter != nil { - _ = formatter.WriteCSS(r.CSSWriter, style) - } - return ast.WalkContinue, nil - } - } - - var c CodeBlockContext - if r.WrapperRenderer != nil { - c = newCodeBlockContext(language, false, attrs) - r.WrapperRenderer(w, c, true) - } else { - _, _ = w.WriteString("
    ')
    -	}
    -	l := n.Lines().Len()
    -	for i := 0; i < l; i++ {
    -		line := n.Lines().At(i)
    -		r.Writer.RawWrite(w, line.Value(source))
    -	}
    -	if r.WrapperRenderer != nil {
    -		r.WrapperRenderer(w, c, false)
    -	} else {
    -		_, _ = w.WriteString("
    \n") - } - return ast.WalkContinue, nil -} - -type highlighting struct { - options []Option -} - -// Highlighting is a goldmark.Extender implementation. -var Highlighting = &highlighting{ - options: []Option{}, -} - -// NewHighlighting returns a new extension with given options. -func NewHighlighting(opts ...Option) goldmark.Extender { - return &highlighting{ - options: opts, - } -} - -// Extend implements goldmark.Extender. -func (e *highlighting) Extend(m goldmark.Markdown) { - m.Renderer().AddOptions(renderer.WithNodeRenderers( - util.Prioritized(NewHTMLRenderer(e.options...), 200), - )) -} diff --git a/gno.land/pkg/gnoweb/markdown/highlighting_test.go b/gno.land/pkg/gnoweb/markdown/highlighting_test.go deleted file mode 100644 index 25bc4fedd61..00000000000 --- a/gno.land/pkg/gnoweb/markdown/highlighting_test.go +++ /dev/null @@ -1,568 +0,0 @@ -// This file was copied from https://github.com/yuin/goldmark-highlighting - -package markdown - -import ( - "bytes" - "fmt" - "strings" - "testing" - - "github.com/alecthomas/chroma/v2" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/testutil" - "github.com/yuin/goldmark/util" -) - -func TestHighlighting(t *testing.T) { - var css bytes.Buffer - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - WithStyle("monokai"), - WithCSSWriter(&css), - WithFormatOptions( - chromahtml.WithClasses(true), - chromahtml.WithLineNumbers(false), - ), - WithWrapperRenderer(func(w util.BufWriter, c CodeBlockContext, entering bool) { - _, ok := c.Language() - if entering { - if !ok { - w.WriteString("
    ")
    -							return
    -						}
    -						w.WriteString(`
    `) - } else { - if !ok { - w.WriteString("
    ") - return - } - w.WriteString(`
`) - } - }), - WithCodeBlockOptions(func(c CodeBlockContext) []chromahtml.Option { - if language, ok := c.Language(); ok { - // Turn on line numbers for Go only. - if string(language) == "go" { - return []chromahtml.Option{ - chromahtml.WithLineNumbers(true), - } - } - } - return nil - }), - ), - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte(` -Title -======= -`+"``` go\n"+`func main() { - fmt.Println("ok") -} -`+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -

Title

-
1func main() {
-2    fmt.Println("ok")
-3}
-
-`) { - t.Errorf("failed to render HTML\n%s", buffer.String()) - } - - expected := strings.TrimSpace(`/* Background */ .bg { color: #f8f8f2; background-color: #272822; } -/* PreWrapper */ .chroma { color: #f8f8f2; background-color: #272822; } -/* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #f8f8f2; background-color: #3c3d38 } -/* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #f8f8f2; background-color: #3c3d38 } -/* Error */ .chroma .err { color: #960050; background-color: #1e0010 } -/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit } -/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } -/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } -/* LineHighlight */ .chroma .hl { background-color: #3c3d38 } -/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* Line */ .chroma .line { display: flex; } -/* Keyword */ .chroma .k { color: #66d9ef } -/* KeywordConstant */ .chroma .kc { color: #66d9ef } -/* KeywordDeclaration */ .chroma .kd { color: #66d9ef } -/* KeywordNamespace */ .chroma .kn { color: #f92672 } -/* KeywordPseudo */ .chroma .kp { color: #66d9ef } -/* KeywordReserved */ .chroma .kr { color: #66d9ef } -/* KeywordType */ .chroma .kt { color: #66d9ef } -/* NameAttribute */ .chroma .na { color: #a6e22e } -/* NameClass */ .chroma .nc { color: #a6e22e } -/* NameConstant */ .chroma .no { color: #66d9ef } -/* NameDecorator */ .chroma .nd { color: #a6e22e } -/* NameException */ .chroma .ne { color: #a6e22e } -/* NameFunction */ .chroma .nf { color: #a6e22e } -/* NameOther */ .chroma .nx { color: #a6e22e } -/* NameTag */ .chroma .nt { color: #f92672 } -/* Literal */ .chroma .l { color: #ae81ff } -/* LiteralDate */ .chroma .ld { color: #e6db74 } -/* LiteralString */ .chroma .s { color: #e6db74 } -/* LiteralStringAffix */ .chroma .sa { color: #e6db74 } -/* LiteralStringBacktick */ .chroma .sb { color: #e6db74 } -/* LiteralStringChar */ .chroma .sc { color: #e6db74 } -/* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 } -/* LiteralStringDoc */ .chroma .sd { color: #e6db74 } -/* LiteralStringDouble */ .chroma .s2 { color: #e6db74 } -/* LiteralStringEscape */ .chroma .se { color: #ae81ff } -/* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 } -/* LiteralStringInterpol */ .chroma .si { color: #e6db74 } -/* LiteralStringOther */ .chroma .sx { color: #e6db74 } -/* LiteralStringRegex */ .chroma .sr { color: #e6db74 } -/* LiteralStringSingle */ .chroma .s1 { color: #e6db74 } -/* LiteralStringSymbol */ .chroma .ss { color: #e6db74 } -/* LiteralNumber */ .chroma .m { color: #ae81ff } -/* LiteralNumberBin */ .chroma .mb { color: #ae81ff } -/* LiteralNumberFloat */ .chroma .mf { color: #ae81ff } -/* LiteralNumberHex */ .chroma .mh { color: #ae81ff } -/* LiteralNumberInteger */ .chroma .mi { color: #ae81ff } -/* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff } -/* LiteralNumberOct */ .chroma .mo { color: #ae81ff } -/* Operator */ .chroma .o { color: #f92672 } -/* OperatorWord */ .chroma .ow { color: #f92672 } -/* Comment */ .chroma .c { color: #75715e } -/* CommentHashbang */ .chroma .ch { color: #75715e } -/* CommentMultiline */ .chroma .cm { color: #75715e } -/* CommentSingle */ .chroma .c1 { color: #75715e } -/* CommentSpecial */ .chroma .cs { color: #75715e } -/* CommentPreproc */ .chroma .cp { color: #75715e } -/* CommentPreprocFile */ .chroma .cpf { color: #75715e } -/* GenericDeleted */ .chroma .gd { color: #f92672 } -/* GenericEmph */ .chroma .ge { font-style: italic } -/* GenericInserted */ .chroma .gi { color: #a6e22e } -/* GenericStrong */ .chroma .gs { font-weight: bold } -/* GenericSubheading */ .chroma .gu { color: #75715e }`) - - gotten := strings.TrimSpace(css.String()) - - if expected != gotten { - diff := testutil.DiffPretty([]byte(expected), []byte(gotten)) - t.Errorf("incorrect CSS.\n%s", string(diff)) - } -} - -func TestHighlighting2(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - Highlighting, - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte(` -Title -======= -`+"```"+` -func main() { - fmt.Println("ok") -} -`+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -

Title

-
func main() {
-    fmt.Println("ok")
-}
-
-`) { - t.Error("failed to render HTML") - } -} - -func TestHighlighting3(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - Highlighting, - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte(` -Title -======= - -`+"```"+`cpp {hl_lines=[1,2]} -#include -int main() { - std::cout<< "hello" << std::endl; -} -`+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -

Title

-
#include <iostream>
-int main() {
-    std::cout<< "hello" << std::endl;
-}
-
-`) { - t.Errorf("failed to render HTML:\n%s", buffer.String()) - } -} - -func TestHighlightingCustom(t *testing.T) { - custom := chroma.MustNewStyle("custom", chroma.StyleEntries{ - chroma.Background: "#cccccc bg:#1d1d1d", - chroma.Comment: "#999999", - chroma.CommentSpecial: "#cd0000", - chroma.Keyword: "#cc99cd", - chroma.KeywordDeclaration: "#cc99cd", - chroma.KeywordNamespace: "#cc99cd", - chroma.KeywordType: "#cc99cd", - chroma.Operator: "#67cdcc", - chroma.OperatorWord: "#cdcd00", - chroma.NameClass: "#f08d49", - chroma.NameBuiltin: "#f08d49", - chroma.NameFunction: "#f08d49", - chroma.NameException: "bold #666699", - chroma.NameVariable: "#00cdcd", - chroma.LiteralString: "#7ec699", - chroma.LiteralNumber: "#f08d49", - chroma.LiteralStringBoolean: "#f08d49", - chroma.GenericHeading: "bold #000080", - chroma.GenericSubheading: "bold #800080", - chroma.GenericDeleted: "#e2777a", - chroma.GenericInserted: "#cc99cd", - chroma.GenericError: "#e2777a", - chroma.GenericEmph: "italic", - chroma.GenericStrong: "bold", - chroma.GenericPrompt: "bold #000080", - chroma.GenericOutput: "#888", - chroma.GenericTraceback: "#04D", - chroma.GenericUnderline: "underline", - chroma.Error: "border:#e2777a", - }) - - var css bytes.Buffer - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - WithStyle("monokai"), // to make sure it is overrided even if present - WithCustomStyle(custom), - WithCSSWriter(&css), - WithFormatOptions( - chromahtml.WithClasses(true), - chromahtml.WithLineNumbers(false), - ), - WithWrapperRenderer(func(w util.BufWriter, c CodeBlockContext, entering bool) { - _, ok := c.Language() - if entering { - if !ok { - w.WriteString("
")
-							return
-						}
-						w.WriteString(`
`) - } else { - if !ok { - w.WriteString("
") - return - } - w.WriteString(``) - } - }), - WithCodeBlockOptions(func(c CodeBlockContext) []chromahtml.Option { - if language, ok := c.Language(); ok { - // Turn on line numbers for Go only. - if string(language) == "go" { - return []chromahtml.Option{ - chromahtml.WithLineNumbers(true), - } - } - } - return nil - }), - ), - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte(` -Title -======= -`+"``` go\n"+`func main() { - fmt.Println("ok") -} -`+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -

Title

-
1func main() {
-2    fmt.Println("ok")
-3}
-
-`) { - t.Error("failed to render HTML", buffer.String()) - } - - expected := strings.TrimSpace(`/* Background */ .bg { color: #cccccc; background-color: #1d1d1d; } -/* PreWrapper */ .chroma { color: #cccccc; background-color: #1d1d1d; } -/* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #cccccc; background-color: #333333 } -/* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #cccccc; background-color: #333333 } -/* Error */ .chroma .err { } -/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit } -/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } -/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } -/* LineHighlight */ .chroma .hl { background-color: #333333 } -/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 } -/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 } -/* Line */ .chroma .line { display: flex; } -/* Keyword */ .chroma .k { color: #cc99cd } -/* KeywordConstant */ .chroma .kc { color: #cc99cd } -/* KeywordDeclaration */ .chroma .kd { color: #cc99cd } -/* KeywordNamespace */ .chroma .kn { color: #cc99cd } -/* KeywordPseudo */ .chroma .kp { color: #cc99cd } -/* KeywordReserved */ .chroma .kr { color: #cc99cd } -/* KeywordType */ .chroma .kt { color: #cc99cd } -/* NameBuiltin */ .chroma .nb { color: #f08d49 } -/* NameClass */ .chroma .nc { color: #f08d49 } -/* NameException */ .chroma .ne { color: #666699; font-weight: bold } -/* NameFunction */ .chroma .nf { color: #f08d49 } -/* NameVariable */ .chroma .nv { color: #00cdcd } -/* LiteralString */ .chroma .s { color: #7ec699 } -/* LiteralStringAffix */ .chroma .sa { color: #7ec699 } -/* LiteralStringBacktick */ .chroma .sb { color: #7ec699 } -/* LiteralStringChar */ .chroma .sc { color: #7ec699 } -/* LiteralStringDelimiter */ .chroma .dl { color: #7ec699 } -/* LiteralStringDoc */ .chroma .sd { color: #7ec699 } -/* LiteralStringDouble */ .chroma .s2 { color: #7ec699 } -/* LiteralStringEscape */ .chroma .se { color: #7ec699 } -/* LiteralStringHeredoc */ .chroma .sh { color: #7ec699 } -/* LiteralStringInterpol */ .chroma .si { color: #7ec699 } -/* LiteralStringOther */ .chroma .sx { color: #7ec699 } -/* LiteralStringRegex */ .chroma .sr { color: #7ec699 } -/* LiteralStringSingle */ .chroma .s1 { color: #7ec699 } -/* LiteralStringSymbol */ .chroma .ss { color: #7ec699 } -/* LiteralNumber */ .chroma .m { color: #f08d49 } -/* LiteralNumberBin */ .chroma .mb { color: #f08d49 } -/* LiteralNumberFloat */ .chroma .mf { color: #f08d49 } -/* LiteralNumberHex */ .chroma .mh { color: #f08d49 } -/* LiteralNumberInteger */ .chroma .mi { color: #f08d49 } -/* LiteralNumberIntegerLong */ .chroma .il { color: #f08d49 } -/* LiteralNumberOct */ .chroma .mo { color: #f08d49 } -/* Operator */ .chroma .o { color: #67cdcc } -/* OperatorWord */ .chroma .ow { color: #cdcd00 } -/* Comment */ .chroma .c { color: #999999 } -/* CommentHashbang */ .chroma .ch { color: #999999 } -/* CommentMultiline */ .chroma .cm { color: #999999 } -/* CommentSingle */ .chroma .c1 { color: #999999 } -/* CommentSpecial */ .chroma .cs { color: #cd0000 } -/* CommentPreproc */ .chroma .cp { color: #999999 } -/* CommentPreprocFile */ .chroma .cpf { color: #999999 } -/* GenericDeleted */ .chroma .gd { color: #e2777a } -/* GenericEmph */ .chroma .ge { font-style: italic } -/* GenericError */ .chroma .gr { color: #e2777a } -/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold } -/* GenericInserted */ .chroma .gi { color: #cc99cd } -/* GenericOutput */ .chroma .go { color: #888888 } -/* GenericPrompt */ .chroma .gp { color: #000080; font-weight: bold } -/* GenericStrong */ .chroma .gs { font-weight: bold } -/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold } -/* GenericTraceback */ .chroma .gt { color: #0044dd } -/* GenericUnderline */ .chroma .gl { text-decoration: underline }`) - - gotten := strings.TrimSpace(css.String()) - - if expected != gotten { - diff := testutil.DiffPretty([]byte(expected), []byte(gotten)) - t.Errorf("incorrect CSS.\n%s", string(diff)) - } -} - -func TestHighlightingHlLines(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - WithFormatOptions( - chromahtml.WithClasses(true), - ), - ), - ), - ) - - for i, test := range []struct { - attributes string - expect []int - }{ - {`hl_lines=["2"]`, []int{2}}, - {`hl_lines=["2-3",5],linenostart=5`, []int{2, 3, 5}}, - {`hl_lines=["2-3"]`, []int{2, 3}}, - {`hl_lines=["2-3",5],linenostart="5"`, []int{2, 3}}, // linenostart must be a number. string values are ignored - } { - t.Run(fmt.Sprint(i), func(t *testing.T) { - var buffer bytes.Buffer - codeBlock := fmt.Sprintf(`bash {%s} -LINE1 -LINE2 -LINE3 -LINE4 -LINE5 -LINE6 -LINE7 -LINE8 -`, test.attributes) - - if err := markdown.Convert([]byte(` -`+"```"+codeBlock+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - - for _, line := range test.expect { - expectStr := fmt.Sprintf("LINE%d\n", line) - if !strings.Contains(buffer.String(), expectStr) { - t.Fatal("got\n", buffer.String(), "\nexpected\n", expectStr) - } - } - }) - } -} - -type nopPreWrapper struct{} - -// Start is called to write a start
 element.
-func (nopPreWrapper) Start(code bool, styleAttr string) string { return "" }
-
-// End is called to write the end 
element. -func (nopPreWrapper) End(code bool) string { return "" } - -func TestHighlightingLinenos(t *testing.T) { - outputLineNumbersInTable := `
- -
-1 - -LINE1 -
-
` - - for i, test := range []struct { - attributes string - lineNumbers bool - lineNumbersInTable bool - expect string - }{ - {`linenos=true`, false, false, `1LINE1 -`}, - {`linenos=false`, false, false, `LINE1 -`}, - {``, true, false, `1LINE1 -`}, - {``, true, true, outputLineNumbersInTable}, - {`linenos=inline`, true, true, `1LINE1 -`}, - {`linenos=foo`, false, false, `1LINE1 -`}, - {`linenos=table`, false, false, outputLineNumbersInTable}, - } { - t.Run(fmt.Sprint(i), func(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - WithFormatOptions( - chromahtml.WithLineNumbers(test.lineNumbers), - chromahtml.LineNumbersInTable(test.lineNumbersInTable), - chromahtml.WithPreWrapper(nopPreWrapper{}), - chromahtml.WithClasses(true), - ), - ), - ), - ) - - var buffer bytes.Buffer - codeBlock := fmt.Sprintf(`bash {%s} -LINE1 -`, test.attributes) - - content := "```" + codeBlock + "```" - - if err := markdown.Convert([]byte(content), &buffer); err != nil { - t.Fatal(err) - } - - s := strings.TrimSpace(buffer.String()) - - if s != test.expect { - t.Fatal("got\n", s, "\nexpected\n", test.expect) - } - }) - } -} - -func TestHighlightingGuessLanguage(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - WithGuessLanguage(true), - WithFormatOptions( - chromahtml.WithClasses(true), - chromahtml.WithLineNumbers(true), - ), - ), - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte("```"+` -LINE -`+"```"), &buffer); err != nil { - t.Fatal(err) - } - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -
1LINE
-
-`) { - t.Errorf("render mismatch, got\n%s", buffer.String()) - } -} - -func TestCoalesceNeeded(t *testing.T) { - markdown := goldmark.New( - goldmark.WithExtensions( - NewHighlighting( - // WithGuessLanguage(true), - WithFormatOptions( - chromahtml.WithClasses(true), - chromahtml.WithLineNumbers(true), - ), - ), - ), - ) - var buffer bytes.Buffer - if err := markdown.Convert([]byte("```http"+` -GET /foo HTTP/1.1 -Content-Type: application/json -User-Agent: foo - -{ - "hello": "world" -} -`+"```"), &buffer); err != nil { - t.Fatal(err) - } - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -
1GET /foo HTTP/1.1
-2Content-Type: application/json
-3User-Agent: foo
-4
-5{
-6  "hello": "world"
-7}
-
-`) { - t.Errorf("render mismatch, got\n%s", buffer.String()) - } -} diff --git a/go.mod b/go.mod index ed7c3b75528..280ca3ae602 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/yuin/goldmark v1.7.2 + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.etcd.io/bbolt v1.3.11 go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 diff --git a/go.sum b/go.sum index a4ccfbbdd66..9c4d20dbad6 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,10 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= @@ -51,6 +53,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -144,8 +148,11 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= From 6472eea9bb1b460471941d6f8e028ce309a5c2b1 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:38:24 +0100 Subject: [PATCH 07/75] fix(gnoweb): simplify url parsing system (#3366) Co-authored-by: Morgan --- gno.land/pkg/gnoweb/app_test.go | 4 +- gno.land/pkg/gnoweb/handler.go | 48 ++--- gno.land/pkg/gnoweb/url.go | 229 ++++++++++++++--------- gno.land/pkg/gnoweb/url_test.go | 313 +++++++++++++++++++++++++++++++- 4 files changed, 468 insertions(+), 126 deletions(-) diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 78fe197a134..4fac6e0b971 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -73,6 +73,7 @@ func TestRoutes(t *testing.T) { for _, r := range routes { t.Run(fmt.Sprintf("test route %s", r.route), func(t *testing.T) { + t.Logf("input: %q", r.route) request := httptest.NewRequest(http.MethodGet, r.route, nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) @@ -125,7 +126,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) - fmt.Println("HELLO:", response.Body.String()) + assert.Contains(t, response.Body.String(), "sa.gno.services") }) } @@ -143,6 +144,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) + assert.NotContains(t, response.Body.String(), "sa.gno.services") }) } diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index bc87f057e26..0a0ee69c3f0 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -99,11 +99,11 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { indexData.HeaderData.WebQuery = gnourl.WebQuery // Render - switch gnourl.Kind() { - case KindRealm, KindPure: + switch { + case gnourl.IsRealm(), gnourl.IsPure(): status, err = h.renderPackage(&body, gnourl) default: - h.logger.Debug("invalid page kind", "kind", gnourl.Kind) + h.logger.Debug("invalid path: path is neither a pure package or a realm") status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") } } @@ -129,10 +129,8 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { func (h *WebHandler) renderPackage(w io.Writer, gnourl *GnoURL) (status int, err error) { h.logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - kind := gnourl.Kind() - // Display realm help page? - if kind == KindRealm && gnourl.WebQuery.Has("help") { + if gnourl.WebQuery.Has("help") { return h.renderRealmHelp(w, gnourl) } @@ -140,26 +138,11 @@ func (h *WebHandler) renderPackage(w io.Writer, gnourl *GnoURL) (status int, err switch { case gnourl.WebQuery.Has("source"): return h.renderRealmSource(w, gnourl) - case kind == KindPure, - strings.HasSuffix(gnourl.Path, "/"), - isFile(gnourl.Path): - i := strings.LastIndexByte(gnourl.Path, '/') - if i < 0 { - return http.StatusInternalServerError, fmt.Errorf("unable to get ending slash for %q", gnourl.Path) - } - + case gnourl.IsFile(): // Fill webquery with file infos - gnourl.WebQuery.Set("source", "") // set source - - file := gnourl.Path[i+1:] - if file == "" { - return h.renderRealmDirectory(w, gnourl) - } - - gnourl.WebQuery.Set("file", file) - gnourl.Path = gnourl.Path[:i] - return h.renderRealmSource(w, gnourl) + case gnourl.IsDir(), gnourl.IsPure(): + return h.renderRealmDirectory(w, gnourl) } // Render content into the content buffer @@ -250,12 +233,16 @@ func (h *WebHandler) renderRealmSource(w io.Writer, gnourl *GnoURL) (status int, return http.StatusOK, components.RenderStatusComponent(w, "no files available") } + file := gnourl.WebQuery.Get("file") // webquery override file + if file == "" { + file = gnourl.File + } + var fileName string - file := gnourl.WebQuery.Get("file") if file == "" { - fileName = files[0] + fileName = files[0] // Default to the first file if none specified } else if slices.Contains(files, file) { - fileName = file + fileName = file // Use specified file if it exists } else { h.logger.Error("unable to render source", "file", file, "err", "file does not exist") return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") @@ -370,10 +357,3 @@ func generateBreadcrumbPaths(path string) []components.BreadcrumbPart { return parts } - -// IsFile checks if the last element of the path is a file (has an extension) -func isFile(path string) bool { - base := filepath.Base(path) - ext := filepath.Ext(base) - return ext != "" -} diff --git a/gno.land/pkg/gnoweb/url.go b/gno.land/pkg/gnoweb/url.go index bc03f2182d9..105ac382800 100644 --- a/gno.land/pkg/gnoweb/url.go +++ b/gno.land/pkg/gnoweb/url.go @@ -4,145 +4,212 @@ import ( "errors" "fmt" "net/url" + "path/filepath" "regexp" "strings" ) -type PathKind byte +var ErrURLInvalidPath = errors.New("invalid path") -const ( - KindInvalid PathKind = 0 - KindRealm PathKind = 'r' - KindPure PathKind = 'p' -) +// rePkgOrRealmPath matches and validates a flexible path. +var rePkgOrRealmPath = regexp.MustCompile(`^/[a-z][a-z0-9_/]*$`) // GnoURL decomposes the parts of an URL to query a realm. type GnoURL struct { // Example full path: - // gno.land/r/demo/users:jae$help&a=b?c=d + // gno.land/r/demo/users/render.gno:jae$help&a=b?c=d Domain string // gno.land Path string // /r/demo/users Args string // jae WebQuery url.Values // help&a=b Query url.Values // c=d + File string // render.gno } -func (url GnoURL) EncodeArgs() string { +// EncodeFlag is used to specify which URL components to encode. +type EncodeFlag int + +const ( + EncodePath EncodeFlag = 1 << iota // Encode the path component + EncodeArgs // Encode the arguments component + EncodeWebQuery // Encode the web query component + EncodeQuery // Encode the query component + EncodeNoEscape // Disable escaping of arguments +) + +// Encode constructs a URL string from the components of a GnoURL struct, +// encoding the specified components based on the provided EncodeFlag bitmask. +// +// The function selectively encodes the URL's path, arguments, web query, and +// query parameters, depending on the flags set in encodeFlags. +// +// Returns a string representing the encoded URL. +// +// Example: +// +// gnoURL := GnoURL{ +// Domain: "gno.land", +// Path: "/r/demo/users", +// Args: "john", +// File: "render.gno", +// } +// +// encodedURL := gnoURL.Encode(EncodePath | EncodeArgs) +// fmt.Println(encodedURL) // Output: /r/demo/users/render.gno:john +// +// URL components are encoded using url.PathEscape unless EncodeNoEscape is specified. +func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { var urlstr strings.Builder - if url.Args != "" { - urlstr.WriteString(url.Args) + + if encodeFlags.Has(EncodePath) { + path := gnoURL.Path + if !encodeFlags.Has(EncodeNoEscape) { + path = url.PathEscape(path) + } + + urlstr.WriteString(gnoURL.Path) } - if len(url.Query) > 0 { - urlstr.WriteString("?" + url.Query.Encode()) + if len(gnoURL.File) > 0 { + urlstr.WriteRune('/') + urlstr.WriteString(gnoURL.File) } - return urlstr.String() -} + if encodeFlags.Has(EncodeArgs) && gnoURL.Args != "" { + if encodeFlags.Has(EncodePath) { + urlstr.WriteRune(':') + } -func (url GnoURL) EncodePath() string { - var urlstr strings.Builder - urlstr.WriteString(url.Path) - if url.Args != "" { - urlstr.WriteString(":" + url.Args) + // XXX: Arguments should ideally always be escaped, + // but this may require changes in some realms. + args := gnoURL.Args + if !encodeFlags.Has(EncodeNoEscape) { + args = escapeDollarSign(url.PathEscape(args)) + } + + urlstr.WriteString(args) + } + + if encodeFlags.Has(EncodeWebQuery) && len(gnoURL.WebQuery) > 0 { + urlstr.WriteRune('$') + urlstr.WriteString(gnoURL.WebQuery.Encode()) } - if len(url.Query) > 0 { - urlstr.WriteString("?" + url.Query.Encode()) + if encodeFlags.Has(EncodeQuery) && len(gnoURL.Query) > 0 { + urlstr.WriteRune('?') + urlstr.WriteString(gnoURL.Query.Encode()) } return urlstr.String() } -func (url GnoURL) EncodeWebPath() string { - var urlstr strings.Builder - urlstr.WriteString(url.Path) - if url.Args != "" { - pathEscape := escapeDollarSign(url.Args) - urlstr.WriteString(":" + pathEscape) - } +// Has checks if the EncodeFlag contains all the specified flags. +func (f EncodeFlag) Has(flags EncodeFlag) bool { + return f&flags != 0 +} - if len(url.WebQuery) > 0 { - urlstr.WriteString("$" + url.WebQuery.Encode()) - } +func escapeDollarSign(s string) string { + return strings.ReplaceAll(s, "$", "%24") +} - if len(url.Query) > 0 { - urlstr.WriteString("?" + url.Query.Encode()) - } +// EncodeArgs encodes the arguments and query parameters into a string. +// This function is intended to be passed as a realm `Render` argument. +func (gnoURL GnoURL) EncodeArgs() string { + return gnoURL.Encode(EncodeArgs | EncodeQuery | EncodeNoEscape) +} - return urlstr.String() +// EncodeURL encodes the path, arguments, and query parameters into a string. +// This function provides the full representation of the URL without the web query. +func (gnoURL GnoURL) EncodeURL() string { + return gnoURL.Encode(EncodePath | EncodeArgs | EncodeQuery) } -func (url GnoURL) Kind() PathKind { - if len(url.Path) < 2 { - return KindInvalid - } - pk := PathKind(url.Path[1]) - switch pk { - case KindPure, KindRealm: - return pk - } - return KindInvalid +// EncodeWebURL encodes the path, package arguments, web query, and query into a string. +// This function provides the full representation of the URL. +func (gnoURL GnoURL) EncodeWebURL() string { + return gnoURL.Encode(EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery) } -var ( - ErrURLMalformedPath = errors.New("malformed URL path") - ErrURLInvalidPathKind = errors.New("invalid path kind") -) +// IsPure checks if the URL path represents a pure path. +func (gnoURL GnoURL) IsPure() bool { + return strings.HasPrefix(gnoURL.Path, "/p/") +} -// reRealName match a realm path -// - matches[1]: path -// - matches[2]: path args -var reRealmPath = regexp.MustCompile(`^` + - `(/(?:[a-zA-Z0-9_-]+)/` + // path kind - `[a-zA-Z][a-zA-Z0-9_-]*` + // First path segment - `(?:/[a-zA-Z][.a-zA-Z0-9_-]*)*/?)` + // Additional path segments - `([:$](?:.*))?$`, // Remaining portions args, separate by `$` or `:` -) +// IsRealm checks if the URL path represents a realm path. +func (gnoURL GnoURL) IsRealm() bool { + return strings.HasPrefix(gnoURL.Path, "/r/") +} + +// IsFile checks if the URL path represents a file. +func (gnoURL GnoURL) IsFile() bool { + return gnoURL.File != "" +} + +// IsDir checks if the URL path represents a directory. +func (gnoURL GnoURL) IsDir() bool { + return !gnoURL.IsFile() && + len(gnoURL.Path) > 0 && gnoURL.Path[len(gnoURL.Path)-1] == '/' +} + +func (gnoURL GnoURL) IsValid() bool { + return rePkgOrRealmPath.MatchString(gnoURL.Path) +} +// ParseGnoURL parses a URL into a GnoURL structure, extracting and validating its components. func ParseGnoURL(u *url.URL) (*GnoURL, error) { - matches := reRealmPath.FindStringSubmatch(u.EscapedPath()) - if len(matches) != 3 { - return nil, fmt.Errorf("%w: %s", ErrURLMalformedPath, u.Path) + var webargs string + path, args, found := strings.Cut(u.EscapedPath(), ":") + if found { + args, webargs, _ = strings.Cut(args, "$") + } else { + path, webargs, _ = strings.Cut(path, "$") + } + + upath, err := url.PathUnescape(path) + if err != nil { + return nil, fmt.Errorf("unable to unescape path %q: %w", path, err) } - path := matches[1] - args := matches[2] + var file string + + // A file is considered as one that either ends with an extension or + // contains an uppercase rune + ext := filepath.Ext(upath) + base := filepath.Base(upath) + if ext != "" || strings.ToLower(base) != base { + file = base + upath = strings.TrimSuffix(upath, base) - if len(args) > 0 { - switch args[0] { - case ':': - args = args[1:] - case '$': - default: - return nil, fmt.Errorf("%w: %s", ErrURLMalformedPath, u.Path) + // Trim last slash if any + if i := strings.LastIndexByte(upath, '/'); i > 0 { + upath = upath[:i] } } - var err error + if !rePkgOrRealmPath.MatchString(upath) { + return nil, fmt.Errorf("%w: %q", ErrURLInvalidPath, upath) + } + webquery := url.Values{} - args, webargs, found := strings.Cut(args, "$") - if found { - if webquery, err = url.ParseQuery(webargs); err != nil { - return nil, fmt.Errorf("unable to parse webquery %q: %w ", webquery, err) + if len(webargs) > 0 { + var parseErr error + if webquery, parseErr = url.ParseQuery(webargs); parseErr != nil { + return nil, fmt.Errorf("unable to parse webquery %q: %w", webargs, parseErr) } } uargs, err := url.PathUnescape(args) if err != nil { - return nil, fmt.Errorf("unable to unescape path %q: %w", args, err) + return nil, fmt.Errorf("unable to unescape args %q: %w", args, err) } return &GnoURL{ - Path: path, + Path: upath, Args: uargs, WebQuery: webquery, Query: u.Query(), Domain: u.Hostname(), + File: file, }, nil } - -func escapeDollarSign(s string) string { - return strings.ReplaceAll(s, "$", "%24") -} diff --git a/gno.land/pkg/gnoweb/url_test.go b/gno.land/pkg/gnoweb/url_test.go index 73cfdda69bd..b4e901d4f10 100644 --- a/gno.land/pkg/gnoweb/url_test.go +++ b/gno.land/pkg/gnoweb/url_test.go @@ -19,8 +19,9 @@ func TestParseGnoURL(t *testing.T) { Name: "malformed url", Input: "https://gno.land/r/dem)o:$?", Expected: nil, - Err: ErrURLMalformedPath, + Err: ErrURLInvalidPath, }, + { Name: "simple", Input: "https://gno.land/r/simple/test", @@ -30,8 +31,32 @@ func TestParseGnoURL(t *testing.T) { WebQuery: url.Values{}, Query: url.Values{}, }, - Err: nil, }, + + { + Name: "file", + Input: "https://gno.land/r/simple/test/encode.gno", + Expected: &GnoURL{ + Domain: "gno.land", + Path: "/r/simple/test", + WebQuery: url.Values{}, + Query: url.Values{}, + File: "encode.gno", + }, + }, + + { + Name: "complex file path", + Input: "https://gno.land/r/simple/test///...gno", + Expected: &GnoURL{ + Domain: "gno.land", + Path: "/r/simple/test//", + WebQuery: url.Values{}, + Query: url.Values{}, + File: "...gno", + }, + }, + { Name: "webquery + query", Input: "https://gno.land/r/demo/foo$help&func=Bar&name=Baz", @@ -46,7 +71,6 @@ func TestParseGnoURL(t *testing.T) { Query: url.Values{}, Domain: "gno.land", }, - Err: nil, }, { @@ -61,7 +85,6 @@ func TestParseGnoURL(t *testing.T) { Query: url.Values{}, Domain: "gno.land", }, - Err: nil, }, { @@ -78,7 +101,6 @@ func TestParseGnoURL(t *testing.T) { }, Domain: "gno.land", }, - Err: nil, }, { @@ -93,7 +115,6 @@ func TestParseGnoURL(t *testing.T) { }, Domain: "gno.land", }, - Err: nil, }, { @@ -108,22 +129,140 @@ func TestParseGnoURL(t *testing.T) { Query: url.Values{}, Domain: "gno.land", }, - Err: nil, }, - // XXX: more tests + { + Name: "unknown path kind", + Input: "https://gno.land/x/demo/foo", + Expected: &GnoURL{ + Path: "/x/demo/foo", + Args: "", + WebQuery: url.Values{}, + Query: url.Values{}, + Domain: "gno.land", + }, + }, + + { + Name: "empty path", + Input: "https://gno.land/r/", + Expected: &GnoURL{ + Path: "/r/", + Args: "", + WebQuery: url.Values{}, + Query: url.Values{}, + Domain: "gno.land", + }, + }, + + { + Name: "complex query", + Input: "https://gno.land/r/demo/foo$help?func=Bar&name=Baz&age=30", + Expected: &GnoURL{ + Path: "/r/demo/foo", + Args: "", + WebQuery: url.Values{ + "help": []string{""}, + }, + Query: url.Values{ + "func": []string{"Bar"}, + "name": []string{"Baz"}, + "age": []string{"30"}, + }, + Domain: "gno.land", + }, + }, + + { + Name: "multiple web queries", + Input: "https://gno.land/r/demo/foo$help&func=Bar$test=123", + Expected: &GnoURL{ + Path: "/r/demo/foo", + Args: "", + WebQuery: url.Values{ + "help": []string{""}, + "func": []string{"Bar$test=123"}, + }, + Query: url.Values{}, + Domain: "gno.land", + }, + }, + + { + Name: "webquery-args-webquery", + Input: "https://gno.land/r/demo/aaa$bbb:CCC&DDD$EEE", + Err: ErrURLInvalidPath, // `/r/demo/aaa$bbb` is an invalid path + }, + + { + Name: "args-webquery-args", + Input: "https://gno.land/r/demo/aaa:BBB$CCC&DDD:EEE", + Expected: &GnoURL{ + Domain: "gno.land", + Path: "/r/demo/aaa", + Args: "BBB", + WebQuery: url.Values{ + "CCC": []string{""}, + "DDD:EEE": []string{""}, + }, + Query: url.Values{}, + }, + }, + + { + Name: "escaped characters in args", + Input: "https://gno.land/r/demo/foo:example%20with%20spaces$tz=Europe/Paris", + Expected: &GnoURL{ + Path: "/r/demo/foo", + Args: "example with spaces", + WebQuery: url.Values{ + "tz": []string{"Europe/Paris"}, + }, + Query: url.Values{}, + Domain: "gno.land", + }, + }, + + { + Name: "file in path + args + query", + Input: "https://gno.land/r/demo/foo/render.gno:example$tz=Europe/Paris", + Expected: &GnoURL{ + Path: "/r/demo/foo", + File: "render.gno", + Args: "example", + WebQuery: url.Values{ + "tz": []string{"Europe/Paris"}, + }, + Query: url.Values{}, + Domain: "gno.land", + }, + }, + + { + Name: "no extension file", + Input: "https://gno.land/r/demo/lIcEnSe", + Expected: &GnoURL{ + Path: "/r/demo", + File: "lIcEnSe", + Args: "", + WebQuery: url.Values{}, + Query: url.Values{}, + Domain: "gno.land", + }, + }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { + t.Logf("testing input: %q", tc.Input) + u, err := url.Parse(tc.Input) require.NoError(t, err) result, err := ParseGnoURL(u) if tc.Err == nil { require.NoError(t, err) - t.Logf("parsed: %s", result.EncodePath()) - t.Logf("parsed web: %s", result.EncodeWebPath()) + t.Logf("encoded web path: %q", result.EncodeWebURL()) } else { require.Error(t, err) require.ErrorIs(t, err, tc.Err) @@ -133,3 +272,157 @@ func TestParseGnoURL(t *testing.T) { }) } } + +func TestEncode(t *testing.T) { + testCases := []struct { + Name string + GnoURL GnoURL + EncodeFlags EncodeFlag + Expected string + }{ + { + Name: "Encode Path Only", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + }, + EncodeFlags: EncodePath, + Expected: "/r/demo/foo", + }, + + { + Name: "Encode Path and File", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + File: "render.gno", + }, + EncodeFlags: EncodePath, + Expected: "/r/demo/foo/render.gno", + }, + + { + Name: "Encode Path, File, and Args", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + File: "render.gno", + Args: "example", + }, + EncodeFlags: EncodePath | EncodeArgs, + Expected: "/r/demo/foo/render.gno:example", + }, + + { + Name: "Encode Path and Args", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example", + }, + EncodeFlags: EncodePath | EncodeArgs, + Expected: "/r/demo/foo:example", + }, + + { + Name: "Encode Path, Args, and WebQuery", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example", + WebQuery: url.Values{ + "tz": []string{"Europe/Paris"}, + }, + }, + EncodeFlags: EncodePath | EncodeArgs | EncodeWebQuery, + Expected: "/r/demo/foo:example$tz=Europe%2FParis", + }, + + { + Name: "Encode Full URL", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example", + WebQuery: url.Values{ + "tz": []string{"Europe/Paris"}, + }, + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery, + Expected: "/r/demo/foo:example$tz=Europe%2FParis?hello=42", + }, + + { + Name: "Encode Args and Query", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "hello Jo$ny", + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodeArgs | EncodeQuery, + Expected: "hello%20Jo%24ny?hello=42", + }, + + { + Name: "Encode Args and Query (No Escape)", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "hello Jo$ny", + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodeArgs | EncodeQuery | EncodeNoEscape, + Expected: "hello Jo$ny?hello=42", + }, + + { + Name: "Encode Args and Query", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example", + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodeArgs | EncodeQuery, + Expected: "example?hello=42", + }, + + { + Name: "Encode with Escaped Characters", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example with spaces", + WebQuery: url.Values{ + "tz": []string{"Europe/Paris"}, + }, + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery, + Expected: "/r/demo/foo:example%20with%20spaces$tz=Europe%2FParis?hello=42", + }, + + { + Name: "Encode Path, Args, and Query", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + Args: "example", + Query: url.Values{ + "hello": []string{"42"}, + }, + }, + EncodeFlags: EncodePath | EncodeArgs | EncodeQuery, + Expected: "/r/demo/foo:example?hello=42", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + result := tc.GnoURL.Encode(tc.EncodeFlags) + require.True(t, tc.GnoURL.IsValid(), "gno url is not valid") + assert.Equal(t, tc.Expected, result) + }) + } +} From 750c07ada485a6e6b5e9193132637bc46fb0f3b3 Mon Sep 17 00:00:00 2001 From: piux2 <90544084+piux2@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:09:05 -0800 Subject: [PATCH 08/75] fix: increase max block gas limit (#3384) Increase the block gas limit accordingly as we update the gas configuration. --- gno.land/cmd/gnoland/start.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index eaaf7293986..cb5d54a513a 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -374,10 +374,10 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro gen.ConsensusParams = abci.ConsensusParams{ Block: &abci.BlockParams{ // TODO: update limits. - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms }, } From 68aff6464dfba782903cdb5e3b318a9b233a479e Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 9 Jan 2025 10:33:16 +0100 Subject: [PATCH 09/75] feat(gnovm): implement overflow checking at VM level (#3250) I propose that we implement overflow checking directly in gnovm opcodes, and that gnovm always enforces overflow checking. Overflow checking becomes a capacity of the Gno language and the Gno virtual machine. It's important for a smart contract platform to offer by default, and without user or developer effort, the strongest guarantees on numerical operations. In that topic, Gno would be superior to the standard Go runtime which, like C and most other languages, don't address this internally beside constants (to preserve the best possible native performances), and rely on external user code. It would also simplify the user code and avoid to use specific libraries. For example, in `gnovm/stdlibs/std/coins.go`, for the `Coin.Add` method: Before: ```go import "math/overflow" func (c Coin) Add(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) sum, ok := overflow.Add64(c.Amount, other.Amount) if !ok { panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) } c.Amount = sum return c } ``` After: ```go func (c Coin) Add(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) c.Amount += other.Amount return c } ``` with the same behaviour for overflow checking. Note also that the new version, is not only simpler, but also faster, because overflow checking is performed natively, and not interpreted. Integer overflow handling is only implemented for signed integers. Unsigned integers, on purpose, just wrap around when reaching their maximum or minimum values. This is intended to support all crypto, hash and bitwise operations which may rely on that wrap around property. Division by zero is still handled both in signed and unsigned integers. Note: from now, on security level, the use of unsigned integers for standard numeric operations should be probably considered suspicious. ## Benchmark To measure the impact of overflow, I execute the following benchmarks: First a micro benchmark comparing an addition of 2 ints, with and without overflow: ```go //go:noinline func AddNoOverflow(x, y int) int { return x + y } func BenchmarkAddNoOverflow(b *testing.B) { x, y := 4, 3 c := 0 for range b.N { c = AddNoOverflow(x, y) } if c != 7 { b.Error("invalid result") } } func BenchmarkAddOverflow(b *testing.B) { x, y := 4, 3 c := 0 for range b.N { c = overflow.Addp(x, y) } if c != 7 { b.Error("invalid result") } } ``` The implementation of overflow checking is taken from http://github.com/gnolang/overflow, already used in tm2. It gives the following results: ```console $ go test -v- run=^# -benchmem -bench=Overflow goos: darwin goarch: arm64 pkg: github.com/gnolang/gno/gnovm/pkg/gnolang cpu: Apple M1 BenchmarkAddNoOverflow BenchmarkAddNoOverflow-8 1000000000 0.9392 ns/op 0 B/op 0 allocs/op BenchmarkAddOverflow BenchmarkAddOverflow-8 568881582 2.101 ns/op 0 B/op 0 allocs/op PASS ok github.com/gnolang/gno/gnovm/pkg/gnolang 2.640s ``` Checking overflow doubles the execution time of an addition from 1 ns/op to 2 ns/op. But at 2 ns, the total time is still an order of magnitude lower than the cost of running the VM. The impact of overflow check doesn't even appear when benchmarking at VM level with the following: ```go func BenchmarkOpAdd(b *testing.B) { m := NewMachine("bench", nil) x := TypedValue{T: IntType} x.SetInt(4) y := TypedValue{T: IntType} y.SetInt(3) b.ResetTimer() for range b.N { m.PushOp(OpHalt) m.PushExpr(&BinaryExpr{}) m.PushValue(x) m.PushValue(y) m.PushOp(OpAdd) m.Run() } } ``` Which gives something like: ```console $ go test -v -benchmem -bench=OpAdd -run=^# goos: darwin goarch: arm64 pkg: github.com/gnolang/gno/gnovm/pkg/gnolang cpu: Apple M1 BenchmarkOpAdd BenchmarkOpAdd-8 16069832 74.41 ns/op 163 B/op 1 allocs/op PASS ok github.com/gnolang/gno/gnovm/pkg/gnolang 1.526 ``` Where the execution time varie from 60 ns/op to 100 ns/op for both versions of addition, with or without overflow. ## Related PRs and issues - PRs: - #3197 - #3192 - #3117 - #2983 - #2905 - #2698 - Issues: - #2873 - #1844 - #1729
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- examples/gno.land/p/demo/grc/grc20/token.gno | 22 +- gnovm/pkg/gnolang/op_bench_test.go | 70 +++ gnovm/pkg/gnolang/op_binary.go | 315 ++++++----- gnovm/pkg/gnolang/op_inc_dec.go | 25 +- gnovm/stdlibs/generated.go | 1 - gnovm/stdlibs/math/const_test.gno | 77 ++- gnovm/stdlibs/math/overflow/overflow.gno | 501 ------------------ gnovm/stdlibs/math/overflow/overflow_test.gno | 200 ------- gnovm/stdlibs/std/coins.gno | 26 +- gnovm/tests/files/overflow0.gno | 10 + gnovm/tests/files/overflow1.gno | 10 + gnovm/tests/files/overflow2.gno | 10 + gnovm/tests/files/overflow3.gno | 10 + gnovm/tests/files/overflow4.gno | 10 + gnovm/tests/files/overflow5.gno | 10 + gnovm/tests/files/recover14.gno | 2 +- misc/genstd/util.go | 3 +- tm2/pkg/overflow/README.md | 60 +-- tm2/pkg/overflow/overflow_impl.go | 119 ++--- tm2/pkg/overflow/overflow_template.sh | 135 +++-- tm2/pkg/overflow/overflow_test.go | 14 +- 21 files changed, 569 insertions(+), 1061 deletions(-) create mode 100644 gnovm/pkg/gnolang/op_bench_test.go delete mode 100644 gnovm/stdlibs/math/overflow/overflow.gno delete mode 100644 gnovm/stdlibs/math/overflow/overflow_test.gno create mode 100644 gnovm/tests/files/overflow0.gno create mode 100644 gnovm/tests/files/overflow1.gno create mode 100644 gnovm/tests/files/overflow2.gno create mode 100644 gnovm/tests/files/overflow3.gno create mode 100644 gnovm/tests/files/overflow4.gno create mode 100644 gnovm/tests/files/overflow5.gno diff --git a/examples/gno.land/p/demo/grc/grc20/token.gno b/examples/gno.land/p/demo/grc/grc20/token.gno index 3ab3abc63a3..4986eaebf04 100644 --- a/examples/gno.land/p/demo/grc/grc20/token.gno +++ b/examples/gno.land/p/demo/grc/grc20/token.gno @@ -1,7 +1,6 @@ package grc20 import ( - "math/overflow" "std" "strconv" @@ -170,17 +169,24 @@ func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) err } // Mint increases the total supply of the token and adds the specified amount to the specified address. -func (led *PrivateLedger) Mint(address std.Address, amount uint64) error { +func (led *PrivateLedger) Mint(address std.Address, amount uint64) (err error) { if !address.IsValid() { return ErrInvalidAddress } - // XXX: math/overflow is not supporting uint64. - // This checks prevents overflow but makes the totalSupply limited to a uint63. - sum, ok := overflow.Add64(int64(led.totalSupply), int64(amount)) - if !ok { - return ErrOverflow - } + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + err = ErrOverflow + } + }() + + // Convert amount and totalSupply to signed integers to enable + // overflow checking (not occuring on unsigned) when computing the sum. + // The maximum value for totalSupply is therefore 1<<63. + sum := int64(led.totalSupply) + int64(amount) led.totalSupply = uint64(sum) currentBalance := led.balanceOf(address) diff --git a/gnovm/pkg/gnolang/op_bench_test.go b/gnovm/pkg/gnolang/op_bench_test.go new file mode 100644 index 00000000000..5874f980285 --- /dev/null +++ b/gnovm/pkg/gnolang/op_bench_test.go @@ -0,0 +1,70 @@ +package gnolang + +import ( + "testing" + + "github.com/gnolang/gno/tm2/pkg/overflow" +) + +func BenchmarkOpAdd(b *testing.B) { + m := NewMachine("bench", nil) + x := TypedValue{T: IntType} + x.SetInt(4) + y := TypedValue{T: IntType} + y.SetInt(3) + + b.ResetTimer() + + for range b.N { + m.PushOp(OpHalt) + m.PushExpr(&BinaryExpr{}) + m.PushValue(x) + m.PushValue(y) + m.PushOp(OpAdd) + m.Run() + } +} + +//go:noinline +func AddNoOverflow(x, y int) int { return x + y } + +func BenchmarkAddNoOverflow(b *testing.B) { + x, y := 4, 3 + c := 0 + for range b.N { + c = AddNoOverflow(x, y) + } + if c != 7 { + b.Error("invalid result") + } +} + +func BenchmarkAddOverflow(b *testing.B) { + x, y := 4, 3 + c := 0 + for range b.N { + c = overflow.Addp(x, y) + } + if c != 7 { + b.Error("invalid result") + } +} + +func TestOpAdd1(t *testing.T) { + m := NewMachine("test", nil) + a := TypedValue{T: IntType} + a.SetInt(4) + b := TypedValue{T: IntType} + b.SetInt(3) + t.Log("a:", a, "b:", b) + + start := m.NumValues + m.PushOp(OpHalt) + m.PushExpr(&BinaryExpr{}) + m.PushValue(a) + m.PushValue(b) + m.PushOp(OpAdd) + m.Run() + res := m.ReapValues(start) + t.Log("res:", res) +} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 6d26fa7ce54..0f66da5e685 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/tm2/pkg/overflow" ) // ---------------------------------------- @@ -183,7 +184,9 @@ func (m *Machine) doOpAdd() { } // add rv to lv. - addAssign(m.Alloc, lv, rv) + if err := addAssign(m.Alloc, lv, rv); err != nil { + panic(err) + } } func (m *Machine) doOpSub() { @@ -197,7 +200,9 @@ func (m *Machine) doOpSub() { } // sub rv from lv. - subAssign(lv, rv) + if err := subAssign(lv, rv); err != nil { + panic(err) + } } func (m *Machine) doOpBor() { @@ -253,8 +258,7 @@ func (m *Machine) doOpQuo() { } // lv / rv - err := quoAssign(lv, rv) - if err != nil { + if err := quoAssign(lv, rv); err != nil { panic(err) } } @@ -270,8 +274,7 @@ func (m *Machine) doOpRem() { } // lv % rv - err := remAssign(lv, rv) - if err != nil { + if err := remAssign(lv, rv); err != nil { panic(err) } } @@ -683,23 +686,38 @@ func isGeq(lv, rv *TypedValue) bool { } } -// for doOpAdd and doOpAddAssign. -func addAssign(alloc *Allocator, lv, rv *TypedValue) { +// addAssign adds lv to rv and stores the result to lv. +// It returns an exception in case of overflow on signed integers. +// The assignement is performed even in case of exception. +func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { // set the result in lv. // NOTE this block is replicated in op_assign.go + ok := true switch baseOf(lv.T) { case StringType, UntypedStringType: lv.V = alloc.NewString(lv.GetString() + rv.GetString()) + // Signed integers may overflow, which triggers an exception. case IntType: - lv.SetInt(lv.GetInt() + rv.GetInt()) + var r int + r, ok = overflow.Add(lv.GetInt(), rv.GetInt()) + lv.SetInt(r) case Int8Type: - lv.SetInt8(lv.GetInt8() + rv.GetInt8()) + var r int8 + r, ok = overflow.Add8(lv.GetInt8(), rv.GetInt8()) + lv.SetInt8(r) case Int16Type: - lv.SetInt16(lv.GetInt16() + rv.GetInt16()) + var r int16 + r, ok = overflow.Add16(lv.GetInt16(), rv.GetInt16()) + lv.SetInt16(r) case Int32Type, UntypedRuneType: - lv.SetInt32(lv.GetInt32() + rv.GetInt32()) + var r int32 + r, ok = overflow.Add32(lv.GetInt32(), rv.GetInt32()) + lv.SetInt32(r) case Int64Type: - lv.SetInt64(lv.GetInt64() + rv.GetInt64()) + var r int64 + r, ok = overflow.Add64(lv.GetInt64(), rv.GetInt64()) + lv.SetInt64(r) + // Unsigned integers do not overflow, they just wrap. case UintType: lv.SetUint(lv.GetUint() + rv.GetUint()) case Uint8Type: @@ -739,23 +757,42 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) { lv.T, )) } + if !ok { + return &Exception{Value: typedString("addition overflow")} + } + return nil } -// for doOpSub and doOpSubAssign. -func subAssign(lv, rv *TypedValue) { +// subAssign subtracts lv to rv and stores the result to lv. +// It returns an exception in case of overflow on signed integers. +// The subtraction is performed even in case of exception. +func subAssign(lv, rv *TypedValue) *Exception { // set the result in lv. // NOTE this block is replicated in op_assign.go + ok := true switch baseOf(lv.T) { + // Signed integers may overflow, which triggers an exception. case IntType: - lv.SetInt(lv.GetInt() - rv.GetInt()) + var r int + r, ok = overflow.Sub(lv.GetInt(), rv.GetInt()) + lv.SetInt(r) case Int8Type: - lv.SetInt8(lv.GetInt8() - rv.GetInt8()) + var r int8 + r, ok = overflow.Sub8(lv.GetInt8(), rv.GetInt8()) + lv.SetInt8(r) case Int16Type: - lv.SetInt16(lv.GetInt16() - rv.GetInt16()) + var r int16 + r, ok = overflow.Sub16(lv.GetInt16(), rv.GetInt16()) + lv.SetInt16(r) case Int32Type, UntypedRuneType: - lv.SetInt32(lv.GetInt32() - rv.GetInt32()) + var r int32 + r, ok = overflow.Sub32(lv.GetInt32(), rv.GetInt32()) + lv.SetInt32(r) case Int64Type: - lv.SetInt64(lv.GetInt64() - rv.GetInt64()) + var r int64 + r, ok = overflow.Sub64(lv.GetInt64(), rv.GetInt64()) + lv.SetInt64(r) + // Unsigned integers do not overflow, they just wrap. case UintType: lv.SetUint(lv.GetUint() - rv.GetUint()) case Uint8Type: @@ -795,23 +832,39 @@ func subAssign(lv, rv *TypedValue) { lv.T, )) } + if !ok { + return &Exception{Value: typedString("subtraction overflow")} + } + return nil } // for doOpMul and doOpMulAssign. -func mulAssign(lv, rv *TypedValue) { +func mulAssign(lv, rv *TypedValue) *Exception { // set the result in lv. // NOTE this block is replicated in op_assign.go + ok := true switch baseOf(lv.T) { + // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(lv.GetInt() * rv.GetInt()) + var r int + r, ok = overflow.Mul(lv.GetInt(), rv.GetInt()) + lv.SetInt(r) case Int8Type: - lv.SetInt8(lv.GetInt8() * rv.GetInt8()) + var r int8 + r, ok = overflow.Mul8(lv.GetInt8(), rv.GetInt8()) + lv.SetInt8(r) case Int16Type: - lv.SetInt16(lv.GetInt16() * rv.GetInt16()) + var r int16 + r, ok = overflow.Mul16(lv.GetInt16(), rv.GetInt16()) + lv.SetInt16(r) case Int32Type, UntypedRuneType: - lv.SetInt32(lv.GetInt32() * rv.GetInt32()) + var r int32 + r, ok = overflow.Mul32(lv.GetInt32(), rv.GetInt32()) + lv.SetInt32(r) case Int64Type: - lv.SetInt64(lv.GetInt64() * rv.GetInt64()) + var r int64 + r, ok = overflow.Mul64(lv.GetInt64(), rv.GetInt64()) + lv.SetInt64(r) case UintType: lv.SetUint(lv.GetUint() * rv.GetUint()) case Uint8Type: @@ -849,96 +902,105 @@ func mulAssign(lv, rv *TypedValue) { lv.T, )) } + if !ok { + return &Exception{Value: typedString("multiplication overflow")} + } + return nil } // for doOpQuo and doOpQuoAssign. func quoAssign(lv, rv *TypedValue) *Exception { - expt := &Exception{ - Value: typedString("division by zero"), - } - // set the result in lv. // NOTE this block is replicated in op_assign.go + ok := true switch baseOf(lv.T) { + // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - if rv.GetInt() == 0 { - return expt - } - lv.SetInt(lv.GetInt() / rv.GetInt()) + var q int + q, _, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) + lv.SetInt(q) case Int8Type: - if rv.GetInt8() == 0 { - return expt - } - lv.SetInt8(lv.GetInt8() / rv.GetInt8()) + var q int8 + q, _, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) + lv.SetInt8(q) case Int16Type: - if rv.GetInt16() == 0 { - return expt - } - lv.SetInt16(lv.GetInt16() / rv.GetInt16()) + var q int16 + q, _, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) + lv.SetInt16(q) case Int32Type, UntypedRuneType: - if rv.GetInt32() == 0 { - return expt - } - lv.SetInt32(lv.GetInt32() / rv.GetInt32()) + var q int32 + q, _, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) + lv.SetInt32(q) case Int64Type: - if rv.GetInt64() == 0 { - return expt - } - lv.SetInt64(lv.GetInt64() / rv.GetInt64()) + var q int64 + q, _, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) + lv.SetInt64(q) + // Unsigned integers do not cause overflow, but a division by 0 may still occur. case UintType: - if rv.GetUint() == 0 { - return expt + y := rv.GetUint() + ok = y != 0 + if ok { + lv.SetUint(lv.GetUint() / y) } - lv.SetUint(lv.GetUint() / rv.GetUint()) case Uint8Type: - if rv.GetUint8() == 0 { - return expt + y := rv.GetUint8() + ok = y != 0 + if ok { + lv.SetUint8(lv.GetUint8() / y) } - lv.SetUint8(lv.GetUint8() / rv.GetUint8()) case DataByteType: - if rv.GetUint8() == 0 { - return expt + y := rv.GetUint8() + ok = y != 0 + if ok { + lv.SetDataByte(lv.GetDataByte() / y) } - lv.SetDataByte(lv.GetDataByte() / rv.GetUint8()) case Uint16Type: - if rv.GetUint16() == 0 { - return expt + y := rv.GetUint16() + ok = y != 0 + if ok { + lv.SetUint16(lv.GetUint16() / y) } - lv.SetUint16(lv.GetUint16() / rv.GetUint16()) case Uint32Type: - if rv.GetUint32() == 0 { - return expt + y := rv.GetUint32() + ok = y != 0 + if ok { + lv.SetUint32(lv.GetUint32() / y) } - lv.SetUint32(lv.GetUint32() / rv.GetUint32()) case Uint64Type: - if rv.GetUint64() == 0 { - return expt + y := rv.GetUint64() + ok = y != 0 + if ok { + lv.SetUint64(lv.GetUint64() / y) } - lv.SetUint64(lv.GetUint64() / rv.GetUint64()) + // XXX Handling float overflows is more complex. case Float32Type: // NOTE: gno doesn't fuse *+. - if rv.GetFloat32() == 0 { - return expt + y := rv.GetFloat32() + ok = y != 0 + if ok { + lv.SetFloat32(lv.GetFloat32() / y) } - lv.SetFloat32(lv.GetFloat32() / rv.GetFloat32()) // XXX FOR DETERMINISM, PANIC IF NAN. case Float64Type: // NOTE: gno doesn't fuse *+. - if rv.GetFloat64() == 0 { - return expt + y := rv.GetFloat64() + ok = y != 0 + if ok { + lv.SetFloat64(lv.GetFloat64() / y) } - lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64()) // XXX FOR DETERMINISM, PANIC IF NAN. case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { - return expt + ok = false + break } lb := lv.GetBigInt() lb = big.NewInt(0).Quo(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} case BigdecType, UntypedBigdecType: if rv.GetBigDec().Cmp(apd.New(0, 0)) == 0 { - return expt + ok = false + break } lb := lv.GetBigDec() rb := rv.GetBigDec() @@ -955,81 +1017,83 @@ func quoAssign(lv, rv *TypedValue) *Exception { )) } + if !ok { + return &Exception{Value: typedString("division by zero or overflow")} + } return nil } // for doOpRem and doOpRemAssign. func remAssign(lv, rv *TypedValue) *Exception { - expt := &Exception{ - Value: typedString("division by zero"), - } - // set the result in lv. // NOTE this block is replicated in op_assign.go + ok := true switch baseOf(lv.T) { + // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - if rv.GetInt() == 0 { - return expt - } - lv.SetInt(lv.GetInt() % rv.GetInt()) + var r int + _, r, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) + lv.SetInt(r) case Int8Type: - if rv.GetInt8() == 0 { - return expt - } - lv.SetInt8(lv.GetInt8() % rv.GetInt8()) + var r int8 + _, r, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) + lv.SetInt8(r) case Int16Type: - if rv.GetInt16() == 0 { - return expt - } - lv.SetInt16(lv.GetInt16() % rv.GetInt16()) + var r int16 + _, r, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) + lv.SetInt16(r) case Int32Type, UntypedRuneType: - if rv.GetInt32() == 0 { - return expt - } - lv.SetInt32(lv.GetInt32() % rv.GetInt32()) + var r int32 + _, r, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) + lv.SetInt32(r) case Int64Type: - if rv.GetInt64() == 0 { - return expt - } - lv.SetInt64(lv.GetInt64() % rv.GetInt64()) + var r int64 + _, r, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) + lv.SetInt64(r) + // Unsigned integers do not cause overflow, but a division by 0 may still occur. case UintType: - if rv.GetUint() == 0 { - return expt + y := rv.GetUint() + ok = y != 0 + if ok { + lv.SetUint(lv.GetUint() % y) } - lv.SetUint(lv.GetUint() % rv.GetUint()) case Uint8Type: - if rv.GetUint8() == 0 { - return expt + y := rv.GetUint8() + ok = y != 0 + if ok { + lv.SetUint8(lv.GetUint8() % y) } - lv.SetUint8(lv.GetUint8() % rv.GetUint8()) case DataByteType: - if rv.GetUint8() == 0 { - return expt + y := rv.GetUint8() + ok = y != 0 + if ok { + lv.SetDataByte(lv.GetDataByte() % y) } - lv.SetDataByte(lv.GetDataByte() % rv.GetUint8()) case Uint16Type: - if rv.GetUint16() == 0 { - return expt + y := rv.GetUint16() + ok = y != 0 + if ok { + lv.SetUint16(lv.GetUint16() % y) } - lv.SetUint16(lv.GetUint16() % rv.GetUint16()) case Uint32Type: - if rv.GetUint32() == 0 { - return expt + y := rv.GetUint32() + ok = y != 0 + if ok { + lv.SetUint32(lv.GetUint32() % y) } - lv.SetUint32(lv.GetUint32() % rv.GetUint32()) case Uint64Type: - if rv.GetUint64() == 0 { - return expt + y := rv.GetUint64() + ok = y != 0 + if ok { + lv.SetUint64(lv.GetUint64() % y) } - lv.SetUint64(lv.GetUint64() % rv.GetUint64()) case BigintType, UntypedBigintType: - if rv.GetBigInt().Sign() == 0 { - return expt + ok = rv.GetBigInt().Sign() != 0 + if ok { + lb := lv.GetBigInt() + lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) + lv.V = BigintValue{V: lb} } - - lb := lv.GetBigInt() - lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) - lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( "operators %% and %%= not defined for %s", @@ -1037,6 +1101,9 @@ func remAssign(lv, rv *TypedValue) *Exception { )) } + if !ok { + return &Exception{Value: typedString("division by zero or overflow")} + } return nil } diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 7a8a885bcf0..1e68e195596 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/tm2/pkg/overflow" ) func (m *Machine) doOpInc() { @@ -31,16 +32,18 @@ func (m *Machine) doOpInc() { // because it could be a type alias // type num int switch baseOf(lv.T) { + // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(lv.GetInt() + 1) + lv.SetInt(overflow.Addp(lv.GetInt(), 1)) case Int8Type: - lv.SetInt8(lv.GetInt8() + 1) + lv.SetInt8(overflow.Add8p(lv.GetInt8(), 1)) case Int16Type: - lv.SetInt16(lv.GetInt16() + 1) + lv.SetInt16(overflow.Add16p(lv.GetInt16(), 1)) case Int32Type: - lv.SetInt32(lv.GetInt32() + 1) + lv.SetInt32(overflow.Add32p(lv.GetInt32(), 1)) case Int64Type: - lv.SetInt64(lv.GetInt64() + 1) + lv.SetInt64(overflow.Add64p(lv.GetInt64(), 1)) + // Unsigned integers do not overflow, they just wrap. case UintType: lv.SetUint(lv.GetUint() + 1) case Uint8Type: @@ -101,16 +104,18 @@ func (m *Machine) doOpDec() { } } switch baseOf(lv.T) { + // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(lv.GetInt() - 1) + lv.SetInt(overflow.Subp(lv.GetInt(), 1)) case Int8Type: - lv.SetInt8(lv.GetInt8() - 1) + lv.SetInt8(overflow.Sub8p(lv.GetInt8(), 1)) case Int16Type: - lv.SetInt16(lv.GetInt16() - 1) + lv.SetInt16(overflow.Sub16p(lv.GetInt16(), 1)) case Int32Type: - lv.SetInt32(lv.GetInt32() - 1) + lv.SetInt32(overflow.Sub32p(lv.GetInt32(), 1)) case Int64Type: - lv.SetInt64(lv.GetInt64() - 1) + lv.SetInt64(overflow.Sub64p(lv.GetInt64(), 1)) + // Unsigned integers do not overflow, they just wrap. case UintType: lv.SetUint(lv.GetUint() - 1) case Uint8Type: diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index c1198e5f351..d5ab052028f 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -995,7 +995,6 @@ var initOrder = [...]string{ "hash", "hash/adler32", "html", - "math/overflow", "math/rand", "path", "sort", diff --git a/gnovm/stdlibs/math/const_test.gno b/gnovm/stdlibs/math/const_test.gno index b892a12898b..fbe59d61878 100644 --- a/gnovm/stdlibs/math/const_test.gno +++ b/gnovm/stdlibs/math/const_test.gno @@ -31,19 +31,76 @@ func TestMaxUint(t *testing.T) { } func TestMaxInt(t *testing.T) { - if v := int(math.MaxInt); v+1 != math.MinInt { - t.Errorf("MaxInt should wrap around to MinInt: %d", v+1) + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + } + }() + v := int(math.MaxInt) + if v+1 == math.MinInt { + t.Errorf("int should overflow") } - if v := int8(math.MaxInt8); v+1 != math.MinInt8 { - t.Errorf("MaxInt8 should wrap around to MinInt8: %d", v+1) + t.Errorf("expected panic did not occur") +} + +func TestMaxInt8(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + } + }() + v := int8(math.MaxInt8) + if v+1 == math.MinInt8 { + t.Errorf("int8 should overflow") } - if v := int16(math.MaxInt16); v+1 != math.MinInt16 { - t.Errorf("MaxInt16 should wrap around to MinInt16: %d", v+1) + t.Errorf("expected panic did not occur") +} + +func TestMaxInt16(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + } + }() + v := int16(math.MaxInt16) + if v+1 == math.MinInt16 { + t.Errorf("int16 should overflow") } - if v := int32(math.MaxInt32); v+1 != math.MinInt32 { - t.Errorf("MaxInt32 should wrap around to MinInt32: %d", v+1) + t.Errorf("expected panic did not occur") +} + +func TestMaxInt32(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + } + }() + v := int32(math.MaxInt32) + if v+1 == math.MinInt32 { + t.Errorf("int32 should overflow") } - if v := int64(math.MaxInt64); v+1 != math.MinInt64 { - t.Errorf("MaxInt64 should wrap around to MinInt64: %d", v+1) + t.Errorf("expected panic did not occur") +} + +func TestMaxInt64(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if r != "addition overflow" { + panic(r) + } + } + }() + v := int64(math.MaxInt64) + if v+1 == math.MinInt64 { + t.Errorf("int64 should overflow") } + t.Errorf("expected panic did not occur") } diff --git a/gnovm/stdlibs/math/overflow/overflow.gno b/gnovm/stdlibs/math/overflow/overflow.gno deleted file mode 100644 index 0bc2e03a522..00000000000 --- a/gnovm/stdlibs/math/overflow/overflow.gno +++ /dev/null @@ -1,501 +0,0 @@ -// This is modified from https://github.com/JohnCGriffin/overflow (MIT). -// NOTE: there was a bug with the original Quotient* functions, and -// testing method. These have been fixed here, and tests ported to -// tests/files/maths_int*.go respectively. -// Note: moved over from p/demo/maths. - -/* -Package overflow offers overflow-checked integer arithmetic operations -for int, int32, and int64. Each of the operations returns a -result,bool combination. This was prompted by the need to know when -to flow into higher precision types from the math.big library. - -For instance, assuing a 64 bit machine: - -10 + 20 -> 30 -int(math.MaxInt64) + 1 -> -9223372036854775808 - -whereas - -overflow.Add(10,20) -> (30, true) -overflow.Add(math.MaxInt64,1) -> (0, false) - -Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized. - -If anybody wishes an unsigned version, submit a pull request for code -and new tests. -*/ -package overflow - -import "math" - -//go:generate ./overflow_template.sh - -func _is64Bit() bool { - maxU32 := uint(math.MaxUint32) - return ((maxU32 << 1) >> 1) == maxU32 -} - -/********** PARTIAL TEST COVERAGE FROM HERE DOWN ************* - -The only way that I could see to do this is a combination of -my normal 64 bit system and a GopherJS running on Node. My -understanding is that its ints are 32 bit. - -So, FEEL FREE to carefully review the code visually. - -*************************************************************/ - -// Unspecified size, i.e. normal signed int - -// Add sums two ints, returning the result and a boolean status. -func Add(a, b int) (int, bool) { - if _is64Bit() { - r64, ok := Add64(int64(a), int64(b)) - return int(r64), ok - } - r32, ok := Add32(int32(a), int32(b)) - return int(r32), ok -} - -// Sub returns the difference of two ints and a boolean status. -func Sub(a, b int) (int, bool) { - if _is64Bit() { - r64, ok := Sub64(int64(a), int64(b)) - return int(r64), ok - } - r32, ok := Sub32(int32(a), int32(b)) - return int(r32), ok -} - -// Mul returns the product of two ints and a boolean status. -func Mul(a, b int) (int, bool) { - if _is64Bit() { - r64, ok := Mul64(int64(a), int64(b)) - return int(r64), ok - } - r32, ok := Mul32(int32(a), int32(b)) - return int(r32), ok -} - -// Div returns the quotient of two ints and a boolean status -func Div(a, b int) (int, bool) { - if _is64Bit() { - r64, ok := Div64(int64(a), int64(b)) - return int(r64), ok - } - r32, ok := Div32(int32(a), int32(b)) - return int(r32), ok -} - -// Quo returns the quotient, remainder and status of two ints -func Quo(a, b int) (int, int, bool) { - if _is64Bit() { - q64, r64, ok := Quo64(int64(a), int64(b)) - return int(q64), int(r64), ok - } - q32, r32, ok := Quo32(int32(a), int32(b)) - return int(q32), int(r32), ok -} - -/************* Panic versions for int ****************/ - -// Addp returns the sum of two ints, panicking on overflow -func Addp(a, b int) int { - r, ok := Add(a, b) - if !ok { - panic("addition overflow") - } - return r -} - -// Subp returns the difference of two ints, panicking on overflow. -func Subp(a, b int) int { - r, ok := Sub(a, b) - if !ok { - panic("subtraction overflow") - } - return r -} - -// Mulp returns the product of two ints, panicking on overflow. -func Mulp(a, b int) int { - r, ok := Mul(a, b) - if !ok { - panic("multiplication overflow") - } - return r -} - -// Divp returns the quotient of two ints, panicking on overflow. -func Divp(a, b int) int { - r, ok := Div(a, b) - if !ok { - panic("division failure") - } - return r -} - -//---------------------------------------- -// This is generated code, created by overflow_template.sh executed -// by "go generate" - -// Add8 performs + operation on two int8 operands -// returning a result and status -func Add8(a, b int8) (int8, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false -} - -// Add8p is the unchecked panicking version of Add8 -func Add8p(a, b int8) int8 { - r, ok := Add8(a, b) - if !ok { - panic("addition overflow") - } - return r -} - -// Sub8 performs - operation on two int8 operands -// returning a result and status -func Sub8(a, b int8) (int8, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false -} - -// Sub8p is the unchecked panicking version of Sub8 -func Sub8p(a, b int8) int8 { - r, ok := Sub8(a, b) - if !ok { - panic("subtraction overflow") - } - return r -} - -// Mul8 performs * operation on two int8 operands -// returning a result and status -func Mul8(a, b int8) (int8, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul8p is the unchecked panicking version of Mul8 -func Mul8p(a, b int8) int8 { - r, ok := Mul8(a, b) - if !ok { - panic("multiplication overflow") - } - return r -} - -// Div8 performs / operation on two int8 operands -// returning a result and status -func Div8(a, b int8) (int8, bool) { - q, _, ok := Quo8(a, b) - return q, ok -} - -// Div8p is the unchecked panicking version of Div8 -func Div8p(a, b int8) int8 { - r, ok := Div8(a, b) - if !ok { - panic("division failure") - } - return r -} - -// Quo8 performs + operation on two int8 operands -// returning a quotient, a remainder and status -func Quo8(a, b int8) (int8, int8, bool) { - if b == 0 { - return 0, 0, false - } else if b == -1 && a == int8(math.MinInt8) { - return 0, 0, false - } - c := a / b - return c, a % b, true -} - -// Add16 performs + operation on two int16 operands -// returning a result and status -func Add16(a, b int16) (int16, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false -} - -// Add16p is the unchecked panicking version of Add16 -func Add16p(a, b int16) int16 { - r, ok := Add16(a, b) - if !ok { - panic("addition overflow") - } - return r -} - -// Sub16 performs - operation on two int16 operands -// returning a result and status -func Sub16(a, b int16) (int16, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false -} - -// Sub16p is the unchecked panicking version of Sub16 -func Sub16p(a, b int16) int16 { - r, ok := Sub16(a, b) - if !ok { - panic("subtraction overflow") - } - return r -} - -// Mul16 performs * operation on two int16 operands -// returning a result and status -func Mul16(a, b int16) (int16, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul16p is the unchecked panicking version of Mul16 -func Mul16p(a, b int16) int16 { - r, ok := Mul16(a, b) - if !ok { - panic("multiplication overflow") - } - return r -} - -// Div16 performs / operation on two int16 operands -// returning a result and status -func Div16(a, b int16) (int16, bool) { - q, _, ok := Quo16(a, b) - return q, ok -} - -// Div16p is the unchecked panicking version of Div16 -func Div16p(a, b int16) int16 { - r, ok := Div16(a, b) - if !ok { - panic("division failure") - } - return r -} - -// Quo16 performs + operation on two int16 operands -// returning a quotient, a remainder and status -func Quo16(a, b int16) (int16, int16, bool) { - if b == 0 { - return 0, 0, false - } else if b == -1 && a == int16(math.MinInt16) { - return 0, 0, false - } - c := a / b - return c, a % b, true -} - -// Add32 performs + operation on two int32 operands -// returning a result and status -func Add32(a, b int32) (int32, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false -} - -// Add32p is the unchecked panicking version of Add32 -func Add32p(a, b int32) int32 { - r, ok := Add32(a, b) - if !ok { - panic("addition overflow") - } - return r -} - -// Sub32 performs - operation on two int32 operands -// returning a result and status -func Sub32(a, b int32) (int32, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false -} - -// Sub32p is the unchecked panicking version of Sub32 -func Sub32p(a, b int32) int32 { - r, ok := Sub32(a, b) - if !ok { - panic("subtraction overflow") - } - return r -} - -// Mul32 performs * operation on two int32 operands -// returning a result and status -func Mul32(a, b int32) (int32, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul32p is the unchecked panicking version of Mul32 -func Mul32p(a, b int32) int32 { - r, ok := Mul32(a, b) - if !ok { - panic("multiplication overflow") - } - return r -} - -// Div32 performs / operation on two int32 operands -// returning a result and status -func Div32(a, b int32) (int32, bool) { - q, _, ok := Quo32(a, b) - return q, ok -} - -// Div32p is the unchecked panicking version of Div32 -func Div32p(a, b int32) int32 { - r, ok := Div32(a, b) - if !ok { - panic("division failure") - } - return r -} - -// Quo32 performs + operation on two int32 operands -// returning a quotient, a remainder and status -func Quo32(a, b int32) (int32, int32, bool) { - if b == 0 { - return 0, 0, false - } else if b == -1 && a == int32(math.MinInt32) { - return 0, 0, false - } - c := a / b - return c, a % b, true -} - -// Add64 performs + operation on two int64 operands -// returning a result and status -func Add64(a, b int64) (int64, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false -} - -// Add64p is the unchecked panicking version of Add64 -func Add64p(a, b int64) int64 { - r, ok := Add64(a, b) - if !ok { - panic("addition overflow") - } - return r -} - -// Sub64 performs - operation on two int64 operands -// returning a result and status -func Sub64(a, b int64) (int64, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false -} - -// Sub64p is the unchecked panicking version of Sub64 -func Sub64p(a, b int64) int64 { - r, ok := Sub64(a, b) - if !ok { - panic("subtraction overflow") - } - return r -} - -// Mul64 performs * operation on two int64 operands -// returning a result and status -func Mul64(a, b int64) (int64, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false -} - -// Mul64p is the unchecked panicking version of Mul64 -func Mul64p(a, b int64) int64 { - r, ok := Mul64(a, b) - if !ok { - panic("multiplication overflow") - } - return r -} - -// Div64 performs / operation on two int64 operands -// returning a result and status -func Div64(a, b int64) (int64, bool) { - q, _, ok := Quo64(a, b) - return q, ok -} - -// Div64p is the unchecked panicking version of Div64 -func Div64p(a, b int64) int64 { - r, ok := Div64(a, b) - if !ok { - panic("division failure") - } - return r -} - -// Quo64 performs + operation on two int64 operands -// returning a quotient, a remainder and status -func Quo64(a, b int64) (int64, int64, bool) { - if b == 0 { - return 0, 0, false - } else if b == -1 && a == math.MinInt64 { - return 0, 0, false - } - c := a / b - return c, a % b, true -} diff --git a/gnovm/stdlibs/math/overflow/overflow_test.gno b/gnovm/stdlibs/math/overflow/overflow_test.gno deleted file mode 100644 index b7881aec480..00000000000 --- a/gnovm/stdlibs/math/overflow/overflow_test.gno +++ /dev/null @@ -1,200 +0,0 @@ -package overflow - -import ( - "math" - "testing" -) - -// sample all possibilities of 8 bit numbers -// by checking against 64 bit numbers - -func TestAlgorithms(t *testing.T) { - errors := 0 - - for a64 := int64(math.MinInt8); a64 <= int64(math.MaxInt8); a64++ { - for b64 := int64(math.MinInt8); b64 <= int64(math.MaxInt8) && errors < 10; b64++ { - - a8 := int8(a64) - b8 := int8(b64) - - if int64(a8) != a64 || int64(b8) != b64 { - t.Fatal("LOGIC FAILURE IN TEST") - } - - // ADDITION - { - r64 := a64 + b64 - - // now the verification - result, ok := Add8(a8, b8) - if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v + %v = %v instead of %v\n", - a8, b8, result, r64) - errors++ - } - if !ok && int64(result) == r64 { - t.Fail() - errors++ - } - } - - // SUBTRACTION - { - r64 := a64 - b64 - - // now the verification - result, ok := Sub8(a8, b8) - if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v - %v = %v instead of %v\n", - a8, b8, result, r64) - } - if !ok && int64(result) == r64 { - t.Fail() - errors++ - } - } - - // MULTIPLICATION - { - r64 := a64 * b64 - - // now the verification - result, ok := Mul8(a8, b8) - if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v * %v = %v instead of %v\n", - a8, b8, result, r64) - errors++ - } - if !ok && int64(result) == r64 { - t.Fail() - errors++ - } - } - - // DIVISION - if b8 != 0 { - r64 := a64 / b64 - rem64 := a64 % b64 - - // now the verification - result, rem, ok := Quo8(a8, b8) - if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v / %v = %v instead of %v\n", - a8, b8, result, r64) - errors++ - } - if ok && int64(rem) != rem64 { - t.Errorf("failed to fail on %v %% %v = %v instead of %v\n", - a8, b8, rem, rem64) - errors++ - } - } - } - } -} - -func TestQuotient(t *testing.T) { - q, r, ok := Quo(100, 3) - if r != 1 || q != 33 || !ok { - t.Errorf("expected 100/3 => 33, r=1") - } - if _, _, ok = Quo(1, 0); ok { - t.Error("unexpected lack of failure") - } -} - -func TestLong(t *testing.T) { - if testing.Short() { - t.Skip() - } - - ctr := int64(0) - - for a64 := int64(math.MinInt16); a64 <= int64(math.MaxInt16); a64++ { - for b64 := int64(math.MinInt16); b64 <= int64(math.MaxInt16); b64++ { - a16 := int16(a64) - b16 := int16(b64) - if int64(a16) != a64 || int64(b16) != b64 { - panic("LOGIC FAILURE IN TEST") - } - ctr++ - - // ADDITION - { - r64 := a64 + b64 - - // now the verification - result, ok := Add16(a16, b16) - if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { - if !ok || int64(result) != r64 { - println("add", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("add", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // SUBTRACTION - { - r64 := a64 - b64 - - // now the verification - result, ok := Sub16(a16, b16) - if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { - if !ok || int64(result) != r64 { - println("sub", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("sub", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // MULTIPLICATION - { - r64 := a64 * b64 - - // now the verification - result, ok := Mul16(a16, b16) - if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { - if !ok || int64(result) != r64 { - println("mul", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("mul", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // DIVISION - if b16 != 0 { - r64 := a64 / b64 - - // now the verification - result, _, ok := Quo16(a16, b16) - if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { - if !ok || int64(result) != r64 { - println("quo", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("quo", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - } - } - println("done", ctr) -} diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index 47e88e238d2..679674e443e 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -1,9 +1,6 @@ package std -import ( - "math/overflow" - "strconv" -) +import "strconv" // NOTE: this is selectively copied over from tm2/pkgs/std/coin.go @@ -56,13 +53,7 @@ func (c Coin) IsEqual(other Coin) bool { // An invalid result panics. func (c Coin) Add(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - - sum, ok := overflow.Add64(c.Amount, other.Amount) - if !ok { - panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) - } - - c.Amount = sum + c.Amount += other.Amount return c } @@ -72,13 +63,7 @@ func (c Coin) Add(other Coin) Coin { // An invalid result panics. func (c Coin) Sub(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - - dff, ok := overflow.Sub64(c.Amount, other.Amount) - if !ok { - panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) - } - c.Amount = dff - + c.Amount -= other.Amount return c } @@ -113,10 +98,7 @@ func NewCoins(coins ...Coin) Coins { for _, coin := range coins { if currentAmount, exists := coinMap[coin.Denom]; exists { - var ok bool - if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { - panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) - } + coinMap[coin.Denom] = currentAmount + coin.Amount } else { coinMap[coin.Denom] = coin.Amount } diff --git a/gnovm/tests/files/overflow0.gno b/gnovm/tests/files/overflow0.gno new file mode 100644 index 00000000000..1313f064322 --- /dev/null +++ b/gnovm/tests/files/overflow0.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int8 = -1<<7, -1, 0 + c = a / b // overflow: -128 instead of 128 + println(c) +} + +// Error: +// division by zero or overflow diff --git a/gnovm/tests/files/overflow1.gno b/gnovm/tests/files/overflow1.gno new file mode 100644 index 00000000000..a416e9a3498 --- /dev/null +++ b/gnovm/tests/files/overflow1.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int16 = -1<<15, -1, 0 + c = a / b // overflow: -32768 instead of 32768 + println(c) +} + +// Error: +// division by zero or overflow diff --git a/gnovm/tests/files/overflow2.gno b/gnovm/tests/files/overflow2.gno new file mode 100644 index 00000000000..353729bcdf2 --- /dev/null +++ b/gnovm/tests/files/overflow2.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int32 = -1<<31, -1, 0 + c = a / b // overflow: -2147483648 instead of 2147483648 + println(c) +} + +// Error: +// division by zero or overflow diff --git a/gnovm/tests/files/overflow3.gno b/gnovm/tests/files/overflow3.gno new file mode 100644 index 00000000000..a09c59dfb03 --- /dev/null +++ b/gnovm/tests/files/overflow3.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int64 = -1<<63, -1, 0 + c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 + println(c) +} + +// Error: +// division by zero or overflow diff --git a/gnovm/tests/files/overflow4.gno b/gnovm/tests/files/overflow4.gno new file mode 100644 index 00000000000..26b05567b07 --- /dev/null +++ b/gnovm/tests/files/overflow4.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int = -1<<63, -1, 0 + c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 + println(c) +} + +// Error: +// division by zero or overflow diff --git a/gnovm/tests/files/overflow5.gno b/gnovm/tests/files/overflow5.gno new file mode 100644 index 00000000000..ef7f976eb24 --- /dev/null +++ b/gnovm/tests/files/overflow5.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a, b, c int = -5, 7, 0 + c = a % b // 0 quotient triggers a false negative in gnolang/overflow + println(c) +} + +// Output: +// -5 diff --git a/gnovm/tests/files/recover14.gno b/gnovm/tests/files/recover14.gno index 30a34ab291a..3c96404fcbe 100644 --- a/gnovm/tests/files/recover14.gno +++ b/gnovm/tests/files/recover14.gno @@ -12,4 +12,4 @@ func main() { } // Output: -// recover: division by zero +// recover: division by zero or overflow diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 025fe4b673e..13e90836f36 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -70,7 +70,8 @@ func findDirs() (gitRoot string, relPath string, err error) { } p := wd for { - if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + // .git is normally a directory, or a file in case of a git worktree. + if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { // make relPath relative to the git root rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / diff --git a/tm2/pkg/overflow/README.md b/tm2/pkg/overflow/README.md index 55a9ba4c327..26ba7dc9985 100644 --- a/tm2/pkg/overflow/README.md +++ b/tm2/pkg/overflow/README.md @@ -2,26 +2,25 @@ Check for int/int8/int16/int64/int32 integer overflow in Golang arithmetic. -Forked from https://github.com/JohnCGriffin/overflow +Originally forked from https://github.com/JohnCGriffin/overflow. ### Install -``` -go get github.com/johncgriffin/overflow -``` -Note that because Go has no template types, the majority of repetitive code is -generated by overflow_template.sh. If you have to change an -algorithm, change it there and regenerate the Go code via: -``` + +The majority of repetitive code is generated by overflow_template.sh. If you +have to change an algorithm, change it there and regenerate the Go code via: + +```sh go generate ``` + ### Synopsis -``` +```go package main import "fmt" import "math" -import "github.com/JohnCGriffin/overflow" +import "github.com/gnolang/gno/tm2/pkg/overflow" func main() { @@ -29,38 +28,33 @@ func main() { for i := 0; i < 10; i++ { sum, ok := overflow.Add(addend, i) - fmt.Printf("%v+%v -> (%v,%v)\n", + fmt.Printf("%v+%v -> (%v, %v)\n", addend, i, sum, ok) } } ``` + yields the output -``` -9223372036854775802+0 -> (9223372036854775802,true) -9223372036854775802+1 -> (9223372036854775803,true) -9223372036854775802+2 -> (9223372036854775804,true) -9223372036854775802+3 -> (9223372036854775805,true) -9223372036854775802+4 -> (9223372036854775806,true) -9223372036854775802+5 -> (9223372036854775807,true) -9223372036854775802+6 -> (0,false) -9223372036854775802+7 -> (0,false) -9223372036854775802+8 -> (0,false) -9223372036854775802+9 -> (0,false) + +```console +9223372036854775802+0 -> (9223372036854775802, true) +9223372036854775802+1 -> (9223372036854775803, true) +9223372036854775802+2 -> (9223372036854775804, true) +9223372036854775802+3 -> (9223372036854775805, true) +9223372036854775802+4 -> (9223372036854775806, true) +9223372036854775802+5 -> (9223372036854775807, true) +9223372036854775802+6 -> (0, false) +9223372036854775802+7 -> (0, false) +9223372036854775802+8 -> (0, false) +9223372036854775802+9 -> (0, false) ``` For int, int64, and int32 types, provide Add, Add32, Add64, Sub, Sub32, Sub64, etc. -Unsigned types not covered at the moment, but such additions are welcome. ### Stay calm and panic -There's a good case to be made that a panic is an unidiomatic but proper response. Iff you -believe that there's no valid way to continue your program after math goes wayward, you can -use the easier Addp, Mulp, Subp, and Divp versions which return the normal result or panic. - - - - - - - +There's a good case to be made that a panic is an unidiomatic but proper +response. If you believe that there's no valid way to continue your program +after math goes wayward, you can use the easier Addp, Mulp, Subp, and Divp +versions which return the normal result or panic. diff --git a/tm2/pkg/overflow/overflow_impl.go b/tm2/pkg/overflow/overflow_impl.go index a9a90c43835..0f057f65387 100644 --- a/tm2/pkg/overflow/overflow_impl.go +++ b/tm2/pkg/overflow/overflow_impl.go @@ -1,10 +1,8 @@ package overflow -// This is generated code, created by overflow_template.sh executed -// by "go generate" +// Code generated by overflow_template.sh from 'go generate'. DO NOT EDIT. -// Add8 performs + operation on two int8 operands -// returning a result and status +// Add8 performs + operation on two int8 operands, returning a result and status. func Add8(a, b int8) (int8, bool) { c := a + b if (c > a) == (b > 0) { @@ -13,7 +11,7 @@ func Add8(a, b int8) (int8, bool) { return c, false } -// Add8p is the unchecked panicing version of Add8 +// Add8p is the unchecked panicing version of Add8. func Add8p(a, b int8) int8 { r, ok := Add8(a, b) if !ok { @@ -22,8 +20,7 @@ func Add8p(a, b int8) int8 { return r } -// Sub8 performs - operation on two int8 operands -// returning a result and status +// Sub8 performs - operation on two int8 operands, returning a result and status. func Sub8(a, b int8) (int8, bool) { c := a - b if (c < a) == (b > 0) { @@ -32,7 +29,7 @@ func Sub8(a, b int8) (int8, bool) { return c, false } -// Sub8p is the unchecked panicing version of Sub8 +// Sub8p is the unchecked panicing version of Sub8. func Sub8p(a, b int8) int8 { r, ok := Sub8(a, b) if !ok { @@ -41,8 +38,7 @@ func Sub8p(a, b int8) int8 { return r } -// Mul8 performs * operation on two int8 operands -// returning a result and status +// Mul8 performs * operation on two int8 operands returning a result and status. func Mul8(a, b int8) (int8, bool) { if a == 0 || b == 0 { return 0, true @@ -56,7 +52,7 @@ func Mul8(a, b int8) (int8, bool) { return c, false } -// Mul8p is the unchecked panicing version of Mul8 +// Mul8p is the unchecked panicing version of Mul8. func Mul8p(a, b int8) int8 { r, ok := Mul8(a, b) if !ok { @@ -65,14 +61,13 @@ func Mul8p(a, b int8) int8 { return r } -// Div8 performs / operation on two int8 operands -// returning a result and status +// Div8 performs / operation on two int8 operands, returning a result and status. func Div8(a, b int8) (int8, bool) { q, _, ok := Quotient8(a, b) return q, ok } -// Div8p is the unchecked panicing version of Div8 +// Div8p is the unchecked panicing version of Div8. func Div8p(a, b int8) int8 { r, ok := Div8(a, b) if !ok { @@ -81,19 +76,19 @@ func Div8p(a, b int8) int8 { return r } -// Quotient8 performs + operation on two int8 operands -// returning a quotient, a remainder and status +// Quotient8 performs / operation on two int8 operands, returning a quotient, +// a remainder and status. func Quotient8(a, b int8) (int8, int8, bool) { if b == 0 { return 0, 0, false } c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient + return c, a%b, status } -// Add16 performs + operation on two int16 operands -// returning a result and status + +// Add16 performs + operation on two int16 operands, returning a result and status. func Add16(a, b int16) (int16, bool) { c := a + b if (c > a) == (b > 0) { @@ -102,7 +97,7 @@ func Add16(a, b int16) (int16, bool) { return c, false } -// Add16p is the unchecked panicing version of Add16 +// Add16p is the unchecked panicing version of Add16. func Add16p(a, b int16) int16 { r, ok := Add16(a, b) if !ok { @@ -111,8 +106,7 @@ func Add16p(a, b int16) int16 { return r } -// Sub16 performs - operation on two int16 operands -// returning a result and status +// Sub16 performs - operation on two int16 operands, returning a result and status. func Sub16(a, b int16) (int16, bool) { c := a - b if (c < a) == (b > 0) { @@ -121,7 +115,7 @@ func Sub16(a, b int16) (int16, bool) { return c, false } -// Sub16p is the unchecked panicing version of Sub16 +// Sub16p is the unchecked panicing version of Sub16. func Sub16p(a, b int16) int16 { r, ok := Sub16(a, b) if !ok { @@ -130,8 +124,7 @@ func Sub16p(a, b int16) int16 { return r } -// Mul16 performs * operation on two int16 operands -// returning a result and status +// Mul16 performs * operation on two int16 operands returning a result and status. func Mul16(a, b int16) (int16, bool) { if a == 0 || b == 0 { return 0, true @@ -145,7 +138,7 @@ func Mul16(a, b int16) (int16, bool) { return c, false } -// Mul16p is the unchecked panicing version of Mul16 +// Mul16p is the unchecked panicing version of Mul16. func Mul16p(a, b int16) int16 { r, ok := Mul16(a, b) if !ok { @@ -154,14 +147,13 @@ func Mul16p(a, b int16) int16 { return r } -// Div16 performs / operation on two int16 operands -// returning a result and status +// Div16 performs / operation on two int16 operands, returning a result and status. func Div16(a, b int16) (int16, bool) { q, _, ok := Quotient16(a, b) return q, ok } -// Div16p is the unchecked panicing version of Div16 +// Div16p is the unchecked panicing version of Div16. func Div16p(a, b int16) int16 { r, ok := Div16(a, b) if !ok { @@ -170,19 +162,19 @@ func Div16p(a, b int16) int16 { return r } -// Quotient16 performs + operation on two int16 operands -// returning a quotient, a remainder and status +// Quotient16 performs / operation on two int16 operands, returning a quotient, +// a remainder and status. func Quotient16(a, b int16) (int16, int16, bool) { if b == 0 { return 0, 0, false } c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient + return c, a%b, status } -// Add32 performs + operation on two int32 operands -// returning a result and status + +// Add32 performs + operation on two int32 operands, returning a result and status. func Add32(a, b int32) (int32, bool) { c := a + b if (c > a) == (b > 0) { @@ -191,7 +183,7 @@ func Add32(a, b int32) (int32, bool) { return c, false } -// Add32p is the unchecked panicing version of Add32 +// Add32p is the unchecked panicing version of Add32. func Add32p(a, b int32) int32 { r, ok := Add32(a, b) if !ok { @@ -200,8 +192,7 @@ func Add32p(a, b int32) int32 { return r } -// Sub32 performs - operation on two int32 operands -// returning a result and status +// Sub32 performs - operation on two int32 operands, returning a result and status. func Sub32(a, b int32) (int32, bool) { c := a - b if (c < a) == (b > 0) { @@ -210,7 +201,7 @@ func Sub32(a, b int32) (int32, bool) { return c, false } -// Sub32p is the unchecked panicing version of Sub32 +// Sub32p is the unchecked panicing version of Sub32. func Sub32p(a, b int32) int32 { r, ok := Sub32(a, b) if !ok { @@ -219,8 +210,7 @@ func Sub32p(a, b int32) int32 { return r } -// Mul32 performs * operation on two int32 operands -// returning a result and status +// Mul32 performs * operation on two int32 operands returning a result and status. func Mul32(a, b int32) (int32, bool) { if a == 0 || b == 0 { return 0, true @@ -234,7 +224,7 @@ func Mul32(a, b int32) (int32, bool) { return c, false } -// Mul32p is the unchecked panicing version of Mul32 +// Mul32p is the unchecked panicing version of Mul32. func Mul32p(a, b int32) int32 { r, ok := Mul32(a, b) if !ok { @@ -243,14 +233,13 @@ func Mul32p(a, b int32) int32 { return r } -// Div32 performs / operation on two int32 operands -// returning a result and status +// Div32 performs / operation on two int32 operands, returning a result and status. func Div32(a, b int32) (int32, bool) { q, _, ok := Quotient32(a, b) return q, ok } -// Div32p is the unchecked panicing version of Div32 +// Div32p is the unchecked panicing version of Div32. func Div32p(a, b int32) int32 { r, ok := Div32(a, b) if !ok { @@ -259,19 +248,19 @@ func Div32p(a, b int32) int32 { return r } -// Quotient32 performs + operation on two int32 operands -// returning a quotient, a remainder and status +// Quotient32 performs / operation on two int32 operands, returning a quotient, +// a remainder and status. func Quotient32(a, b int32) (int32, int32, bool) { if b == 0 { return 0, 0, false } c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient + return c, a%b, status } -// Add64 performs + operation on two int64 operands -// returning a result and status + +// Add64 performs + operation on two int64 operands, returning a result and status. func Add64(a, b int64) (int64, bool) { c := a + b if (c > a) == (b > 0) { @@ -280,7 +269,7 @@ func Add64(a, b int64) (int64, bool) { return c, false } -// Add64p is the unchecked panicing version of Add64 +// Add64p is the unchecked panicing version of Add64. func Add64p(a, b int64) int64 { r, ok := Add64(a, b) if !ok { @@ -289,8 +278,7 @@ func Add64p(a, b int64) int64 { return r } -// Sub64 performs - operation on two int64 operands -// returning a result and status +// Sub64 performs - operation on two int64 operands, returning a result and status. func Sub64(a, b int64) (int64, bool) { c := a - b if (c < a) == (b > 0) { @@ -299,7 +287,7 @@ func Sub64(a, b int64) (int64, bool) { return c, false } -// Sub64p is the unchecked panicing version of Sub64 +// Sub64p is the unchecked panicing version of Sub64. func Sub64p(a, b int64) int64 { r, ok := Sub64(a, b) if !ok { @@ -308,8 +296,7 @@ func Sub64p(a, b int64) int64 { return r } -// Mul64 performs * operation on two int64 operands -// returning a result and status +// Mul64 performs * operation on two int64 operands returning a result and status. func Mul64(a, b int64) (int64, bool) { if a == 0 || b == 0 { return 0, true @@ -323,7 +310,7 @@ func Mul64(a, b int64) (int64, bool) { return c, false } -// Mul64p is the unchecked panicing version of Mul64 +// Mul64p is the unchecked panicing version of Mul64. func Mul64p(a, b int64) int64 { r, ok := Mul64(a, b) if !ok { @@ -332,14 +319,13 @@ func Mul64p(a, b int64) int64 { return r } -// Div64 performs / operation on two int64 operands -// returning a result and status +// Div64 performs / operation on two int64 operands, returning a result and status. func Div64(a, b int64) (int64, bool) { q, _, ok := Quotient64(a, b) return q, ok } -// Div64p is the unchecked panicing version of Div64 +// Div64p is the unchecked panicing version of Div64. func Div64p(a, b int64) int64 { r, ok := Div64(a, b) if !ok { @@ -348,13 +334,14 @@ func Div64p(a, b int64) int64 { return r } -// Quotient64 performs + operation on two int64 operands -// returning a quotient, a remainder and status +// Quotient64 performs / operation on two int64 operands, returning a quotient, +// a remainder and status. func Quotient64(a, b int64) (int64, int64, bool) { if b == 0 { return 0, 0, false } c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient + return c, a%b, status } + diff --git a/tm2/pkg/overflow/overflow_template.sh b/tm2/pkg/overflow/overflow_template.sh index a2a85f2c581..0cc3c9595bf 100755 --- a/tm2/pkg/overflow/overflow_template.sh +++ b/tm2/pkg/overflow/overflow_template.sh @@ -4,109 +4,94 @@ exec > overflow_impl.go echo "package overflow -// This is generated code, created by overflow_template.sh executed -// by \"go generate\" - -" - +// Code generated by overflow_template.sh from 'go generate'. DO NOT EDIT." for SIZE in 8 16 32 64 do -echo " - -// Add${SIZE} performs + operation on two int${SIZE} operands -// returning a result and status + echo " +// Add${SIZE} performs + operation on two int${SIZE} operands, returning a result and status. func Add${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { - c := a + b - if (c > a) == (b > 0) { - return c, true - } - return c, false + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false } -// Add${SIZE}p is the unchecked panicing version of Add${SIZE} +// Add${SIZE}p is the unchecked panicing version of Add${SIZE}. func Add${SIZE}p(a, b int${SIZE}) int${SIZE} { - r, ok := Add${SIZE}(a, b) - if !ok { - panic(\"addition overflow\") - } - return r + r, ok := Add${SIZE}(a, b) + if !ok { + panic(\"addition overflow\") + } + return r } - -// Sub${SIZE} performs - operation on two int${SIZE} operands -// returning a result and status +// Sub${SIZE} performs - operation on two int${SIZE} operands, returning a result and status. func Sub${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { - c := a - b - if (c < a) == (b > 0) { - return c, true - } - return c, false + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false } -// Sub${SIZE}p is the unchecked panicing version of Sub${SIZE} +// Sub${SIZE}p is the unchecked panicing version of Sub${SIZE}. func Sub${SIZE}p(a, b int${SIZE}) int${SIZE} { - r, ok := Sub${SIZE}(a, b) - if !ok { - panic(\"subtraction overflow\") - } - return r + r, ok := Sub${SIZE}(a, b) + if !ok { + panic(\"subtraction overflow\") + } + return r } - -// Mul${SIZE} performs * operation on two int${SIZE} operands -// returning a result and status +// Mul${SIZE} performs * operation on two int${SIZE} operands returning a result and status. func Mul${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { - if a == 0 || b == 0 { - return 0, true - } - c := a * b - if (c < 0) == ((a < 0) != (b < 0)) { - if c/b == a { - return c, true - } - } - return c, false + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false } -// Mul${SIZE}p is the unchecked panicing version of Mul${SIZE} +// Mul${SIZE}p is the unchecked panicing version of Mul${SIZE}. func Mul${SIZE}p(a, b int${SIZE}) int${SIZE} { - r, ok := Mul${SIZE}(a, b) - if !ok { - panic(\"multiplication overflow\") - } - return r + r, ok := Mul${SIZE}(a, b) + if !ok { + panic(\"multiplication overflow\") + } + return r } - - -// Div${SIZE} performs / operation on two int${SIZE} operands -// returning a result and status +// Div${SIZE} performs / operation on two int${SIZE} operands, returning a result and status. func Div${SIZE}(a, b int${SIZE}) (int${SIZE}, bool) { - q, _, ok := Quotient${SIZE}(a, b) - return q, ok + q, _, ok := Quotient${SIZE}(a, b) + return q, ok } -// Div${SIZE}p is the unchecked panicing version of Div${SIZE} +// Div${SIZE}p is the unchecked panicing version of Div${SIZE}. func Div${SIZE}p(a, b int${SIZE}) int${SIZE} { - r, ok := Div${SIZE}(a, b) - if !ok { - panic(\"division failure\") - } - return r + r, ok := Div${SIZE}(a, b) + if !ok { + panic(\"division failure\") + } + return r } -// Quotient${SIZE} performs + operation on two int${SIZE} operands -// returning a quotient, a remainder and status +// Quotient${SIZE} performs / operation on two int${SIZE} operands, returning a quotient, +// a remainder and status. func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { - if b == 0 { - return 0, 0, false - } - c := a / b - status := (c < 0) == ((a < 0) != (b < 0)) - return c, a % b, status + if b == 0 { + return 0, 0, false + } + c := a / b + status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient + return c, a%b, status } " done - -go run -modfile ../../../misc/devdeps/go.mod mvdan.cc/gofumpt -w overflow_impl.go diff --git a/tm2/pkg/overflow/overflow_test.go b/tm2/pkg/overflow/overflow_test.go index 2b2d345b55d..e6327c9e862 100644 --- a/tm2/pkg/overflow/overflow_test.go +++ b/tm2/pkg/overflow/overflow_test.go @@ -28,8 +28,7 @@ func TestAlgorithms(t *testing.T) { // now the verification result, ok := Add8(a8, b8) if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v + %v = %v instead of %v\n", - a8, b8, result, r64) + t.Errorf("failed to fail on %v + %v = %v instead of %v\n", a8, b8, result, r64) errors++ } if !ok && int64(result) == r64 { @@ -45,8 +44,7 @@ func TestAlgorithms(t *testing.T) { // now the verification result, ok := Sub8(a8, b8) if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v - %v = %v instead of %v\n", - a8, b8, result, r64) + t.Errorf("failed to fail on %v - %v = %v instead of %v\n", a8, b8, result, r64) } if !ok && int64(result) == r64 { t.Fail() @@ -61,8 +59,7 @@ func TestAlgorithms(t *testing.T) { // now the verification result, ok := Mul8(a8, b8) if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v * %v = %v instead of %v\n", - a8, b8, result, r64) + t.Errorf("failed to fail on %v * %v = %v instead of %v\n", a8, b8, result, r64) errors++ } if !ok && int64(result) == r64 { @@ -78,11 +75,10 @@ func TestAlgorithms(t *testing.T) { // now the verification result, _, ok := Quotient8(a8, b8) if ok && int64(result) != r64 { - t.Errorf("failed to fail on %v / %v = %v instead of %v\n", - a8, b8, result, r64) + t.Errorf("failed to fail on %v / %v = %v instead of %v\n", a8, b8, result, r64) errors++ } - if !ok && result != 0 && int64(result) == r64 { + if !ok && int64(result) == r64 { t.Fail() errors++ } From 60acdf10b51e1447d7b1408d621e65246a7643f3 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:35:45 +0100 Subject: [PATCH 10/75] feat(gnoweb): add breadcrumb args (#3465) --- gno.land/pkg/gnoweb/Makefile | 5 +- gno.land/pkg/gnoweb/components/breadcrumb.go | 3 +- .../pkg/gnoweb/components/breadcrumb.gohtml | 10 +++- gno.land/pkg/gnoweb/handler.go | 20 ++++--- gno.land/pkg/gnoweb/public/styles.css | 2 +- gno.land/pkg/gnoweb/url.go | 59 ++++++++++++++++--- gno.land/pkg/gnoweb/url_test.go | 34 +++++++++++ 7 files changed, 110 insertions(+), 23 deletions(-) diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 61397fef54f..39c9d20ab10 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -13,6 +13,7 @@ input_css := frontend/css/input.css output_css := $(PUBLIC_DIR)/styles.css tw_version := 3.4.14 tw_config_path := frontend/css/tx.config.js +templates_files := $(shell find . -iname '*.gohtml') # static config src_dir_static := frontend/static @@ -42,8 +43,8 @@ all: generate generate: css ts static css: $(output_css) -$(output_css): $(input_css) - npx -y tailwindcss@$(tw_version) -c $(tw_config_path) -i $< -o $@ --minify # tailwind +$(output_css): $(input_css) $(templates_files) + npx -y tailwindcss@$(tw_version) -c $(tw_config_path) -i $(input_css) -o $@ --minify # tailwind touch $@ ts: $(output_js) diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.go b/gno.land/pkg/gnoweb/components/breadcrumb.go index 9e7a97b2fae..8eda02a9f4d 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.go +++ b/gno.land/pkg/gnoweb/components/breadcrumb.go @@ -6,11 +6,12 @@ import ( type BreadcrumbPart struct { Name string - Path string + URL string } type BreadcrumbData struct { Parts []BreadcrumbPart + Args string } func RenderBreadcrumpComponent(w io.Writer, data BreadcrumbData) error { diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml index a3301cb037e..0118dff5333 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml @@ -6,7 +6,13 @@ {{- else }}
  • {{- end }} - {{ $part.Name }}
  • + {{ $part.Name }} + + {{- end }} + {{- if .Args }} +
  • + {{ .Args }} +
  • {{- end }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 0a0ee69c3f0..20f19e4405a 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -94,8 +94,8 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { indexData.HeadData.Title = "gno.land - " + gnourl.Path // Header - indexData.HeaderData.RealmPath = gnourl.Path - indexData.HeaderData.Breadcrumb.Parts = generateBreadcrumbPaths(gnourl.Path) + indexData.HeaderData.RealmPath = gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape) + indexData.HeaderData.Breadcrumb = generateBreadcrumbPaths(gnourl) indexData.HeaderData.WebQuery = gnourl.WebQuery // Render @@ -339,21 +339,25 @@ func (h *WebHandler) highlightSource(fileName string, src []byte) ([]byte, error return buff.Bytes(), nil } -func generateBreadcrumbPaths(path string) []components.BreadcrumbPart { - split := strings.Split(path, "/") - parts := []components.BreadcrumbPart{} +func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { + split := strings.Split(url.Path, "/") + var data components.BreadcrumbData var name string for i := range split { if name = split[i]; name == "" { continue } - parts = append(parts, components.BreadcrumbPart{ + data.Parts = append(data.Parts, components.BreadcrumbPart{ Name: name, - Path: strings.Join(split[:i+1], "/"), + URL: strings.Join(split[:i+1], "/"), }) } - return parts + if args := url.EncodeArgs(); args != "" { + data.Args = args + } + + return data } diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index a1d7860c63e..ce6c8bae639 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/url.go b/gno.land/pkg/gnoweb/url.go index 105ac382800..9127225d490 100644 --- a/gno.land/pkg/gnoweb/url.go +++ b/gno.land/pkg/gnoweb/url.go @@ -6,6 +6,7 @@ import ( "net/url" "path/filepath" "regexp" + "slices" "strings" ) @@ -31,7 +32,8 @@ type GnoURL struct { type EncodeFlag int const ( - EncodePath EncodeFlag = 1 << iota // Encode the path component + EncodeDomain EncodeFlag = 1 << iota // Encode the domain component + EncodePath // Encode the path component EncodeArgs // Encode the arguments component EncodeWebQuery // Encode the web query component EncodeQuery // Encode the query component @@ -62,13 +64,15 @@ const ( func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { var urlstr strings.Builder + noEscape := encodeFlags.Has(EncodeNoEscape) + + if encodeFlags.Has(EncodeDomain) { + urlstr.WriteString(gnoURL.Domain) + } + if encodeFlags.Has(EncodePath) { path := gnoURL.Path - if !encodeFlags.Has(EncodeNoEscape) { - path = url.PathEscape(path) - } - - urlstr.WriteString(gnoURL.Path) + urlstr.WriteString(path) } if len(gnoURL.File) > 0 { @@ -84,7 +88,7 @@ func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { // XXX: Arguments should ideally always be escaped, // but this may require changes in some realms. args := gnoURL.Args - if !encodeFlags.Has(EncodeNoEscape) { + if !noEscape { args = escapeDollarSign(url.PathEscape(args)) } @@ -93,12 +97,20 @@ func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { if encodeFlags.Has(EncodeWebQuery) && len(gnoURL.WebQuery) > 0 { urlstr.WriteRune('$') - urlstr.WriteString(gnoURL.WebQuery.Encode()) + if noEscape { + urlstr.WriteString(NoEscapeQuery(gnoURL.WebQuery)) + } else { + urlstr.WriteString(gnoURL.WebQuery.Encode()) + } } if encodeFlags.Has(EncodeQuery) && len(gnoURL.Query) > 0 { urlstr.WriteRune('?') - urlstr.WriteString(gnoURL.Query.Encode()) + if noEscape { + urlstr.WriteString(NoEscapeQuery(gnoURL.Query)) + } else { + urlstr.WriteString(gnoURL.Query.Encode()) + } } return urlstr.String() @@ -213,3 +225,32 @@ func ParseGnoURL(u *url.URL) (*GnoURL, error) { File: file, }, nil } + +// NoEscapeQuery generates a URL-encoded query string from the given url.Values, +// without escaping the keys and values. The query parameters are sorted by key. +func NoEscapeQuery(v url.Values) string { + // Encode encodes the values into “URL encoded” form + // ("bar=baz&foo=quux") sorted by key. + if len(v) == 0 { + return "" + } + var buf strings.Builder + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) + } + slices.Sort(keys) + for _, k := range keys { + vs := v[k] + keyEscaped := k + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(keyEscaped) + buf.WriteByte('=') + buf.WriteString(v) + } + } + return buf.String() +} diff --git a/gno.land/pkg/gnoweb/url_test.go b/gno.land/pkg/gnoweb/url_test.go index b4e901d4f10..7a491eaa149 100644 --- a/gno.land/pkg/gnoweb/url_test.go +++ b/gno.land/pkg/gnoweb/url_test.go @@ -280,6 +280,40 @@ func TestEncode(t *testing.T) { EncodeFlags EncodeFlag Expected string }{ + { + Name: "encode domain", + GnoURL: GnoURL{ + Domain: "gno.land", + Path: "/r/demo/foo", + }, + EncodeFlags: EncodeDomain, + Expected: "gno.land", + }, + + { + Name: "encode web query without escape", + GnoURL: GnoURL{ + Domain: "gno.land", + Path: "/r/demo/foo", + WebQuery: url.Values{ + "help": []string{""}, + "fun$c": []string{"B$ ar"}, + }, + }, + EncodeFlags: EncodeWebQuery | EncodeNoEscape, + Expected: "$fun$c=B$ ar&help=", + }, + + { + Name: "encode domain and path", + GnoURL: GnoURL{ + Domain: "gno.land", + Path: "/r/demo/foo", + }, + EncodeFlags: EncodeDomain | EncodePath, + Expected: "gno.land/r/demo/foo", + }, + { Name: "Encode Path Only", GnoURL: GnoURL{ From ac30689b64a3948acc62f58216ba81fb95b1d547 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Villaquiran Date: Thu, 9 Jan 2025 15:05:13 +0100 Subject: [PATCH 11/75] feat: add genesis packages with transaction signing (#3280) closes #1104
    Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
    --- contribs/gnodev/pkg/dev/node.go | 2 + .../internal/txs/txs_add_packages.go | 112 ++++++-- .../internal/txs/txs_add_packages_test.go | 239 ++++++++++++++++-- gno.land/pkg/gnoclient/integration_test.go | 6 +- gno.land/pkg/gnoland/app.go | 16 +- gno.land/pkg/gnoland/node_inmemory.go | 22 +- gno.land/pkg/integration/node_testing.go | 1 + gno.land/pkg/integration/pkgloader.go | 11 +- gno.land/pkg/integration/signer.go | 33 +++ .../testdata/event_multi_msg.txtar | 9 +- .../testdata/gnokey_simulate.txtar | 20 +- .../testdata/gnoweb_airgapped.txtar | 9 +- .../testdata/restart_missing_type.txtar | 6 +- .../integration/testdata/simulate_gas.txtar | 4 +- .../pkg/integration/testscript_gnoland.go | 10 +- misc/autocounterd/go.sum | 4 - tm2/pkg/sdk/auth/ante.go | 8 +- tm2/pkg/sdk/auth/ante_test.go | 6 +- 18 files changed, 416 insertions(+), 102 deletions(-) create mode 100644 gno.land/pkg/integration/signer.go diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index fa9e2d11e29..12a88490515 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -489,6 +489,8 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) // Speed up stdlib loading after first start (saves about 2-3 seconds on each reload). nodeConfig.CacheStdlibLoad = true nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock + // Genesis verification is always false with Gnodev + nodeConfig.SkipGenesisVerification = true // recoverFromError handles panics and converts them to errors. recoverFromError := func() { diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index cf863c72116..0ab5724154e 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -5,8 +5,9 @@ import ( "errors" "flag" "fmt" + "os" - "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" @@ -15,28 +16,45 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) -var ( - errInvalidPackageDir = errors.New("invalid package directory") - errInvalidDeployerAddr = errors.New("invalid deployer address") +const ( + defaultAccount_Name = "test1" + defaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + defaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" + defaultAccount_publicKey = "gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pq0skzdkmzu0r9h6gny6eg8c9dc303xrrudee6z4he4y7cs5rnjwmyf40yaj" ) +var errInvalidPackageDir = errors.New("invalid package directory") + // Keep in sync with gno.land/cmd/start.go -var ( - defaultCreator = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 - genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) -) +var genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) type addPkgCfg struct { - txsCfg *txsCfg - deployerAddress string + txsCfg *txsCfg + keyName string + gnoHome string // default GNOHOME env var, just here to ease testing with parallel tests + insecurePasswordStdin bool } func (c *addPkgCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( - &c.deployerAddress, - "deployer-address", - defaultCreator.String(), - "the address that will be used to deploy the package", + &c.keyName, + "key-name", + "", + "The package deployer key name or address contained on gnokey", + ) + + fs.StringVar( + &c.gnoHome, + "gno-home", + os.Getenv("GNOHOME"), + "the gno home directory", + ) + + fs.BoolVar( + &c.insecurePasswordStdin, + "insecure-password-stdin", + false, + "the gno home directory", ) } @@ -65,10 +83,15 @@ func execTxsAddPackages( io commands.IO, args []string, ) error { + var ( + keyname = defaultAccount_Name + keybase keys.Keybase + pass string + ) // Load the genesis - genesis, loadErr := types.GenesisDocFromFile(cfg.txsCfg.GenesisPath) - if loadErr != nil { - return fmt.Errorf("unable to load genesis, %w", loadErr) + genesis, err := types.GenesisDocFromFile(cfg.txsCfg.GenesisPath) + if err != nil { + return fmt.Errorf("unable to load genesis, %w", err) } // Make sure the package dir is set @@ -76,19 +99,30 @@ func execTxsAddPackages( return errInvalidPackageDir } - var ( - creator = defaultCreator - err error - ) - - // Check if the deployer address is set - if cfg.deployerAddress != defaultCreator.String() { - creator, err = crypto.AddressFromString(cfg.deployerAddress) + if cfg.keyName != "" { + keyname = cfg.keyName + keybase, err = keys.NewKeyBaseFromDir(cfg.gnoHome) + if err != nil { + return fmt.Errorf("unable to load keybase: %w", err) + } + pass, err = io.GetPassword("Enter password.", cfg.insecurePasswordStdin) + if err != nil { + return fmt.Errorf("cannot read password: %w", err) + } + } else { + keybase = keys.NewInMemory() + _, err := keybase.CreateAccount(defaultAccount_Name, defaultAccount_Seed, "", "", 0, 0) if err != nil { - return fmt.Errorf("%w, %w", errInvalidDeployerAddr, err) + return fmt.Errorf("unable to create account: %w", err) } } + info, err := keybase.GetByNameOrAddress(keyname) + if err != nil { + return fmt.Errorf("unable to find key in keybase: %w", err) + } + + creator := info.GetAddress() parsedTxs := make([]gnoland.TxWithMetadata, 0) for _, path := range args { // Generate transactions from the packages (recursively) @@ -97,6 +131,10 @@ func execTxsAddPackages( return fmt.Errorf("unable to load txs from directory, %w", err) } + if err := signTxs(txs, keybase, genesis.ChainID, keyname, pass); err != nil { + return fmt.Errorf("unable to sign txs, %w", err) + } + parsedTxs = append(parsedTxs, txs...) } @@ -117,3 +155,25 @@ func execTxsAddPackages( return nil } + +func signTxs(txs []gnoland.TxWithMetadata, keybase keys.Keybase, chainID, keyname string, password string) error { + for index, tx := range txs { + // Here accountNumber and sequenceNumber are set to 0 because they are considered as 0 on genesis transactions. + signBytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to load txs from directory, %w", err) + } + signature, publicKey, err := keybase.Sign(keyname, password, signBytes) + if err != nil { + return fmt.Errorf("unable sign tx %w", err) + } + txs[index].Tx.Signatures = []std.Signature{ + { + PubKey: publicKey, + Signature: signature, + }, + } + } + + return nil +} diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go index c3405d6ff8d..38d930401e8 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages_test.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages_test.go @@ -2,9 +2,11 @@ package txs import ( "context" + "encoding/hex" "fmt" "os" "path/filepath" + "strings" "testing" "github.com/gnolang/contribs/gnogenesis/internal/common" @@ -12,6 +14,8 @@ import ( vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,6 +23,7 @@ import ( func TestGenesis_Txs_Add_Packages(t *testing.T) { t.Parallel() + const addPkgExpectedSignature = "cfe5a15d8def04cbdaf9d08e2511db7928152b26419c4577cbfa282c83118852411f3de5d045ce934555572c21bda8042ce5c64b793a01748e49cf2cff7c2983" t.Run("invalid genesis file", func(t *testing.T) { t.Parallel() @@ -60,8 +65,10 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { assert.ErrorContains(t, cmdErr, errInvalidPackageDir.Error()) }) - t.Run("invalid deployer address", func(t *testing.T) { + t.Run("non existent key", func(t *testing.T) { t.Parallel() + keybaseDir := t.TempDir() + keyname := "beep-boop" tempGenesis, cleanup := testutils.NewTestFile(t) t.Cleanup(cleanup) @@ -69,24 +76,36 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + io := commands.NewTestIO() + io.SetIn( + strings.NewReader( + fmt.Sprintf( + "%s\n", + "password", + ), + ), + ) // Create the command - cmd := NewTxsCmd(commands.NewTestIO()) + cmd := NewTxsCmd(io) args := []string{ "add", "packages", "--genesis-path", tempGenesis.Name(), t.TempDir(), // package dir - "--deployer-address", - "beep-boop", // invalid address + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase + "--insecure-password-stdin", } // Run the command cmdErr := cmd.ParseAndRun(context.Background(), args) - assert.ErrorIs(t, cmdErr, errInvalidDeployerAddr) + assert.ErrorContains(t, cmdErr, "Key "+keyname+" not found") }) - t.Run("valid package", func(t *testing.T) { + t.Run("existent key wrong password", func(t *testing.T) { t.Parallel() tempGenesis, cleanup := testutils.NewTestFile(t) @@ -94,32 +113,189 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { genesis := common.GetDefaultGenesis() require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() + keybaseDir = t.TempDir() + keyname = "beep-boop" + password = "somepass" + ) + createValidFile(t, dir, packagePath) + // Create key + kb, err := keys.NewKeyBaseFromDir(keybaseDir) + require.NoError(t, err) + mnemonic, err := client.GenerateMnemonic(256) + require.NoError(t, err) + _, err = kb.CreateAccount(keyname, mnemonic, "", password+"wrong", 0, 0) + require.NoError(t, err) + + io := commands.NewTestIO() + io.SetIn( + strings.NewReader( + fmt.Sprintf( + "%s\n", + password, + ), + ), + ) + + // Create the command + cmd := NewTxsCmd(io) + args := []string{ + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase + "--insecure-password-stdin", + dir, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, "unable to sign txs") + }) + + t.Run("existent key correct password", func(t *testing.T) { + t.Parallel() + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := common.GetDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) // Prepare the package var ( packagePath = "gno.land/p/demo/cuttlas" dir = t.TempDir() + keybaseDir = t.TempDir() + keyname = "beep-boop" + password = "somepass" ) + createValidFile(t, dir, packagePath) + // Create key + kb, err := keys.NewKeyBaseFromDir(keybaseDir) + require.NoError(t, err) + info, err := kb.CreateAccount(keyname, defaultAccount_Seed, "", password, 0, 0) + require.NoError(t, err) - createFile := func(path, data string) { - file, err := os.Create(path) - require.NoError(t, err) + io := commands.NewTestIO() + io.SetIn( + strings.NewReader( + fmt.Sprintf( + "%s\n", + password, + ), + ), + ) - _, err = file.WriteString(data) - require.NoError(t, err) + // Create the command + cmd := NewTxsCmd(io) + args := []string{ + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + "--key-name", + keyname, // non-existent key name + "--gno-home", + keybaseDir, // temporaryDir for keybase + "--insecure-password-stdin", + dir, } - // Create the gno.mod file - createFile( - filepath.Join(dir, "gno.mod"), - fmt.Sprintf("module %s\n", packagePath), + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + require.Equal(t, 1, len(state.Txs)) + require.Equal(t, 1, len(state.Txs[0].Tx.Msgs)) + + msgAddPkg, ok := state.Txs[0].Tx.Msgs[0].(vmm.MsgAddPackage) + require.True(t, ok) + require.Equal(t, info.GetPubKey(), state.Txs[0].Tx.Signatures[0].PubKey) + require.Equal(t, addPkgExpectedSignature, hex.EncodeToString(state.Txs[0].Tx.Signatures[0].Signature)) + + assert.Equal(t, packagePath, msgAddPkg.Package.Path) + }) + + t.Run("ok default key", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := common.GetDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() + keybaseDir = t.TempDir() ) + createValidFile(t, dir, packagePath) + + // Create the command + cmd := NewTxsCmd(commands.NewTestIO()) + args := []string{ + "add", + "packages", + "--genesis-path", + tempGenesis.Name(), + "--gno-home", + keybaseDir, // temporaryDir for keybase + dir, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) - // Create a simple main.gno - createFile( - filepath.Join(dir, "main.gno"), - "package cuttlas\n\nfunc Example() string {\nreturn \"Manos arriba!\"\n}", + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + require.Equal(t, 1, len(state.Txs)) + require.Equal(t, 1, len(state.Txs[0].Tx.Msgs)) + + msgAddPkg, ok := state.Txs[0].Tx.Msgs[0].(vmm.MsgAddPackage) + require.True(t, ok) + require.Equal(t, defaultAccount_publicKey, state.Txs[0].Tx.Signatures[0].PubKey.String()) + require.Equal(t, addPkgExpectedSignature, hex.EncodeToString(state.Txs[0].Tx.Signatures[0].Signature)) + + assert.Equal(t, packagePath, msgAddPkg.Package.Path) + }) + + t.Run("valid package", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := common.GetDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + // Prepare the package + var ( + packagePath = "gno.land/p/demo/cuttlas" + dir = t.TempDir() ) + createValidFile(t, dir, packagePath) // Create the command cmd := NewTxsCmd(commands.NewTestIO()) @@ -148,7 +324,32 @@ func TestGenesis_Txs_Add_Packages(t *testing.T) { msgAddPkg, ok := state.Txs[0].Tx.Msgs[0].(vmm.MsgAddPackage) require.True(t, ok) + require.Equal(t, defaultAccount_publicKey, state.Txs[0].Tx.Signatures[0].PubKey.String()) + require.Equal(t, addPkgExpectedSignature, hex.EncodeToString(state.Txs[0].Tx.Signatures[0].Signature)) assert.Equal(t, packagePath, msgAddPkg.Package.Path) }) } + +func createValidFile(t *testing.T, dir string, packagePath string) { + t.Helper() + createFile := func(path, data string) { + file, err := os.Create(path) + require.NoError(t, err) + + _, err = file.WriteString(data) + require.NoError(t, err) + } + + // Create the gno.mod file + createFile( + filepath.Join(dir, "gno.mod"), + fmt.Sprintf("module %s\n", packagePath), + ) + + // Create a simple main.gno + createFile( + filepath.Join(dir, "main.gno"), + "package cuttlas\n\nfunc Example() string {\nreturn \"Manos arriba!\"\n}", + ) +} diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index d3d4e0d2c52..bfcaaec999e 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -713,12 +713,12 @@ func loadpkgs(t *testing.T, rootdir string, paths ...string) []gnoland.TxWithMet err := loader.LoadPackage(examplesDir, path, "") require.NoErrorf(t, err, "`loadpkg` unable to load package(s) from %q: %s", path, err) } + privKey, err := integration.GeneratePrivKeyFromMnemonic(integration.DefaultAccount_Seed, "", 0, 0) + require.NoError(t, err) - creator := crypto.MustAddressFromString(integration.DefaultAccount_Address) defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) - meta, err := loader.LoadPackages(creator, defaultFee, nil) + meta, err := loader.LoadPackages(privKey, defaultFee, nil) require.NoError(t, err) - return meta } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 0de26defad6..80c58e9e982 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -34,12 +34,13 @@ import ( // AppOptions contains the options to create the gno.land ABCI application. type AppOptions struct { - DB dbm.DB // required - Logger *slog.Logger // required - EventSwitch events.EventSwitch // required - VMOutput io.Writer // optional - InitChainerConfig // options related to InitChainer - MinGasPrices string // optional + DB dbm.DB // required + Logger *slog.Logger // required + EventSwitch events.EventSwitch // required + VMOutput io.Writer // optional + SkipGenesisVerification bool // default to verify genesis transactions + InitChainerConfig // options related to InitChainer + MinGasPrices string // optional } // TestAppOptions provides a "ready" default [AppOptions] for use with @@ -54,6 +55,7 @@ func TestAppOptions(db dbm.DB) *AppOptions { StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), CacheStdlibLoad: true, }, + SkipGenesisVerification: true, } } @@ -110,7 +112,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Set AnteHandler authOptions := auth.AnteOptions{ - VerifyGenesisSignatures: false, // for development + VerifyGenesisSignatures: !cfg.SkipGenesisVerification, } authAnteHandler := auth.NewAnteHandler( acctKpr, bankKpr, auth.DefaultSigVerificationGasConsumer, authOptions) diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 56a32e6e025..cc9e74a78d8 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -20,11 +20,12 @@ import ( ) type InMemoryNodeConfig struct { - PrivValidator bft.PrivValidator // identity of the validator - Genesis *bft.GenesisDoc - TMConfig *tmcfg.Config - DB db.DB // will be initialized if nil - VMOutput io.Writer // optional + PrivValidator bft.PrivValidator // identity of the validator + Genesis *bft.GenesisDoc + TMConfig *tmcfg.Config + DB db.DB // will be initialized if nil + VMOutput io.Writer // optional + SkipGenesisVerification bool // If StdlibDir not set, then it's filepath.Join(TMConfig.RootDir, "gnovm", "stdlibs") InitChainerConfig @@ -112,11 +113,12 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, // Initialize the application with the provided options gnoApp, err := NewAppWithOptions(&AppOptions{ - Logger: logger, - DB: cfg.DB, - EventSwitch: evsw, - InitChainerConfig: cfg.InitChainerConfig, - VMOutput: cfg.VMOutput, + Logger: logger, + DB: cfg.DB, + EventSwitch: evsw, + InitChainerConfig: cfg.InitChainerConfig, + VMOutput: cfg.VMOutput, + SkipGenesisVerification: cfg.SkipGenesisVerification, }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index fdf94c8c545..7965f228fc2 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -56,6 +56,7 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem // It will return the default creator address of the loaded packages. func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...gnoland.TxWithMetadata) (*gnoland.InMemoryNodeConfig, bft.Address) { cfg := TestingMinimalNodeConfig(gnoroot) + cfg.SkipGenesisVerification = true creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 541e24b96eb..7e7e817dd92 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -10,7 +10,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/packages" - bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -39,7 +39,7 @@ func (pl *PkgsLoader) SetPatch(replace, with string) { pl.patchs[replace] = with } -func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std.Coins) ([]gnoland.TxWithMetadata, error) { +func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, deposit std.Coins) ([]gnoland.TxWithMetadata, error) { pkgslist, err := pl.List().Sort() // sorts packages by their dependencies. if err != nil { return nil, fmt.Errorf("unable to sort packages: %w", err) @@ -47,7 +47,7 @@ func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std txs := make([]gnoland.TxWithMetadata, len(pkgslist)) for i, pkg := range pkgslist { - tx, err := gnoland.LoadPackage(pkg, creator, fee, deposit) + tx, err := gnoland.LoadPackage(pkg, creatorKey.PubKey().Address(), fee, deposit) if err != nil { return nil, fmt.Errorf("unable to load pkg %q: %w", pkg.Name, err) } @@ -77,6 +77,11 @@ func (pl *PkgsLoader) LoadPackages(creator bft.Address, fee std.Fee, deposit std } } + err = SignTxs(txs, creatorKey, "tendermint_test") + if err != nil { + return nil, fmt.Errorf("unable to sign txs: %w", err) + } + return txs, nil } diff --git a/gno.land/pkg/integration/signer.go b/gno.land/pkg/integration/signer.go new file mode 100644 index 00000000000..b32cd9c59bc --- /dev/null +++ b/gno.land/pkg/integration/signer.go @@ -0,0 +1,33 @@ +package integration + +import ( + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +// SignTxs will sign all txs passed as argument using the private key +// this signature is only valid for genesis transactions as accountNumber and sequence are 0 +func SignTxs(txs []gnoland.TxWithMetadata, privKey crypto.PrivKey, chainID string) error { + for index, tx := range txs { + bytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to get sign bytes for transaction, %w", err) + } + signature, err := privKey.Sign(bytes) + if err != nil { + return fmt.Errorf("unable to sign transaction, %w", err) + } + + txs[index].Tx.Signatures = []std.Signature{ + { + PubKey: privKey.PubKey(), + Signature: signature, + }, + } + } + return nil +} diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 84afe3cc6a4..13a448e7f8c 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -11,16 +11,19 @@ stdout 'data: {' stdout ' "BaseAccount": {' stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": null,' +stdout ' "public_key": {' +stdout ' "@type": "/tm.PubKeySecp256k1",' +stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' +stdout ' },' stdout ' "account_number": "0",' -stdout ' "sequence": "0"' +stdout ' "sequence": "1"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 0 -account-sequence 0 test1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 0 -account-sequence 1 test1 stdout 'Tx successfully signed and saved to ' ## broadcast diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index 8db2c7302fc..db3cd527eb3 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -7,41 +7,41 @@ gnoland start # Initial state: assert that sequence == 0. gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "0"' +stdout '"sequence": "1"' # attempt adding the "test" package. # the package has a syntax error; simulation should catch this ahead of time and prevent the tx. # -simulate test ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "0"' +stdout '"sequence": "1"' # -simulate only ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "0"' +stdout '"sequence": "1"' # -simulate skip ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "1"' +stdout '"sequence": "2"' # attempt calling hello.SetName correctly. # -simulate test and skip should do it successfully, -simulate only should not. # -simulate test gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "2"' +stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "2"' +stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "3"' +stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' @@ -51,19 +51,19 @@ stdout 'Hello, George!' # -simulate test ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "3"' +stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "3"' +stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 gnokey query auth/accounts/$USER_ADDR_test1 -stdout '"sequence": "4"' +stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' diff --git a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar index 3ed35a1b1d3..02bd8058214 100644 --- a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar +++ b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar @@ -14,9 +14,12 @@ stdout 'data: {' stdout ' "BaseAccount": {' stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": null,' +stdout ' "public_key": {' +stdout ' "@type": "/tm.PubKeySecp256k1",' +stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' +stdout ' },' stdout ' "account_number": "0",' -stdout ' "sequence": "0"' +stdout ' "sequence": "4"' stdout ' }' stdout '}' ! stderr '.+' # empty @@ -26,7 +29,7 @@ gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 10000 cp stdout call.tx # Sign -gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 0 -account-sequence 0 test1 +gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 0 -account-sequence 4 test1 cmpenv stdout sign.stdout.golden gnokey broadcast $WORK/call.tx diff --git a/gno.land/pkg/integration/testdata/restart_missing_type.txtar b/gno.land/pkg/integration/testdata/restart_missing_type.txtar index b02acc16d96..09e1a27d6f4 100644 --- a/gno.land/pkg/integration/testdata/restart_missing_type.txtar +++ b/gno.land/pkg/integration/testdata/restart_missing_type.txtar @@ -5,15 +5,15 @@ loadpkg gno.land/p/demo/avl gnoland start -gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 test1 +gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 1 test1 ! gnokey broadcast $WORK/tx1.tx stderr 'out of gas' -gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 test1 +gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 2 test1 gnokey broadcast $WORK/tx2.tx stdout 'OK!' -gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 test1 +gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 3 test1 gnokey broadcast $WORK/tx3.tx stdout 'OK!' diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 8550419f205..4c5213da345 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 96411' +stdout 'GAS USED: 99015' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 96411' # same as simulate only +stdout 'GAS USED: 99015' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index ae484a07669..9781799ea7d 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -117,7 +117,7 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { nodesManager := NewNodesManager() - defaultPK, err := generatePrivKeyFromMnemonic(DefaultAccount_Seed, "", 0, 0) + defaultPK, err := GeneratePrivKeyFromMnemonic(DefaultAccount_Seed, "", 0, 0) require.NoError(t, err) var buildOnce sync.Once @@ -237,6 +237,9 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) func(ts *testscript.TestScript, neg bool, args []string) { t.Helper() + defaultPK, err := GeneratePrivKeyFromMnemonic(DefaultAccount_Seed, "", 0, 0) + require.NoError(t, err) + return func(ts *testscript.TestScript, neg bool, args []string) { sid := getNodeSID(ts) @@ -265,9 +268,8 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun } pkgs := ts.Value(envKeyPkgsLoader).(*PkgsLoader) - creator := crypto.MustAddressFromString(DefaultAccount_Address) defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) - pkgsTxs, err := pkgs.LoadPackages(creator, defaultFee, nil) + pkgsTxs, err := pkgs.LoadPackages(defaultPK, defaultFee, nil) if err != nil { ts.Fatalf("unable to load packages txs: %s", err) } @@ -765,7 +767,7 @@ func buildGnoland(t *testing.T, rootdir string) string { } // GeneratePrivKeyFromMnemonic generates a crypto.PrivKey from a mnemonic. -func generatePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, index uint32) (crypto.PrivKey, error) { +func GeneratePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, index uint32) (crypto.PrivKey, error) { // Generate Seed from Mnemonic seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) if err != nil { diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index e4051e3a5a4..bd88dd5d08c 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -157,10 +157,6 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= -go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index bec2c501f61..f05a8eff0a7 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -419,16 +419,20 @@ func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit int64) sdk.Context { // GetSignBytes returns a slice of bytes to sign over for a given transaction // and an account. func GetSignBytes(chainID string, tx std.Tx, acc std.Account, genesis bool) ([]byte, error) { - var accNum uint64 + var ( + accNum uint64 + accSequence uint64 + ) if !genesis { accNum = acc.GetAccountNumber() + accSequence = acc.GetSequence() } return std.GetSignaturePayload( std.SignDoc{ ChainID: chainID, AccountNumber: accNum, - Sequence: acc.GetSequence(), + Sequence: accSequence, Fee: tx.Fee, Msgs: tx.Msgs, Memo: tx.Memo, diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 78018b415eb..7c6ace51e4e 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -209,8 +209,8 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, []uint64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, std.UnauthorizedError{}) - // from correct account number - seqs = []uint64{1} + // At genesis account number is zero + seqs = []uint64{0} tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, []uint64{0}, seqs, fee) checkValidTx(t, anteHandler, ctx, tx, false) @@ -223,7 +223,7 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, false, std.UnauthorizedError{}) // correct account numbers - privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []uint64{0, 0}, []uint64{2, 0} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []uint64{0, 0}, []uint64{0, 0} tx = tu.NewTestTx(t, ctx.ChainID(), msgs, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx, false) } From aa031a6677032e72ae7752aca7ec333ac282ea2c Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 9 Jan 2025 06:30:33 -0800 Subject: [PATCH 12/75] feat: add fuzzers for gnovm/pkg/gnolang/ParseFile + ConvertUntypedBigDecToFloat (#3455) To harden the security of Gno, this change introduces fuzzers that so far have already rediscovered a cockroadch/apd/v3 bug per https://github.com/cockroachdb/apd/issues/120#issuecomment-2417135080 Updates #3087 Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- gnovm/pkg/gnolang/fuzz_test.go | 89 +++++++++++++++++++ .../gnolang/testdata/corpra/parsefile/a.go | 9 ++ .../gnolang/testdata/corpra/parsefile/b.go | 16 ++++ .../testdata/corpra/parsefile/bug_3013.go | 22 +++++ .../corpra/parsefile/bug_3014_redefine.go | 10 +++ .../corpra/parsefile/bug_3014_redefine2.go | 21 +++++ .../corpra/parsefile/bug_3014_redefine3.go | 8 ++ .../corpra/parsefile/bug_3014_redefine4.go | 11 +++ .../corpra/parsefile/bug_3014_redefine5.go | 13 +++ .../corpra/parsefile/bug_3014_redefine6.go | 6 ++ .../4be24841138e3224 | 2 + .../94196a9456e79dac | 2 + 12 files changed, 209 insertions(+) create mode 100644 gnovm/pkg/gnolang/fuzz_test.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go create mode 100644 gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go create mode 100644 gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/4be24841138e3224 create mode 100644 gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/94196a9456e79dac diff --git a/gnovm/pkg/gnolang/fuzz_test.go b/gnovm/pkg/gnolang/fuzz_test.go new file mode 100644 index 00000000000..977c7453b90 --- /dev/null +++ b/gnovm/pkg/gnolang/fuzz_test.go @@ -0,0 +1,89 @@ +package gnolang + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/cockroachdb/apd/v3" +) + +func FuzzConvertUntypedBigdecToFloat(f *testing.F) { + // 1. Firstly add seeds. + seeds := []string{ + "-100000", + "100000", + "0", + } + + check := new(apd.Decimal) + for _, seed := range seeds { + if check.UnmarshalText([]byte(seed)) == nil { + f.Add(seed) + } + } + + f.Fuzz(func(t *testing.T, apdStr string) { + switch { + case strings.HasPrefix(apdStr, ".-"): + return + } + + v := new(apd.Decimal) + if err := v.UnmarshalText([]byte(apdStr)); err != nil { + return + } + if _, err := v.Float64(); err != nil { + return + } + + bd := BigdecValue{ + V: v, + } + dst := new(TypedValue) + typ := Float64Type + ConvertUntypedBigdecTo(dst, bd, typ) + }) +} + +func FuzzParseFile(f *testing.F) { + // 1. Add the corpra. + parseFileDir := filepath.Join("testdata", "corpra", "parsefile") + paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go")) + if err != nil { + f.Fatal(err) + } + + // Also load in files from gno/gnovm/tests/files + pc, curFile, _, _ := runtime.Caller(0) + curFileDir := filepath.Dir(curFile) + gnovmTestFilesDir, err := filepath.Abs(filepath.Join(curFileDir, "..", "..", "tests", "files")) + if err != nil { + _ = pc // To silence the arbitrary golangci linter. + f.Fatal(err) + } + globGnoTestFiles := filepath.Join(gnovmTestFilesDir, "*.gno") + gnoTestFiles, err := filepath.Glob(globGnoTestFiles) + if err != nil { + f.Fatal(err) + } + if len(gnoTestFiles) == 0 { + f.Fatalf("no files found from globbing %q", globGnoTestFiles) + } + paths = append(paths, gnoTestFiles...) + + for _, path := range paths { + blob, err := os.ReadFile(path) + if err != nil { + f.Fatal(err) + } + f.Add(string(blob)) + } + + // 2. Now run the fuzzer. + f.Fuzz(func(t *testing.T, goFileContents string) { + _, _ = ParseFile("a.go", goFileContents) + }) +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go new file mode 100644 index 00000000000..ae05a655fd7 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go @@ -0,0 +1,9 @@ +package main + +import + _ "math/big" +) + +func main() { + println("Foo") +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go new file mode 100644 index 00000000000..07592ff47ed --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go @@ -0,0 +1,16 @@ +package main + +import "crypto/rand" + +func init() { +} + +func init() { +} + +func init() { +} + +func it() { + _ = rand.Read +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go new file mode 100644 index 00000000000..f664f68f1b6 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go @@ -0,0 +1,22 @@ +package main + +import "testing" + +func TestDummy(t *testing.T) { + testTable := []struct { + name string + }{ + { + "one", + }, + { + "two", + }, + } + + for _, testCase := range testTable { + testCase := testCase + + println(testCase.name) + } +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go new file mode 100644 index 00000000000..877b5fafc1d --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go @@ -0,0 +1,10 @@ +package main + +var ss = []int{1, 2, 3} + +func main() { + for _, s := range ss { + s := s + println(s) + } +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go new file mode 100644 index 00000000000..450406a2202 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go @@ -0,0 +1,21 @@ +package main + +var testTable = []struct { + name string +}{ + { + "one", + }, + { + "two", + }, +} + +func main() { + + for _, testCase := range testTable { + testCase := testCase + + println(testCase.name) + } +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go new file mode 100644 index 00000000000..74ed729fb28 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go @@ -0,0 +1,8 @@ +package main + +func main() { + for i := 0; i < 3; i++ { + i := i + println(i) + } +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go new file mode 100644 index 00000000000..db27dcdc6bf --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go @@ -0,0 +1,11 @@ +package main + +func main() { + a := 1 + b := 3 + println(a, b) // prints 1 3 + + // Re-declaration of 'a' is allowed because 'c' is a new variable + a, c := 2, 5 + println(a, c) // prints 2 5 +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go new file mode 100644 index 00000000000..97ec50f3330 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go @@ -0,0 +1,13 @@ +package main + +func main() { + a := 1 + println(a) // prints 1 + + if true { + a := 2 // valid: new scope inside the if statement + println(a) // prints 2 + } + + println(a) // prints 1: outer variable is unchanged +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go new file mode 100644 index 00000000000..49b871be9c0 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go @@ -0,0 +1,6 @@ +package main + +func main() { + a, b := 1, 2 + a, b := 3, 4 +} diff --git a/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/4be24841138e3224 b/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/4be24841138e3224 new file mode 100644 index 00000000000..8894efa8d8e --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/4be24841138e3224 @@ -0,0 +1,2 @@ +go test fuzz v1 +string(".-700000000000000000000000000000000000000") diff --git a/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/94196a9456e79dac b/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/94196a9456e79dac new file mode 100644 index 00000000000..a317fbab107 --- /dev/null +++ b/gnovm/pkg/gnolang/testdata/fuzz/FuzzConvertUntypedBigdecToFloat/94196a9456e79dac @@ -0,0 +1,2 @@ +go test fuzz v1 +string("200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") From b60f86478da2bc8ee429221134f9fd5ffed465cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:10:01 -0700 Subject: [PATCH 13/75] chore(deps): bump golang.org/x/image from 0.0.0-20191206065243-da761ea9ff43 to 0.18.0 in /contribs/gnomd in the go_modules group across 1 directory (#3153) Bumps the go_modules group with 1 update in the /contribs/gnomd directory: [golang.org/x/image](https://github.com/golang/image). Updates `golang.org/x/image` from 0.0.0-20191206065243-da761ea9ff43 to 0.18.0
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/image&package-manager=go_modules&previous-version=0.0.0-20191206065243-da761ea9ff43&new-version=0.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) You can trigger a rebase of this PR by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/gnolang/gno/network/alerts).
    > **Note** > Automatic rebases have been disabled on this pull request as it has been open for over 30 days. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- contribs/gnomd/go.mod | 2 +- contribs/gnomd/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 423e4414a79..57c07621324 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -21,7 +21,7 @@ require ( github.com/mattn/go-isatty v0.0.11 // indirect github.com/mattn/go-runewidth v0.0.12 // indirect github.com/rivo/uniseg v0.1.0 // indirect - golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect + golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect ) diff --git a/contribs/gnomd/go.sum b/contribs/gnomd/go.sum index 0ff70dd99fb..3d4666530b1 100644 --- a/contribs/gnomd/go.sum +++ b/contribs/gnomd/go.sum @@ -55,8 +55,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 h1:gQ6GUSD102fPgli+Yb4cR/cGaHF7tNBt+GYoRCpGC7s= golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= From 41f876359b512b41e01a86fc46bf598083c98b60 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 9 Jan 2025 09:33:14 -0800 Subject: [PATCH 14/75] feat: fuzz gnovm/pkg/transpiler.Transpile (#3457) Adds a fuzzer for Transpile, which found bugs: * #3425 in which this following Go program crashed the transpiler ```go package A import(""//" ""/***/) ``` * #3426 which generated an input that revealed the fact that Gno deviates from Go by allowing unused variables yet Go's standard is strict on unused variables like this program ```go package main func main() { const c1 = 1 < 8 main() 1 } ``` which we shouldn't allow * partially #3428 which revealed the discrepancy in Gno that the overflow detection is still lacking as the following program is invalid Go but Gno allowed it to run ```go package main func main() { const c1 = int8(1) << 8 println(c1) } ``` because 1<<8 (256) is higher than the range of int8 for which the maximum is `(1<<7) - 1 aka 127` Updates #3087 Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- gnovm/pkg/transpiler/fuzz_test.go | 80 +++++++++++++++++++ .../fuzz/FuzzTranspiling/26ec2c0a22e242fc | 2 + .../fuzz/FuzzTranspiling/54f7287f473abfa5 | 2 + .../fuzz/FuzzTranspiling/703a1cacabb84c6d | 2 + .../fuzz/FuzzTranspiling/bcd60839c81ca478 | 2 + .../fuzz/FuzzTranspiling/bf4f7515b25bf71b | 2 + 6 files changed, 90 insertions(+) create mode 100644 gnovm/pkg/transpiler/fuzz_test.go create mode 100644 gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/26ec2c0a22e242fc create mode 100644 gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/54f7287f473abfa5 create mode 100644 gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/703a1cacabb84c6d create mode 100644 gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bcd60839c81ca478 create mode 100644 gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bf4f7515b25bf71b diff --git a/gnovm/pkg/transpiler/fuzz_test.go b/gnovm/pkg/transpiler/fuzz_test.go new file mode 100644 index 00000000000..68414c21e9a --- /dev/null +++ b/gnovm/pkg/transpiler/fuzz_test.go @@ -0,0 +1,80 @@ +package transpiler + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +func FuzzTranspiling(f *testing.F) { + if testing.Short() { + f.Skip("Running in -short mode") + } + + // 1. Derive the seeds from our seedGnoFiles. + breakRoot := filepath.Join("gnolang", "gno") + pc, thisFile, _, _ := runtime.Caller(0) + index := strings.Index(thisFile, breakRoot) + _ = pc // to silence the pedantic golangci linter. + rootPath := thisFile[:index+len(breakRoot)] + examplesDir := filepath.Join(rootPath, "examples") + ffs := os.DirFS(examplesDir) + fs.WalkDir(ffs, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + panic(err) + } + if !strings.HasSuffix(path, ".gno") { + return nil + } + file, err := ffs.Open(path) + if err != nil { + panic(err) + } + blob, err := io.ReadAll(file) + file.Close() + if err != nil { + panic(err) + } + f.Add(blob) + return nil + }) + + // 2. Run the fuzzers. + f.Fuzz(func(t *testing.T, gnoSourceCode []byte) { + // 3. Add timings to ensure that if transpiling takes a long time + // to run, that we report this as problematic. + doneCh := make(chan bool, 1) + readyCh := make(chan bool) + go func() { + defer func() { + r := recover() + if r == nil { + return + } + + sr := fmt.Sprintf("%s", r) + if !strings.Contains(sr, "invalid line number ") { + panic(r) + } + }() + close(readyCh) + defer close(doneCh) + _, _ = Transpile(string(gnoSourceCode), "gno", "in.gno") + doneCh <- true + }() + + <-readyCh + + select { + case <-time.After(2 * time.Second): + t.Fatalf("took more than 2 seconds to transpile\n\n%s", gnoSourceCode) + case <-doneCh: + } + }) +} diff --git a/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/26ec2c0a22e242fc b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/26ec2c0a22e242fc new file mode 100644 index 00000000000..d82acb3eaa5 --- /dev/null +++ b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/26ec2c0a22e242fc @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("package\tA\nimport(\"\"//\"\n\"\"/***/)") diff --git a/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/54f7287f473abfa5 b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/54f7287f473abfa5 new file mode 100644 index 00000000000..a3accb3dd8e --- /dev/null +++ b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/54f7287f473abfa5 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("package A\nimport(\"\"//\"\n\"\"/***/)") diff --git a/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/703a1cacabb84c6d b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/703a1cacabb84c6d new file mode 100644 index 00000000000..c08c7cfe904 --- /dev/null +++ b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/703a1cacabb84c6d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("package A\ncon\x12\n\xec|b\x80\xddQst(\n/*\n\n\n\n\n\n\nka\n*/A)") diff --git a/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bcd60839c81ca478 b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bcd60839c81ca478 new file mode 100644 index 00000000000..df9d3b1b5b9 --- /dev/null +++ b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bcd60839c81ca478 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("//\"\npackage\tA\nimport(\"\"//\"\n\"\"/***/)") diff --git a/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bf4f7515b25bf71b b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bf4f7515b25bf71b new file mode 100644 index 00000000000..cbc2bd9be5b --- /dev/null +++ b/gnovm/pkg/transpiler/testdata/fuzz/FuzzTranspiling/bf4f7515b25bf71b @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("//0000\x170000000000:0\npackage A") From e5b90958c8f5dcaa8b1163cebbfffd82c7d2bc8c Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 9 Jan 2025 10:37:30 -0800 Subject: [PATCH 15/75] perf(gnovm): cache PkgIDFromPkgPath for higher performance (#3424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change comes from noticing that PkgIDFromPkgPath is very heavily invoked within the VM yet its results with the same inputs produced deterministic results aka SHA256(path)[:20] Previously just spinning up the VM would take 80 seconds, with this change that's shaved by ~8-10 seconds down and with repeatable and visible results exhibited through ### Benchmark: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta PkgIDFromPkgPath-8 1.96µs ± 2% 0.35µs ± 1% -82.40% (p=0.000 n=20+18) name old alloc/op new alloc/op delta PkgIDFromPkgPath-8 296B ± 0% 168B ± 0% -43.24% (p=0.000 n=20+20) name old allocs/op new allocs/op delta PkgIDFromPkgPath-8 9.00 ± 0% 7.00 ± 0% -22.22% (p=0.000 n=20+20) ``` ### Profiles: * Before ```shell (pprof) list PkgIDFromPkgPath Total: 100.94s ROUTINE ======================== github.com/gnolang/gno/gnovm/pkg/gnolang.PkgIDFromPkgPath in $PATH 220ms 9.26s (flat, cum) 9.17% of Total . . 74:func PkgIDFromPkgPath(path string) PkgID { 220ms 9.26s 75: return PkgID{HashBytes([]byte(path))} . . 76:} . . 77: . . 78:// Returns the ObjectID of the PackageValue associated with path. . . 79:func ObjectIDFromPkgPath(path string) ObjectID { . . 80: pkgID := PkgIDFromPkgPath(path) ``` * After ```shell (pprof) list PkgIDFromPkgPath Total: 93.22s ROUTINE ======================== github.com/gnolang/gno/gnovm/pkg/gnolang.PkgIDFromPkgPath in $PATH 210ms 1.55s (flat, cum) 1.66% of Total 50ms 50ms 78:func PkgIDFromPkgPath(path string) PkgID { . 490ms 79: pkgIDMu.Lock() 10ms 10ms 80: defer pkgIDMu.Unlock() . . 81: 10ms 730ms 82: pkgID, ok := pkgIDFromPkgPathCache[path] . . 83: if !ok { . . 84: pkgID = new(PkgID) . . 85: *pkgID = PkgID{HashBytes([]byte(path))} . . 86: pkgIDFromPkgPathCache[path] = pkgID . . 87: } 140ms 270ms 88: return *pkgID . . 89:} . . 90: . . 91:// Returns the ObjectID of the PackageValue associated with path. . . 92:func ObjectIDFromPkgPath(path string) ObjectID { . . 93: pkgID := PkgIDFromPkgPath(path) ``` Fixes #3423 --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Morgan Bazalgette --- gnovm/pkg/gnolang/bench_test.go | 31 +++++++++++++++++++++++++++++++ gnovm/pkg/gnolang/realm.go | 20 +++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 gnovm/pkg/gnolang/bench_test.go diff --git a/gnovm/pkg/gnolang/bench_test.go b/gnovm/pkg/gnolang/bench_test.go new file mode 100644 index 00000000000..b638ab66cd0 --- /dev/null +++ b/gnovm/pkg/gnolang/bench_test.go @@ -0,0 +1,31 @@ +package gnolang + +import ( + "testing" +) + +var sink any = nil + +var pkgIDPaths = []string{ + "encoding/json", + "math/bits", + "github.com/gnolang/gno/gnovm/pkg/gnolang", + "a", + " ", + "", + "github.com/gnolang/gno/gnovm/pkg/gnolang/vendor/pkg/github.com/gnolang/vendored", +} + +func BenchmarkPkgIDFromPkgPath(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, path := range pkgIDPaths { + sink = PkgIDFromPkgPath(path) + } + } + + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index d822eb290eb..04de760037a 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strings" + "sync" bm "github.com/gnolang/gno/gnovm/pkg/benchops" ) @@ -71,8 +72,25 @@ func (pid PkgID) Bytes() []byte { return pid.Hashlet[:] } +var ( + pkgIDFromPkgPathCacheMu sync.Mutex // protects the shared cache. + // TODO: later on switch this to an LRU if needed to ensure + // fixed memory caps. For now though it isn't a problem: + // https://github.com/gnolang/gno/pull/3424#issuecomment-2564571785 + pkgIDFromPkgPathCache = make(map[string]*PkgID, 100) +) + func PkgIDFromPkgPath(path string) PkgID { - return PkgID{HashBytes([]byte(path))} + pkgIDFromPkgPathCacheMu.Lock() + defer pkgIDFromPkgPathCacheMu.Unlock() + + pkgID, ok := pkgIDFromPkgPathCache[path] + if !ok { + pkgID = new(PkgID) + *pkgID = PkgID{HashBytes([]byte(path))} + pkgIDFromPkgPathCache[path] = pkgID + } + return *pkgID } // Returns the ObjectID of the PackageValue associated with path. From 58ce7933e79d8d7ec745f2deb1c4b91df459f7a9 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 9 Jan 2025 20:11:37 +0100 Subject: [PATCH 16/75] test: fix adding examples directory in fuzzer (#3470) Fixes master CI. --- gnovm/pkg/transpiler/fuzz_test.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/gnovm/pkg/transpiler/fuzz_test.go b/gnovm/pkg/transpiler/fuzz_test.go index 68414c21e9a..d3ab26eea86 100644 --- a/gnovm/pkg/transpiler/fuzz_test.go +++ b/gnovm/pkg/transpiler/fuzz_test.go @@ -1,4 +1,4 @@ -package transpiler +package transpiler_test import ( "fmt" @@ -6,10 +6,12 @@ import ( "io/fs" "os" "path/filepath" - "runtime" "strings" "testing" "time" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func FuzzTranspiling(f *testing.F) { @@ -18,13 +20,7 @@ func FuzzTranspiling(f *testing.F) { } // 1. Derive the seeds from our seedGnoFiles. - breakRoot := filepath.Join("gnolang", "gno") - pc, thisFile, _, _ := runtime.Caller(0) - index := strings.Index(thisFile, breakRoot) - _ = pc // to silence the pedantic golangci linter. - rootPath := thisFile[:index+len(breakRoot)] - examplesDir := filepath.Join(rootPath, "examples") - ffs := os.DirFS(examplesDir) + ffs := os.DirFS(filepath.Join(gnoenv.RootDir(), "examples")) fs.WalkDir(ffs, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { panic(err) @@ -65,7 +61,7 @@ func FuzzTranspiling(f *testing.F) { }() close(readyCh) defer close(doneCh) - _, _ = Transpile(string(gnoSourceCode), "gno", "in.gno") + _, _ = transpiler.Transpile(string(gnoSourceCode), "gno", "in.gno") doneCh <- true }() From c561e326cc3c97f511226aa318f7c2606e1191b2 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Fri, 10 Jan 2025 04:25:23 +0900 Subject: [PATCH 17/75] style: fix breadcrumb long query (#3467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the styling issue with breadcrumbs for long paths. When the query is excessively long, the input text may overflow beyond the breadcrumb and become visible across it. Capture d’écran 2025-01-09 à 23 09 17 Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- gno.land/pkg/gnoweb/components/breadcrumb.gohtml | 8 ++++---- gno.land/pkg/gnoweb/public/styles.css | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml index 0118dff5333..9295b17b6f5 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml @@ -1,16 +1,16 @@ {{ define "breadcrumb" }} -
      +
        {{- range $index, $part := .Parts }} {{- if $index }} -
      1. +
      2. {{- else }} -
      3. +
      4. {{- end }} {{ $part.Name }}
      5. {{- end }} {{- if .Args }} -
      6. +
      7. {{ .Args }}
      8. {{- end }} diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index ce6c8bae639..a6771695454 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From 817e71f7f5a21498429f74f1dc5e83ce0d6b4896 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 9 Jan 2025 12:11:42 -0800 Subject: [PATCH 18/75] perf: use fmt.Fprintf to avoid unnecessary string+args + WriteString (#3434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a bid to remove unnecessary CPU and memory bloat for the gnovm which takes the order of minutes to run certain code, I noticed the pattern: io.StringWriter.WriteString(fmt.Sprintf(...)) in which fmt.Sprintf(...) has to create a string by inserting all arguments into the format specifiers then pass that into .WriteString which defeats the entire purpose of io.StringWriter.WriteString that *bytes.Buffer and *strings.Builder implement. Just from picking a single benchmark that already exists results in improvements in all dimensions: ```shell name old time/op new time/op delta StringLargeData-8 8.87ms ± 1% 8.28ms ± 3% -6.68% (p=0.000 n=17+19) name old alloc/op new alloc/op delta StringLargeData-8 8.44MB ± 0% 7.78MB ± 0% -7.75% (p=0.000 n=20+19) name old allocs/op new allocs/op delta StringLargeData-8 94.1k ± 0% 70.1k ± 0% -25.51% (p=0.000 n=15+20) ``` for heavily used code this is going to reduce on garbage collection cycles too. Fixes #3433 --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Morgan --- contribs/gnomigrate/internal/txs/txs.go | 2 +- gnovm/pkg/gnolang/gno_test.go | 2 + gnovm/pkg/gnolang/machine.go | 39 ++++++------ gnovm/pkg/gnolang/machine_test.go | 59 +++++++++++++++++++ go.mod | 1 + misc/docs-linter/jsx.go | 2 +- misc/docs-linter/links.go | 2 +- misc/docs-linter/main.go | 4 +- misc/docs-linter/urls.go | 2 +- tm2/pkg/bft/rpc/lib/server/handlers.go | 4 +- .../rpc/lib/server/write_endpoints_test.go | 33 +++++++++++ tm2/pkg/sdk/auth/params.go | 17 +++--- tm2/pkg/sdk/auth/params_test.go | 24 ++++++++ 13 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go diff --git a/contribs/gnomigrate/internal/txs/txs.go b/contribs/gnomigrate/internal/txs/txs.go index 4c65ca6ef0b..231428d5064 100644 --- a/contribs/gnomigrate/internal/txs/txs.go +++ b/contribs/gnomigrate/internal/txs/txs.go @@ -184,7 +184,7 @@ func processFile(ctx context.Context, io commands.IO, source, destination string continue } - if _, err = outputFile.WriteString(fmt.Sprintf("%s\n", string(marshaledData))); err != nil { + if _, err = fmt.Fprintf(outputFile, "%s\n", marshaledData); err != nil { io.ErrPrintfln("unable to save to output file, %s", err) } } diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 89458667997..5a8c6faf315 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -36,6 +36,8 @@ func setupMachine(b *testing.B, numValues, numStmts, numExprs, numBlocks, numFra func BenchmarkStringLargeData(b *testing.B) { m := setupMachine(b, 10000, 5000, 5000, 2000, 3000, 1000) + b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { _ = m.String() diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 4480a89d16f..75d12ac5402 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -2117,6 +2117,10 @@ func (m *Machine) Printf(format string, args ...interface{}) { } func (m *Machine) String() string { + if m == nil { + return "Machine:nil" + } + // Calculate some reasonable total length to avoid reallocation // Assuming an average length of 32 characters per string var ( @@ -2131,25 +2135,26 @@ func (m *Machine) String() string { totalLength = vsLength + ssLength + xsLength + bsLength + obsLength + fsLength + exceptionsLength ) - var builder strings.Builder + var sb strings.Builder + builder := &sb // Pointer for use in fmt.Fprintf. builder.Grow(totalLength) - builder.WriteString(fmt.Sprintf("Machine:\n PreprocessorMode: %v\n Op: %v\n Values: (len: %d)\n", m.PreprocessorMode, m.Ops[:m.NumOps], m.NumValues)) + fmt.Fprintf(builder, "Machine:\n PreprocessorMode: %v\n Op: %v\n Values: (len: %d)\n", m.PreprocessorMode, m.Ops[:m.NumOps], m.NumValues) for i := m.NumValues - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Values[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Values[i]) } builder.WriteString(" Exprs:\n") for i := len(m.Exprs) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Exprs[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Exprs[i]) } builder.WriteString(" Stmts:\n") for i := len(m.Stmts) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Stmts[i])) + fmt.Fprintf(builder, " #%d %v\n", i, m.Stmts[i]) } builder.WriteString(" Blocks:\n") @@ -2166,17 +2171,17 @@ func (m *Machine) String() string { if pv, ok := b.Source.(*PackageNode); ok { // package blocks have too much, so just // print the pkgpath. - builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, pv.PkgPath)) + fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, pv.PkgPath) } else { bsi := b.StringIndented(" ") - builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, bsi)) + fmt.Fprintf(builder, " %s(%d) %s\n", gens, gen, bsi) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" "))) + fmt.Fprintf(builder, " (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" ")) sts := b.GetSource(m.Store).GetStaticBlock().Types - builder.WriteString(fmt.Sprintf(" (s typs) %s(%d) %s\n", gens, gen, sts)) + fmt.Fprintf(builder, " (s typs) %s(%d) %s\n", gens, gen, sts) } } @@ -2187,7 +2192,7 @@ func (m *Machine) String() string { case *Block: b = bp case RefValue: - builder.WriteString(fmt.Sprintf(" (block ref %v)\n", bp.ObjectID)) + fmt.Fprintf(builder, " (block ref %v)\n", bp.ObjectID) b = nil default: panic("should not happen") @@ -2206,12 +2211,12 @@ func (m *Machine) String() string { if _, ok := b.Source.(*PackageNode); ok { break // done, skip *PackageNode. } else { - builder.WriteString(fmt.Sprintf(" #%d %s\n", i, - b.StringIndented(" "))) + fmt.Fprintf(builder, " #%d %s\n", i, + b.StringIndented(" ")) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (static) #%d %s\n", i, - sb.StringIndented(" "))) + fmt.Fprintf(builder, " (static) #%d %s\n", i, + sb.StringIndented(" ")) } } } @@ -2219,17 +2224,17 @@ func (m *Machine) String() string { builder.WriteString(" Frames:\n") for i := len(m.Frames) - 1; i >= 0; i-- { - builder.WriteString(fmt.Sprintf(" #%d %s\n", i, m.Frames[i])) + fmt.Fprintf(builder, " #%d %s\n", i, m.Frames[i]) } if m.Realm != nil { - builder.WriteString(fmt.Sprintf(" Realm:\n %s\n", m.Realm.Path)) + fmt.Fprintf(builder, " Realm:\n %s\n", m.Realm.Path) } builder.WriteString(" Exceptions:\n") for _, ex := range m.Exceptions { - builder.WriteString(fmt.Sprintf(" %s\n", ex.Sprint(m))) + fmt.Fprintf(builder, " %s\n", ex.Sprint(m)) } return builder.String() diff --git a/gnovm/pkg/gnolang/machine_test.go b/gnovm/pkg/gnolang/machine_test.go index c3b2118f099..c2ab8ea12c5 100644 --- a/gnovm/pkg/gnolang/machine_test.go +++ b/gnovm/pkg/gnolang/machine_test.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/iavl" stypes "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" ) @@ -56,3 +57,61 @@ func TestRunMemPackageWithOverrides_revertToOld(t *testing.T) { assert.Equal(t, StringKind, v.T.Kind()) assert.Equal(t, StringValue("1"), v.V) } + +func TestMachineString(t *testing.T) { + cases := []struct { + name string + in *Machine + want string + }{ + { + "nil Machine", + nil, + "Machine:nil", + }, + { + "created with defaults", + NewMachineWithOptions(MachineOptions{}), + "Machine:\n PreprocessorMode: false\n Op: []\n Values: (len: 0)\n Exprs:\n Stmts:\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + { + "created with store and defaults", + func() *Machine { + db := memdb.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + return NewMachine("std", store) + }(), + "Machine:\n PreprocessorMode: false\n Op: []\n Values: (len: 0)\n Exprs:\n Stmts:\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + { + "filled in", + func() *Machine { + db := memdb.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + m := NewMachine("std", store) + m.PushOp(OpHalt) + m.PushExpr(&BasicLitExpr{ + Kind: INT, + Value: "100", + }) + m.Blocks = make([]*Block, 1, 1) + m.PushStmts(S(Call(X("Redecl"), 11))) + return m + }(), + "Machine:\n PreprocessorMode: false\n Op: [OpHalt]\n Values: (len: 0)\n Exprs:\n #0 100\n Stmts:\n #0 Redecl(11)\n Blocks:\n Blocks (other):\n Frames:\n Exceptions:\n", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + got := tt.in.String() + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Fatalf("Mismatch: got - want +\n%s", diff) + } + }) + } +} diff --git a/go.mod b/go.mod index 280ca3ae602..cd038e2ae65 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/fortytw2/leaktest v1.3.0 + github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.0 github.com/gorilla/websocket v1.5.3 github.com/libp2p/go-buffer-pool v0.1.0 diff --git a/misc/docs-linter/jsx.go b/misc/docs-linter/jsx.go index d0307680a0c..eb041a78386 100644 --- a/misc/docs-linter/jsx.go +++ b/misc/docs-linter/jsx.go @@ -50,7 +50,7 @@ func lintJSX(filepathToJSX map[string][]string) (string, error) { found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", tag, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", tag, filePath) } } diff --git a/misc/docs-linter/links.go b/misc/docs-linter/links.go index 744917d8dfb..e34d35d9f58 100644 --- a/misc/docs-linter/links.go +++ b/misc/docs-linter/links.go @@ -80,7 +80,7 @@ func lintLocalLinks(filepathToLinks map[string][]string, docsPath string) (strin found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", link, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", link, filePath) } } } diff --git a/misc/docs-linter/main.go b/misc/docs-linter/main.go index 97d80316108..5d7cdf37982 100644 --- a/misc/docs-linter/main.go +++ b/misc/docs-linter/main.go @@ -61,8 +61,8 @@ func execLint(cfg *cfg, ctx context.Context) (string, error) { } // Main buffer to write to the end user after linting - var output bytes.Buffer - output.WriteString(fmt.Sprintf("Linting %s...\n", absPath)) + var output bytes.Buffer + fmt.Fprintf(&output, "Linting %s...\n", absPath) // Find docs files to lint mdFiles, err := findFilePaths(cfg.docsPath) diff --git a/misc/docs-linter/urls.go b/misc/docs-linter/urls.go index 093e624d81e..098d0a05524 100644 --- a/misc/docs-linter/urls.go +++ b/misc/docs-linter/urls.go @@ -66,7 +66,7 @@ func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, found = true } - output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", url, filePath)) + fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", url, filePath) lock.Unlock() } diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index 88ee26da4a9..9e10596a975 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -939,7 +939,7 @@ func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[st for _, name := range noArgNames { link := fmt.Sprintf("//%s/%s", r.Host, name) - buf.WriteString(fmt.Sprintf("%s
        ", link, link)) + fmt.Fprintf(buf, "%s
        ", link, link) } buf.WriteString("
        Endpoints that require arguments:
        ") @@ -952,7 +952,7 @@ func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[st link += "&" } } - buf.WriteString(fmt.Sprintf("%s
        ", link, link)) + fmt.Fprintf(buf, "%s
        ", link, link) } buf.WriteString("") w.Header().Set("Content-Type", "text/html") diff --git a/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go b/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go new file mode 100644 index 00000000000..b01144f9273 --- /dev/null +++ b/tm2/pkg/bft/rpc/lib/server/write_endpoints_test.go @@ -0,0 +1,33 @@ +package rpcserver + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + + types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" +) + +func TestWriteListOfEndpoints(t *testing.T) { + funcMap := map[string]*RPCFunc{ + "c": NewWSRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), + "d": {}, + } + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + rec := httptest.NewRecorder() + writeListOfEndpoints(rec, req, funcMap) + res := rec.Result() + assert.Equal(t, res.StatusCode, 200, "Should always return 200") + blob, err := io.ReadAll(res.Body) + assert.NoError(t, err) + gotResp := string(blob) + wantResp := `
        Available endpoints:
        //localhost/d

        Endpoints that require arguments:
        //localhost/c?s=_&i=_
        ` + if diff := cmp.Diff(gotResp, wantResp); diff != "" { + t.Fatalf("Mismatch response: got - want +\n%s", diff) + } +} diff --git a/tm2/pkg/sdk/auth/params.go b/tm2/pkg/sdk/auth/params.go index 3fe08ed444d..fda85c7a3d6 100644 --- a/tm2/pkg/sdk/auth/params.go +++ b/tm2/pkg/sdk/auth/params.go @@ -69,15 +69,16 @@ func DefaultParams() Params { // String implements the stringer interface. func (p Params) String() string { - var sb strings.Builder + var builder strings.Builder + sb := &builder // Pointer for use with fmt.Fprintf sb.WriteString("Params: \n") - sb.WriteString(fmt.Sprintf("MaxMemoBytes: %d\n", p.MaxMemoBytes)) - sb.WriteString(fmt.Sprintf("TxSigLimit: %d\n", p.TxSigLimit)) - sb.WriteString(fmt.Sprintf("TxSizeCostPerByte: %d\n", p.TxSizeCostPerByte)) - sb.WriteString(fmt.Sprintf("SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519)) - sb.WriteString(fmt.Sprintf("SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1)) - sb.WriteString(fmt.Sprintf("GasPricesChangeCompressor: %d\n", p.GasPricesChangeCompressor)) - sb.WriteString(fmt.Sprintf("TargetGasRatio: %d\n", p.TargetGasRatio)) + fmt.Fprintf(sb, "MaxMemoBytes: %d\n", p.MaxMemoBytes) + fmt.Fprintf(sb, "TxSigLimit: %d\n", p.TxSigLimit) + fmt.Fprintf(sb, "TxSizeCostPerByte: %d\n", p.TxSizeCostPerByte) + fmt.Fprintf(sb, "SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519) + fmt.Fprintf(sb, "SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1) + fmt.Fprintf(sb, "GasPricesChangeCompressor: %d\n", p.GasPricesChangeCompressor) + fmt.Fprintf(sb, "TargetGasRatio: %d\n", p.TargetGasRatio) return sb.String() } diff --git a/tm2/pkg/sdk/auth/params_test.go b/tm2/pkg/sdk/auth/params_test.go index 4b5a6b15789..36a52ac9001 100644 --- a/tm2/pkg/sdk/auth/params_test.go +++ b/tm2/pkg/sdk/auth/params_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" ) @@ -105,3 +106,26 @@ func TestNewParams(t *testing.T) { t.Errorf("NewParams() = %+v, want %+v", params, expectedParams) } } + +func TestParamsString(t *testing.T) { + cases := []struct { + name string + params Params + want string + }{ + {"blank params", Params{}, "Params: \nMaxMemoBytes: 0\nTxSigLimit: 0\nTxSizeCostPerByte: 0\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\n"}, + {"some values", Params{ + MaxMemoBytes: 1_000_000, + TxSizeCostPerByte: 8192, + }, "Params: \nMaxMemoBytes: 1000000\nTxSigLimit: 0\nTxSizeCostPerByte: 8192\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\n"}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + got := tt.params.String() + if diff := cmp.Diff(got, tt.want); diff != "" { + t.Fatalf("Mismatch: got - want +\n%s", diff) + } + }) + } +} From b92a6a43dc70ecea11f3ffad525d4a2e200247c3 Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Thu, 9 Jan 2025 21:18:55 +0100 Subject: [PATCH 19/75] feat(gnovm/pkg/packages): categorize imports (#3323) This makes the imports utils split imports by file kinds, allowing to make explicit decisions about what imports to use at the various callsites - Create `FileKind` enum to categorize gno files, with variants `PackageSource`, `Test`, `XTest` and `Filetest` - Create `GetFileKind` util to derive the `FileKind` from a file name and body - Create `ImportsMap` type that maps `FileKind`s to lists of imports. It has a single method `Merge` to select and merge various imports from multiple `FileKind`s - Modify the`packages.Imports` helper to return an `ImportsMap` instead of a `[]string` and adapt callsites by using`ImportMap.Merge` to preserve existing behavior This is something I need for #3304 and #2932 but to help reviews I made an atomic PR here instead --------- Signed-off-by: Norman Meier Co-authored-by: Morgan --- gno.land/pkg/integration/pkgloader.go | 3 +- gnovm/cmd/gno/download_deps.go | 3 +- gnovm/pkg/doc/dirs.go | 5 ++- gnovm/pkg/gnomod/pkg.go | 6 ++- gnovm/pkg/packages/filekind.go | 56 ++++++++++++++++++++++++ gnovm/pkg/packages/filekind_test.go | 63 +++++++++++++++++++++++++++ gnovm/pkg/packages/imports.go | 59 ++++++++++++++++++++----- gnovm/pkg/packages/imports_test.go | 59 +++++++++++++++++++------ gnovm/pkg/test/imports.go | 3 +- 9 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 gnovm/pkg/packages/filekind.go create mode 100644 gnovm/pkg/packages/filekind_test.go diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 7e7e817dd92..e40e8ff1eb5 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -131,10 +131,11 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { if err != nil { return fmt.Errorf("unable to read package at %q: %w", currentPkg.Dir, err) } - imports, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) for _, imp := range imports { if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { continue diff --git a/gnovm/cmd/gno/download_deps.go b/gnovm/cmd/gno/download_deps.go index 5a8c50be20b..4e638eb4970 100644 --- a/gnovm/cmd/gno/download_deps.go +++ b/gnovm/cmd/gno/download_deps.go @@ -25,10 +25,11 @@ func downloadDeps(io commands.IO, pkgDir string, gnoMod *gnomod.File, fetcher pk if err != nil { return fmt.Errorf("read package at %q: %w", pkgDir, err) } - imports, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { return fmt.Errorf("read imports at %q: %w", pkgDir, err) } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, pkgPath := range imports { resolved := gnoMod.Resolve(module.Version{Path: pkgPath.PkgPath}) diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index b287fd20708..4c481324e9a 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -105,11 +105,12 @@ func packageImportsRecursive(root string, pkgPath string) []string { pkg = &gnovm.MemPackage{} } - resRaw, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { // ignore packages with invalid imports - resRaw = nil + importsMap = nil } + resRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) res := make([]string, len(resRaw)) for idx, imp := range resRaw { res[idx] = imp.PkgPath diff --git a/gnovm/pkg/gnomod/pkg.go b/gnovm/pkg/gnomod/pkg.go index a0831d494b0..85f1d31442d 100644 --- a/gnovm/pkg/gnomod/pkg.go +++ b/gnovm/pkg/gnomod/pkg.go @@ -121,11 +121,13 @@ func ListPkgs(root string) (PkgList, error) { pkg = &gnovm.MemPackage{} } - importsRaw, err := packages.Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) if err != nil { // ignore imports on error - importsRaw = nil + importsMap = nil } + importsRaw := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) + imports := make([]string, 0, len(importsRaw)) for _, imp := range importsRaw { // remove self and standard libraries from imports diff --git a/gnovm/pkg/packages/filekind.go b/gnovm/pkg/packages/filekind.go new file mode 100644 index 00000000000..ed2ca84b7d0 --- /dev/null +++ b/gnovm/pkg/packages/filekind.go @@ -0,0 +1,56 @@ +package packages + +import ( + "fmt" + "go/parser" + "go/token" + "strings" +) + +// FileKind represent the category a gno source file falls in, can be one of: +// +// - [FileKindPackageSource] -> A *.gno file that will be included in the gnovm package +// +// - [FileKindTest] -> A *_test.gno file that will be used for testing +// +// - [FileKindXTest] -> A *_test.gno file with a package name ending in _test that will be used for blackbox testing +// +// - [FileKindFiletest] -> A *_filetest.gno file that will be used for snapshot testing +type FileKind uint + +const ( + FileKindUnknown FileKind = iota + FileKindPackageSource + FileKindTest + FileKindXTest + FileKindFiletest +) + +// GetFileKind analyzes a file's name and body to get it's [FileKind], fset is optional +func GetFileKind(filename string, body string, fset *token.FileSet) (FileKind, error) { + if !strings.HasSuffix(filename, ".gno") { + return FileKindUnknown, fmt.Errorf("%s:1:1: not a gno file", filename) + } + + if strings.HasSuffix(filename, "_filetest.gno") { + return FileKindFiletest, nil + } + + if !strings.HasSuffix(filename, "_test.gno") { + return FileKindPackageSource, nil + } + + if fset == nil { + fset = token.NewFileSet() + } + ast, err := parser.ParseFile(fset, filename, body, parser.PackageClauseOnly) + if err != nil { + return FileKindUnknown, err + } + packageName := ast.Name.Name + + if strings.HasSuffix(packageName, "_test") { + return FileKindXTest, nil + } + return FileKindTest, nil +} diff --git a/gnovm/pkg/packages/filekind_test.go b/gnovm/pkg/packages/filekind_test.go new file mode 100644 index 00000000000..bd06b49fb45 --- /dev/null +++ b/gnovm/pkg/packages/filekind_test.go @@ -0,0 +1,63 @@ +package packages + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetFileKind(t *testing.T) { + tcs := []struct { + name string + filename string + body string + fileKind FileKind + errorContains string + }{ + { + name: "compiled", + filename: "foo.gno", + fileKind: FileKindPackageSource, + }, + { + name: "test", + filename: "foo_test.gno", + body: "package foo", + fileKind: FileKindTest, + }, + { + name: "xtest", + filename: "foo_test.gno", + body: "package foo_test", + fileKind: FileKindXTest, + }, + { + name: "filetest", + filename: "foo_filetest.gno", + fileKind: FileKindFiletest, + }, + { + name: "err_badpkgclause", + filename: "foo_test.gno", + body: "pakage foo", + errorContains: "foo_test.gno:1:1: expected 'package', found pakage", + }, + { + name: "err_notgnofile", + filename: "foo.gno.bck", + errorContains: `foo.gno.bck:1:1: not a gno file`, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + out, err := GetFileKind(tc.filename, tc.body, nil) + if len(tc.errorContains) != 0 { + require.ErrorContains(t, err, tc.errorContains) + } else { + require.NoError(t, err) + } + require.Equal(t, tc.fileKind, out) + }) + } +} diff --git a/gnovm/pkg/packages/imports.go b/gnovm/pkg/packages/imports.go index 201965bc588..3bc60be6664 100644 --- a/gnovm/pkg/packages/imports.go +++ b/gnovm/pkg/packages/imports.go @@ -14,33 +14,40 @@ import ( // Imports returns the list of gno imports from a [gnovm.MemPackage]. // fset is optional. -func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) ([]FileImport, error) { - allImports := make([]FileImport, 0, 16) - seen := make(map[string]struct{}, 16) +func Imports(pkg *gnovm.MemPackage, fset *token.FileSet) (ImportsMap, error) { + res := make(ImportsMap, 16) + seen := make(map[FileKind]map[string]struct{}, 16) + for _, file := range pkg.Files { if !strings.HasSuffix(file.Name, ".gno") { continue } - if strings.HasSuffix(file.Name, "_filetest.gno") { - continue + + fileKind, err := GetFileKind(file.Name, file.Body, fset) + if err != nil { + return nil, err } imports, err := FileImports(file.Name, file.Body, fset) if err != nil { return nil, err } for _, im := range imports { - if _, ok := seen[im.PkgPath]; ok { + if _, ok := seen[fileKind][im.PkgPath]; ok { continue } - allImports = append(allImports, im) - seen[im.PkgPath] = struct{}{} + res[fileKind] = append(res[fileKind], im) + if _, ok := seen[fileKind]; !ok { + seen[fileKind] = make(map[string]struct{}, 16) + } + seen[fileKind][im.PkgPath] = struct{}{} } } - sort.Slice(allImports, func(i, j int) bool { - return allImports[i].PkgPath < allImports[j].PkgPath - }) - return allImports, nil + for _, imports := range res { + sortImports(imports) + } + + return res, nil } // FileImport represents an import @@ -75,3 +82,31 @@ func FileImports(filename string, src string, fset *token.FileSet) ([]FileImport } return res, nil } + +type ImportsMap map[FileKind][]FileImport + +// Merge merges imports, it removes duplicates and sorts the result +func (imap ImportsMap) Merge(kinds ...FileKind) []FileImport { + res := make([]FileImport, 0, 16) + seen := make(map[string]struct{}, 16) + + for _, kind := range kinds { + for _, im := range imap[kind] { + if _, ok := seen[im.PkgPath]; ok { + continue + } + seen[im.PkgPath] = struct{}{} + + res = append(res, im) + } + } + + sortImports(res) + return res +} + +func sortImports(imports []FileImport) { + sort.Slice(imports, func(i, j int) bool { + return imports[i].PkgPath < imports[j].PkgPath + }) +} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index 3750aa9108c..f9f58b967c8 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -58,13 +58,26 @@ func TestImports(t *testing.T) { ) `, }, + { + name: "file2_test.gno", + data: ` + package tmp_test + + import ( + "testing" + + "gno.land/p/demo/testpkg" + "gno.land/p/demo/xtestdep" + ) + `, + }, { name: "z_0_filetest.gno", data: ` package main import ( - "gno.land/p/demo/filetestpkg" + "gno.land/p/demo/filetestdep" ) `, }, @@ -95,17 +108,28 @@ func TestImports(t *testing.T) { }, } - // Expected list of imports + // Expected lists of imports // - ignore subdirs // - ignore duplicate - // - ignore *_filetest.gno // - should be sorted - expected := []string{ - "gno.land/p/demo/pkg1", - "gno.land/p/demo/pkg2", - "gno.land/p/demo/testpkg", - "std", - "testing", + expected := map[FileKind][]string{ + FileKindPackageSource: { + "gno.land/p/demo/pkg1", + "gno.land/p/demo/pkg2", + "std", + }, + FileKindTest: { + "gno.land/p/demo/testpkg", + "testing", + }, + FileKindXTest: { + "gno.land/p/demo/testpkg", + "gno.land/p/demo/xtestdep", + "testing", + }, + FileKindFiletest: { + "gno.land/p/demo/filetestdep", + }, } // Create subpkg dir @@ -120,12 +144,19 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - imports, err := Imports(pkg, nil) + + importsMap, err := Imports(pkg, nil) require.NoError(t, err) - importsStrings := make([]string, len(imports)) - for idx, imp := range imports { - importsStrings[idx] = imp.PkgPath + + // ignore specs + got := map[FileKind][]string{} + for key, vals := range importsMap { + stringVals := make([]string, len(vals)) + for i, val := range vals { + stringVals[i] = val.PkgPath + } + got[key] = stringVals } - require.Equal(t, expected, importsStrings) + require.Equal(t, expected, got) } diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 8b24fdeaa77..a8dd709e501 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -242,10 +242,11 @@ func LoadImports(store gno.Store, memPkg *gnovm.MemPackage) (err error) { }() fset := token.NewFileSet() - imports, err := packages.Imports(memPkg, fset) + importsMap, err := packages.Imports(memPkg, fset) if err != nil { return err } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindXTest) for _, imp := range imports { if gno.IsRealmPath(imp.PkgPath) { // Don't eagerly load realms. From 384d2beae48b93db9da9022b530c488837820a2c Mon Sep 17 00:00:00 2001 From: matijamarjanovic <93043005+matijamarjanovic@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:39:30 +0100 Subject: [PATCH 20/75] feat: add btree_dao to demo (#3388) ## Description BTree DAO is a little organisation of people who used BTree by @wyhaines. It serves the purpose of demonstrating some of the functionalities like iterating through the list from start and the end, adding nodes and general implementation of the BTree. Besides that, it encourages developers to use BTree and join the DAO. ### How it works: Currently the realm has 2 ways of members joining: - Either by submiting the BTree instance used in some other realm - Or by sending a direct transaction using gnokey or Studio Connect having string as argument Both ways allow members to become a part of the DAO but at different levels: the idea is that if a user decides to submit his own BTree he becomes a true member, and if a user supports the cause and just want to hang around until he gets the hang of the BTree implementation he can do so by joining thorugh a 'seed'. In the realm joining functions are PlantTree and PlantSeed for different roles. When a member joins he gets minted an NFT made using basic_nft.gno from GRC721 which is like his little proof of membership. ### Concerns To become a 'tree' member developer needs to submit his whole BTree which might be a little unsafe as that BTree might contain some sensitive information. I would like to hear an opinion on this of someone more experienced, as it is right now I have been extra careful not to expose any of the information (besides size) from the submitted BTrees. ### Contributors checklist: - [x] Create a BTree to store all the member info - [x] Implement Record interface, make nodes' creation times be compared - [x] Implement DAO joining process for 2 types of members - [x] Make a Render() function to display member addresses - [x] Demonstrate more of BTree functionalities - [x] Add tests --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../gno.land/r/demo/btree_dao/btree_dao.gno | 209 ++++++++++++++++++ .../r/demo/btree_dao/btree_dao_test.gno | 97 ++++++++ examples/gno.land/r/demo/btree_dao/gno.mod | 1 + 3 files changed, 307 insertions(+) create mode 100644 examples/gno.land/r/demo/btree_dao/btree_dao.gno create mode 100644 examples/gno.land/r/demo/btree_dao/btree_dao_test.gno create mode 100644 examples/gno.land/r/demo/btree_dao/gno.mod diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao.gno b/examples/gno.land/r/demo/btree_dao/btree_dao.gno new file mode 100644 index 00000000000..c90742eb29b --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/btree_dao.gno @@ -0,0 +1,209 @@ +package btree_dao + +import ( + "errors" + "std" + "strings" + "time" + + "gno.land/p/demo/btree" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" +) + +// RegistrationDetails holds the details of a user's registration in the BTree DAO. +// It stores the user's address, registration time, their B-Tree if they planted one, +// and their NFT ID. +type RegistrationDetails struct { + Address std.Address + RegTime time.Time + UserBTree *btree.BTree + NFTID string +} + +// Less implements the btree.Record interface for RegistrationDetails. +// It compares two RegistrationDetails based on their registration time. +// Returns true if the current registration time is before the other registration time. +func (rd *RegistrationDetails) Less(than btree.Record) bool { + other := than.(*RegistrationDetails) + return rd.RegTime.Before(other.RegTime) +} + +var ( + dao = grc721.NewBasicNFT("BTree DAO", "BTDAO") + tokenID = 0 + members = btree.New() +) + +// PlantTree allows a user to plant their B-Tree in the DAO forest. +// It mints an NFT to the user and registers their tree in the DAO. +// Returns an error if the tree is already planted, empty, or if NFT minting fails. +func PlantTree(userBTree *btree.BTree) error { + return plantImpl(userBTree, "") +} + +// PlantSeed allows a user to register as a seed in the DAO with a message. +// It mints an NFT to the user and registers them as a seed member. +// Returns an error if the message is empty or if NFT minting fails. +func PlantSeed(message string) error { + return plantImpl(nil, message) +} + +// plantImpl is the internal implementation that handles both tree planting and seed registration. +// For tree planting (userBTree != nil), it verifies the tree isn't already planted and isn't empty. +// For seed planting (userBTree == nil), it verifies the seed message isn't empty. +// In both cases, it mints an NFT to the user and adds their registration details to the members tree. +// Returns an error if any validation fails or if NFT minting fails. +func plantImpl(userBTree *btree.BTree, seedMessage string) error { + // Get the caller's address + userAddress := std.GetOrigCaller() + + var nftID string + var regDetails *RegistrationDetails + + if userBTree != nil { + // Handle tree planting + var treeExists bool + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == userBTree { + treeExists = true + return false + } + return true + }) + if treeExists { + return errors.New("tree is already planted in the forest") + } + + if userBTree.Len() == 0 { + return errors.New("cannot plant an empty tree") + } + + nftID = ufmt.Sprintf("%d", tokenID) + regDetails = &RegistrationDetails{ + Address: userAddress, + RegTime: time.Now(), + UserBTree: userBTree, + NFTID: nftID, + } + } else { + // Handle seed planting + if seedMessage == "" { + return errors.New("seed message cannot be empty") + } + nftID = "seed_" + ufmt.Sprintf("%d", tokenID) + regDetails = &RegistrationDetails{ + Address: userAddress, + RegTime: time.Now(), + UserBTree: nil, + NFTID: nftID, + } + } + + // Mint an NFT to the user + err := dao.Mint(userAddress, grc721.TokenID(nftID)) + if err != nil { + return err + } + + members.Insert(regDetails) + tokenID++ + return nil +} + +// Render generates a Markdown representation of the DAO members. +// It displays: +// - Total number of NFTs minted +// - Total number of members +// - Size of the biggest planted tree +// - The first 3 members (OGs) +// - The latest 10 members +// Each member entry includes their address and owned NFTs (🌳 for trees, 🌱 for seeds). +// The path parameter is currently unused. +// Returns a formatted Markdown string. +func Render(path string) string { + var latestMembers []string + var ogMembers []string + + // Get total size and first member + totalSize := members.Len() + biggestTree := 0 + if maxMember := members.Max(); maxMember != nil { + if userBTree := maxMember.(*RegistrationDetails).UserBTree; userBTree != nil { + biggestTree = userBTree.Len() + } + } + + // Collect the latest 10 members + members.Descend(func(record btree.Record) bool { + if len(latestMembers) < 10 { + regDetails := record.(*RegistrationDetails) + addr := regDetails.Address + nftList := "" + balance, err := dao.BalanceOf(addr) + if err == nil && balance > 0 { + nftList = " (NFTs: " + for i := uint64(0); i < balance; i++ { + if i > 0 { + nftList += ", " + } + if regDetails.UserBTree == nil { + nftList += "🌱#" + regDetails.NFTID + } else { + nftList += "🌳#" + regDetails.NFTID + } + } + nftList += ")" + } + latestMembers = append(latestMembers, string(addr)+nftList) + return true + } + return false + }) + + // Collect the first 3 members (OGs) + members.Ascend(func(record btree.Record) bool { + if len(ogMembers) < 3 { + regDetails := record.(*RegistrationDetails) + addr := regDetails.Address + nftList := "" + balance, err := dao.BalanceOf(addr) + if err == nil && balance > 0 { + nftList = " (NFTs: " + for i := uint64(0); i < balance; i++ { + if i > 0 { + nftList += ", " + } + if regDetails.UserBTree == nil { + nftList += "🌱#" + regDetails.NFTID + } else { + nftList += "🌳#" + regDetails.NFTID + } + } + nftList += ")" + } + ogMembers = append(ogMembers, string(addr)+nftList) + return true + } + return false + }) + + var sb strings.Builder + + sb.WriteString(md.H1("B-Tree DAO Members")) + sb.WriteString(md.H2("Total NFTs Minted")) + sb.WriteString(ufmt.Sprintf("Total NFTs minted: %d\n\n", dao.TokenCount())) + sb.WriteString(md.H2("Member Stats")) + sb.WriteString(ufmt.Sprintf("Total members: %d\n", totalSize)) + if biggestTree > 0 { + sb.WriteString(ufmt.Sprintf("Biggest tree size: %d\n", biggestTree)) + } + sb.WriteString(md.H2("OG Members")) + sb.WriteString(md.BulletList(ogMembers)) + sb.WriteString(md.H2("Latest Members")) + sb.WriteString(md.BulletList(latestMembers)) + + return sb.String() +} diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno new file mode 100644 index 00000000000..0514f52f7b4 --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno @@ -0,0 +1,97 @@ +package btree_dao + +import ( + "std" + "strings" + "testing" + "time" + + "gno.land/p/demo/btree" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func setupTest() { + std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + members = btree.New() +} + +type TestElement struct { + value int +} + +func (te *TestElement) Less(than btree.Record) bool { + return te.value < than.(*TestElement).value +} + +func TestPlantTree(t *testing.T) { + setupTest() + + tree := btree.New() + elements := []int{30, 10, 50, 20, 40} + for _, val := range elements { + tree.Insert(&TestElement{value: val}) + } + + err := PlantTree(tree) + urequire.NoError(t, err) + + found := false + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == tree { + found = true + return false + } + return true + }) + uassert.True(t, found) + + err = PlantTree(tree) + uassert.Error(t, err) + + emptyTree := btree.New() + err = PlantTree(emptyTree) + uassert.Error(t, err) +} + +func TestPlantSeed(t *testing.T) { + setupTest() + + err := PlantSeed("Hello DAO!") + urequire.NoError(t, err) + + found := false + members.Ascend(func(record btree.Record) bool { + regDetails := record.(*RegistrationDetails) + if regDetails.UserBTree == nil { + found = true + uassert.NotEmpty(t, regDetails.NFTID) + uassert.True(t, strings.Contains(regDetails.NFTID, "seed_")) + return false + } + return true + }) + uassert.True(t, found) + + err = PlantSeed("") + uassert.Error(t, err) +} + +func TestRegistrationDetailsOrdering(t *testing.T) { + setupTest() + + rd1 := &RegistrationDetails{ + Address: std.Address("test1"), + RegTime: time.Now(), + NFTID: "0", + } + rd2 := &RegistrationDetails{ + Address: std.Address("test2"), + RegTime: time.Now().Add(time.Hour), + NFTID: "1", + } + + uassert.True(t, rd1.Less(rd2)) + uassert.False(t, rd2.Less(rd1)) +} diff --git a/examples/gno.land/r/demo/btree_dao/gno.mod b/examples/gno.land/r/demo/btree_dao/gno.mod new file mode 100644 index 00000000000..01b99acc300 --- /dev/null +++ b/examples/gno.land/r/demo/btree_dao/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/btree_dao From 24385052d54be962581d989cc9349a7b9250d4c7 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:44:25 +0000 Subject: [PATCH 21/75] feat(examples): add p/moul/addrset (#3448) See https://github.com/gnolang/gno/pull/3166#discussion_r1904564428 for context. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- examples/gno.land/p/moul/addrset/addrset.gno | 100 ++++++++++ .../gno.land/p/moul/addrset/addrset_test.gno | 174 ++++++++++++++++++ examples/gno.land/p/moul/addrset/gno.mod | 1 + 3 files changed, 275 insertions(+) create mode 100644 examples/gno.land/p/moul/addrset/addrset.gno create mode 100644 examples/gno.land/p/moul/addrset/addrset_test.gno create mode 100644 examples/gno.land/p/moul/addrset/gno.mod diff --git a/examples/gno.land/p/moul/addrset/addrset.gno b/examples/gno.land/p/moul/addrset/addrset.gno new file mode 100644 index 00000000000..0bb8165f9fe --- /dev/null +++ b/examples/gno.land/p/moul/addrset/addrset.gno @@ -0,0 +1,100 @@ +// Package addrset provides a specialized set data structure for managing unique Gno addresses. +// +// It is built on top of an AVL tree for efficient operations and maintains addresses in sorted order. +// This package is particularly useful when you need to: +// - Track a collection of unique addresses (e.g., for whitelists, participants, etc.) +// - Efficiently check address membership +// - Support pagination when displaying addresses +// +// Example usage: +// +// import ( +// "std" +// "gno.land/p/moul/addrset" +// ) +// +// func MyHandler() { +// // Create a new address set +// var set addrset.Set +// +// // Add some addresses +// addr1 := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") +// addr2 := std.Address("g1sss5g0rkqr88k4u648yd5d3l9t4d8vvqwszqth") +// +// set.Add(addr1) // returns true (newly added) +// set.Add(addr2) // returns true (newly added) +// set.Add(addr1) // returns false (already exists) +// +// // Check membership +// if set.Has(addr1) { +// // addr1 is in the set +// } +// +// // Get size +// size := set.Size() // returns 2 +// +// // Iterate with pagination (10 items per page, starting at offset 0) +// set.IterateByOffset(0, 10, func(addr std.Address) bool { +// // Process addr +// return false // continue iteration +// }) +// +// // Remove an address +// set.Remove(addr1) // returns true (was present) +// set.Remove(addr1) // returns false (not present) +// } +package addrset + +import ( + "std" + + "gno.land/p/demo/avl" +) + +type Set struct { + tree avl.Tree +} + +// Add inserts an address into the set. +// Returns true if the address was newly added, false if it already existed. +func (s *Set) Add(addr std.Address) bool { + return !s.tree.Set(string(addr), nil) +} + +// Remove deletes an address from the set. +// Returns true if the address was found and removed, false if it didn't exist. +func (s *Set) Remove(addr std.Address) bool { + _, removed := s.tree.Remove(string(addr)) + return removed +} + +// Has checks if an address exists in the set. +func (s *Set) Has(addr std.Address) bool { + return s.tree.Has(string(addr)) +} + +// Size returns the number of addresses in the set. +func (s *Set) Size() int { + return s.tree.Size() +} + +// IterateByOffset walks through addresses starting at the given offset. +// The callback should return true to stop iteration. +func (s *Set) IterateByOffset(offset int, count int, cb func(addr std.Address) bool) { + s.tree.IterateByOffset(offset, count, func(key string, _ interface{}) bool { + return cb(std.Address(key)) + }) +} + +// ReverseIterateByOffset walks through addresses in reverse order starting at the given offset. +// The callback should return true to stop iteration. +func (s *Set) ReverseIterateByOffset(offset int, count int, cb func(addr std.Address) bool) { + s.tree.ReverseIterateByOffset(offset, count, func(key string, _ interface{}) bool { + return cb(std.Address(key)) + }) +} + +// Tree returns the underlying AVL tree for advanced usage. +func (s *Set) Tree() avl.ITree { + return &s.tree +} diff --git a/examples/gno.land/p/moul/addrset/addrset_test.gno b/examples/gno.land/p/moul/addrset/addrset_test.gno new file mode 100644 index 00000000000..c3e27eab1df --- /dev/null +++ b/examples/gno.land/p/moul/addrset/addrset_test.gno @@ -0,0 +1,174 @@ +package addrset + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +func TestSet(t *testing.T) { + addr1 := std.Address("addr1") + addr2 := std.Address("addr2") + addr3 := std.Address("addr3") + + tests := []struct { + name string + actions func(s *Set) + size int + has map[std.Address]bool + addrs []std.Address // for iteration checks + }{ + { + name: "empty set", + actions: func(s *Set) {}, + size: 0, + has: map[std.Address]bool{addr1: false}, + }, + { + name: "single address", + actions: func(s *Set) { + s.Add(addr1) + }, + size: 1, + has: map[std.Address]bool{ + addr1: true, + addr2: false, + }, + addrs: []std.Address{addr1}, + }, + { + name: "multiple addresses", + actions: func(s *Set) { + s.Add(addr1) + s.Add(addr2) + s.Add(addr3) + }, + size: 3, + has: map[std.Address]bool{ + addr1: true, + addr2: true, + addr3: true, + }, + addrs: []std.Address{addr1, addr2, addr3}, + }, + { + name: "remove address", + actions: func(s *Set) { + s.Add(addr1) + s.Add(addr2) + s.Remove(addr1) + }, + size: 1, + has: map[std.Address]bool{ + addr1: false, + addr2: true, + }, + addrs: []std.Address{addr2}, + }, + { + name: "duplicate adds", + actions: func(s *Set) { + uassert.True(t, s.Add(addr1)) // first add returns true + uassert.False(t, s.Add(addr1)) // second add returns false + uassert.True(t, s.Remove(addr1)) // remove existing returns true + uassert.False(t, s.Remove(addr1)) // remove non-existing returns false + }, + size: 0, + has: map[std.Address]bool{ + addr1: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var set Set + + // Execute test actions + tt.actions(&set) + + // Check size + uassert.Equal(t, tt.size, set.Size()) + + // Check existence + for addr, expected := range tt.has { + uassert.Equal(t, expected, set.Has(addr)) + } + + // Check iteration if addresses are specified + if tt.addrs != nil { + collected := []std.Address{} + set.IterateByOffset(0, 10, func(addr std.Address) bool { + collected = append(collected, addr) + return false + }) + + // Check length + uassert.Equal(t, len(tt.addrs), len(collected)) + + // Check each address + for i, addr := range tt.addrs { + uassert.Equal(t, addr, collected[i]) + } + } + }) + } +} + +func TestSetIterationLimits(t *testing.T) { + tests := []struct { + name string + addrs []std.Address + offset int + limit int + expected int + }{ + { + name: "zero offset full list", + addrs: []std.Address{"a1", "a2", "a3"}, + offset: 0, + limit: 10, + expected: 3, + }, + { + name: "offset with limit", + addrs: []std.Address{"a1", "a2", "a3", "a4"}, + offset: 1, + limit: 2, + expected: 2, + }, + { + name: "offset beyond size", + addrs: []std.Address{"a1", "a2"}, + offset: 3, + limit: 1, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var set Set + for _, addr := range tt.addrs { + set.Add(addr) + } + + // Test forward iteration + count := 0 + set.IterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool { + count++ + return false + }) + uassert.Equal(t, tt.expected, count) + + // Test reverse iteration + count = 0 + set.ReverseIterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool { + count++ + return false + }) + uassert.Equal(t, tt.expected, count) + }) + } +} diff --git a/examples/gno.land/p/moul/addrset/gno.mod b/examples/gno.land/p/moul/addrset/gno.mod new file mode 100644 index 00000000000..45bb53b399c --- /dev/null +++ b/examples/gno.land/p/moul/addrset/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/addrset From 15e58960326612bb7943197e5307fd109b6742f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:13:30 +0100 Subject: [PATCH 22/75] fix: add support for signing lazily loaded transactions in genesis (#3468) ## Description This PR fixes an issue where genesis transactions added to `genesis.json` through `--lazy` fail, since the signatures are missing. It also introduces support for disabling genesis sig verification altogether. Why this was needed: - Portal Loop transactions are signed with a valid account number and sequence (not 0), and when they are replayed (they are shoved into a new aggregated `genesis.json`), their signatures are also migrated. Upon initializing the chain, this would cause the signature verification to fail (the sig verification process for genesis txs expects account number and sequence values of 0, but this is not the case) @moul, the transaction signatures in `gno.land/genesis/genesis_txs.jsonl` are invalid, and will always fail when being verified --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- .github/workflows/portal-loop.yml | 1 + .../internal/txs/txs_add_packages.go | 1 - gno.land/cmd/gnoland/start.go | 104 +++++++++++++----- gno.land/pkg/gnoland/app.go | 22 +++- gno.land/pkg/gnoland/app_test.go | 2 +- gno.land/pkg/gnoland/genesis.go | 9 +- gno.land/pkg/gnoland/types.go | 29 +++++ gno.land/pkg/gnoland/types_test.go | 27 +++++ gno.land/pkg/integration/node_testing.go | 2 +- gno.land/pkg/integration/pkgloader.go | 3 +- gno.land/pkg/integration/signer.go | 33 ------ .../testdata/event_multi_msg.txtar | 23 ++-- .../pkg/integration/testdata/gnokey.txtar | 10 +- .../testdata/gnoweb_airgapped.txtar | 21 ++-- .../testdata/restart_missing_type.txtar | 16 ++- .../integration/testdata/simulate_gas.txtar | 4 +- misc/loop/scripts/start.sh | 3 +- tm2/pkg/sdk/auth/ante.go | 6 +- tm2/pkg/sdk/auth/ante_test.go | 4 +- 19 files changed, 207 insertions(+), 113 deletions(-) delete mode 100644 gno.land/pkg/integration/signer.go diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index b5cafa459a7..aeb59d4dc77 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -45,6 +45,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} test-portal-loop-docker-compose: + if: ${{ false }} runs-on: ubuntu-latest timeout-minutes: 10 steps: diff --git a/contribs/gnogenesis/internal/txs/txs_add_packages.go b/contribs/gnogenesis/internal/txs/txs_add_packages.go index 0ab5724154e..53c0bb4b686 100644 --- a/contribs/gnogenesis/internal/txs/txs_add_packages.go +++ b/contribs/gnogenesis/internal/txs/txs_add_packages.go @@ -18,7 +18,6 @@ import ( const ( defaultAccount_Name = "test1" - defaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" defaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" defaultAccount_publicKey = "gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pq0skzdkmzu0r9h6gny6eg8c9dc303xrrudee6z4he4y7cs5rnjwmyf40yaj" ) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index cb5d54a513a..4f380031be4 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -45,22 +45,20 @@ var startGraphic = strings.ReplaceAll(` /___/ `, "'", "`") -var ( - // Keep in sync with contribs/gnogenesis/internal/txs/txs_add_packages.go - genesisDeployAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // test1 - genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) -) +// Keep in sync with contribs/gnogenesis/internal/txs/txs_add_packages.go +var genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) type startCfg struct { - gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 - genesisFile string - chainID string - dataDir string - lazyInit bool + gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipGenesisSigVerification bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisFile string + chainID string + dataDir string + lazyInit bool logLevel string logFormat string @@ -86,7 +84,6 @@ func newStartCmd(io commands.IO) *commands.Command { func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { gnoroot := gnoenv.RootDir() defaultGenesisBalancesFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt") - defaultGenesisTxsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.jsonl") fs.BoolVar( &c.skipFailingGenesisTxs, @@ -95,6 +92,13 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "don't panic when replaying invalid genesis txs", ) + fs.BoolVar( + &c.skipGenesisSigVerification, + "skip-genesis-sig-verification", + false, + "don't panic when replaying invalidly signed genesis txs", + ) + fs.StringVar( &c.genesisBalancesFile, "genesis-balances-file", @@ -105,7 +109,7 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.genesisTxsFile, "genesis-txs-file", - defaultGenesisTxsFile, + "", "initial txs to replay", ) @@ -218,7 +222,7 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { ) // Init a new genesis.json - if err := lazyInitGenesis(io, c, genesisPath, privateKey.GetPubKey()); err != nil { + if err := lazyInitGenesis(io, c, genesisPath, privateKey.Key.PrivKey); err != nil { return fmt.Errorf("unable to initialize genesis.json, %w", err) } } @@ -238,7 +242,16 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { minGasPrices := cfg.Application.MinGasPrices // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger, minGasPrices) + cfg.LocalApp, err = gnoland.NewApp( + nodeDir, + gnoland.GenesisAppConfig{ + SkipFailingTxs: c.skipFailingGenesisTxs, + SkipSigVerification: c.skipGenesisSigVerification, + }, + evsw, + logger, + minGasPrices, + ) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } @@ -334,7 +347,7 @@ func lazyInitGenesis( io commands.IO, c *startCfg, genesisPath string, - publicKey crypto.PubKey, + privateKey crypto.PrivKey, ) error { // Check if the genesis.json is present if osm.FileExists(genesisPath) { @@ -342,7 +355,7 @@ func lazyInitGenesis( } // Generate the new genesis.json file - if err := generateGenesisFile(genesisPath, publicKey, c); err != nil { + if err := generateGenesisFile(genesisPath, privateKey, c); err != nil { return fmt.Errorf("unable to generate genesis file, %w", err) } @@ -367,7 +380,21 @@ func initializeLogger(io io.WriteCloser, logLevel, logFormat string) (*zap.Logge return log.GetZapLoggerFn(format)(io, level), nil } -func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { +func generateGenesisFile(genesisFile string, privKey crypto.PrivKey, c *startCfg) error { + var ( + pubKey = privKey.PubKey() + // There is an active constraint for gno.land transactions: + // + // All transaction messages' (MsgSend, MsgAddPkg...) "author" field, + // specific to the message type ("creator", "sender"...), must match + // the signature address contained in the transaction itself. + // This means that if MsgSend is originating from address A, + // the owner of the private key for address A needs to sign the transaction + // containing the message. Every message in a transaction needs to + // originate from the same account that signed the transaction + txSender = pubKey.Address() + ) + gen := &bft.GenesisDoc{} gen.GenesisTime = time.Now() gen.ChainID = c.chainID @@ -383,8 +410,8 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro gen.Validators = []bft.GenesisValidator{ { - Address: pk.Address(), - PubKey: pk, + Address: pubKey.Address(), + PubKey: pubKey, Power: 10, Name: "testvalidator", }, @@ -398,22 +425,43 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro // Load examples folder examplesDir := filepath.Join(c.gnoRootDir, "examples") - pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, genesisDeployAddress, genesisDeployFee) + pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, txSender, genesisDeployFee) if err != nil { return fmt.Errorf("unable to load examples folder: %w", err) } // Load Genesis TXs - genesisTxs, err := gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote) - if err != nil { - return fmt.Errorf("unable to load genesis txs file: %w", err) + var genesisTxs []gnoland.TxWithMetadata + + if c.genesisTxsFile != "" { + genesisTxs, err = gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote) + if err != nil { + return fmt.Errorf("unable to load genesis txs file: %w", err) + } } genesisTxs = append(pkgsTxs, genesisTxs...) + // Sign genesis transactions, with the default key (test1) + if err = gnoland.SignGenesisTxs(genesisTxs, privKey, c.chainID); err != nil { + return fmt.Errorf("unable to sign genesis txs: %w", err) + } + + // Make sure the genesis transaction author has sufficient + // balance to cover transaction deployments in genesis. + // + // During the init-chainer process, the account that authors the + // genesis transactions needs to have a sufficient balance + // to cover outstanding transaction costs. + // Since the cost can't be estimated upfront at this point, the balance + // set is an arbitrary value based on a "best guess" basis. + // There should be a larger discussion if genesis transactions should consume gas, at all + deployerBalance := int64(len(genesisTxs)) * 10_000_000 // ~10 GNOT per tx + balances.Set(txSender, std.NewCoins(std.NewCoin("ugnot", deployerBalance))) + // Construct genesis AppState. defaultGenState := gnoland.DefaultGenState() - defaultGenState.Balances = balances + defaultGenState.Balances = balances.List() defaultGenState.Txs = genesisTxs gen.AppState = defaultGenState diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 80c58e9e982..0826071b9f5 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -182,10 +182,25 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { return baseApp, nil } +// GenesisAppConfig wraps the most important +// genesis params relating to the App +type GenesisAppConfig struct { + SkipFailingTxs bool // does not stop the chain from starting if any tx fails + SkipSigVerification bool // does not verify the transaction signatures in genesis +} + +// NewTestGenesisAppConfig returns a testing genesis app config +func NewTestGenesisAppConfig() GenesisAppConfig { + return GenesisAppConfig{ + SkipFailingTxs: true, + SkipSigVerification: true, + } +} + // NewApp creates the gno.land application. func NewApp( dataRootDir string, - skipFailingGenesisTxs bool, + genesisCfg GenesisAppConfig, evsw events.EventSwitch, logger *slog.Logger, minGasPrices string, @@ -199,9 +214,10 @@ func NewApp( GenesisTxResultHandler: PanicOnFailingTxResultHandler, StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"), }, - MinGasPrices: minGasPrices, + MinGasPrices: minGasPrices, + SkipGenesisVerification: genesisCfg.SkipSigVerification, } - if skipFailingGenesisTxs { + if genesisCfg.SkipFailingTxs { cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler } diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 56a15fed5a9..361d7505157 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -134,7 +134,7 @@ func TestNewApp(t *testing.T) { // NewApp should have good defaults and manage to run InitChain. td := t.TempDir() - app, err := NewApp(td, true, events.NewEventSwitch(), log.NewNoopLogger(), "") + app, err := NewApp(td, NewTestGenesisAppConfig(), events.NewEventSwitch(), log.NewNoopLogger(), "") require.NoError(t, err, "NewApp should be successful") resp := app.InitChain(abci.RequestInitChain{ diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index d7844d77b57..a754e7a4644 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -20,7 +20,7 @@ import ( const initGasPrice = "1ugnot/1000gas" // LoadGenesisBalancesFile loads genesis balances from the provided file path. -func LoadGenesisBalancesFile(path string) ([]Balance, error) { +func LoadGenesisBalancesFile(path string) (Balances, error) { // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot content, err := osm.ReadFile(path) if err != nil { @@ -28,7 +28,7 @@ func LoadGenesisBalancesFile(path string) ([]Balance, error) { } lines := strings.Split(string(content), "\n") - balances := make([]Balance, 0, len(lines)) + balances := make(Balances, len(lines)) for _, line := range lines { line = strings.TrimSpace(line) @@ -56,10 +56,7 @@ func LoadGenesisBalancesFile(path string) ([]Balance, error) { return nil, fmt.Errorf("invalid balance coins %s: %w", parts[1], err) } - balances = append(balances, Balance{ - Address: addr, - Amount: coins, - }) + balances.Set(addr, coins) } return balances, nil diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index ed35c4141f4..66fb2f54e8a 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -8,6 +8,7 @@ import ( "os" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk/auth" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -86,3 +87,31 @@ func ReadGenesisTxs(ctx context.Context, path string) ([]TxWithMetadata, error) return txs, nil } + +// SignGenesisTxs will sign all txs passed as argument using the private key. +// This signature is only valid for genesis transactions as the account number and sequence are 0 +func SignGenesisTxs(txs []TxWithMetadata, privKey crypto.PrivKey, chainID string) error { + for index, tx := range txs { + // Upon verifying genesis transactions, the account number and sequence are considered to be 0. + // The reason for this is that it is not possible to know the account number (or sequence!) in advance + // when generating the genesis transaction signature + bytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to get sign bytes for transaction, %w", err) + } + + signature, err := privKey.Sign(bytes) + if err != nil { + return fmt.Errorf("unable to sign genesis transaction, %w", err) + } + + txs[index].Tx.Signatures = []std.Signature{ + { + PubKey: privKey.PubKey(), + Signature: signature, + }, + } + } + + return nil +} diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go index b4625d6d7d6..c501325bc3e 100644 --- a/gno.land/pkg/gnoland/types_test.go +++ b/gno.land/pkg/gnoland/types_test.go @@ -11,6 +11,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" @@ -129,3 +130,29 @@ func TestReadGenesisTxs(t *testing.T) { } }) } + +func TestSignGenesisTx(t *testing.T) { + t.Parallel() + + var ( + txs = generateTxs(t, 100) + privKey = secp256k1.GenPrivKey() + pubKey = privKey.PubKey() + chainID = "testing" + ) + + // Make sure the transactions are properly signed + require.NoError(t, SignGenesisTxs(txs, privKey, chainID)) + + // Make sure the signatures are valid + for _, tx := range txs { + payload, err := tx.Tx.GetSignBytes(chainID, 0, 0) + require.NoError(t, err) + + sigs := tx.Tx.GetSignatures() + require.Len(t, sigs, 1) + + assert.True(t, pubKey.Equals(sigs[0].PubKey)) + assert.True(t, pubKey.VerifyBytes(payload, sigs[0].Signature)) + } +} diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index 7965f228fc2..edcf53de5d3 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -148,7 +148,7 @@ func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balanc genesisBalances, err := gnoland.LoadGenesisBalancesFile(balanceFile) require.NoError(t, err) - return genesisBalances + return genesisBalances.List() } // LoadDefaultGenesisParamFile loads the default genesis balance file for testing. diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index e40e8ff1eb5..71b1491b2a8 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -77,8 +77,7 @@ func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, depos } } - err = SignTxs(txs, creatorKey, "tendermint_test") - if err != nil { + if err = gnoland.SignGenesisTxs(txs, creatorKey, "tendermint_test"); err != nil { return nil, fmt.Errorf("unable to sign txs: %w", err) } diff --git a/gno.land/pkg/integration/signer.go b/gno.land/pkg/integration/signer.go deleted file mode 100644 index b32cd9c59bc..00000000000 --- a/gno.land/pkg/integration/signer.go +++ /dev/null @@ -1,33 +0,0 @@ -package integration - -import ( - "fmt" - - "github.com/gnolang/gno/gno.land/pkg/gnoland" - - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" -) - -// SignTxs will sign all txs passed as argument using the private key -// this signature is only valid for genesis transactions as accountNumber and sequence are 0 -func SignTxs(txs []gnoland.TxWithMetadata, privKey crypto.PrivKey, chainID string) error { - for index, tx := range txs { - bytes, err := tx.Tx.GetSignBytes(chainID, 0, 0) - if err != nil { - return fmt.Errorf("unable to get sign bytes for transaction, %w", err) - } - signature, err := privKey.Sign(bytes) - if err != nil { - return fmt.Errorf("unable to sign transaction, %w", err) - } - - txs[index].Tx.Signatures = []std.Signature{ - { - PubKey: privKey.PubKey(), - Signature: signature, - }, - } - } - return nil -} diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 13a448e7f8c..4c8de856f03 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -1,29 +1,30 @@ # load the package from $WORK directory loadpkg gno.land/r/demo/simple_event $WORK/event +# add a random user +adduserfrom user1 'success myself purchase tray reject demise scene little legend someone lunar hope media goat regular test area smart save flee surround attack rapid smoke' +stdout 'g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0' + # start a new node gnoland start -## test1 account should be available on default -gnokey query auth/accounts/${USER_ADDR_test1} +## account should be available since it has an initial balance +gnokey query auth/accounts/g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0 stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": {' -stdout ' "@type": "/tm.PubKeySecp256k1",' -stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' -stdout ' },' -stdout ' "account_number": "0",' -stdout ' "sequence": "1"' +stdout ' "public_key": null,' +stdout ' "account_number": "57",' +stdout ' "sequence": "0"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 0 -account-sequence 1 test1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 57 -account-sequence 0 user1 stdout 'Tx successfully signed and saved to ' ## broadcast @@ -49,5 +50,5 @@ func Event(value string) { } -- multi/multi_msg.tx -- -{"msg":[{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} +{"msg":[{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index 123a0ce291c..35759fa25dd 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -1,18 +1,22 @@ # test basic gnokey integrations commands # golden files have been generated using UPDATE_SCRIPTS=true +# add a random user +adduserfrom user1 'alpha ability feed thrive color fee grace message chief helmet laundry inmate index brave luxury toddler spawn vague index able zone shoe collect escape' +stdout 'g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m' + # start gnoland gnoland start ## test1 account should be available on default -gnokey query auth/accounts/${USER_ADDR_test1} +gnokey query auth/accounts/g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "0",' +stdout ' "account_number": "57",' stdout ' "sequence": "0"' stdout ' }' stdout '}' diff --git a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar index 02bd8058214..838db121442 100644 --- a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar +++ b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar @@ -4,32 +4,33 @@ # load the package from $WORK directory loadpkg gno.land/r/demo/echo +# add a random user +adduserfrom user1 'lamp any denial pulse used shoot gap error denial mansion hurry foot solution grab winner congress drastic cat bamboo chicken color digital coffee unknown' +stdout 'g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva' + # start the node gnoland start # Query account -gnokey query auth/accounts/${USER_ADDR_test1} +gnokey query auth/accounts/g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "address": "g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva",' stdout ' "coins": "[0-9]*ugnot",' # dynamic -stdout ' "public_key": {' -stdout ' "@type": "/tm.PubKeySecp256k1",' -stdout ' "value": "A\+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"' -stdout ' },' -stdout ' "account_number": "0",' -stdout ' "sequence": "4"' +stdout ' "public_key": null,' +stdout ' "account_number": "57",' +stdout ' "sequence": "0"' stdout ' }' stdout '}' ! stderr '.+' # empty # Create transaction -gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -args "HELLO" test1 +gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 1000000ugnot -gas-wanted 2000000 -send "" -args "HELLO" user1 cp stdout call.tx # Sign -gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 0 -account-sequence 4 test1 +gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 57 -account-sequence 0 user1 cmpenv stdout sign.stdout.golden gnokey broadcast $WORK/call.tx diff --git a/gno.land/pkg/integration/testdata/restart_missing_type.txtar b/gno.land/pkg/integration/testdata/restart_missing_type.txtar index 09e1a27d6f4..cc8ed702734 100644 --- a/gno.land/pkg/integration/testdata/restart_missing_type.txtar +++ b/gno.land/pkg/integration/testdata/restart_missing_type.txtar @@ -1,3 +1,7 @@ +# add a random user +adduserfrom user1 'bone make joy hospital hawk crew civil relief maple alter always frozen category emerge fun inflict room sphere casino vital scheme basket omit wrap' +stdout 'g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4' + # This txtar is a regression test for a bug, whereby a type is committed to # the defaultStore.cacheTypes map, but not to the underlying store (due to a # failing transaction). @@ -5,15 +9,15 @@ loadpkg gno.land/p/demo/avl gnoland start -gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 1 test1 +gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 -account-number 57 user1 ! gnokey broadcast $WORK/tx1.tx stderr 'out of gas' -gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 2 test1 +gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 -account-number 57 user1 gnokey broadcast $WORK/tx2.tx stdout 'OK!' -gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 3 test1 +gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 -account-number 57 user1 gnokey broadcast $WORK/tx3.tx stdout 'OK!' @@ -24,7 +28,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic", "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", @@ -99,7 +103,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic", "path": "gno.land/p/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic", @@ -174,7 +178,7 @@ gnoland restart "msg": [ { "@type": "/vm.m_addpkg", - "creator": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "creator": "g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4", "package": { "name": "zentasktic_core", "path": "gno.land/r/g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3/zentasktic_core", diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 4c5213da345..57be82b75ff 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 99015' +stdout 'GAS USED: 99339' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 99015' # same as simulate only +stdout 'GAS USED: 99339' # same as simulate only -- package/package.gno -- diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index db36de39f2a..fd753324f5d 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -36,4 +36,5 @@ gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" exec gnoland start \ --chainid="${CHAIN_ID}" \ --lazy \ - --skip-failing-genesis-txs + --skip-failing-genesis-txs \ + --skip-genesis-sig-verification diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index f05a8eff0a7..f941f398b17 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -215,7 +215,7 @@ func processSig( ctx sdk.Context, acc std.Account, sig std.Signature, signBytes []byte, simulate bool, params Params, sigGasConsumer SignatureVerificationGasConsumer, ) (updatedAcc std.Account, res sdk.Result) { - pubKey, res := ProcessPubKey(acc, sig, simulate) + pubKey, res := ProcessPubKey(acc, sig) if !res.IsOK() { return nil, res } @@ -243,7 +243,7 @@ func processSig( // ProcessPubKey verifies that the given account address matches that of the // std.Signature. In addition, it will set the public key of the account if it // has not been set. -func ProcessPubKey(acc std.Account, sig std.Signature, simulate bool) (crypto.PubKey, sdk.Result) { +func ProcessPubKey(acc std.Account, sig std.Signature) (crypto.PubKey, sdk.Result) { // If pubkey is not known for account, set it from the std.Signature. pubKey := acc.GetPubKey() if pubKey == nil { @@ -271,7 +271,7 @@ func DefaultSigVerificationGasConsumer( switch pubkey := pubkey.(type) { case ed25519.PubKeyEd25519: meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") - return abciResult(std.ErrInvalidPubKey("ED25519 public keys are unsupported")) + return sdk.Result{} case secp256k1.PubKeySecp256k1: meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 7c6ace51e4e..430954a0867 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -623,7 +623,7 @@ func TestProcessPubKey(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, err := ProcessPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + _, err := ProcessPubKey(tt.args.acc, tt.args.sig) require.Equal(t, tt.wantErr, !err.IsOK()) }) } @@ -655,7 +655,7 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { gasConsumed int64 shouldErr bool }{ - {"PubKeyEd25519", args{store.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, true}, + {"PubKeyEd25519", args{store.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, false}, {"PubKeySecp256k1", args{store.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostSecp256k1, false}, {"Multisig", args{store.NewInfiniteGasMeter(), amino.MustMarshal(multisignature1), multisigKey1, params}, expectedCost1, false}, {"unknown key", args{store.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, From a57311bd67984628eaec5206698e26d7f58b27b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:27:24 +0100 Subject: [PATCH 23/75] fix: re-enable the portal loop CI (#3474) ## Description This PR re-enables the portal loop "CI". I won't go into details on why this was needed in the first place, with the intention to not lose absolutely all credibility for our deployment and testing workflows --- .github/workflows/portal-loop.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/portal-loop.yml b/.github/workflows/portal-loop.yml index aeb59d4dc77..b5cafa459a7 100644 --- a/.github/workflows/portal-loop.yml +++ b/.github/workflows/portal-loop.yml @@ -45,7 +45,6 @@ jobs: labels: ${{ steps.meta.outputs.labels }} test-portal-loop-docker-compose: - if: ${{ false }} runs-on: ubuntu-latest timeout-minutes: 10 steps: From 1b89166af37dd2ee63f5a30b81769532fd0bb1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 10 Jan 2025 00:40:28 +0100 Subject: [PATCH 24/75] fix: use the `genesis/genesis_txs.jsonl` for the portal loop (#3475) ## Description This PR specifies the base file for the Portal Loop genesis transactions, as it is empty by default. --- misc/loop/scripts/start.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index fd753324f5d..bdabd2ac40f 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -11,10 +11,11 @@ GENESIS_BALANCES_FILE=${GENESIS_BALANCES_FILE:-""} SEEDS=${SEEDS:-""} PERSISTENT_PEERS=${PERSISTENT_PEERS:-""} +FINAL_GENESIS_TXS_SHEET="/gnoroot/gno.land/genesis/genesis_txs.jsonl" -echo "" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl +echo "" >> $FINAL_GENESIS_TXS_SHEET echo "" >> /gnoroot/gno.land/genesis/genesis_balances.jsonl -cat "${GENESIS_BACKUP_FILE}" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl +cat "${GENESIS_BACKUP_FILE}" >> $FINAL_GENESIS_TXS_SHEET cat "${GENESIS_BALANCES_FILE}" >> /gnoroot/gno.land/genesis/genesis_balances.jsonl # Initialize the secrets @@ -35,6 +36,7 @@ gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" # reading and piping to the gnoland genesis commands exec gnoland start \ --chainid="${CHAIN_ID}" \ + --genesis-txs-file="${FINAL_GENESIS_TXS_SHEET}" \ --lazy \ --skip-failing-genesis-txs \ --skip-genesis-sig-verification From 36c8f0e643c82fa157fe373d9a0442dfa7e67afb Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:10:04 +0100 Subject: [PATCH 25/75] feat: add account number / sequence as env in txtar (#3477) --- gno.land/pkg/integration/doc.go | 14 +- .../pkg/integration/testdata/addpkg.txtar | 4 +- .../testdata/addpkg_namespace.txtar | 12 +- .../integration/testdata/adduserfrom.txtar | 4 +- .../testdata/event_multi_msg.txtar | 9 +- .../pkg/integration/testdata/gnokey.txtar | 11 +- .../testdata/gnokey_simulate.txtar | 20 +- .../pkg/integration/testdata/patchpkg.txtar | 4 +- .../pkg/integration/testdata/prevrealm.txtar | 30 +-- .../realm_banker_issued_coin_denom.txtar | 16 +- .../pkg/integration/testscript_gnoland.go | 205 +++++++++--------- gno.land/pkg/integration/utils.go | 73 +++++++ ...stscript_gnoland_test.go => utils_test.go} | 0 13 files changed, 236 insertions(+), 166 deletions(-) create mode 100644 gno.land/pkg/integration/utils.go rename gno.land/pkg/integration/{testscript_gnoland_test.go => utils_test.go} (100%) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index 3e09d627c9a..d93d4607a59 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -102,11 +102,17 @@ // The path where the gnoland node stores its configuration and data. It's // set only if the node has started. // -// - USER_SEED_test1: -// Contains the seed for the test1 account. +// - xxx_user_seed: +// Where `xxx` is the account name; Contains the seed for the test1 account. // -// - USER_ADDR_test1: -// Contains the address for the test1 account. +// - xxx_user_addr: +// Where `xxx` is the account name; Contains the address for the test1 account. +// +// - xxx_account_num: +// Where `xxx` is the account name; Contains the account number for the test1 account. +// +// - xxx_account_seq: +// Where `xxx` is the account name; Contains the address for the test1 account. // // - RPC_ADDR: // Points to the gnoland node's remote address. It's set only if the node has started. diff --git a/gno.land/pkg/integration/testdata/addpkg.txtar b/gno.land/pkg/integration/testdata/addpkg.txtar index 8594e6596ce..15e8ad222f2 100644 --- a/gno.land/pkg/integration/testdata/addpkg.txtar +++ b/gno.land/pkg/integration/testdata/addpkg.txtar @@ -4,7 +4,7 @@ gnoland start ## deploy realm -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## check output stdout OK! @@ -15,7 +15,7 @@ stdout 'EVENTS: \[\]' stdout 'TX HASH: ' ## call added realm -gnokey maketx call -pkgpath gno.land/r/$USER_ADDR_test1/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 +gnokey maketx call -pkgpath gno.land/r/$test1_user_addr/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 ## check output stdout '\("hello world!" string\)' diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index 89da8a51820..f529c176f36 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -4,7 +4,7 @@ loadpkg gno.land/r/sys/users adduser admin adduser gui -patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $USER_ADDR_admin # use our custom admin +patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $admin_user_addr # use our custom admin gnoland start @@ -20,7 +20,7 @@ stdout 'false' # Gui should be able to addpkg on test1 addr # gui addpkg -> gno.land/r//mysuperpkg -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui stdout 'OK!' # Gui should be able to addpkg on random name @@ -43,12 +43,12 @@ stdout 'true' # Try to add a pkg an with unregistered user # gui addpkg -> gno.land/r//one -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stderr 'unauthorized user' # Try to add a pkg with an unregistered user, on their own address as namespace # gui addpkg -> gno.land/r//one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_gui/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stdout 'OK!' ## Test unregistered namespace @@ -63,12 +63,12 @@ stderr 'unauthorized user' # Test admin invites gui # admin call -> demo/users.Invite -gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_gui admin +gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $gui_user_addr admin stdout 'OK!' # test gui register namespace # gui call -> demo/users.Register -gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_admin -args 'guiland' -args 'im gui' gui +gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $admin_user_addr -args 'guiland' -args 'im gui' gui stdout 'OK!' # Test gui publishing on guiland/one diff --git a/gno.land/pkg/integration/testdata/adduserfrom.txtar b/gno.land/pkg/integration/testdata/adduserfrom.txtar index 47ec70b00e6..8bbfaa738fd 100644 --- a/gno.land/pkg/integration/testdata/adduserfrom.txtar +++ b/gno.land/pkg/integration/testdata/adduserfrom.txtar @@ -14,13 +14,13 @@ stdout 'g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp' gnoland start ## check users initial balance -gnokey query bank/balances/${USER_ADDR_user1} +gnokey query bank/balances/$user1_user_addr stdout '10000000ugnot' gnokey query bank/balances/g18e22n23g462drp4pyszyl6e6mwxkaylthgeeq4 stdout '10000000ugnot' -gnokey query auth/accounts/${USER_ADDR_user3} +gnokey query auth/accounts/$user3_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 4c8de856f03..3c5667b73b0 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -13,18 +13,18 @@ gnokey query auth/accounts/g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0 stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 57 -account-sequence 0 user1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number $user1_account_num -account-sequence $user1_account_seq user1 stdout 'Tx successfully signed and saved to ' ## broadcast @@ -51,4 +51,3 @@ func Event(value string) { -- multi/multi_msg.tx -- {"msg":[{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} - diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index 35759fa25dd..3268782b1ca 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -2,22 +2,21 @@ # golden files have been generated using UPDATE_SCRIPTS=true # add a random user -adduserfrom user1 'alpha ability feed thrive color fee grace message chief helmet laundry inmate index brave luxury toddler spawn vague index able zone shoe collect escape' -stdout 'g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m' +adduser user1 # start gnoland gnoland start ## test1 account should be available on default -gnokey query auth/accounts/g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m +gnokey query auth/accounts/$user1_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index db3cd527eb3..31b2249f8bb 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -6,41 +6,41 @@ loadpkg gno.land/r/hello $WORK/hello gnoland start # Initial state: assert that sequence == 0. -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # attempt adding the "test" package. # the package has a syntax error; simulation should catch this ahead of time and prevent the tx. # -simulate test ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate only ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate skip ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "2"' # attempt calling hello.SetName correctly. # -simulate test and skip should do it successfully, -simulate only should not. # -simulate test gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' @@ -50,19 +50,19 @@ stdout 'Hello, George!' # none should change the name (ie. panic rollbacks). # -simulate test ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' diff --git a/gno.land/pkg/integration/testdata/patchpkg.txtar b/gno.land/pkg/integration/testdata/patchpkg.txtar index c5962709625..0a1a7fa993d 100644 --- a/gno.land/pkg/integration/testdata/patchpkg.txtar +++ b/gno.land/pkg/integration/testdata/patchpkg.txtar @@ -2,13 +2,13 @@ loadpkg gno.land/r/dev/admin $WORK adduser dev -patchpkg "g1abcde" $USER_ADDR_dev +patchpkg "g1abcde" $dev_user_addr gnoland start gnokey maketx call -pkgpath gno.land/r/dev/admin -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ! stdout g1abcde -stdout $USER_ADDR_dev +stdout $dev_user_addr -- admin.gno -- package admin diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 20317d87345..4bbe16c3205 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -30,65 +30,65 @@ loadpkg gno.land/p/demo/bar $WORK/p/demo/bar ## start a new node gnoland start -env RFOO_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 +env RFOO_USER_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 # Test cases ## 1. MsgCall -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 2. MsgCall -> myrealm.B -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 3. MsgCall -> r/foo.A -> myrlm.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 4. MsgCall -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## remove due to update to maketx call can only call realm (case 5, 6, 13) ## 5. MsgCall -> p/demo/bar.A: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 6. MsgCall -> p/demo/bar.B: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 7. MsgRun -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 8. MsgRun -> myrealm.B -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 9. MsgRun -> r/foo.A -> myrlm.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 10. MsgRun -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 13. MsgCall -> std.PrevRealm(): user address ## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 14. MsgRun -> std.PrevRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} -- r/myrlm/myrlm.gno -- package myrlm diff --git a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar index be9a686bac6..a55604267ae 100644 --- a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar @@ -19,31 +19,31 @@ gnokey maketx addpkg -pkgdir $WORK/invalid_realm_denom -pkgpath gno.land/r/test/ gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check test2 balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '' ## mint coin from banker -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${USER_ADDR_test2} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${test2_user_addr} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after minting, without patching banker will return '31337ugnot' -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31337/gno.land/r/test/realm_banker:ugnot"' ## burn coin -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${USER_ADDR_test2} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${test2_user_addr} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after burning -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31330/gno.land/r/test/realm_banker:ugnot"' ## transfer 1ugnot to test2 for gas-fee of below tx -gnokey maketx send -send "1ugnot" -to ${USER_ADDR_test2} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx send -send "1ugnot" -to ${test2_user_addr} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## transfer coin gnokey maketx send -send "1330/gno.land/r/test/realm_banker:ugnot" -to g1yr0dpfgthph7y6mepdx8afuec4q3ga2lg8tjt0 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check sender balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"30000/gno.land/r/test/realm_banker:ugnot"' ## check receiver balance @@ -121,4 +121,4 @@ func Mint(addr std.Address, denom string, amount int64) { func Burn(addr std.Address, denom string, amount int64) { banker := std.GetBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, denom, amount) -} \ No newline at end of file +} diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index 9781799ea7d..1531b83dfef 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -20,6 +20,9 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/amino" + rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -45,6 +48,7 @@ const ( envKeyPrivValKey envKeyExecCommand envKeyExecBin + envKeyBase ) type commandkind int @@ -158,13 +162,18 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { } kb.ImportPrivKey(DefaultAccount_Name, defaultPK, "") - env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) - env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) + env.Setenv(DefaultAccount_Name+"_user_seed", DefaultAccount_Seed) + env.Setenv(DefaultAccount_Name+"_user_addr", DefaultAccount_Address) // New private key env.Values[envKeyPrivValKey] = ed25519.GenPrivKey() + + // Set gno dbdir env.Setenv("GNO_DBDIR", dbdir) + // Setup account store + env.Values[envKeyBase] = kb + // Generate node short id var sid string { @@ -215,6 +224,7 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { "adduserfrom": adduserfromCmd(nodesManager), "patchpkg": patchpkgCmd(), "loadpkg": loadpkgCmd(gnoRootDir), + "scanf": loadpkgCmd(gnoRootDir), } // Initialize cmds map if needed @@ -305,8 +315,11 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun }) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: cfg}) - ts.Setenv("RPC_ADDR", nodep.Address()) + + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node started successfully") case "restart": @@ -337,6 +350,9 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun ts.Setenv("RPC_ADDR", nodep.Address()) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: node.cfg}) + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node restarted successfully") case "stop": @@ -534,6 +550,64 @@ func loadpkgCmd(gnoRootDir string) func(ts *testscript.TestScript, neg bool, arg } } +func loadUserEnv(ts *testscript.TestScript, remote string) error { + const path = "auth/accounts" + + // List all accounts + kb := ts.Value(envKeyBase).(keys.Keybase) + accounts, err := kb.List() + if err != nil { + ts.Fatalf("query accounts: unable to list keys: %s", err) + } + + cli, err := rpcclient.NewHTTPClient(remote) + if err != nil { + return fmt.Errorf("unable create rpc client %q: %w", remote, err) + } + + batch := cli.NewBatch() + for _, account := range accounts { + accountPath := filepath.Join(path, account.GetAddress().String()) + if err := batch.ABCIQuery(accountPath, []byte{}); err != nil { + return fmt.Errorf("unable to create query request: %w", err) + } + } + + batchRes, err := batch.Send(context.Background()) + if err != nil { + return fmt.Errorf("unable to query accounts: %w", err) + } + + if len(batchRes) != len(accounts) { + ts.Fatalf("query accounts: len(res) != len(accounts)") + } + + for i, res := range batchRes { + account := accounts[i] + name := account.GetName() + qres := res.(*ctypes.ResultABCIQuery) + + if err := qres.Response.Error; err != nil { + ts.Fatalf("query account %q error: %s", account.GetName(), err.Error()) + } + + var qret struct{ BaseAccount std.BaseAccount } + if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil { + ts.Fatalf("query account %q unarmshal error: %s", account.GetName(), err.Error()) + } + + strAccountNumber := strconv.Itoa(int(qret.BaseAccount.GetAccountNumber())) + ts.Setenv(name+"_account_num", strAccountNumber) + ts.Logf("[%q] account number: %s", name, strAccountNumber) + + strAccountSequence := strconv.Itoa(int(qret.BaseAccount.GetSequence())) + ts.Setenv(name+"_account_seq", strAccountSequence) + ts.Logf("[%q] account sequence: %s", name, strAccountNumber) + } + + return nil +} + type tsLogWriter struct { ts *testscript.TestScript } @@ -589,94 +663,8 @@ func setupNode(ts *testscript.TestScript, ctx context.Context, cfg *ProcessNodeC return nil } -// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and -// processes them. The function handles quoted phrases and escape characters within these strings. -func unquote(args []string) ([]string, error) { - const quote = '"' - - parts := []string{} - var inQuote bool - - var part strings.Builder - for _, arg := range args { - var escaped bool - for _, c := range arg { - if escaped { - // If the character is meant to be escaped, it is processed with Unquote. - // We use `Unquote` here for two main reasons: - // 1. It will validate that the escape sequence is correct - // 2. It converts the escaped string to its corresponding raw character. - // For example, "\\t" becomes '\t'. - uc, err := strconv.Unquote(`"\` + string(c) + `"`) - if err != nil { - return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) - } - - part.WriteString(uc) - escaped = false - continue - } - - // If we are inside a quoted string and encounter an escape character, - // flag the next character as `escaped` - if inQuote && c == '\\' { - escaped = true - continue - } - - // Detect quote and toggle inQuote state - if c == quote { - inQuote = !inQuote - continue - } - - // Handle regular character - part.WriteRune(c) - } - - // If we're inside a quote, add a single space. - // It reflects one or multiple spaces between args in the original string. - if inQuote { - part.WriteRune(' ') - continue - } - - // Finalize part, add to parts, and reset for next part - parts = append(parts, part.String()) - part.Reset() - } - - // Check if a quote is left open - if inQuote { - return nil, errors.New("unfinished quote") - } - - return parts, nil -} - -func getNodeSID(ts *testscript.TestScript) string { - return ts.Getenv("SID") -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %q command failure: %s", cmd, err) - } - } else { - if neg { - ts.Fatalf("unexpected %q command success", cmd) - } - } -} - -type envSetter interface { - Setenv(key, value string) -} - // createAccount creates a new account with the given name and adds it to the keybase. -func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland.Balance, error) { +func createAccount(ts *testscript.TestScript, kb keys.Keybase, accountName string) (gnoland.Balance, error) { var balance gnoland.Balance entropy, err := bip39.NewEntropy(256) if err != nil { @@ -688,23 +676,11 @@ func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland. return balance, fmt.Errorf("error generating mnemonic: %w", err) } - var keyInfo keys.Info - if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { - return balance, fmt.Errorf("unable to create account: %w", err) - } - - address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) - - return gnoland.Balance{ - Address: address, - Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, - }, nil + return createAccountFrom(ts, kb, accountName, mnemonic, 0, 0) } // createAccountFrom creates a new account with the given metadata and adds it to the keybase. -func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { +func createAccountFrom(ts *testscript.TestScript, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { var balance gnoland.Balance // check if mnemonic is valid @@ -718,8 +694,8 @@ func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic str } address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) + ts.Setenv(accountName+"_user_seed", mnemonic) + ts.Setenv(accountName+"_user_addr", address.String()) return gnoland.Balance{ Address: address, @@ -787,3 +763,20 @@ func GeneratePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, inde privKey := secp256k1.PrivKeySecp256k1(derivedPriv) return privKey, nil } + +func getNodeSID(ts *testscript.TestScript) string { + return ts.Getenv("SID") +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %q command failure: %s", cmd, err) + } + } else { + if neg { + ts.Fatalf("unexpected %q command success", cmd) + } + } +} diff --git a/gno.land/pkg/integration/utils.go b/gno.land/pkg/integration/utils.go new file mode 100644 index 00000000000..bc9e7f1e220 --- /dev/null +++ b/gno.land/pkg/integration/utils.go @@ -0,0 +1,73 @@ +package integration + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and +// processes them. The function handles quoted phrases and escape characters within these strings. +func unquote(args []string) ([]string, error) { + const quote = '"' + + parts := []string{} + var inQuote bool + + var part strings.Builder + for _, arg := range args { + var escaped bool + for _, c := range arg { + if escaped { + // If the character is meant to be escaped, it is processed with Unquote. + // We use `Unquote` here for two main reasons: + // 1. It will validate that the escape sequence is correct + // 2. It converts the escaped string to its corresponding raw character. + // For example, "\\t" becomes '\t'. + uc, err := strconv.Unquote(`"\` + string(c) + `"`) + if err != nil { + return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) + } + + part.WriteString(uc) + escaped = false + continue + } + + // If we are inside a quoted string and encounter an escape character, + // flag the next character as `escaped` + if inQuote && c == '\\' { + escaped = true + continue + } + + // Detect quote and toggle inQuote state + if c == quote { + inQuote = !inQuote + continue + } + + // Handle regular character + part.WriteRune(c) + } + + // If we're inside a quote, add a single space. + // It reflects one or multiple spaces between args in the original string. + if inQuote { + part.WriteRune(' ') + continue + } + + // Finalize part, add to parts, and reset for next part + parts = append(parts, part.String()) + part.Reset() + } + + // Check if a quote is left open + if inQuote { + return nil, errors.New("unfinished quote") + } + + return parts, nil +} diff --git a/gno.land/pkg/integration/testscript_gnoland_test.go b/gno.land/pkg/integration/utils_test.go similarity index 100% rename from gno.land/pkg/integration/testscript_gnoland_test.go rename to gno.land/pkg/integration/utils_test.go From ddfff6096fc96631f15a9fae1bd6f72e15d40e13 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 13 Jan 2025 11:35:05 +0100 Subject: [PATCH 26/75] chore(gnovm): remove unused attributes (#3492) --- gnovm/pkg/gnolang/nodes.go | 6 ++---- gnovm/pkg/gnolang/preprocess.go | 12 +----------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 0496d37ed72..b85d1ac7026 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -153,10 +153,8 @@ const ( ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE" ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE" ATTR_IOTA GnoAttribute = "ATTR_IOTA" - ATTR_LOCATIONED GnoAttribute = "ATTR_LOCATIONE" // XXX DELETE - ATTR_GOTOLOOP_STMT GnoAttribute = "ATTR_GOTOLOOP_STMT" // XXX delete? - ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. - ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. + ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. + ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" ) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 79695d8888a..ddfd1851989 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2723,17 +2723,10 @@ func findGotoLoopDefines(ctx BlockNode, bn BlockNode) { // Otherwise mark stmt as gotoloop. case Stmt: // we're done if we - // re-encounter origGotoStmtm. + // re-encounter origGotoStmt. if n == origGoto { - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_EXIT // done } - // otherwise set attribute. - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_CONTINUE // Special case, maybe convert // NameExprTypeDefine to @@ -4804,9 +4797,6 @@ func setNodeLines(n Node) { // based on sparse expectations on block nodes, and ensures uniqueness of BlockNode.Locations. // Ensures uniqueness of BlockNode.Locations. func setNodeLocations(pkgPath string, fileName string, n Node) { - if n.GetAttribute(ATTR_LOCATIONED) == true { - return // locations already set (typically n is a filenode). - } if pkgPath == "" || fileName == "" { panic("missing package path or file name") } From 663edac6246046f6cac0af912b0c38722f5eaa66 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:11:33 +0100 Subject: [PATCH 27/75] feat(gnovm): add software floating point package (#3185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #312 The idea to use softfloat and work originates from this PR: https://github.com/gnolang/gno/pull/2863
        Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
        --------- Co-authored-by: Morgan Bazalgette Co-authored-by: Miloš Živković --- gno.land/pkg/sdk/vm/convert.go | 5 +- gnovm/pkg/gnolang/frame.go | 5 +- gnovm/pkg/gnolang/gonative.go | 19 +- gnovm/pkg/gnolang/internal/softfloat/copy.sh | 32 + .../internal/softfloat/runtime_softfloat64.go | 631 ++++++++++++++++++ .../softfloat/runtime_softfloat64_test.go | 204 ++++++ .../gnolang/internal/softfloat/softfloat.go | 134 ++++ gnovm/pkg/gnolang/op_binary.go | 46 +- gnovm/pkg/gnolang/op_inc_dec.go | 9 +- gnovm/pkg/gnolang/op_unary.go | 5 +- gnovm/pkg/gnolang/values.go | 21 +- gnovm/pkg/gnolang/values_conversions.go | 258 +++---- gnovm/pkg/gnolang/values_conversions_test.go | 3 +- gnovm/pkg/gnolang/values_string.go | 9 +- gnovm/tests/files/float8.gno | 166 +++++ 15 files changed, 1360 insertions(+), 187 deletions(-) create mode 100644 gnovm/pkg/gnolang/internal/softfloat/copy.sh create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go create mode 100644 gnovm/pkg/gnolang/internal/softfloat/softfloat.go create mode 100644 gnovm/tests/files/float8.gno diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index cafb6cad67f..dbaabcfbc4b 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -3,6 +3,7 @@ package vm import ( "encoding/base64" "fmt" + "math" "strconv" "strings" @@ -143,11 +144,11 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { return case gno.Float32Type: value := convertFloat(arg, 32) - tv.SetFloat32(float32(value)) + tv.SetFloat32(math.Float32bits(float32(value))) return case gno.Float64Type: value := convertFloat(arg, 64) - tv.SetFloat64(value) + tv.SetFloat64(math.Float64bits(value)) return default: panic(fmt.Sprintf("unexpected primitive type %s", bt.String())) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 2ac1027eb32..60f19979b7a 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -2,6 +2,7 @@ package gnolang import ( "fmt" + "math" "strings" ) @@ -207,9 +208,9 @@ func toConstExpTrace(cte *ConstExpr) string { case Uint64Type: return fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - return fmt.Sprintf("%v", tv.GetFloat32()) + return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - return fmt.Sprintf("%v", tv.GetFloat64()) + return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) } } diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 5a39c76b5e1..85fc8b70051 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -2,7 +2,10 @@ package gnolang import ( "fmt" + "math" "reflect" + + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // NOTE @@ -329,9 +332,9 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) case reflect.Array: tv.V = alloc.NewNative(rv) case reflect.Slice: @@ -428,11 +431,11 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case Float32Kind: if lvl != 0 { - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) } case Float64Kind: if lvl != 0 { - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) } case BigintKind: panic("not yet implemented") @@ -644,9 +647,9 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) case reflect.Array: rvl := rv.Len() if rv.Type().Elem().Kind() == reflect.Uint8 { @@ -1049,9 +1052,9 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case Uint64Type: rv.SetUint(tv.GetUint64()) case Float32Type: - rv.SetFloat(float64(tv.GetFloat32())) + rv.SetFloat(math.Float64frombits(softfloat.F32to64(tv.GetFloat32()))) case Float64Type: - rv.SetFloat(tv.GetFloat64()) + rv.SetFloat(math.Float64frombits(tv.GetFloat64())) default: panic(fmt.Sprintf( "unexpected type %s", diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh new file mode 100644 index 00000000000..6d2a8f80462 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/copy.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# softfloat64.go: +# - add header +# - change package name +cat > runtime_softfloat64.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go +sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go + +# softfloat64_test.go: +# - add header +# - change package name +# - change import to right package +# - change GOARCH to runtime.GOARCH, and import the "runtime" package +cat > runtime_softfloat64_test.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go +sed -i 's/^package runtime_test$/package softfloat_test/ +s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"# +s/GOARCH/runtime.GOARCH/g +16a\ + "runtime"' runtime_softfloat64_test.go \ No newline at end of file diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go new file mode 100644 index 00000000000..cf2ad5afd8a --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go @@ -0,0 +1,631 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Software IEEE754 64-bit floating point. +// Only referred to (and thus linked in) by softfloat targets +// and by tests in this directory. + +package softfloat + +const ( + mantbits64 uint = 52 + expbits64 uint = 11 + bias64 = -1<<(expbits64-1) + 1 + + nan64 uint64 = (1<>mantbits64) & (1<>mantbits32) & (1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<>= shift + if fs == gs { + fm += gm + } else { + fm -= gm + if trunc != 0 { + fm-- + } + } + if fm == 0 { + fs = 0 + } + return fpack64(fs, fm, fe-2, trunc) +} + +func fsub64(f, g uint64) uint64 { + return fadd64(f, fneg64(g)) +} + +func fneg64(f uint64) uint64 { + return f ^ (1 << (mantbits64 + expbits64)) +} + +func fmul64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN * g or f * NaN = NaN + return nan64 + + case fi && gi: // Inf * Inf = Inf (with sign adjusted) + return f ^ gs + + case fi && gm == 0, fm == 0 && gi: // 0 * Inf = Inf * 0 = NaN + return nan64 + + case fm == 0: // 0 * x = 0 (with sign adjusted) + return f ^ gs + + case gm == 0: // x * 0 = 0 (with sign adjusted) + return g ^ fs + } + + // 53-bit * 53-bit = 107- or 108-bit + lo, hi := mullu(fm, gm) + shift := mantbits64 - 1 + trunc := lo & (1<>shift + return fpack64(fs^gs, mant, fe+ge-1, trunc) +} + +func fdiv64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN / g = f / NaN = NaN + return nan64 + + case fi && gi: // ±Inf / ±Inf = NaN + return nan64 + + case !fi && !gi && fm == 0 && gm == 0: // 0 / 0 = NaN + return nan64 + + case fi, !gi && gm == 0: // Inf / g = f / 0 = Inf + return fs ^ gs ^ inf64 + + case gi, fm == 0: // f / Inf = 0 / g = Inf + return fs ^ gs ^ 0 + } + _, _, _, _ = fi, fn, gi, gn + + // 53-bit<<54 / 53-bit = 53- or 54-bit. + shift := mantbits64 + 2 + q, r := divlu(fm>>(64-shift), fm<> 32) + if fi { + return fs32 ^ inf32 + } + const d = mantbits64 - mantbits32 - 1 + return fpack32(fs32, uint32(fm>>d), fe-1, uint32(fm&(1< gs: // f < 0, g > 0 + return -1, false + + case fs < gs: // f > 0, g < 0 + return +1, false + + // Same sign, not NaN. + // Can compare encodings directly now. + // Reverse for sign. + case fs == 0 && f < g, fs != 0 && f > g: + return -1, false + + case fs == 0 && f > g, fs != 0 && f < g: + return +1, false + } + + // f == g + return 0, false +} + +func f64toint(f uint64) (val int64, ok bool) { + fs, fm, fe, fi, fn := funpack64(f) + + switch { + case fi, fn: // NaN + return 0, false + + case fe < -1: // f < 0.5 + return 0, false + + case fe > 63: // f >= 2^63 + if fs != 0 && fm == 0 { // f == -2^63 + return -1 << 63, true + } + if fs != 0 { + return 0, false + } + return 0, false + } + + for fe > int(mantbits64) { + fe-- + fm <<= 1 + } + for fe < int(mantbits64) { + fe++ + fm >>= 1 + } + val = int64(fm) + if fs != 0 { + val = -val + } + return val, true +} + +func fintto64(val int64) (f uint64) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + return fpack64(fs, mant, int(mantbits64), 0) +} +func fintto32(val int64) (f uint32) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + // Reduce mantissa size until it fits into a uint32. + // Keep track of the bits we throw away, and if any are + // nonzero or them into the lowest bit. + exp := int(mantbits32) + var trunc uint32 + for mant >= 1<<32 { + trunc |= uint32(mant) & 1 + mant >>= 1 + exp++ + } + + return fpack32(uint32(fs>>32), uint32(mant), exp, trunc) +} + +// 64x64 -> 128 multiply. +// adapted from hacker's delight. +func mullu(u, v uint64) (lo, hi uint64) { + const ( + s = 32 + mask = 1<> s + v0 := v & mask + v1 := v >> s + w0 := u0 * v0 + t := u1*v0 + w0>>s + w1 := t & mask + w2 := t >> s + w1 += u0 * v1 + return u * v, u1*v1 + w2 + w1>>s +} + +// 128/64 -> 64 quotient, 64 remainder. +// adapted from hacker's delight +func divlu(u1, u0, v uint64) (q, r uint64) { + const b = 1 << 32 + + if u1 >= v { + return 1<<64 - 1, 1<<64 - 1 + } + + // s = nlz(v); v <<= s + s := uint(0) + for v&(1<<63) == 0 { + s++ + v <<= 1 + } + + vn1 := v >> 32 + vn0 := v & (1<<32 - 1) + un32 := u1<>(64-s) + un10 := u0 << s + un1 := un10 >> 32 + un0 := un10 & (1<<32 - 1) + q1 := un32 / vn1 + rhat := un32 - q1*vn1 + +again1: + if q1 >= b || q1*vn0 > b*rhat+un1 { + q1-- + rhat += vn1 + if rhat < b { + goto again1 + } + } + + un21 := un32*b + un1 - q1*v + q0 := un21 / vn1 + rhat = un21 - q0*vn1 + +again2: + if q0 >= b || q0*vn0 > b*rhat+un0 { + q0-- + rhat += vn1 + if rhat < b { + goto again2 + } + } + + return q1*b + q0, (un21*b + un0 - q0*v) >> s +} + +func fadd32(x, y uint32) uint32 { + return f64to32(fadd64(f32to64(x), f32to64(y))) +} + +func fmul32(x, y uint32) uint32 { + return f64to32(fmul64(f32to64(x), f32to64(y))) +} + +func fdiv32(x, y uint32) uint32 { + // TODO: are there double-rounding problems here? See issue 48807. + return f64to32(fdiv64(f32to64(x), f32to64(y))) +} + +func feq32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp == 0 && !nan +} + +func fgt32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 1 && !nan +} + +func fge32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 0 && !nan +} + +func feq64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp == 0 && !nan +} + +func fgt64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 1 && !nan +} + +func fge64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 0 && !nan +} + +func fint32to32(x int32) uint32 { + return fintto32(int64(x)) +} + +func fint32to64(x int32) uint64 { + return fintto64(int64(x)) +} + +func fint64to32(x int64) uint32 { + return fintto32(x) +} + +func fint64to64(x int64) uint64 { + return fintto64(x) +} + +func f32toint32(x uint32) int32 { + val, _ := f64toint(f32to64(x)) + return int32(val) +} + +func f32toint64(x uint32) int64 { + val, _ := f64toint(f32to64(x)) + return val +} + +func f64toint32(x uint64) int32 { + val, _ := f64toint(x) + return int32(val) +} + +func f64toint64(x uint64) int64 { + val, _ := f64toint(x) + return val +} + +func f64touint64(x uint64) uint64 { + var m uint64 = 0x43e0000000000000 // float64 1<<63 + if fgt64(m, x) { + return uint64(f64toint64(x)) + } + y := fadd64(x, -m) + z := uint64(f64toint64(y)) + return z | (1 << 63) +} + +func f32touint64(x uint32) uint64 { + var m uint32 = 0x5f000000 // float32 1<<63 + if fgt32(m, x) { + return uint64(f32toint64(x)) + } + y := fadd32(x, -m) + z := uint64(f32toint64(y)) + return z | (1 << 63) +} + +func fuint64to64(x uint64) uint64 { + if int64(x) >= 0 { + return fint64to64(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to64(int64(z)) + return fadd64(r, r) +} + +func fuint64to32(x uint64) uint32 { + if int64(x) >= 0 { + return fint64to32(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to32(int64(z)) + return fadd32(r, r) +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go new file mode 100644 index 00000000000..c57fe08b0ef --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -0,0 +1,204 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package softfloat_test + +import ( + "math" + "math/rand" + . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" + "testing" + "runtime" +) + +// turn uint64 op into float64 op +func fop(f func(x, y uint64) uint64) func(x, y float64) float64 { + return func(x, y float64) float64 { + bx := math.Float64bits(x) + by := math.Float64bits(y) + return math.Float64frombits(f(bx, by)) + } +} + +func add(x, y float64) float64 { return x + y } +func sub(x, y float64) float64 { return x - y } +func mul(x, y float64) float64 { return x * y } +func div(x, y float64) float64 { return x / y } + +func TestFloat64(t *testing.T) { + base := []float64{ + 0, + math.Copysign(0, -1), + -1, + 1, + math.NaN(), + math.Inf(+1), + math.Inf(-1), + 0.1, + 1.5, + 1.9999999999999998, // all 1s mantissa + 1.3333333333333333, // 1.010101010101... + 1.1428571428571428, // 1.001001001001... + 1.112536929253601e-308, // first normal + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 3, + 12, + 1234, + 123456, + -0.1, + -1.5, + -1.9999999999999998, + -1.3333333333333333, + -1.1428571428571428, + -2, + -3, + 1e-200, + 1e-300, + 1e-310, + 5e-324, + 1e-105, + 1e-305, + 1e+200, + 1e+306, + 1e+307, + 1e+308, + } + all := make([]float64, 200) + copy(all, base) + for i := len(base); i < len(all); i++ { + all[i] = rand.NormFloat64() + } + + test(t, "+", add, fop(Fadd64), all) + test(t, "-", sub, fop(Fsub64), all) + if runtime.GOARCH != "386" { // 386 is not precise! + test(t, "*", mul, fop(Fmul64), all) + test(t, "/", div, fop(Fdiv64), all) + } +} + +// 64 -hw-> 32 -hw-> 64 +func trunc32(f float64) float64 { + return float64(float32(f)) +} + +// 64 -sw->32 -hw-> 64 +func to32sw(f float64) float64 { + return float64(math.Float32frombits(F64to32(math.Float64bits(f)))) +} + +// 64 -hw->32 -sw-> 64 +func to64sw(f float64) float64 { + return math.Float64frombits(F32to64(math.Float32bits(float32(f)))) +} + +// float64 -hw-> int64 -hw-> float64 +func hwint64(f float64) float64 { + return float64(int64(f)) +} + +// float64 -hw-> int32 -hw-> float64 +func hwint32(f float64) float64 { + return float64(int32(f)) +} + +// float64 -sw-> int64 -hw-> float64 +func toint64sw(f float64) float64 { + i, ok := F64toint(math.Float64bits(f)) + if !ok { + // There's no right answer for out of range. + // Match the hardware to pass the test. + i = int64(f) + } + return float64(i) +} + +// float64 -hw-> int64 -sw-> float64 +func fromint64sw(f float64) float64 { + return math.Float64frombits(Fintto64(int64(f))) +} + +var nerr int + +func err(t *testing.T, format string, args ...any) { + t.Errorf(format, args...) + + // cut errors off after a while. + // otherwise we spend all our time + // allocating memory to hold the + // formatted output. + if nerr++; nerr >= 10 { + t.Fatal("too many errors") + } +} + +func test(t *testing.T, op string, hw, sw func(float64, float64) float64, all []float64) { + for _, f := range all { + for _, g := range all { + h := hw(f, g) + s := sw(f, g) + if !same(h, s) { + err(t, "%g %s %g = sw %g, hw %g\n", f, op, g, s, h) + } + testu(t, "to32", trunc32, to32sw, h) + testu(t, "to64", trunc32, to64sw, h) + testu(t, "toint64", hwint64, toint64sw, h) + testu(t, "fromint64", hwint64, fromint64sw, h) + testcmp(t, f, h) + testcmp(t, h, f) + testcmp(t, g, h) + testcmp(t, h, g) + } + } +} + +func testu(t *testing.T, op string, hw, sw func(float64) float64, v float64) { + h := hw(v) + s := sw(v) + if !same(h, s) { + err(t, "%s %g = sw %g, hw %g\n", op, v, s, h) + } +} + +func hwcmp(f, g float64) (cmp int, isnan bool) { + switch { + case f < g: + return -1, false + case f > g: + return +1, false + case f == g: + return 0, false + } + return 0, true // must be NaN +} + +func testcmp(t *testing.T, f, g float64) { + hcmp, hisnan := hwcmp(f, g) + scmp, sisnan := Fcmp64(math.Float64bits(f), math.Float64bits(g)) + if int32(hcmp) != scmp || hisnan != sisnan { + err(t, "cmp(%g, %g) = sw %v, %v, hw %v, %v\n", f, g, scmp, sisnan, hcmp, hisnan) + } +} + +func same(f, g float64) bool { + if math.IsNaN(f) && math.IsNaN(g) { + return true + } + if math.Copysign(1, f) != math.Copysign(1, g) { + return false + } + return f == g +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go new file mode 100644 index 00000000000..30f66dff620 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -0,0 +1,134 @@ +// Package softfloat is a copy of the Go runtime's softfloat64.go file. +// It is a pure software floating point implementation. It can be used to +// perform determinstic, hardware-independent floating point computations. +// +// This package uses shortnames to refer to its different operations. Here is a +// quick reference: +// +// add f + g +// sub f - g +// mul f * g +// div f / g +// neg (- f) +// eq f == g +// gt f > g +// ge f >= g +package softfloat + +// This file mostly exports the functions from runtime_softfloat64.go + +//go:generate sh copy.sh + +const ( + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 +) + +func Fadd64(f, g uint64) uint64 { return fadd64(f, g) } +func Fsub64(f, g uint64) uint64 { return fsub64(f, g) } +func Fmul64(f, g uint64) uint64 { return fmul64(f, g) } +func Fdiv64(f, g uint64) uint64 { return fdiv64(f, g) } +func Fneg64(f uint64) uint64 { return fneg64(f) } +func Feq64(f, g uint64) bool { return feq64(f, g) } +func Fgt64(f, g uint64) bool { return fgt64(f, g) } +func Fge64(f, g uint64) bool { return fge64(f, g) } + +func Fadd32(f, g uint32) uint32 { return fadd32(f, g) } +func Fsub32(f, g uint32) uint32 { return fadd32(f, Fneg32(g)) } +func Fmul32(f, g uint32) uint32 { return fmul32(f, g) } +func Fdiv32(f, g uint32) uint32 { return fdiv32(f, g) } +func Feq32(f, g uint32) bool { return feq32(f, g) } +func Fgt32(f, g uint32) bool { return fgt32(f, g) } +func Fge32(f, g uint32) bool { return fge32(f, g) } +func Flt32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= -1 && !nan +} + +func Fle32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= 0 && !nan +} + +func Flt64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= -1 && !nan +} + +func Fle64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= 0 && !nan +} + +func Fcmp64(f, g uint64) (cmp int32, isnan bool) { return fcmp64(f, g) } + +func Fneg32(f uint32) uint32 { + // Not defined in runtime - this is a copy similar to fneg64. + return f ^ (1 << (mantbits32 + expbits32)) +} + +// Conversions + +func Fintto64(val int64) (f uint64) { return fintto64(val) } +func Fintto32(val int64) (f uint32) { return fintto32(val) } + +func F32to64(f uint32) uint64 { return f32to64(f) } +func F32toint32(x uint32) int32 { return f32toint32(x) } +func F32toint64(x uint32) int64 { return f32toint64(x) } +func F32touint64(x uint32) uint64 { return f32touint64(x) } +func F64to32(f uint64) uint32 { return f64to32(f) } +func F64toint(f uint64) (val int64, ok bool) { return f64toint(f) } +func F64toint32(x uint64) int32 { return f64toint32(x) } +func F64toint64(x uint64) int64 { return f64toint64(x) } +func F64touint64(x uint64) uint64 { return f64touint64(x) } +func Fint32to32(x int32) uint32 { return fint32to32(x) } +func Fint32to64(x int32) uint64 { return fint32to64(x) } +func Fint64to32(x int64) uint32 { return fint64to32(x) } +func Fint64to64(x int64) uint64 { return fint64to64(x) } +func Fuint64to32(x uint64) uint32 { return fuint64to32(x) } +func Fuint64to64(x uint64) uint64 { return fuint64to64(x) } + +// unpack64 unpacks the float64 f into sign, exp, mantissa, isInf, isNaN. + +func Funpack32(f uint32) (sign, mant uint32, exp int, inf, nan bool) { return funpack32(f) } +func Funpack64(f uint64) (sign, mant uint64, exp int, inf, nan bool) { return funpack64(f) } + +// Trunc + +func Ftrunc64(f uint64) uint64 { return trunc(f) } +func Ftrunc32(f uint32) uint32 { return f64to32(trunc(f32to64(f))) } + +func trunc(x uint64) uint64 { + cmp, _ := Fcmp64(x, Fintto64(0)) + if _, _, _, isInf, IsNaN := Funpack64(x); cmp == 0 || isInf || IsNaN { + return x + } + + d, _ := modf(x) + return d +} + +func modf(u uint64) (it uint64, frac uint64) { + if Flt64(u, fint64to64(1)) { + switch { + case Flt64(u, fint64to64(0)): + it, frac = modf(Fneg64(u)) + return -it, -frac + case feq64(u, fint64to64(0)): + return u, u // Return -0, -0 when f == -0 + } + return 0, u + } + + it = u + e := uint(it>>shift)&mask - bias + + // Keep the top 12+e bits, the integer part; clear the rest. + if e < 64-12 { + it &^= 1<<(64-12-e) - 1 + } + + frac = fsub64(u, it) + return +} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0f66da5e685..765f3ccbfbd 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/gnolang/gno/tm2/pkg/overflow" ) @@ -394,9 +395,9 @@ func isEql(store Store, lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() == rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() == rv.GetFloat32()) // XXX determinism? + return softfloat.Feq32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism? + return softfloat.Feq64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -535,9 +536,9 @@ func isLss(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() < rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism? + return softfloat.Flt32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism? + return softfloat.Flt64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -579,9 +580,9 @@ func isLeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() <= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism? + return softfloat.Fle32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism? + return softfloat.Fle64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -623,9 +624,9 @@ func isGtr(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() > rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism? + return softfloat.Fgt32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism? + return softfloat.Fgt64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -667,9 +668,9 @@ func isGeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() >= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism? + return softfloat.Fge32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism? + return softfloat.Fge64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -732,10 +733,10 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() + rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() + rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() + rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, rv.GetBigInt()) @@ -807,10 +808,10 @@ func subAssign(lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() - rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() - rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() - rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) @@ -879,10 +880,10 @@ func mulAssign(lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() * rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() * rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fmul32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() * rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fmul64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) @@ -975,20 +976,17 @@ func quoAssign(lv, rv *TypedValue) *Exception { // XXX Handling float overflows is more complex. case Float32Type: // NOTE: gno doesn't fuse *+. - y := rv.GetFloat32() - ok = y != 0 + ok = !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) + if ok { - lv.SetFloat32(lv.GetFloat32() / y) + lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32())) } - // XXX FOR DETERMINISM, PANIC IF NAN. case Float64Type: // NOTE: gno doesn't fuse *+. - y := rv.GetFloat64() - ok = y != 0 + ok = !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) if ok { - lv.SetFloat64(lv.GetFloat64() / y) + lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) } - // XXX FOR DETERMINISM, PANIC IF NAN. case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { ok = false diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 1e68e195596..c67a4be6ed5 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/gnolang/gno/tm2/pkg/overflow" ) @@ -57,9 +58,9 @@ func (m *Machine) doOpInc() { case Uint64Type: lv.SetUint64(lv.GetUint64() + 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() + 1) + lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: - lv.SetFloat64(lv.GetFloat64() + 1) + lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), softfloat.Fintto64(1))) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, big.NewInt(1)) @@ -129,9 +130,9 @@ func (m *Machine) doOpDec() { case Uint64Type: lv.SetUint64(lv.GetUint64() - 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() - 1) + lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: - lv.SetFloat64(lv.GetFloat64() - 1) + lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), softfloat.Fintto64(1))) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, big.NewInt(1)) diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go index 9c330c7f8f1..469c80b8dac 100644 --- a/gnovm/pkg/gnolang/op_unary.go +++ b/gnovm/pkg/gnolang/op_unary.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) func (m *Machine) doOpUpos() { @@ -46,9 +47,9 @@ func (m *Machine) doOpUneg() { case Uint64Type: xv.SetUint64(-xv.GetUint64()) case Float32Type: - xv.SetFloat32(-xv.GetFloat32()) + xv.SetFloat32(softfloat.Fneg32(xv.GetFloat32())) case Float64Type: - xv.SetFloat64(-xv.GetFloat64()) + xv.SetFloat64(softfloat.Fneg64(xv.GetFloat64())) case UntypedBigintType, BigintType: bv := xv.V.(BigintValue) xv.V = BigintValue{V: new(big.Int).Neg(bv.V)} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 4c2e2835f95..da887764c8e 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -3,7 +3,6 @@ package gnolang import ( "encoding/binary" "fmt" - "math" "math/big" "reflect" "strconv" @@ -1121,13 +1120,13 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { return data case Float32Type: data = make([]byte, 4) - u32 := math.Float32bits(tv.GetFloat32()) + u32 := tv.GetFloat32() binary.LittleEndian.PutUint32( data, u32) return data case Float64Type: data = make([]byte, 8) - u64 := math.Float64bits(tv.GetFloat64()) + u64 := tv.GetFloat64() binary.LittleEndian.PutUint64( data, u64) return data @@ -1450,7 +1449,7 @@ func (tv *TypedValue) GetUint64() uint64 { return *(*uint64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat32(n float32) { +func (tv *TypedValue) SetFloat32(n uint32) { if debug { if tv.T.Kind() != Float32Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1458,10 +1457,10 @@ func (tv *TypedValue) SetFloat32(n float32) { tv.T.String())) } } - *(*float32)(unsafe.Pointer(&tv.N)) = n + *(*uint32)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat32() float32 { +func (tv *TypedValue) GetFloat32() uint32 { if debug { if tv.T != nil && tv.T.Kind() != Float32Kind { panic(fmt.Sprintf( @@ -1469,10 +1468,10 @@ func (tv *TypedValue) GetFloat32() float32 { tv.T.String())) } } - return *(*float32)(unsafe.Pointer(&tv.N)) + return *(*uint32)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat64(n float64) { +func (tv *TypedValue) SetFloat64(n uint64) { if debug { if tv.T.Kind() != Float64Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1480,10 +1479,10 @@ func (tv *TypedValue) SetFloat64(n float64) { tv.T.String())) } } - *(*float64)(unsafe.Pointer(&tv.N)) = n + *(*uint64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat64() float64 { +func (tv *TypedValue) GetFloat64() uint64 { if debug { if tv.T != nil && tv.T.Kind() != Float64Kind { panic(fmt.Sprintf( @@ -1491,7 +1490,7 @@ func (tv *TypedValue) GetFloat64() float64 { tv.T.String())) } } - return *(*float64)(unsafe.Pointer(&tv.N)) + return *(*uint64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) GetBigInt() *big.Int { diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index baeded76c1a..e1c9378fe67 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // t cannot be nil or untyped or DataByteType. @@ -163,11 +164,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto32(int64(tv.GetInt())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto64(int64(tv.GetInt())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -233,11 +234,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -304,11 +305,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -379,11 +380,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to32(tv.GetInt32()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to64(tv.GetInt32()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -456,11 +457,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to32(tv.GetInt64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to64(tv.GetInt64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -533,11 +534,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -602,11 +603,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -673,11 +674,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -746,11 +747,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint32())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint32())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -825,11 +826,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to32(tv.GetUint64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to64(tv.GetUint64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -847,156 +848,155 @@ GNO_CASE: switch k { case IntKind: validate(Float32Kind, IntKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat32()) // XXX determinism? + x := int(softfloat.F32toint64(tv.GetFloat32())) tv.T = t tv.SetInt(x) case Int8Kind: validate(Float32Kind, Int8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat32()) // XXX determinism? + x := int8(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float32Kind, Int16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat32()) // XXX determinism? + x := int16(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float32Kind, Int32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32toint32(tv.GetFloat32()) tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float32Kind, Int64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - return val == trunc + return softfloat.Feq32(trunc, tv.GetFloat32()) }) - x := int64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32toint64(tv.GetFloat32()) tv.T = t tv.SetInt64(x) case UintKind: validate(Float32Kind, UintKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat32()) // XXX determinism? + x := uint(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float32Kind, Uint8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat32()) // XXX determinism? + x := uint8(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float32Kind, Uint16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat32()) // XXX determinism? + x := uint16(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float32Kind, Uint32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat32()) // XXX determinism? + x := uint32(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float32Kind, Uint64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32touint64(tv.GetFloat32()) tv.T = t tv.SetUint64(x) case Float32Kind: - x := tv.GetFloat32() // XXX determinism? + x := tv.GetFloat32() // ??? tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32to64(tv.GetFloat32()) tv.T = t tv.SetFloat64(x) default: @@ -1008,160 +1008,160 @@ GNO_CASE: switch k { case IntKind: validate(Float64Kind, IntKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat64()) // XXX determinism? + xp, _ := softfloat.F64toint(tv.GetFloat64()) + x := int(xp) tv.T = t tv.SetInt(x) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat64()) // XXX determinism? + x := int8(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float64Kind, Int16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat64()) // XXX determinism? + x := int16(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float64Kind, Int32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint32(tv.GetFloat64()) tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float64Kind, Int64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - return val == trunc + return softfloat.Feq64(trunc, tv.GetFloat64()) }) - x := int64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint64(tv.GetFloat64()) tv.T = t tv.SetInt64(x) case UintKind: validate(Float64Kind, UintKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F64touint64(trunc) + + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat64()) // XXX determinism? + x := uint(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float64Kind, Uint8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat64()) // XXX determinism? + x := uint8(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float64Kind, Uint16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat64()) // XXX determinism? + x := uint16(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float64Kind, Uint32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) - - if val != trunc { + trunc := softfloat.Ftrunc64(tv.GetFloat64()) + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat64()) // XXX determinism? + x := uint32(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float64Kind, Uint64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(tv.GetFloat64(), trunc) { return false } - return trunc >= 0 && trunc <= math.MaxUint64 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint64 }) - x := uint64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64touint64(tv.GetFloat64()) tv.T = t tv.SetUint64(x) case Float32Kind: validate(Float64Kind, Float32Kind, func() bool { - return tv.GetFloat64() <= math.MaxFloat32 + return softfloat.Fle64(tv.GetFloat64(), math.Float64bits(float64(math.MaxFloat32))) }) - x := float32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64to32(tv.GetFloat64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := tv.GetFloat64() // XXX determinism? + x := tv.GetFloat64() // ??? tv.T = t tv.SetFloat64(x) default: @@ -1481,7 +1481,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f32 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float32 (too close to zero)") } - dst.SetFloat32(f32) + dst.SetFloat32(math.Float32bits(f32)) return // done case Float64Kind: dst.T = t @@ -1495,7 +1495,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f64 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float64 (too close to zero)") } - dst.SetFloat64(f64) + dst.SetFloat64(math.Float64bits(f64)) return // done case BigdecKind: dst.T = t @@ -1610,7 +1610,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { dst.T = Float64Type dst.V = nil f, _ := bd.Float64() - dst.SetFloat64(f) + dst.SetFloat64(math.Float64bits(f)) return case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: fallthrough @@ -1636,7 +1636,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(float64(f32), 0) { panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") } - dst.SetFloat32(f32) + dst.SetFloat32(math.Float32bits(f32)) return case Float64Kind: dst.T = t @@ -1648,7 +1648,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(f64, 0) { panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") } - dst.SetFloat64(f64) + dst.SetFloat64(math.Float64bits(f64)) return default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index 7ffa3e98c71..5538e973bdc 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/stretchr/testify/require" ) @@ -24,7 +25,7 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { ConvertUntypedBigdecTo(dst, bd, typ) - require.Equal(t, float64(0), dst.GetFloat64()) + require.True(t, softfloat.Feq64(dst.GetFloat64(), 0)) } func TestBitShiftingOverflow(t *testing.T) { diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index a414f440e4e..fdf0c8f55de 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -2,6 +2,7 @@ package gnolang import ( "fmt" + "math" "reflect" "strconv" "strings" @@ -339,9 +340,9 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo case Uint64Type: return fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - return fmt.Sprintf("%v", tv.GetFloat32()) + return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - return fmt.Sprintf("%v", tv.GetFloat64()) + return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) case UntypedBigintType, BigintType: return tv.V.(BigintValue).V.String() case UntypedBigdecType, BigdecType: @@ -447,9 +448,9 @@ func (tv TypedValue) ProtectedString(seen *seenValues) string { case Uint64Type: vs = fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - vs = fmt.Sprintf("%v", tv.GetFloat32()) + vs = fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - vs = fmt.Sprintf("%v", tv.GetFloat64()) + vs = fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) // Complex types that require recusion protection. default: vs = nilStr diff --git a/gnovm/tests/files/float8.gno b/gnovm/tests/files/float8.gno new file mode 100644 index 00000000000..989d1b4fd61 --- /dev/null +++ b/gnovm/tests/files/float8.gno @@ -0,0 +1,166 @@ +package main + +import "math" + +func main() { + asVars() + asConsts() +} + +func asVars() { + var i8 int8 = 127 + var i16 int16 = 32767 + var i32 int32 = 2147483647 + var i64 int64 = 9223372036854775807 + var i int = 9223372036854775807 + var u8 uint8 = 255 + var u16 uint16 = 65535 + var u32 uint32 = 4294967295 + var u64 uint64 = 18446744073709551615 + var f32Max float32 = math.MaxFloat32 + var f64Max float64 = math.MaxFloat64 + var f64Min float64 = math.SmallestNonzeroFloat64 + var f32Min float32 = math.SmallestNonzeroFloat32 + println(f32Max / 2) + println(f64Max / 2) + println((f32Max - 1) + 1) + println((f64Max - 1) + 1) + println((f32Max / 2) * 2) + println((f64Max / 2) * 2) + println(f32Max - 1) + println(f64Max - 1) + println(f64Min / 2) + println(f32Min / 2) + println(float32(i8)) + println(float64(i8)) + println(float32(i16)) + println(float64(i16)) + println(float32(i32)) + println(float64(i32)) + println(float32(i64)) + println(float64(i64)) + println(float32(i)) + println(float64(i)) + println(float32(u8)) + println(float64(u8)) + println(float32(u16)) + println(float64(u16)) + println(float32(u32)) + println(float64(u32)) + println(float32(u64)) + println(float64(u64)) + println(float32(f32Max)) + println(float64(f32Max)) + println(float64(f64Max)) +} + +func asConsts() { + const i8 int8 = 127 + const i16 int16 = 32767 + const i32 int32 = 2147483647 + const i64 int64 = 9223372036854775807 + const i int = 9223372036854775807 + const u8 uint8 = 255 + const u16 uint16 = 65535 + const u32 uint32 = 4294967295 + const u64 uint64 = 18446744073709551615 + const f32Max float32 = math.MaxFloat32 + const f64Max float64 = math.MaxFloat64 + const f64Min float64 = math.SmallestNonzeroFloat64 + const f32Min float32 = math.SmallestNonzeroFloat32 + println(f32Max / 2) + println(f64Max / 2) + println((f32Max - 1) + 1) + println((f64Max - 1) + 1) + println((f32Max / 2) * 2) + println((f64Max / 2) * 2) + println(f32Max - 1) + println(f64Max - 1) + println(f64Min / 2) + println(f32Min / 2) + println(float32(i8)) + println(float64(i8)) + println(float32(i16)) + println(float64(i16)) + println(float32(i32)) + println(float64(i32)) + println(float32(i64)) + println(float64(i64)) + println(float32(i)) + println(float64(i)) + println(float32(u8)) + println(float64(u8)) + println(float32(u16)) + println(float64(u16)) + println(float32(u32)) + println(float64(u32)) + println(float32(u64)) + println(float64(u64)) + println(float32(f32Max)) + println(float64(f32Max)) + println(float64(f64Max)) +} + +// Output: +// 1.7014117e+38 +// 8.988465674311579e+307 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 0 +// 0 +// 127 +// 127 +// 32767 +// 32767 +// 2.1474836e+09 +// 2.147483647e+09 +// 9.223372e+18 +// 9.223372036854776e+18 +// 9.223372e+18 +// 9.223372036854776e+18 +// 255 +// 255 +// 65535 +// 65535 +// 4.2949673e+09 +// 4.294967295e+09 +// 1.8446744e+19 +// 1.8446744073709552e+19 +// 3.4028235e+38 +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 +// 1.7014117e+38 +// 8.988465674311579e+307 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 0 +// 0 +// 127 +// 127 +// 32767 +// 32767 +// 2.1474836e+09 +// 2.147483647e+09 +// 9.223372e+18 +// 9.223372036854776e+18 +// 9.223372e+18 +// 9.223372036854776e+18 +// 255 +// 255 +// 65535 +// 65535 +// 4.2949673e+09 +// 4.294967295e+09 +// 1.8446744e+19 +// 1.8446744073709552e+19 +// 3.4028235e+38 +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 From ee8dcc6f2180fea94c17da38c9bcaf519a45dd7b Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 13 Jan 2025 13:02:43 +0100 Subject: [PATCH 28/75] feat(github-bot): config improvements (#3466) - change the "docs" merge requirements to allow also for a third-party contribution approved by both core+devrel - disallow merging on all prs with the "don't merge" label - don't require additional information on dependabot PRs --- contribs/github-bot/internal/config/config.go | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index 86acc6cfa83..f80fc86cb11 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -38,17 +38,22 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { { Description: "Changes to 'docs' folder must be reviewed/authored by at least one devrel and one tech-staff", If: c.FileChanged(gh, "^docs/"), - Then: r.Or( - r.And( - r.AuthorInTeam(gh, "devrels"), + Then: r.And( + r.Or( + r.AuthorInTeam(gh, "tech-staff"), r.ReviewByTeamMembers(gh, "tech-staff", 1), ), - r.And( - r.AuthorInTeam(gh, "tech-staff"), + r.Or( + r.AuthorInTeam(gh, "devrels"), r.ReviewByTeamMembers(gh, "devrels", 1), ), ), }, + { + Description: "Must not contain the \"don't merge\" label", + If: c.Label("don't merge"), + Then: r.Never(), + }, } manual := []ManualCheck{ @@ -59,8 +64,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { }, { Description: "The pull request description provides enough details", - If: c.Not(c.AuthorInTeam(gh, "core-contributors")), - Teams: Teams{"core-contributors"}, + If: c.And( + c.Not(c.AuthorInTeam(gh, "core-contributors")), + c.Not(c.Author("dependabot[bot]")), + ), + Teams: Teams{"core-contributors"}, }, { Description: "Determine if infra needs to be updated before merging", From cc5cb36fe65fb3d4f2d8cb0df2dc40f686fb55d3 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Villaquiran Date: Mon, 13 Jan 2025 13:31:45 +0100 Subject: [PATCH 29/75] feat(stdlibs): add package crypto/bech32 (#3375) Related to #1475 --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- .../gno.land/p/demo/testutils/crypto_test.gno | 12 + .../r/demo/keystore/keystore_test.gno | 6 +- .../r/demo/microblog/microblog_test.gno | 2 +- .../testdata/assertorigincall.txtar | 2 +- .../testdata/grc20_invalid_address.txtar | 2 +- .../integration/testdata/grc20_registry.txtar | 4 +- .../integration/testdata/grc721_emit.txtar | 10 +- .../pkg/integration/testdata/issue_1786.txtar | 8 +- .../pkg/integration/testdata/prevrealm.txtar | 2 +- .../pkg/integration/testdata/wugnot.txtar | 14 +- gnovm/stdlibs/crypto/bech32/bech32.gno | 445 +++++++++++ gnovm/stdlibs/crypto/bech32/bech32_test.gno | 691 ++++++++++++++++++ gnovm/stdlibs/crypto/bech32/error.gno | 83 +++ gnovm/stdlibs/crypto/bech32/version.gno | 43 ++ gnovm/stdlibs/generated.go | 101 +-- gnovm/stdlibs/std/crypto.gno | 67 ++ gnovm/stdlibs/std/crypto_test.gno | 19 + gnovm/stdlibs/std/native.gno | 15 - gnovm/stdlibs/std/native.go | 21 - gnovm/tests/files/std5.gno | 2 +- gnovm/tests/files/std8.gno | 2 +- gnovm/tests/files/zrealm_natbind0.gno | 2 +- 22 files changed, 1391 insertions(+), 162 deletions(-) create mode 100644 examples/gno.land/p/demo/testutils/crypto_test.gno create mode 100644 gnovm/stdlibs/crypto/bech32/bech32.gno create mode 100644 gnovm/stdlibs/crypto/bech32/bech32_test.gno create mode 100644 gnovm/stdlibs/crypto/bech32/error.gno create mode 100644 gnovm/stdlibs/crypto/bech32/version.gno diff --git a/examples/gno.land/p/demo/testutils/crypto_test.gno b/examples/gno.land/p/demo/testutils/crypto_test.gno new file mode 100644 index 00000000000..ac77b76dadf --- /dev/null +++ b/examples/gno.land/p/demo/testutils/crypto_test.gno @@ -0,0 +1,12 @@ +package testutils + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestTestAddress(t *testing.T) { + testAddr := TestAddress("author1") + uassert.Equal(t, "g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6", string(testAddr)) +} diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index ffd8e60936f..41597016ea3 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -11,7 +11,11 @@ import ( ) func TestRender(t *testing.T) { - const ( + // https://github.com/gnolang/gno/pull/3375 changed const -> var : + // For some reason non native functions fails on constants with + // constant overflows (code=2), this does not happens when using a variable + // TODO: check this issue after and if this PR is merged + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index a3c8f04ee7f..9ad98d3cbfe 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -10,7 +10,7 @@ import ( ) func TestMicroblog(t *testing.T) { - const ( + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/gno.land/pkg/integration/testdata/assertorigincall.txtar b/gno.land/pkg/integration/testdata/assertorigincall.txtar index 2c4a27f9d06..2c5da25c0aa 100644 --- a/gno.land/pkg/integration/testdata/assertorigincall.txtar +++ b/gno.land/pkg/integration/testdata/assertorigincall.txtar @@ -43,7 +43,7 @@ gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-w stdout 'OK!' ## 3. MsgCall -> myrlm.C: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 4. MsgCall -> r/foo.A -> myrlm.A: PANIC diff --git a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar index d3dcc86725c..0068384903e 100644 --- a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar +++ b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar @@ -4,7 +4,7 @@ loadpkg gno.land/r/demo/foo20 gnoland start # execute Faucet -gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 10000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' # execute Transfer for invalid address diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index df11e92f8db..4377e10a575 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -6,7 +6,7 @@ loadpkg gno.land/r/registry $WORK/registry gnoland start # we call Transfer with foo20, before it's registered -gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 150000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 1500000 -broadcast -chainid=tendermint_test test1 stdout 'not found' # add foo20, and foo20wrapper @@ -14,7 +14,7 @@ gnokey maketx addpkg -pkgdir $WORK/foo20 -pkgpath gno.land/r/foo20 -gas-fee 1000 gnokey maketx addpkg -pkgdir $WORK/foo20wrapper -pkgpath gno.land/r/foo20wrapper -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 # we call Transfer with foo20, after it's registered -gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 800000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 8000000 -broadcast -chainid=tendermint_test test1 stdout 'same address, success!' -- registry/registry.gno -- diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 6b4770e37c6..45101b74634 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -6,23 +6,23 @@ loadpkg gno.land/r/foo721 $WORK/foo721 gnoland start # Mint -gnokey maketx call -pkgpath gno.land/r/foo721 -func Mint -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Mint -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Mint\",\"attrs\":\[{\"key\":\"to\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno.land\/r\/foo721\",\"func\":\"mint\"}\]' # Approve -gnokey maketx call -pkgpath gno.land/r/foo721 -func Approve -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Approve -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Approval\",\"attrs\":\[{\"key\":\"owner\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno.land\/r\/foo721\",\"func\":\"Approve\"}\]' # SetApprovalForAll -gnokey maketx call -pkgpath gno.land/r/foo721 -func SetApprovalForAll -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args false -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func SetApprovalForAll -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args false -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"ApprovalForAll\",\"attrs\":\[{\"key\":\"owner\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"approved\",\"value\":\"false\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"setApprovalForAll\"}\]' # TransferFrom -gnokey maketx call -pkgpath gno.land/r/foo721 -func TransferFrom -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func TransferFrom -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Transfer\",\"attrs\":\[{\"key\":\"from\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"transfer\"}\]' # Burn -gnokey maketx call -pkgpath gno.land/r/foo721 -func Burn -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Burn -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Burn\",\"attrs\":\[{\"key\":\"from\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"Burn\"}\]' diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 0e66a882a6d..1cbaf2c6643 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -9,20 +9,20 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 4bbe16c3205..31f0ca336ba 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -34,7 +34,7 @@ env RFOO_USER_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 # Test cases ## 1. MsgCall -> myrlm.A: user address -gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 stdout ${test1_user_addr} ## 2. MsgCall -> myrealm.B -> myrlm.A: user address diff --git a/gno.land/pkg/integration/testdata/wugnot.txtar b/gno.land/pkg/integration/testdata/wugnot.txtar index 5fa7dab2945..5a63e0148e2 100644 --- a/gno.land/pkg/integration/testdata/wugnot.txtar +++ b/gno.land/pkg/integration/testdata/wugnot.txtar @@ -2,14 +2,14 @@ loadpkg gno.land/r/demo/wugnot gnoland start -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 10000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 stdout '# wrapped GNOT \(\$wugnot\)' stdout 'Decimals..: 0' stdout 'Total supply..: 0' stdout 'Known accounts..: 0' stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 50000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 @@ -18,7 +18,7 @@ stdout 'Known accounts..: 1' stdout 'OK!' # XXX: use test2 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 @@ -27,19 +27,19 @@ stdout 'Known accounts..: 1' # should be 2 once we can use test2 stdout 'OK!' # XXX: replace hardcoded address with test3 -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Transfer -gas-fee 1000000ugnot -gas-wanted 5000000 -args 'g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq' -args '10000000' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Transfer -gas-fee 1000000ugnot -gas-wanted 100000000 -args 'g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq' -args '10000000' -broadcast -chainid=tendermint_test test1 stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 50000000 -args '' -broadcast -chainid=tendermint_test test1 stdout 'Total supply..: 24691356' stdout 'Known accounts..: 2' # should be 3 once we can use test2 stdout 'OK!' # XXX: use test3 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Withdraw -args 10000000 -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Withdraw -args 10000000 -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 100000000 -args '' -broadcast -chainid=tendermint_test test1 stdout 'Total supply..: 14691356' stdout 'Known accounts..: 2' # should be 3 once we can use test2 stdout 'OK!' diff --git a/gnovm/stdlibs/crypto/bech32/bech32.gno b/gnovm/stdlibs/crypto/bech32/bech32.gno new file mode 100644 index 00000000000..92994b28813 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/bech32.gno @@ -0,0 +1,445 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +import ( + "strings" +) + +// charset is the set of characters used in the data section of bech32 strings. +// Note that this is ordered, such that for a given charset[i], i is the binary +// value of the character. +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +// gen encodes the generator polynomial for the bech32 BCH checksum. +var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + +// toBytes converts each character in the string 'chars' to the value of the +// index of the corresponding character in 'charset'. +func toBytes(chars string) ([]byte, error) { + decoded := make([]byte, 0, len(chars)) + for i := 0; i < len(chars); i++ { + index := strings.IndexByte(charset, chars[i]) + if index < 0 { + return nil, ErrNonCharsetChar(chars[i]) + } + decoded = append(decoded, byte(index)) + } + return decoded, nil +} + +// bech32Polymod calculates the BCH checksum for a given hrp, values and +// checksum data. Checksum is optional, and if nil a 0 checksum is assumed. +// +// Values and checksum (if provided) MUST be encoded as 5 bits per element (base +// 32), otherwise the results are undefined. +// +// For more details on the polymod calculation, please refer to BIP 173. +func bech32Polymod(hrp string, values, checksum []byte) int { + chk := 1 + + // Account for the high bits of the HRP in the checksum. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + hiBits := int(hrp[i]) >> 5 + chk = (chk&0x1ffffff)<<5 ^ hiBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the separator (0) between high and low bits of the HRP. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. + b := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + + // Account for the low bits of the HRP. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + loBits := int(hrp[i]) & 31 + chk = (chk&0x1ffffff)<<5 ^ loBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the values. + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + if checksum == nil { + // A nil checksum is used during encoding, so assume all bytes are zero. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. + for v := 0; v < 6; v++ { + b := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } else { + // Checksum is provided during decoding, so use it. + for _, v := range checksum { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } + + return chk +} + +// writeBech32Checksum calculates the checksum data expected for a string that +// will have the given hrp and payload data and writes it to the provided string +// builder. +// +// The payload data MUST be encoded as a base 32 (5 bits per element) byte slice +// and the hrp MUST only use the allowed character set (ascii chars between 33 +// and 126), otherwise the results are undefined. +// +// For more details on the checksum calculation, please refer to BIP 173. +func writeBech32Checksum(hrp string, data []byte, bldr *strings.Builder, + version Version) { + + bech32Const := int(VersionToConsts[version]) + polymod := bech32Polymod(hrp, data, nil) ^ bech32Const + for i := 0; i < 6; i++ { + b := byte((polymod >> uint(5*(5-i))) & 31) + + // This can't fail, given we explicitly cap the previous b byte by the + // first 31 bits. + c := charset[b] + bldr.WriteByte(c) + } +} + +// bech32VerifyChecksum verifies whether the bech32 string specified by the +// provided hrp and payload data (encoded as 5 bits per element byte slice) has +// the correct checksum suffix. The version of bech32 used (bech32 OG, or +// bech32m) is also returned to allow the caller to perform proper address +// validation (segwitv0 should use bech32, v1+ should use bech32m). +// +// Data MUST have more than 6 elements, otherwise this function panics. +// +// For more details on the checksum verification, please refer to BIP 173. +func bech32VerifyChecksum(hrp string, data []byte) (Version, bool) { + checksum := data[len(data)-6:] + values := data[:len(data)-6] + polymod := bech32Polymod(hrp, values, checksum) + + // Before BIP-350, we'd always check this against a static constant of + // 1 to know if the checksum was computed properly. As we want to + // generically support decoding for bech32m as well as bech32, we'll + // look up the returned value and compare it to the set of defined + // constants. + bech32Version, ok := ConstsToVersion[ChecksumConst(polymod)] + if ok { + return bech32Version, true + } + + return VersionUnknown, false +} + +// DecodeNoLimitWithVersion is a bech32 checksum version aware arbitrary string +// length decoder. This function will return the version of the decoded +// checksum constant so higher level validation can be performed to ensure the +// correct version of bech32 was used when encoding. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func DecodeNoLimitWithVersion(bech string) (string, []byte, Version, error) { + // The minimum allowed size of a bech32 string is 8 characters, since it + // needs a non-empty HRP, a separator, and a 6 character checksum. + if len(bech) < 8 { + return "", nil, VersionUnknown, ErrInvalidLength(len(bech)) + } + + // Only ASCII characters between 33 and 126 are allowed. + var hasLower, hasUpper bool + for i := 0; i < len(bech); i++ { + if bech[i] < 33 || bech[i] > 126 { + return "", nil, VersionUnknown, ErrInvalidCharacter(bech[i]) + } + + // The characters must be either all lowercase or all uppercase. Testing + // directly with ascii codes is safe here, given the previous test. + hasLower = hasLower || (bech[i] >= 97 && bech[i] <= 122) + hasUpper = hasUpper || (bech[i] >= 65 && bech[i] <= 90) + if hasLower && hasUpper { + return "", nil, VersionUnknown, ErrMixedCase{} + } + } + + // Bech32 standard uses only the lowercase for of strings for checksum + // calculation. + if hasUpper { + bech = strings.ToLower(bech) + } + + // The string is invalid if the last '1' is non-existent, it is the + // first character of the string (no human-readable part) or one of the + // last 6 characters of the string (since checksum cannot contain '1'). + one := strings.LastIndexByte(bech, '1') + if one < 1 || one+7 > len(bech) { + return "", nil, VersionUnknown, ErrInvalidSeparatorIndex(one) + } + + // The human-readable part is everything before the last '1'. + hrp := bech[:one] + data := bech[one+1:] + + // Each character corresponds to the byte with value of the index in + // 'charset'. + decoded, err := toBytes(data) + if err != nil { + return "", nil, VersionUnknown, err + } + + // Verify if the checksum (stored inside decoded[:]) is valid, given the + // previously decoded hrp. + bech32Version, ok := bech32VerifyChecksum(hrp, decoded) + if !ok { + // Invalid checksum. Calculate what it should have been, so that the + // error contains this information. + + // Extract the payload bytes and actual checksum in the string. + actual := bech[len(bech)-6:] + payload := decoded[:len(decoded)-6] + + // Calculate the expected checksum, given the hrp and payload + // data. We'll actually compute _both_ possibly valid checksum + // to further aide in debugging. + var expectedBldr strings.Builder + expectedBldr.Grow(6) + writeBech32Checksum(hrp, payload, &expectedBldr, Version0) + expectedVersion0 := expectedBldr.String() + + var b strings.Builder + b.Grow(6) + writeBech32Checksum(hrp, payload, &expectedBldr, VersionM) + expectedVersionM := expectedBldr.String() + + err = ErrInvalidChecksum{ + Expected: expectedVersion0, + ExpectedM: expectedVersionM, + Actual: actual, + } + return "", nil, VersionUnknown, err + } + + // We exclude the last 6 bytes, which is the checksum. + return hrp, decoded[:len(decoded)-6], bech32Version, nil +} + +// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable +// part and the data part excluding the checksum. This function does NOT +// validate against the BIP-173 maximum length allowed for bech32 strings and +// is meant for use in custom applications (such as lightning network payment +// requests), NOT on-chain addresses. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func DecodeNoLimit(bech string) (string, []byte, error) { + hrp, data, _, err := DecodeNoLimitWithVersion(bech) + return hrp, data, err +} + +// Decode decodes a bech32 encoded string, returning the human-readable part and +// the data part excluding the checksum. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func Decode(bech string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", nil, ErrInvalidLength(len(bech)) + } + + hrp, data, _, err := DecodeNoLimitWithVersion(bech) + return hrp, data, err +} + +// DecodeGeneric is identical to the existing Decode method, but will also +// return bech32 version that matches the decoded checksum. This method should +// be used when decoding segwit addresses, as it enables additional +// verification to ensure the proper checksum is used. +func DecodeGeneric(bech string) (string, []byte, Version, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", nil, VersionUnknown, ErrInvalidLength(len(bech)) + } + + return DecodeNoLimitWithVersion(bech) +} + +// encodeGeneric is the base bech32 encoding function that is aware of the +// existence of the checksum versions. This method is private, as the Encode +// and EncodeM methods are intended to be used instead. +func encodeGeneric(hrp string, data []byte, + version Version) (string, error) { + + // The resulting bech32 string is the concatenation of the lowercase + // hrp, the separator 1, data and the 6-byte checksum. + hrp = strings.ToLower(hrp) + var bldr strings.Builder + bldr.Grow(len(hrp) + 1 + len(data) + 6) + bldr.WriteString(hrp) + bldr.WriteString("1") + + // Write the data part, using the bech32 charset. + for _, b := range data { + if int(b) >= len(charset) { + return "", ErrInvalidDataByte(b) + } + bldr.WriteByte(charset[b]) + } + + // Calculate and write the checksum of the data. + writeBech32Checksum(hrp, data, &bldr, version) + + return bldr.String(), nil +} + +// Encode encodes a byte slice into a bech32 string with the given +// human-readable part (HRP). The HRP will be converted to lowercase if needed +// since mixed cased encodings are not permitted and lowercase is used for +// checksum purposes. Note that the bytes must each encode 5 bits (base32). +func Encode(hrp string, data []byte) (string, error) { + return encodeGeneric(hrp, data, Version0) +} + +// EncodeM is the exactly same as the Encode method, but it uses the new +// bech32m constant instead of the original one. It should be used whenever one +// attempts to encode a segwit address of v1 and beyond. +func EncodeM(hrp string, data []byte) (string, error) { + return encodeGeneric(hrp, data, VersionM) +} + +// ConvertBits converts a byte slice where each byte is encoding fromBits bits, +// to a byte slice where each byte is encoding toBits bits. +func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { + if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { + return nil, ErrInvalidBitGroups{} + } + + // Determine the maximum size the resulting array can have after base + // conversion, so that we can size it a single time. This might be off + // by a byte depending on whether padding is used or not and if the input + // data is a multiple of both fromBits and toBits, but we ignore that and + // just size it to the maximum possible. + maxSize := len(data)*int(fromBits)/int(toBits) + 1 + + // The final bytes, each byte encoding toBits bits. + regrouped := make([]byte, 0, maxSize) + + // Keep track of the next byte we create and how many bits we have + // added to it out of the toBits goal. + nextByte := byte(0) + filledBits := uint8(0) + + for _, b := range data { + + // Discard unused bits. + b <<= 8 - fromBits + + // How many bits remaining to extract from the input data. + remFromBits := fromBits + for remFromBits > 0 { + // How many bits remaining to be added to the next byte. + remToBits := toBits - filledBits + + // The number of bytes to next extract is the minimum of + // remFromBits and remToBits. + toExtract := remFromBits + if remToBits < toExtract { + toExtract = remToBits + } + + // Add the next bits to nextByte, shifting the already + // added bits to the left. + nextByte = (nextByte << toExtract) | (b >> (8 - toExtract)) + + // Discard the bits we just extracted and get ready for + // next iteration. + b <<= toExtract + remFromBits -= toExtract + filledBits += toExtract + + // If the nextByte is completely filled, we add it to + // our regrouped bytes and start on the next byte. + if filledBits == toBits { + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + } + } + + // We pad any unfinished group if specified. + if pad && filledBits > 0 { + nextByte <<= toBits - filledBits + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + + // Any incomplete group must be <= 4 bits, and all zeroes. + if filledBits > 0 && (filledBits > 4 || nextByte != 0) { + return nil, ErrInvalidIncompleteGroup{} + } + + return regrouped, nil +} + +// EncodeFromBase256 converts a base256-encoded byte slice into a base32-encoded +// byte slice and then encodes it into a bech32 string with the given +// human-readable part (HRP). The HRP will be converted to lowercase if needed +// since mixed cased encodings are not permitted and lowercase is used for +// checksum purposes. +func EncodeFromBase256(hrp string, data []byte) (string, error) { + converted, err := ConvertBits(data, 8, 5, true) + if err != nil { + return "", err + } + return Encode(hrp, converted) +} + +// DecodeToBase256 decodes a bech32-encoded string into its associated +// human-readable part (HRP) and base32-encoded data, converts that data to a +// base256-encoded byte slice and returns it along with the lowercase HRP. +func DecodeToBase256(bech string) (string, []byte, error) { + hrp, data, err := Decode(bech) + if err != nil { + return "", nil, err + } + converted, err := ConvertBits(data, 5, 8, false) + if err != nil { + return "", nil, err + } + return hrp, converted, nil +} diff --git a/gnovm/stdlibs/crypto/bech32/bech32_test.gno b/gnovm/stdlibs/crypto/bech32/bech32_test.gno new file mode 100644 index 00000000000..3f637c40345 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/bech32_test.gno @@ -0,0 +1,691 @@ +// Copyright (c) 2017-2020 The btcsuite developers +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +import ( + "bytes" + "encoding/hex" + "fmt" + "strings" + "testing" +) + +// TestBech32 tests whether decoding and re-encoding the valid BIP-173 test +// vectors works and if decoding invalid test vectors fails for the correct +// reason. +func TestBech32(t *testing.T) { + tests := []struct { + str string + expectedError error + }{ + {"A12UEL5L", nil}, + {"a12uel5l", nil}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", nil}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v", "2y9e2w"}}, // invalid checksum + {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", ErrInvalidCharacter(' ')}, // invalid character (space) in hrp + {"spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidCharacter(127)}, // invalid character (DEL) in hrp + {"split1cheo2y9e2w", ErrNonCharsetChar('o')}, // invalid character (o) in data part + {"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part + {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidSeparatorIndex(0)}, // empty hrp + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", ErrInvalidLength(91)}, // too long + + // Additional test vectors used in bitcoin core + {" 1nwldj5", ErrInvalidCharacter(' ')}, + {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)}, + {"\x801eym55h", ErrInvalidCharacter(0x80)}, + {"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", ErrInvalidLength(91)}, + {"pzry9x0s0muk", ErrInvalidSeparatorIndex(-1)}, + {"1pzry9x0s0muk", ErrInvalidSeparatorIndex(0)}, + {"x1b4n0q5v", ErrNonCharsetChar(98)}, + {"li1dgmt3", ErrInvalidSeparatorIndex(2)}, + {"de1lg7wt\xff", ErrInvalidCharacter(0xff)}, + {"A1G7SGD8", ErrInvalidChecksum{"2uel5l", "2uel5llqfn3a", "g7sgd8"}}, + {"10a06t8", ErrInvalidLength(7)}, + {"1qzzfhee", ErrInvalidSeparatorIndex(0)}, + {"a12UEL5L", ErrMixedCase{}}, + {"A12uEL5L", ErrMixedCase{}}, + } + + for i, test := range tests { + str := test.str + hrp, decoded, err := Decode(str) + if test.expectedError != err { + t.Errorf("%d: expected decoding error %v "+ + "instead got %v", i, test.expectedError, err) + continue + } + + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Check that it encodes to the same string + encoded, err := Encode(hrp, decoded) + if err != nil { + t.Errorf("encoding failed: %v", err) + } + + if encoded != strings.ToLower(str) { + t.Errorf("expected data to encode to %v, but got %v", + str, encoded) + } + + // Flip a bit in the string an make sure it is caught. + pos := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = Decode(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// TestBech32M tests that the following set of strings, based on the test +// vectors in BIP-350 are either valid or invalid using the new bech32m +// checksum algo. Some of these strings are similar to the set of above test +// vectors, but end up with different checksums. +func TestBech32M(t *testing.T) { + tests := []struct { + str string + expectedError error + }{ + {"A1LQFN3A", nil}, + {"a1lqfn3a", nil}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", nil}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", nil}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", nil}, + {"?1v759aa", nil}, + + // Additional test vectors used in bitcoin core + {"\x201xj0phk", ErrInvalidCharacter('\x20')}, + {"\x7f1g6xzxy", ErrInvalidCharacter('\x7f')}, + {"\x801vctc34", ErrInvalidCharacter('\x80')}, + {"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", ErrInvalidLength(91)}, + {"qyrz8wqd2c9m", ErrInvalidSeparatorIndex(-1)}, + {"1qyrz8wqd2c9m", ErrInvalidSeparatorIndex(0)}, + {"y1b0jsk6g", ErrNonCharsetChar(98)}, + {"lt1igcx5c0", ErrNonCharsetChar(105)}, + {"in1muywd", ErrInvalidSeparatorIndex(2)}, + {"mm1crxm3i", ErrNonCharsetChar(105)}, + {"au1s5cgom", ErrNonCharsetChar(111)}, + {"M1VUXWEZ", ErrInvalidChecksum{"mzl49c", "mzl49cw70eq6", "vuxwez"}}, + {"16plkw9", ErrInvalidLength(7)}, + {"1p2gdwpf", ErrInvalidSeparatorIndex(0)}, + + {" 1nwldj5", ErrInvalidCharacter(' ')}, + {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)}, + {"\x801eym55h", ErrInvalidCharacter(0x80)}, + } + + for i, test := range tests { + str := test.str + hrp, decoded, err := Decode(str) + if test.expectedError != err { + t.Errorf("%d: (%v) expected decoding error %v "+ + "instead got %v", i, str, test.expectedError, + err) + continue + } + + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Check that it encodes to the same string, using bech32 m. + encoded, err := EncodeM(hrp, decoded) + if err != nil { + t.Errorf("encoding failed: %v", err) + } + + if encoded != strings.ToLower(str) { + t.Errorf("expected data to encode to %v, but got %v", + str, encoded) + } + + // Flip a bit in the string an make sure it is caught. + pos := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = Decode(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// TestBech32DecodeGeneric tests that given a bech32 string, or a bech32m +// string, the proper checksum version is returned so that callers can perform +// segwit addr validation. +func TestBech32DecodeGeneric(t *testing.T) { + tests := []struct { + str string + version Version + }{ + {"A1LQFN3A", VersionM}, + {"a1lqfn3a", VersionM}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", VersionM}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", VersionM}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", VersionM}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", VersionM}, + {"?1v759aa", VersionM}, + + {"A12UEL5L", Version0}, + {"a12uel5l", Version0}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", Version0}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", Version0}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", Version0}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", Version0}, + + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", Version0}, + {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", Version0}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", VersionM}, + {"BC1SW50QGDZ25J", VersionM}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", VersionM}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", Version0}, + {"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", VersionM}, + {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", VersionM}, + } + for i, test := range tests { + _, _, version, err := DecodeGeneric(test.str) + if err != nil { + t.Errorf("%d: (%v) unexpected error during "+ + "decoding: %v", i, test.str, err) + continue + } + + if version != test.version { + t.Errorf("(%v): invalid version: expected %v, got %v", + test.str, test.version, version) + } + } +} + +// TestMixedCaseEncode ensures mixed case HRPs are converted to lowercase as +// expected when encoding and that decoding the produced encoding when converted +// to all uppercase produces the lowercase HRP and original data. +func TestMixedCaseEncode(t *testing.T) { + tests := []struct { + name string + hrp string + data string + encoded string + }{{ + name: "all uppercase HRP with no data", + hrp: "A", + data: "", + encoded: "a12uel5l", + }, { + name: "all uppercase HRP with data", + hrp: "UPPERCASE", + data: "787878", + encoded: "uppercase10pu8sss7kmp", + }, { + name: "mixed case HRP even offsets uppercase", + hrp: "AbCdEf", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }, { + name: "mixed case HRP odd offsets uppercase ", + hrp: "aBcDeF", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }, { + name: "all lowercase HRP", + hrp: "abcdef", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }} + + for _, test := range tests { + // Convert the text hex to bytes, convert those bytes from base256 to + // base32, then ensure the encoded result with the HRP provided in the + // test data is as expected. + data, err := hex.DecodeString(test.data) + if err != nil { + t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err) + continue + } + convertedData, err := ConvertBits(data, 8, 5, true) + if err != nil { + t.Errorf("%q: unexpected convert bits error: %v", test.name, + err) + continue + } + gotEncoded, err := Encode(test.hrp, convertedData) + if err != nil { + t.Errorf("%q: unexpected encode error: %v", test.name, err) + continue + } + if gotEncoded != test.encoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, test.encoded) + continue + } + + // Ensure the decoding the expected lowercase encoding converted to all + // uppercase produces the lowercase HRP and original data. + gotHRP, gotData, err := Decode(strings.ToUpper(test.encoded)) + if err != nil { + t.Errorf("%q: unexpected decode error: %v", test.name, err) + continue + } + wantHRP := strings.ToLower(test.hrp) + if gotHRP != wantHRP { + t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name, + gotHRP, wantHRP) + continue + } + convertedGotData, err := ConvertBits(gotData, 5, 8, false) + if err != nil { + t.Errorf("%q: unexpected convert bits error: %v", test.name, + err) + continue + } + if !bytes.Equal(convertedGotData, data) { + t.Errorf("%q: mismatched data -- got %x, want %x", test.name, + convertedGotData, data) + continue + } + } +} + +// TestCanDecodeUnlimitedBech32 tests whether decoding a large bech32 string works +// when using the DecodeNoLimit version +func TestCanDecodeUnlimitedBech32(t *testing.T) { + input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd" + + // Sanity check that an input of this length errors on regular Decode() + _, _, err := Decode(input) + if err == nil { + t.Fatalf("Test vector not appropriate") + } + + // Try and decode it. + hrp, data, err := DecodeNoLimit(input) + if err != nil { + t.Fatalf("Expected decoding of large string to work. Got error: %v", err) + } + + // Verify data for correctness. + if hrp != "1" { + t.Fatalf("Unexpected hrp: %v", hrp) + } + decodedHex := fmt.Sprintf("%x", data) + expected := "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000" + if decodedHex != expected { + t.Fatalf("Unexpected decoded data: %s", decodedHex) + } +} + +// TestBech32Base256 ensures decoding and encoding various bech32, HRPs, and +// data produces the expected results when using EncodeFromBase256 and +// DecodeToBase256. It includes tests for proper handling of case +// manipulations. +func TestBech32Base256(t *testing.T) { + tests := []struct { + name string // test name + encoded string // bech32 string to decode + hrp string // expected human-readable part + data string // expected hex-encoded data + err error // expected error + }{{ + name: "all uppercase, no data", + encoded: "A12UEL5L", + hrp: "a", + data: "", + }, { + name: "long hrp with separator and excluded chars, no data", + encoded: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + hrp: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio", + data: "", + }, { + name: "6 char hrp with data with leading zero", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + hrp: "abcdef", + data: "00443214c74254b635cf84653a56d7c675be77df", + }, { + name: "hrp same as separator and max length encoded string", + encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + hrp: "1", + data: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "5 char hrp with data chosen to produce human-readable data part", + encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + hrp: "split", + data: "c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d", + }, { + name: "same as previous but with checksum invalidated", + encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", + err: ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v", "2y9e2w"}, + }, { + name: "hrp with invalid character (space)", + encoded: "s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", + err: ErrInvalidCharacter(' '), + }, { + name: "hrp with invalid character (DEL)", + encoded: "spl\x7ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + err: ErrInvalidCharacter(127), + }, { + name: "data part with invalid character (o)", + encoded: "split1cheo2y9e2w", + err: ErrNonCharsetChar('o'), + }, { + name: "data part too short", + encoded: "split1a2y9w", + err: ErrInvalidSeparatorIndex(5), + }, { + name: "empty hrp", + encoded: "1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + err: ErrInvalidSeparatorIndex(0), + }, { + name: "no separator", + encoded: "pzry9x0s0muk", + err: ErrInvalidSeparatorIndex(-1), + }, { + name: "too long by one char", + encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + err: ErrInvalidLength(91), + }, { + name: "invalid due to mixed case in hrp", + encoded: "aBcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + err: ErrMixedCase{}, + }, { + name: "invalid due to mixed case in data part", + encoded: "abcdef1Qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + err: ErrMixedCase{}, + }} + + for _, test := range tests { + // Ensure the decode either produces an error or not as expected. + str := test.encoded + gotHRP, gotData, err := DecodeToBase256(str) + if test.err != err { + t.Errorf("%q: unexpected decode error -- got %v, want %v", + test.name, err, test.err) + continue + } + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Ensure the expected HRP and original data are as expected. + if gotHRP != test.hrp { + t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name, + gotHRP, test.hrp) + continue + } + data, err := hex.DecodeString(test.data) + if err != nil { + t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err) + continue + } + if !bytes.Equal(gotData, data) { + t.Errorf("%q: mismatched data -- got %x, want %x", test.name, + gotData, data) + continue + } + + // Encode the same data with the HRP converted to all uppercase and + // ensure the result is the lowercase version of the original encoded + // bech32 string. + gotEncoded, err := EncodeFromBase256(strings.ToUpper(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected uppercase HRP encode error: %v", test.name, + err) + } + wantEncoded := strings.ToLower(str) + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Encode the same data with the HRP converted to all lowercase and + // ensure the result is the lowercase version of the original encoded + // bech32 string. + gotEncoded, err = EncodeFromBase256(strings.ToLower(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Encode the same data with the HRP converted to mixed upper and + // lowercase and ensure the result is the lowercase version of the + // original encoded bech32 string. + var mixedHRPBuilder strings.Builder + for i, r := range test.hrp { + if i%2 == 0 { + mixedHRPBuilder.WriteString(strings.ToUpper(string(r))) + continue + } + mixedHRPBuilder.WriteRune(r) + } + gotEncoded, err = EncodeFromBase256(mixedHRPBuilder.String(), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Ensure a bit flip in the string is caught. + pos := strings.LastIndexAny(test.encoded, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = DecodeToBase256(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// BenchmarkEncodeDecodeCycle performs a benchmark for a full encode/decode +// cycle of a bech32 string. It also reports the allocation count, which we +// expect to be 2 for a fully optimized cycle. +func BenchmarkEncodeDecodeCycle(b *testing.B) { + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + // Convert this into a 79-byte, base 32 byte slice. + base32Input, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("failed to convert input to 32 bits-per-element: %v", err) + } + + // Use a fixed hrp for the tests. This should generate an encoded bech32 + // string of size 90 (the maximum allowed by BIP-173). + hrp := "bc" + + // Begin the benchmark. Given that we test one roundtrip per iteration + // (that is, one Encode() and one Decode() operation), we expect at most + // 2 allocations per reported test op. + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + str, err := Encode(hrp, base32Input) + if err != nil { + b.Fatalf("failed to encode input: %v", err) + } + + _, _, err = Decode(str) + if err != nil { + b.Fatalf("failed to decode string: %v", err) + } + } +} + +// TestConvertBits tests whether base conversion works using TestConvertBits(). +func TestConvertBits(t *testing.T) { + tests := []struct { + input string + output string + fromBits uint8 + toBits uint8 + pad bool + }{ + // Trivial empty conversions. + {"", "", 8, 5, false}, + {"", "", 8, 5, true}, + {"", "", 5, 8, false}, + {"", "", 5, 8, true}, + + // Conversions of 0 value with/without padding. + {"00", "00", 8, 5, false}, + {"00", "0000", 8, 5, true}, + {"0000", "00", 5, 8, false}, + {"0000", "0000", 5, 8, true}, + + // Testing when conversion ends exactly at the byte edge. This makes + // both padded and unpadded versions the same. + {"0000000000", "0000000000000000", 8, 5, false}, + {"0000000000", "0000000000000000", 8, 5, true}, + {"0000000000000000", "0000000000", 5, 8, false}, + {"0000000000000000", "0000000000", 5, 8, true}, + + // Conversions of full byte sequences. + {"ffffff", "1f1f1f1f1e", 8, 5, true}, + {"1f1f1f1f1e", "ffffff", 5, 8, false}, + {"1f1f1f1f1e", "ffffff00", 5, 8, true}, + + // Sample random conversions. + {"c9ca", "190705", 8, 5, false}, + {"c9ca", "19070500", 8, 5, true}, + {"19070500", "c9ca", 5, 8, false}, + {"19070500", "c9ca00", 5, 8, true}, + + // Test cases tested on TestConvertBitsFailures with their corresponding + // fixes. + {"ff", "1f1c", 8, 5, true}, + {"1f1c10", "ff20", 5, 8, true}, + + // Large conversions. + { + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1", + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + 8, 5, true, + }, + { + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed100", + 5, 8, true, + }, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + expected, err := hex.DecodeString(tc.output) + if err != nil { + t.Fatalf("invalid test output data: %v", err) + } + + actual, err := ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != nil { + t.Fatalf("test case %d failed: %v", i, err) + } + + if !bytes.Equal(actual, expected) { + t.Fatalf("test case %d has wrong output; expected=%x actual=%x", + i, expected, actual) + } + } +} + +// TestConvertBitsFailures tests for the expected conversion failures of +// ConvertBits(). +func TestConvertBitsFailures(t *testing.T) { + tests := []struct { + input string + fromBits uint8 + toBits uint8 + pad bool + err error + }{ + // Not enough output bytes when not using padding. + {"ff", 8, 5, false, ErrInvalidIncompleteGroup{}}, + {"1f1c10", 5, 8, false, ErrInvalidIncompleteGroup{}}, + + // Unsupported bit conversions. + {"", 0, 5, false, ErrInvalidBitGroups{}}, + {"", 10, 5, false, ErrInvalidBitGroups{}}, + {"", 5, 0, false, ErrInvalidBitGroups{}}, + {"", 5, 10, false, ErrInvalidBitGroups{}}, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + _, err = ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != tc.err { + t.Fatalf("test case %d failure: expected '%v' got '%v'", i, + tc.err, err) + } + } + +} + +// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a higher base into a lower base (e.g. 8 +// => 5). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsDown(b *testing.B) { + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} + +// BenchmarkConvertBitsUp benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a lower base into a higher base (e.g. 5 +// => 8). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsUp(b *testing.B) { + // Use a fixed, 79-byte raw data for testing. + inputData, err := hex.DecodeString("190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} diff --git a/gnovm/stdlibs/crypto/bech32/error.gno b/gnovm/stdlibs/crypto/bech32/error.gno new file mode 100644 index 00000000000..dafe9b68516 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/error.gno @@ -0,0 +1,83 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +// ErrMixedCase is returned when the bech32 string has both lower and uppercase +// characters. +type ErrMixedCase struct{} + +func (e ErrMixedCase) Error() string { + return "string not all lowercase or all uppercase" +} + +// ErrInvalidBitGroups is returned when conversion is attempted between byte +// slices using bit-per-element of unsupported value. +type ErrInvalidBitGroups struct{} + +func (e ErrInvalidBitGroups) Error() string { + return "only bit groups between 1 and 8 allowed" +} + +// ErrInvalidIncompleteGroup is returned when then byte slice used as input has +// data of wrong length. +type ErrInvalidIncompleteGroup struct{} + +func (e ErrInvalidIncompleteGroup) Error() string { + return "invalid incomplete group" +} + +// ErrInvalidLength is returned when the bech32 string has an invalid length +// given the BIP-173 defined restrictions. +type ErrInvalidLength int + +func (e ErrInvalidLength) Error() string { + return "invalid bech32 string length" +} + +// ErrInvalidCharacter is returned when the bech32 string has a character +// outside the range of the supported charset. +type ErrInvalidCharacter rune + +func (e ErrInvalidCharacter) Error() string { + return "invalid character in string: " + string(e) +} + +// ErrInvalidSeparatorIndex is returned when the separator character '1' is +// in an invalid position in the bech32 string. +type ErrInvalidSeparatorIndex int + +func (e ErrInvalidSeparatorIndex) Error() string { + return "invalid separator index" + string(e) +} + +// ErrNonCharsetChar is returned when a character outside of the specific +// bech32 charset is used in the string. +type ErrNonCharsetChar rune + +func (e ErrNonCharsetChar) Error() string { + return "invalid character not part of charset" +} + +// ErrInvalidChecksum is returned when the extracted checksum of the string +// is different than what was expected. Both the original version, as well as +// the new bech32m checksum may be specified. +type ErrInvalidChecksum struct { + Expected string + ExpectedM string + Actual string +} + +func (e ErrInvalidChecksum) Error() string { + return "invalid checksum (expected (bech32=" + e.Expected + + " bech32m=)" + e.ExpectedM + ", got " + e.Actual + ")" +} + +// ErrInvalidDataByte is returned when a byte outside the range required for +// conversion into a string was found. +type ErrInvalidDataByte byte + +func (e ErrInvalidDataByte) Error() string { + return "invalid data byte: " + string(e) +} diff --git a/gnovm/stdlibs/crypto/bech32/version.gno b/gnovm/stdlibs/crypto/bech32/version.gno new file mode 100644 index 00000000000..147037db9aa --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/version.gno @@ -0,0 +1,43 @@ +package bech32 + +// ChecksumConst is a type that represents the currently defined bech32 +// checksum constants. +type ChecksumConst int + +const ( + // Version0Const is the original constant used in the checksum + // verification for bech32. + Version0Const ChecksumConst = 1 + + // VersionMConst is the new constant used for bech32m checksum + // verification. + VersionMConst ChecksumConst = 0x2bc830a3 +) + +// Version defines the current set of bech32 versions. +type Version uint8 + +const ( + // Version0 defines the original bech version. + Version0 Version = iota + + // VersionM is the new bech32 version defined in BIP-350, also known as + // bech32m. + VersionM + + // VersionUnknown denotes an unknown bech version. + VersionUnknown +) + +// VersionToConsts maps bech32 versions to the checksum constant to be used +// when encoding, and asserting a particular version when decoding. +var VersionToConsts = map[Version]ChecksumConst{ + Version0: Version0Const, + VersionM: VersionMConst, +} + +// ConstsToVersion maps a bech32 constant to the version it's associated with. +var ConstsToVersion = map[ChecksumConst]Version{ + Version0Const: Version0, + VersionMConst: VersionM, +} diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index d5ab052028f..6e757561ef2 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -640,106 +640,6 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, - { - "std", - "derivePkgAddr", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - r0 := libs_std.X_derivePkgAddr(p0) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, - { - "std", - "encodeBech32", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - {Name: gno.N("p1"), Type: gno.X("[20]byte")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - p1 [20]byte - rp1 = reflect.ValueOf(&p1).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - - r0 := libs_std.X_encodeBech32(p0, p1) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, - { - "std", - "decodeBech32", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - {Name: gno.N("r1"), Type: gno.X("[20]byte")}, - {Name: gno.N("r2"), Type: gno.X("bool")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - r0, r1, r2 := libs_std.X_decodeBech32(p0) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r1).Elem(), - )) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r2).Elem(), - )) - }, - }, { "std", "assertCallerIsRealm", @@ -977,6 +877,7 @@ var initOrder = [...]string{ "bytes", "strings", "bufio", + "crypto/bech32", "encoding/binary", "math/bits", "math", diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index 402a6af3e22..adb6f687b56 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -1,7 +1,16 @@ package std +import ( + "crypto/bech32" + "crypto/sha256" + "errors" +) + type Address string // NOTE: bech32 +// bech32AddrPrefix defines the Bech32 prefix of an address +const bech32AddrPrefix = "g" + func (a Address) String() string { return string(a) } @@ -15,3 +24,61 @@ func (a Address) IsValid() bool { const RawAddressSize = 20 type RawAddress [RawAddressSize]byte + +func EncodeBech32(prefix string, bz [20]byte) Address { + b32, err := convertAndEncode(prefix, bz[:]) + if err != nil { + panic(err) // should not happen + } + return Address(b32) +} + +func DecodeBech32(addr Address) (string, [20]byte, bool) { + prefix, bz, err := decodeAndConvert(string(addr)) + if err != nil || len(bz) != 20 { + return "", [20]byte{}, false + } + return prefix, convertTo20Byte(bz), true +} + +func convertAndEncode(hrp string, data []byte) (string, error) { + converted, err := bech32.ConvertBits(data, 8, 5, true) + if err != nil { + return "", errors.New("encoding bech32 failed: " + err.Error()) + } + return bech32.Encode(hrp, converted) +} + +func decodeAndConvert(bech string) (string, []byte, error) { + hrp, data, err := bech32.DecodeNoLimit(bech) + if err != nil { + return "", nil, errors.New("decoding bech32 failed" + err.Error()) + } + converted, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return "", nil, errors.New("decoding bech32 failed" + err.Error()) + } + return hrp, converted, nil +} + +func DerivePkgAddr(pkgPath string) Address { + data := sumTruncated([]byte("pkgPath:" + pkgPath)) + return EncodeBech32(bech32AddrPrefix, data) +} + +func sumTruncated(bz []byte) [20]byte { + hash := sha256.Sum256(bz) + return convertTo20Byte(hash[:RawAddressSize]) +} + +func convertTo20Byte(in []byte) [20]byte { + /* For some reason [20]byte(bz) fails with: 'cannot convert []uint8 to ArrayKind' + Maybe there is an issue to create + */ + result := [20]byte{} + for index, b := range in { + result[index] = b + } + + return result +} diff --git a/gnovm/stdlibs/std/crypto_test.gno b/gnovm/stdlibs/std/crypto_test.gno index 75283c03523..70b42e43860 100644 --- a/gnovm/stdlibs/std/crypto_test.gno +++ b/gnovm/stdlibs/std/crypto_test.gno @@ -39,3 +39,22 @@ func TestValid(t *testing.T) { } } } + +func TestDerivePkgAddr(t *testing.T) { + type test struct { + inputPath string + expected string + } + + testCases := []test{ + {inputPath: "gno.land/r/gnoland/faucet", expected: "g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7"}, + {inputPath: "gno.land/r/demo/tamagotchi", expected: "g1a3tu874agjlkrpzt9x90xv3uzncapcn959yte4"}, + } + + for _, tc := range testCases { + result := DerivePkgAddr(tc.inputPath) + if result.String() != tc.expected { + t.Fatalf("Expected: %t, got: %t", tc.expected, result) + } + } +} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 0dcde1148e1..9cf8808a07e 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -45,25 +45,10 @@ func GetCallerAt(n int) Address { return Address(callerAt(n)) } -func DerivePkgAddr(pkgPath string) Address { - return Address(derivePkgAddr(pkgPath)) -} - -func EncodeBech32(prefix string, bz [20]byte) Address { - return Address(encodeBech32(prefix, bz)) -} - -func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { - return decodeBech32(string(addr)) -} - // Variations which don't use named types. func origSend() (denoms []string, amounts []int64) func origCaller() string func origPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) -func derivePkgAddr(pkgPath string) string -func encodeBech32(prefix string, bz [20]byte) string -func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) func assertCallerIsRealm() diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index fb181d9be31..9e398e907a2 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -2,7 +2,6 @@ package std import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -142,26 +141,6 @@ func currentRealm(m *gno.Machine) (address, pkgPath string) { return X_getRealm(m, 0) } -func X_derivePkgAddr(pkgPath string) string { - return string(gno.DerivePkgAddr(pkgPath).Bech32()) -} - -func X_encodeBech32(prefix string, bytes [20]byte) string { - b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) - if err != nil { - panic(err) // should not happen - } - return b32 -} - -func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { - prefix, bz, err := bech32.Decode(addr) - if err != nil || len(bz) != 20 { - return "", [20]byte{}, false - } - return prefix, [20]byte(bz), true -} - func X_assertCallerIsRealm(m *gno.Machine) { frame := m.Frames[m.NumFrames()-2] if path := frame.LastPackage.PkgPath; !gno.IsRealmPath(path) { diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index e339d7a6364..2f9e98bb4ec 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -13,7 +13,7 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(2) // std/native.gno:45 diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index ee717bf16be..dfc2b8ca5fd 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -23,7 +23,7 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(4) // std/native.gno:45 diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 8e5f641e734..e6ebef6252e 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -69,7 +69,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:9" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:10" // }, // "FileName": "native.gno", // "IsMethod": false, From d54d00470dd1be891e9c22896aa3841fcfe02eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 14 Jan 2025 15:16:14 +0100 Subject: [PATCH 30/75] feat: add `EstimateGas` to gnoclient (#3498) ## Description Closes #1826 along with https://github.com/gnolang/tm2-js-client/pull/192 This PR introduces a method `EstimateGas` to estimate the gas used by a transaction in gnoclient. We can use this call to get a ballpark estimate if a transaction succeeds, and if it does, how much gas it actually used. --- gno.land/pkg/gnoclient/client_test.go | 157 ++++++++++++++++++++++++++ gno.land/pkg/gnoclient/client_txs.go | 47 +++++++- 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 1f8563d34fe..54a15420a66 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -1,8 +1,11 @@ package gnoclient import ( + "errors" "testing" + "github.com/gnolang/gno/tm2/pkg/amino" + abciErrors "github.com/gnolang/gno/tm2/pkg/bft/abci/example/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1409,3 +1412,157 @@ func addPackageSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msg require.NotNil(t, res) return res, nil } + +func TestClient_EstimateGas(t *testing.T) { + t.Parallel() + + t.Run("RPC client not set", func(t *testing.T) { + t.Parallel() + + c := &Client{ + RPCClient: nil, // not set + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, ErrMissingRPCClient) + }) + + t.Run("unsuccessful query, rpc error", func(t *testing.T) { + t.Parallel() + + var ( + rpcErr = errors.New("rpc error") + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return nil, rpcErr + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, rpcErr) + }) + + t.Run("unsuccessful query, process error", func(t *testing.T) { + t.Parallel() + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + ResponseBase: abci.ResponseBase{ + Error: abciErrors.UnknownError{}, + }, + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, abciErrors.UnknownError{}) + }) + + t.Run("invalid response format", func(t *testing.T) { + t.Parallel() + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: []byte("totally valid amino"), + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorContains(t, err, "unable to unmarshal gas estimation response") + }) + + t.Run("valid gas estimation", func(t *testing.T) { + t.Parallel() + + var ( + gasUsed = int64(100000) + deliverResp = &abci.ResponseDeliverTx{ + GasUsed: gasUsed, + } + ) + + // Encode the response + encodedResp, err := amino.Marshal(deliverResp) + require.NoError(t, err) + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: encodedResp, // valid amino binary + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + require.NoError(t, err) + assert.Equal(t, gasUsed, estimate) + }) +} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index d7f6f053242..ab520eceda1 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -1,8 +1,11 @@ package gnoclient import ( + "fmt" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/sdk/bank" @@ -16,6 +19,8 @@ var ( ErrMissingRPCClient = errors.New("missing RPCClient") ) +const simulatePath = ".app/simulate" + // BaseTxCfg defines the base transaction configuration, shared by all message types type BaseTxCfg struct { GasFee string // Gas fee @@ -292,4 +297,44 @@ func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxC return bres, nil } -// TODO: Add more functionality, examples, and unit tests. +// EstimateGas returns the least amount of gas required +// for the transaction to go through on the chain (minimum gas wanted). +// The estimation process assumes the transaction is properly signed +func (c *Client) EstimateGas(tx *std.Tx) (int64, error) { + // Make sure the RPC client is set + if err := c.validateRPCClient(); err != nil { + return 0, err + } + + // Prepare the transaction. + // The transaction needs to be amino-binary encoded + // in order to be estimated + encodedTx, err := amino.Marshal(tx) + if err != nil { + return 0, fmt.Errorf("unable to marshal tx: %w", err) + } + + // Perform the simulation query + resp, err := c.RPCClient.ABCIQuery(simulatePath, encodedTx) + if err != nil { + return 0, fmt.Errorf("unable to perform ABCI query: %w", err) + } + + // Extract the query response + if err = resp.Response.Error; err != nil { + return 0, fmt.Errorf("error encountered during ABCI query: %w", err) + } + + var deliverTx abci.ResponseDeliverTx + if err = amino.Unmarshal(resp.Response.Value, &deliverTx); err != nil { + return 0, fmt.Errorf("unable to unmarshal gas estimation response: %w", err) + } + + if err = deliverTx.Error; err != nil { + return 0, fmt.Errorf("error encountered during gas estimation: %w", err) + } + + // Return the actual value returned by the node + // for executing the transaction + return deliverTx.GasUsed, nil +} From 7761c3ba29695b1621c618d8382d696433622761 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 14 Jan 2025 17:20:59 +0100 Subject: [PATCH 31/75] chore: revert "feat(gnovm): implement overflow checking at VM level" (#3508) Revert #3250 This reverts commit 68aff6464dfba782903cdb5e3b318a9b233a479e. This PR was merged before discussions were complete. It was finally rejected because it does not comply with Go specification, which stipulates that overflow on signed integers do not trigger runtime panic. See https://go.dev/ref/spec#Integer_overflow --- examples/gno.land/p/demo/grc/grc20/token.gno | 22 +- gnovm/pkg/gnolang/op_bench_test.go | 70 --- gnovm/pkg/gnolang/op_binary.go | 305 +++++------ gnovm/pkg/gnolang/op_inc_dec.go | 25 +- gnovm/stdlibs/generated.go | 1 + gnovm/stdlibs/math/const_test.gno | 77 +-- gnovm/stdlibs/math/overflow/overflow.gno | 501 ++++++++++++++++++ gnovm/stdlibs/math/overflow/overflow_test.gno | 200 +++++++ gnovm/stdlibs/std/coins.gno | 26 +- gnovm/tests/files/overflow0.gno | 10 - gnovm/tests/files/overflow1.gno | 10 - gnovm/tests/files/overflow2.gno | 10 - gnovm/tests/files/overflow3.gno | 10 - gnovm/tests/files/overflow4.gno | 10 - gnovm/tests/files/overflow5.gno | 10 - gnovm/tests/files/recover14.gno | 2 +- misc/genstd/util.go | 3 +- 17 files changed, 874 insertions(+), 418 deletions(-) delete mode 100644 gnovm/pkg/gnolang/op_bench_test.go create mode 100644 gnovm/stdlibs/math/overflow/overflow.gno create mode 100644 gnovm/stdlibs/math/overflow/overflow_test.gno delete mode 100644 gnovm/tests/files/overflow0.gno delete mode 100644 gnovm/tests/files/overflow1.gno delete mode 100644 gnovm/tests/files/overflow2.gno delete mode 100644 gnovm/tests/files/overflow3.gno delete mode 100644 gnovm/tests/files/overflow4.gno delete mode 100644 gnovm/tests/files/overflow5.gno diff --git a/examples/gno.land/p/demo/grc/grc20/token.gno b/examples/gno.land/p/demo/grc/grc20/token.gno index 4986eaebf04..3ab3abc63a3 100644 --- a/examples/gno.land/p/demo/grc/grc20/token.gno +++ b/examples/gno.land/p/demo/grc/grc20/token.gno @@ -1,6 +1,7 @@ package grc20 import ( + "math/overflow" "std" "strconv" @@ -169,24 +170,17 @@ func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) err } // Mint increases the total supply of the token and adds the specified amount to the specified address. -func (led *PrivateLedger) Mint(address std.Address, amount uint64) (err error) { +func (led *PrivateLedger) Mint(address std.Address, amount uint64) error { if !address.IsValid() { return ErrInvalidAddress } - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - err = ErrOverflow - } - }() - - // Convert amount and totalSupply to signed integers to enable - // overflow checking (not occuring on unsigned) when computing the sum. - // The maximum value for totalSupply is therefore 1<<63. - sum := int64(led.totalSupply) + int64(amount) + // XXX: math/overflow is not supporting uint64. + // This checks prevents overflow but makes the totalSupply limited to a uint63. + sum, ok := overflow.Add64(int64(led.totalSupply), int64(amount)) + if !ok { + return ErrOverflow + } led.totalSupply = uint64(sum) currentBalance := led.balanceOf(address) diff --git a/gnovm/pkg/gnolang/op_bench_test.go b/gnovm/pkg/gnolang/op_bench_test.go deleted file mode 100644 index 5874f980285..00000000000 --- a/gnovm/pkg/gnolang/op_bench_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package gnolang - -import ( - "testing" - - "github.com/gnolang/gno/tm2/pkg/overflow" -) - -func BenchmarkOpAdd(b *testing.B) { - m := NewMachine("bench", nil) - x := TypedValue{T: IntType} - x.SetInt(4) - y := TypedValue{T: IntType} - y.SetInt(3) - - b.ResetTimer() - - for range b.N { - m.PushOp(OpHalt) - m.PushExpr(&BinaryExpr{}) - m.PushValue(x) - m.PushValue(y) - m.PushOp(OpAdd) - m.Run() - } -} - -//go:noinline -func AddNoOverflow(x, y int) int { return x + y } - -func BenchmarkAddNoOverflow(b *testing.B) { - x, y := 4, 3 - c := 0 - for range b.N { - c = AddNoOverflow(x, y) - } - if c != 7 { - b.Error("invalid result") - } -} - -func BenchmarkAddOverflow(b *testing.B) { - x, y := 4, 3 - c := 0 - for range b.N { - c = overflow.Addp(x, y) - } - if c != 7 { - b.Error("invalid result") - } -} - -func TestOpAdd1(t *testing.T) { - m := NewMachine("test", nil) - a := TypedValue{T: IntType} - a.SetInt(4) - b := TypedValue{T: IntType} - b.SetInt(3) - t.Log("a:", a, "b:", b) - - start := m.NumValues - m.PushOp(OpHalt) - m.PushExpr(&BinaryExpr{}) - m.PushValue(a) - m.PushValue(b) - m.PushOp(OpAdd) - m.Run() - res := m.ReapValues(start) - t.Log("res:", res) -} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 765f3ccbfbd..0e8eec9db23 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -7,7 +7,6 @@ import ( "github.com/cockroachdb/apd/v3" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" - "github.com/gnolang/gno/tm2/pkg/overflow" ) // ---------------------------------------- @@ -185,9 +184,7 @@ func (m *Machine) doOpAdd() { } // add rv to lv. - if err := addAssign(m.Alloc, lv, rv); err != nil { - panic(err) - } + addAssign(m.Alloc, lv, rv) } func (m *Machine) doOpSub() { @@ -201,9 +198,7 @@ func (m *Machine) doOpSub() { } // sub rv from lv. - if err := subAssign(lv, rv); err != nil { - panic(err) - } + subAssign(lv, rv) } func (m *Machine) doOpBor() { @@ -259,7 +254,8 @@ func (m *Machine) doOpQuo() { } // lv / rv - if err := quoAssign(lv, rv); err != nil { + err := quoAssign(lv, rv) + if err != nil { panic(err) } } @@ -275,7 +271,8 @@ func (m *Machine) doOpRem() { } // lv % rv - if err := remAssign(lv, rv); err != nil { + err := remAssign(lv, rv) + if err != nil { panic(err) } } @@ -687,38 +684,23 @@ func isGeq(lv, rv *TypedValue) bool { } } -// addAssign adds lv to rv and stores the result to lv. -// It returns an exception in case of overflow on signed integers. -// The assignement is performed even in case of exception. -func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { +// for doOpAdd and doOpAddAssign. +func addAssign(alloc *Allocator, lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { case StringType, UntypedStringType: lv.V = alloc.NewString(lv.GetString() + rv.GetString()) - // Signed integers may overflow, which triggers an exception. case IntType: - var r int - r, ok = overflow.Add(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() + rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Add8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() + rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Add16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() + rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Add32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() + rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Add64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() + rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() + rv.GetUint()) case Uint8Type: @@ -758,42 +740,23 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("addition overflow")} - } - return nil } -// subAssign subtracts lv to rv and stores the result to lv. -// It returns an exception in case of overflow on signed integers. -// The subtraction is performed even in case of exception. -func subAssign(lv, rv *TypedValue) *Exception { +// for doOpSub and doOpSubAssign. +func subAssign(lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow, which triggers an exception. case IntType: - var r int - r, ok = overflow.Sub(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() - rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Sub8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() - rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Sub16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() - rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Sub32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() - rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Sub64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() - rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() - rv.GetUint()) case Uint8Type: @@ -833,39 +796,23 @@ func subAssign(lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("subtraction overflow")} - } - return nil } // for doOpMul and doOpMulAssign. -func mulAssign(lv, rv *TypedValue) *Exception { +func mulAssign(lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - var r int - r, ok = overflow.Mul(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() * rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Mul8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() * rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Mul16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() * rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Mul32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() * rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Mul64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) + lv.SetInt64(lv.GetInt64() * rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() * rv.GetUint()) case Uint8Type: @@ -903,102 +850,95 @@ func mulAssign(lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("multiplication overflow")} - } - return nil } // for doOpQuo and doOpQuoAssign. func quoAssign(lv, rv *TypedValue) *Exception { + expt := &Exception{ + Value: typedString("division by zero"), + } + // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - var q int - q, _, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) - lv.SetInt(q) + if rv.GetInt() == 0 { + return expt + } + lv.SetInt(lv.GetInt() / rv.GetInt()) case Int8Type: - var q int8 - q, _, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(q) + if rv.GetInt8() == 0 { + return expt + } + lv.SetInt8(lv.GetInt8() / rv.GetInt8()) case Int16Type: - var q int16 - q, _, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(q) + if rv.GetInt16() == 0 { + return expt + } + lv.SetInt16(lv.GetInt16() / rv.GetInt16()) case Int32Type, UntypedRuneType: - var q int32 - q, _, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(q) + if rv.GetInt32() == 0 { + return expt + } + lv.SetInt32(lv.GetInt32() / rv.GetInt32()) case Int64Type: - var q int64 - q, _, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(q) - // Unsigned integers do not cause overflow, but a division by 0 may still occur. + if rv.GetInt64() == 0 { + return expt + } + lv.SetInt64(lv.GetInt64() / rv.GetInt64()) case UintType: - y := rv.GetUint() - ok = y != 0 - if ok { - lv.SetUint(lv.GetUint() / y) + if rv.GetUint() == 0 { + return expt } + lv.SetUint(lv.GetUint() / rv.GetUint()) case Uint8Type: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetUint8(lv.GetUint8() / y) + if rv.GetUint8() == 0 { + return expt } + lv.SetUint8(lv.GetUint8() / rv.GetUint8()) case DataByteType: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetDataByte(lv.GetDataByte() / y) + if rv.GetUint8() == 0 { + return expt } + lv.SetDataByte(lv.GetDataByte() / rv.GetUint8()) case Uint16Type: - y := rv.GetUint16() - ok = y != 0 - if ok { - lv.SetUint16(lv.GetUint16() / y) + if rv.GetUint16() == 0 { + return expt } + lv.SetUint16(lv.GetUint16() / rv.GetUint16()) case Uint32Type: - y := rv.GetUint32() - ok = y != 0 - if ok { - lv.SetUint32(lv.GetUint32() / y) + if rv.GetUint32() == 0 { + return expt } + lv.SetUint32(lv.GetUint32() / rv.GetUint32()) case Uint64Type: - y := rv.GetUint64() - ok = y != 0 - if ok { - lv.SetUint64(lv.GetUint64() / y) + if rv.GetUint64() == 0 { + return expt } - // XXX Handling float overflows is more complex. + lv.SetUint64(lv.GetUint64() / rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - ok = !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) + ok := !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) if ok { lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32())) } case Float64Type: // NOTE: gno doesn't fuse *+. - ok = !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) + ok := !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) if ok { lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) } case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { - ok = false - break + return expt } lb := lv.GetBigInt() lb = big.NewInt(0).Quo(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} case BigdecType, UntypedBigdecType: if rv.GetBigDec().Cmp(apd.New(0, 0)) == 0 { - ok = false - break + return expt } lb := lv.GetBigDec() rb := rv.GetBigDec() @@ -1015,83 +955,81 @@ func quoAssign(lv, rv *TypedValue) *Exception { )) } - if !ok { - return &Exception{Value: typedString("division by zero or overflow")} - } return nil } // for doOpRem and doOpRemAssign. func remAssign(lv, rv *TypedValue) *Exception { + expt := &Exception{ + Value: typedString("division by zero"), + } + // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - var r int - _, r, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + if rv.GetInt() == 0 { + return expt + } + lv.SetInt(lv.GetInt() % rv.GetInt()) case Int8Type: - var r int8 - _, r, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + if rv.GetInt8() == 0 { + return expt + } + lv.SetInt8(lv.GetInt8() % rv.GetInt8()) case Int16Type: - var r int16 - _, r, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + if rv.GetInt16() == 0 { + return expt + } + lv.SetInt16(lv.GetInt16() % rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - _, r, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + if rv.GetInt32() == 0 { + return expt + } + lv.SetInt32(lv.GetInt32() % rv.GetInt32()) case Int64Type: - var r int64 - _, r, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not cause overflow, but a division by 0 may still occur. + if rv.GetInt64() == 0 { + return expt + } + lv.SetInt64(lv.GetInt64() % rv.GetInt64()) case UintType: - y := rv.GetUint() - ok = y != 0 - if ok { - lv.SetUint(lv.GetUint() % y) + if rv.GetUint() == 0 { + return expt } + lv.SetUint(lv.GetUint() % rv.GetUint()) case Uint8Type: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetUint8(lv.GetUint8() % y) + if rv.GetUint8() == 0 { + return expt } + lv.SetUint8(lv.GetUint8() % rv.GetUint8()) case DataByteType: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetDataByte(lv.GetDataByte() % y) + if rv.GetUint8() == 0 { + return expt } + lv.SetDataByte(lv.GetDataByte() % rv.GetUint8()) case Uint16Type: - y := rv.GetUint16() - ok = y != 0 - if ok { - lv.SetUint16(lv.GetUint16() % y) + if rv.GetUint16() == 0 { + return expt } + lv.SetUint16(lv.GetUint16() % rv.GetUint16()) case Uint32Type: - y := rv.GetUint32() - ok = y != 0 - if ok { - lv.SetUint32(lv.GetUint32() % y) + if rv.GetUint32() == 0 { + return expt } + lv.SetUint32(lv.GetUint32() % rv.GetUint32()) case Uint64Type: - y := rv.GetUint64() - ok = y != 0 - if ok { - lv.SetUint64(lv.GetUint64() % y) + if rv.GetUint64() == 0 { + return expt } + lv.SetUint64(lv.GetUint64() % rv.GetUint64()) case BigintType, UntypedBigintType: - ok = rv.GetBigInt().Sign() != 0 - if ok { - lb := lv.GetBigInt() - lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) - lv.V = BigintValue{V: lb} + if rv.GetBigInt().Sign() == 0 { + return expt } + + lb := lv.GetBigInt() + lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) + lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( "operators %% and %%= not defined for %s", @@ -1099,9 +1037,6 @@ func remAssign(lv, rv *TypedValue) *Exception { )) } - if !ok { - return &Exception{Value: typedString("division by zero or overflow")} - } return nil } diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index c67a4be6ed5..708aae821ac 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -6,7 +6,6 @@ import ( "github.com/cockroachdb/apd/v3" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" - "github.com/gnolang/gno/tm2/pkg/overflow" ) func (m *Machine) doOpInc() { @@ -33,18 +32,16 @@ func (m *Machine) doOpInc() { // because it could be a type alias // type num int switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(overflow.Addp(lv.GetInt(), 1)) + lv.SetInt(lv.GetInt() + 1) case Int8Type: - lv.SetInt8(overflow.Add8p(lv.GetInt8(), 1)) + lv.SetInt8(lv.GetInt8() + 1) case Int16Type: - lv.SetInt16(overflow.Add16p(lv.GetInt16(), 1)) + lv.SetInt16(lv.GetInt16() + 1) case Int32Type: - lv.SetInt32(overflow.Add32p(lv.GetInt32(), 1)) + lv.SetInt32(lv.GetInt32() + 1) case Int64Type: - lv.SetInt64(overflow.Add64p(lv.GetInt64(), 1)) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() + 1) case UintType: lv.SetUint(lv.GetUint() + 1) case Uint8Type: @@ -105,18 +102,16 @@ func (m *Machine) doOpDec() { } } switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(overflow.Subp(lv.GetInt(), 1)) + lv.SetInt(lv.GetInt() - 1) case Int8Type: - lv.SetInt8(overflow.Sub8p(lv.GetInt8(), 1)) + lv.SetInt8(lv.GetInt8() - 1) case Int16Type: - lv.SetInt16(overflow.Sub16p(lv.GetInt16(), 1)) + lv.SetInt16(lv.GetInt16() - 1) case Int32Type: - lv.SetInt32(overflow.Sub32p(lv.GetInt32(), 1)) + lv.SetInt32(lv.GetInt32() - 1) case Int64Type: - lv.SetInt64(overflow.Sub64p(lv.GetInt64(), 1)) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() - 1) case UintType: lv.SetUint(lv.GetUint() - 1) case Uint8Type: diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 6e757561ef2..ab35fc6b6bf 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -896,6 +896,7 @@ var initOrder = [...]string{ "hash", "hash/adler32", "html", + "math/overflow", "math/rand", "path", "sort", diff --git a/gnovm/stdlibs/math/const_test.gno b/gnovm/stdlibs/math/const_test.gno index fbe59d61878..b892a12898b 100644 --- a/gnovm/stdlibs/math/const_test.gno +++ b/gnovm/stdlibs/math/const_test.gno @@ -31,76 +31,19 @@ func TestMaxUint(t *testing.T) { } func TestMaxInt(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int(math.MaxInt) - if v+1 == math.MinInt { - t.Errorf("int should overflow") + if v := int(math.MaxInt); v+1 != math.MinInt { + t.Errorf("MaxInt should wrap around to MinInt: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt8(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int8(math.MaxInt8) - if v+1 == math.MinInt8 { - t.Errorf("int8 should overflow") + if v := int8(math.MaxInt8); v+1 != math.MinInt8 { + t.Errorf("MaxInt8 should wrap around to MinInt8: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt16(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int16(math.MaxInt16) - if v+1 == math.MinInt16 { - t.Errorf("int16 should overflow") + if v := int16(math.MaxInt16); v+1 != math.MinInt16 { + t.Errorf("MaxInt16 should wrap around to MinInt16: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt32(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int32(math.MaxInt32) - if v+1 == math.MinInt32 { - t.Errorf("int32 should overflow") + if v := int32(math.MaxInt32); v+1 != math.MinInt32 { + t.Errorf("MaxInt32 should wrap around to MinInt32: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt64(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int64(math.MaxInt64) - if v+1 == math.MinInt64 { - t.Errorf("int64 should overflow") + if v := int64(math.MaxInt64); v+1 != math.MinInt64 { + t.Errorf("MaxInt64 should wrap around to MinInt64: %d", v+1) } - t.Errorf("expected panic did not occur") } diff --git a/gnovm/stdlibs/math/overflow/overflow.gno b/gnovm/stdlibs/math/overflow/overflow.gno new file mode 100644 index 00000000000..0bc2e03a522 --- /dev/null +++ b/gnovm/stdlibs/math/overflow/overflow.gno @@ -0,0 +1,501 @@ +// This is modified from https://github.com/JohnCGriffin/overflow (MIT). +// NOTE: there was a bug with the original Quotient* functions, and +// testing method. These have been fixed here, and tests ported to +// tests/files/maths_int*.go respectively. +// Note: moved over from p/demo/maths. + +/* +Package overflow offers overflow-checked integer arithmetic operations +for int, int32, and int64. Each of the operations returns a +result,bool combination. This was prompted by the need to know when +to flow into higher precision types from the math.big library. + +For instance, assuing a 64 bit machine: + +10 + 20 -> 30 +int(math.MaxInt64) + 1 -> -9223372036854775808 + +whereas + +overflow.Add(10,20) -> (30, true) +overflow.Add(math.MaxInt64,1) -> (0, false) + +Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized. + +If anybody wishes an unsigned version, submit a pull request for code +and new tests. +*/ +package overflow + +import "math" + +//go:generate ./overflow_template.sh + +func _is64Bit() bool { + maxU32 := uint(math.MaxUint32) + return ((maxU32 << 1) >> 1) == maxU32 +} + +/********** PARTIAL TEST COVERAGE FROM HERE DOWN ************* + +The only way that I could see to do this is a combination of +my normal 64 bit system and a GopherJS running on Node. My +understanding is that its ints are 32 bit. + +So, FEEL FREE to carefully review the code visually. + +*************************************************************/ + +// Unspecified size, i.e. normal signed int + +// Add sums two ints, returning the result and a boolean status. +func Add(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Add64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Add32(int32(a), int32(b)) + return int(r32), ok +} + +// Sub returns the difference of two ints and a boolean status. +func Sub(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Sub64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Sub32(int32(a), int32(b)) + return int(r32), ok +} + +// Mul returns the product of two ints and a boolean status. +func Mul(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Mul64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Mul32(int32(a), int32(b)) + return int(r32), ok +} + +// Div returns the quotient of two ints and a boolean status +func Div(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Div64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Div32(int32(a), int32(b)) + return int(r32), ok +} + +// Quo returns the quotient, remainder and status of two ints +func Quo(a, b int) (int, int, bool) { + if _is64Bit() { + q64, r64, ok := Quo64(int64(a), int64(b)) + return int(q64), int(r64), ok + } + q32, r32, ok := Quo32(int32(a), int32(b)) + return int(q32), int(r32), ok +} + +/************* Panic versions for int ****************/ + +// Addp returns the sum of two ints, panicking on overflow +func Addp(a, b int) int { + r, ok := Add(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Subp returns the difference of two ints, panicking on overflow. +func Subp(a, b int) int { + r, ok := Sub(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mulp returns the product of two ints, panicking on overflow. +func Mulp(a, b int) int { + r, ok := Mul(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Divp returns the quotient of two ints, panicking on overflow. +func Divp(a, b int) int { + r, ok := Div(a, b) + if !ok { + panic("division failure") + } + return r +} + +//---------------------------------------- +// This is generated code, created by overflow_template.sh executed +// by "go generate" + +// Add8 performs + operation on two int8 operands +// returning a result and status +func Add8(a, b int8) (int8, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add8p is the unchecked panicking version of Add8 +func Add8p(a, b int8) int8 { + r, ok := Add8(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub8 performs - operation on two int8 operands +// returning a result and status +func Sub8(a, b int8) (int8, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub8p is the unchecked panicking version of Sub8 +func Sub8p(a, b int8) int8 { + r, ok := Sub8(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul8 performs * operation on two int8 operands +// returning a result and status +func Mul8(a, b int8) (int8, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul8p is the unchecked panicking version of Mul8 +func Mul8p(a, b int8) int8 { + r, ok := Mul8(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div8 performs / operation on two int8 operands +// returning a result and status +func Div8(a, b int8) (int8, bool) { + q, _, ok := Quo8(a, b) + return q, ok +} + +// Div8p is the unchecked panicking version of Div8 +func Div8p(a, b int8) int8 { + r, ok := Div8(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo8 performs + operation on two int8 operands +// returning a quotient, a remainder and status +func Quo8(a, b int8) (int8, int8, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int8(math.MinInt8) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add16 performs + operation on two int16 operands +// returning a result and status +func Add16(a, b int16) (int16, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add16p is the unchecked panicking version of Add16 +func Add16p(a, b int16) int16 { + r, ok := Add16(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub16 performs - operation on two int16 operands +// returning a result and status +func Sub16(a, b int16) (int16, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub16p is the unchecked panicking version of Sub16 +func Sub16p(a, b int16) int16 { + r, ok := Sub16(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul16 performs * operation on two int16 operands +// returning a result and status +func Mul16(a, b int16) (int16, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul16p is the unchecked panicking version of Mul16 +func Mul16p(a, b int16) int16 { + r, ok := Mul16(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div16 performs / operation on two int16 operands +// returning a result and status +func Div16(a, b int16) (int16, bool) { + q, _, ok := Quo16(a, b) + return q, ok +} + +// Div16p is the unchecked panicking version of Div16 +func Div16p(a, b int16) int16 { + r, ok := Div16(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo16 performs + operation on two int16 operands +// returning a quotient, a remainder and status +func Quo16(a, b int16) (int16, int16, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int16(math.MinInt16) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add32 performs + operation on two int32 operands +// returning a result and status +func Add32(a, b int32) (int32, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add32p is the unchecked panicking version of Add32 +func Add32p(a, b int32) int32 { + r, ok := Add32(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub32 performs - operation on two int32 operands +// returning a result and status +func Sub32(a, b int32) (int32, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub32p is the unchecked panicking version of Sub32 +func Sub32p(a, b int32) int32 { + r, ok := Sub32(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul32 performs * operation on two int32 operands +// returning a result and status +func Mul32(a, b int32) (int32, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul32p is the unchecked panicking version of Mul32 +func Mul32p(a, b int32) int32 { + r, ok := Mul32(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div32 performs / operation on two int32 operands +// returning a result and status +func Div32(a, b int32) (int32, bool) { + q, _, ok := Quo32(a, b) + return q, ok +} + +// Div32p is the unchecked panicking version of Div32 +func Div32p(a, b int32) int32 { + r, ok := Div32(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo32 performs + operation on two int32 operands +// returning a quotient, a remainder and status +func Quo32(a, b int32) (int32, int32, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int32(math.MinInt32) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add64 performs + operation on two int64 operands +// returning a result and status +func Add64(a, b int64) (int64, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add64p is the unchecked panicking version of Add64 +func Add64p(a, b int64) int64 { + r, ok := Add64(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub64 performs - operation on two int64 operands +// returning a result and status +func Sub64(a, b int64) (int64, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub64p is the unchecked panicking version of Sub64 +func Sub64p(a, b int64) int64 { + r, ok := Sub64(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul64 performs * operation on two int64 operands +// returning a result and status +func Mul64(a, b int64) (int64, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul64p is the unchecked panicking version of Mul64 +func Mul64p(a, b int64) int64 { + r, ok := Mul64(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div64 performs / operation on two int64 operands +// returning a result and status +func Div64(a, b int64) (int64, bool) { + q, _, ok := Quo64(a, b) + return q, ok +} + +// Div64p is the unchecked panicking version of Div64 +func Div64p(a, b int64) int64 { + r, ok := Div64(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo64 performs + operation on two int64 operands +// returning a quotient, a remainder and status +func Quo64(a, b int64) (int64, int64, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt64 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} diff --git a/gnovm/stdlibs/math/overflow/overflow_test.gno b/gnovm/stdlibs/math/overflow/overflow_test.gno new file mode 100644 index 00000000000..b7881aec480 --- /dev/null +++ b/gnovm/stdlibs/math/overflow/overflow_test.gno @@ -0,0 +1,200 @@ +package overflow + +import ( + "math" + "testing" +) + +// sample all possibilities of 8 bit numbers +// by checking against 64 bit numbers + +func TestAlgorithms(t *testing.T) { + errors := 0 + + for a64 := int64(math.MinInt8); a64 <= int64(math.MaxInt8); a64++ { + for b64 := int64(math.MinInt8); b64 <= int64(math.MaxInt8) && errors < 10; b64++ { + + a8 := int8(a64) + b8 := int8(b64) + + if int64(a8) != a64 || int64(b8) != b64 { + t.Fatal("LOGIC FAILURE IN TEST") + } + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v + %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v - %v = %v instead of %v\n", + a8, b8, result, r64) + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v * %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // DIVISION + if b8 != 0 { + r64 := a64 / b64 + rem64 := a64 % b64 + + // now the verification + result, rem, ok := Quo8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v / %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if ok && int64(rem) != rem64 { + t.Errorf("failed to fail on %v %% %v = %v instead of %v\n", + a8, b8, rem, rem64) + errors++ + } + } + } + } +} + +func TestQuotient(t *testing.T) { + q, r, ok := Quo(100, 3) + if r != 1 || q != 33 || !ok { + t.Errorf("expected 100/3 => 33, r=1") + } + if _, _, ok = Quo(1, 0); ok { + t.Error("unexpected lack of failure") + } +} + +func TestLong(t *testing.T) { + if testing.Short() { + t.Skip() + } + + ctr := int64(0) + + for a64 := int64(math.MinInt16); a64 <= int64(math.MaxInt16); a64++ { + for b64 := int64(math.MinInt16); b64 <= int64(math.MaxInt16); b64++ { + a16 := int16(a64) + b16 := int16(b64) + if int64(a16) != a64 || int64(b16) != b64 { + panic("LOGIC FAILURE IN TEST") + } + ctr++ + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("add", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("add", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("sub", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("sub", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("mul", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("mul", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // DIVISION + if b16 != 0 { + r64 := a64 / b64 + + // now the verification + result, _, ok := Quo16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("quo", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("quo", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + } + } + println("done", ctr) +} diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index 679674e443e..47e88e238d2 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -1,6 +1,9 @@ package std -import "strconv" +import ( + "math/overflow" + "strconv" +) // NOTE: this is selectively copied over from tm2/pkgs/std/coin.go @@ -53,7 +56,13 @@ func (c Coin) IsEqual(other Coin) bool { // An invalid result panics. func (c Coin) Add(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - c.Amount += other.Amount + + sum, ok := overflow.Add64(c.Amount, other.Amount) + if !ok { + panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + + c.Amount = sum return c } @@ -63,7 +72,13 @@ func (c Coin) Add(other Coin) Coin { // An invalid result panics. func (c Coin) Sub(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - c.Amount -= other.Amount + + dff, ok := overflow.Sub64(c.Amount, other.Amount) + if !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + c.Amount = dff + return c } @@ -98,7 +113,10 @@ func NewCoins(coins ...Coin) Coins { for _, coin := range coins { if currentAmount, exists := coinMap[coin.Denom]; exists { - coinMap[coin.Denom] = currentAmount + coin.Amount + var ok bool + if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) + } } else { coinMap[coin.Denom] = coin.Amount } diff --git a/gnovm/tests/files/overflow0.gno b/gnovm/tests/files/overflow0.gno deleted file mode 100644 index 1313f064322..00000000000 --- a/gnovm/tests/files/overflow0.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int8 = -1<<7, -1, 0 - c = a / b // overflow: -128 instead of 128 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow1.gno b/gnovm/tests/files/overflow1.gno deleted file mode 100644 index a416e9a3498..00000000000 --- a/gnovm/tests/files/overflow1.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int16 = -1<<15, -1, 0 - c = a / b // overflow: -32768 instead of 32768 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow2.gno b/gnovm/tests/files/overflow2.gno deleted file mode 100644 index 353729bcdf2..00000000000 --- a/gnovm/tests/files/overflow2.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int32 = -1<<31, -1, 0 - c = a / b // overflow: -2147483648 instead of 2147483648 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow3.gno b/gnovm/tests/files/overflow3.gno deleted file mode 100644 index a09c59dfb03..00000000000 --- a/gnovm/tests/files/overflow3.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int64 = -1<<63, -1, 0 - c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow4.gno b/gnovm/tests/files/overflow4.gno deleted file mode 100644 index 26b05567b07..00000000000 --- a/gnovm/tests/files/overflow4.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int = -1<<63, -1, 0 - c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow5.gno b/gnovm/tests/files/overflow5.gno deleted file mode 100644 index ef7f976eb24..00000000000 --- a/gnovm/tests/files/overflow5.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int = -5, 7, 0 - c = a % b // 0 quotient triggers a false negative in gnolang/overflow - println(c) -} - -// Output: -// -5 diff --git a/gnovm/tests/files/recover14.gno b/gnovm/tests/files/recover14.gno index 3c96404fcbe..30a34ab291a 100644 --- a/gnovm/tests/files/recover14.gno +++ b/gnovm/tests/files/recover14.gno @@ -12,4 +12,4 @@ func main() { } // Output: -// recover: division by zero or overflow +// recover: division by zero diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 13e90836f36..025fe4b673e 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -70,8 +70,7 @@ func findDirs() (gitRoot string, relPath string, err error) { } p := wd for { - // .git is normally a directory, or a file in case of a git worktree. - if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { // make relPath relative to the git root rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / From 6909efaac0d4211c3ce894e052fd0d7b90112809 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 14 Jan 2025 17:23:39 +0100 Subject: [PATCH 32/75] refactor(gnovm): move go type checking to own file (#3491) --- gnovm/pkg/gnolang/go2gno.go | 173 ------------- gnovm/pkg/gnolang/go2gno_test.go | 352 ------------------------- gnovm/pkg/gnolang/gotypecheck.go | 179 +++++++++++++ gnovm/pkg/gnolang/gotypecheck_test.go | 359 ++++++++++++++++++++++++++ 4 files changed, 538 insertions(+), 525 deletions(-) create mode 100644 gnovm/pkg/gnolang/gotypecheck.go create mode 100644 gnovm/pkg/gnolang/gotypecheck_test.go diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 82d5c69b08b..4c9de87a6a7 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -31,24 +31,16 @@ package gnolang */ import ( - "bytes" "fmt" "go/ast" - "go/format" "go/parser" "go/token" - "go/types" "os" - "path" "reflect" - "slices" "strconv" - "strings" "github.com/davecgh/go-spew/spew" - "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/tm2/pkg/errors" - "go.uber.org/multierr" ) func MustReadFile(path string) *FileNode { @@ -483,171 +475,6 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } } -//---------------------------------------- -// type checking (using go/types) -// XXX move to gotypecheck.go. - -// MemPackageGetter implements the GetMemPackage() method. It is a subset of -// [Store], separated for ease of testing. -type MemPackageGetter interface { - GetMemPackage(path string) *gnovm.MemPackage -} - -// TypeCheckMemPackage performs type validation and checking on the given -// mempkg. To retrieve dependencies, it uses getter. -// -// The syntax checking is performed entirely using Go's go/types package. -// -// If format is true, the code will be automatically updated with the -// formatted source code. -func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { - return typeCheckMemPackage(mempkg, getter, false, format) -} - -// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], -// but allows re-declarations. -// -// Note: like TypeCheckMemPackage, this function ignores tests and filetests. -func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { - return typeCheckMemPackage(mempkg, getter, true, false) -} - -func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { - var errs error - imp := &gnoImporter{ - getter: getter, - cache: map[string]gnoImporterResult{}, - cfg: &types.Config{ - Error: func(err error) { - errs = multierr.Append(errs, err) - }, - }, - allowRedefinitions: testing, - } - imp.cfg.Importer = imp - - _, err := imp.parseCheckMemPackage(mempkg, format) - // prefer to return errs instead of err: - // err will generally contain only the first error encountered. - if errs != nil { - return errs - } - return err -} - -type gnoImporterResult struct { - pkg *types.Package - err error -} - -type gnoImporter struct { - getter MemPackageGetter - cache map[string]gnoImporterResult - cfg *types.Config - - // allow symbol redefinitions? (test standard libraries) - allowRedefinitions bool -} - -// Unused, but satisfies the Importer interface. -func (g *gnoImporter) Import(path string) (*types.Package, error) { - return g.ImportFrom(path, "", 0) -} - -type importNotFoundError string - -func (e importNotFoundError) Error() string { return "import not found: " + string(e) } - -// ImportFrom returns the imported package for the given import -// path when imported by a package file located in dir. -func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { - if pkg, ok := g.cache[path]; ok { - return pkg.pkg, pkg.err - } - mpkg := g.getter.GetMemPackage(path) - if mpkg == nil { - err := importNotFoundError(path) - g.cache[path] = gnoImporterResult{err: err} - return nil, err - } - fmt := false - result, err := g.parseCheckMemPackage(mpkg, fmt) - g.cache[path] = gnoImporterResult{pkg: result, err: err} - return result, err -} - -func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { - // This map is used to allow for function re-definitions, which are allowed - // in Gno (testing context) but not in Go. - // This map links each function identifier with a closure to remove its - // associated declaration. - var delFunc map[string]func() - if g.allowRedefinitions { - delFunc = make(map[string]func()) - } - - fset := token.NewFileSet() - files := make([]*ast.File, 0, len(mpkg.Files)) - var errs error - for _, file := range mpkg.Files { - // Ignore non-gno files. - // TODO: support filetest type checking. (should probably handle as each its - // own separate pkg, which should also be typechecked) - if !strings.HasSuffix(file.Name, ".gno") || - strings.HasSuffix(file.Name, "_test.gno") || - strings.HasSuffix(file.Name, "_filetest.gno") { - continue - } - - const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution - f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - - if delFunc != nil { - deleteOldIdents(delFunc, f) - } - - // enforce formatting - if fmt { - var buf bytes.Buffer - err = format.Node(&buf, fset, f) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - file.Body = buf.String() - } - - files = append(files, f) - } - if errs != nil { - return nil, errs - } - - return g.cfg.Check(mpkg.Path, fset, files, nil) -} - -func deleteOldIdents(idents map[string]func(), f *ast.File) { - for _, decl := range f.Decls { - fd, ok := decl.(*ast.FuncDecl) - if !ok || fd.Recv != nil { // ignore methods - continue - } - if del := idents[fd.Name.Name]; del != nil { - del() - } - decl := decl - idents[fd.Name.Name] = func() { - // NOTE: cannot use the index as a file may contain multiple decls to be removed, - // so removing one would make all "later" indexes wrong. - f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d }) - } - } -} - //---------------------------------------- // utility methods diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 8aba5d7f293..920c5acd9c8 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -4,10 +4,7 @@ import ( "fmt" "testing" - "github.com/gnolang/gno/gnovm" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/multierr" ) func TestParseForLoop(t *testing.T) { @@ -28,352 +25,3 @@ func main(){ fmt.Printf("AST:\n%#v\n\n", n) fmt.Printf("AST.String():\n%s\n", n.String()) } - -type mockPackageGetter []*gnovm.MemPackage - -func (mi mockPackageGetter) GetMemPackage(path string) *gnovm.MemPackage { - for _, pkg := range mi { - if pkg.Path == path { - return pkg - } - } - return nil -} - -type mockPackageGetterCounts struct { - mockPackageGetter - counts map[string]int -} - -func (mpg mockPackageGetterCounts) GetMemPackage(path string) *gnovm.MemPackage { - mpg.counts[path]++ - return mpg.mockPackageGetter.GetMemPackage(path) -} - -func TestTypeCheckMemPackage(t *testing.T) { - t.Parallel() - - // if len(ss) > 0, then multierr.Errors must decompose it in errors, and - // each error in order must contain the associated string. - errContains := func(s0 string, ss ...string) func(*testing.T, error) { - return func(t *testing.T, err error) { - t.Helper() - errs := multierr.Errors(err) - if len(errs) == 0 { - t.Errorf("expected an error, got nil") - return - } - want := len(ss) + 1 - if len(errs) != want { - t.Errorf("expected %d errors, got %d", want, len(errs)) - return - } - assert.ErrorContains(t, errs[0], s0) - for idx, err := range errs[1:] { - assert.ErrorContains(t, err, ss[idx]) - } - } - } - - type testCase struct { - name string - pkg *gnovm.MemPackage - getter MemPackageGetter - check func(*testing.T, error) - } - tt := []testCase{ - { - "Simple", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - type S struct{} - func A() S { return S{} } - func B() S { return A() }`, - }, - }, - }, - nil, - nil, - }, - { - "WrongReturn", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - type S struct{} - func A() S { return S{} } - func B() S { return 11 }`, - }, - }, - }, - nil, - errContains("cannot use 11"), - }, - { - "ParseError", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello! - func B() int { return 11 }`, - }, - }, - }, - nil, - errContains("found '!'"), - }, - { - "MultiError", - &gnovm.MemPackage{ - Name: "main", - Path: "gno.land/p/demo/main", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package main - func main() { - _, _ = 11 - return 88, 88 - }`, - }, - }, - }, - nil, - errContains("assignment mismatch", "too many return values"), - }, - { - "TestsIgnored", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - func B() int { return 11 }`, - }, - { - Name: "hello_test.gno", - Body: `This is not valid Gno code, but it doesn't matter because test - files are not checked.`, - }, - }, - }, - nil, - nil, - }, - { - "ImportFailed", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{}, - errContains("import not found: std"), - }, - { - "ImportSucceeded", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "std", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package std - type Address string`, - }, - }, - }, - }, - nil, - }, - { - "ImportBadIdent", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "a_completely_different_identifier", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package a_completely_different_identifier - type Address string`, - }, - }, - }, - }, - errContains("undefined: std", "a_completely_different_identifier and not used"), - }, - } - - cacheMpg := mockPackageGetterCounts{ - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "bye", - Path: "bye", - Files: []*gnovm.MemFile{ - { - Name: "bye.gno", - Body: ` - package bye - import "std" - func Bye() std.Address { return "bye" }`, - }, - }, - }, - &gnovm.MemPackage{ - Name: "std", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package std - type Address string`, - }, - }, - }, - }, - make(map[string]int), - } - - tt = append(tt, testCase{ - "ImportWithCache", - // This test will make use of the importer's internal cache for package `std`. - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import ( - "std" - "bye" - ) - func Hello() std.Address { return bye.Bye() }`, - }, - }, - }, - cacheMpg, - func(t *testing.T, err error) { - t.Helper() - require.NoError(t, err) - assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) - }, - }) - - for _, tc := range tt { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - format := false - err := TypeCheckMemPackage(tc.pkg, tc.getter, format) - if tc.check == nil { - assert.NoError(t, err) - } else { - tc.check(t, err) - } - }) - } -} - -func TestTypeCheckMemPackage_format(t *testing.T) { - t.Parallel() - - input := ` - package hello - func Hello(name string) string {return "hello" + name -} - - - -` - - pkg := &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: input, - }, - }, - } - - mpkgGetter := mockPackageGetter{} - format := false - err := TypeCheckMemPackage(pkg, mpkgGetter, format) - assert.NoError(t, err) - assert.Equal(t, input, pkg.Files[0].Body) // unchanged - - expected := `package hello - -func Hello(name string) string { - return "hello" + name -} -` - - format = true - err = TypeCheckMemPackage(pkg, mpkgGetter, format) - assert.NoError(t, err) - assert.NotEqual(t, input, pkg.Files[0].Body) - assert.Equal(t, expected, pkg.Files[0].Body) -} diff --git a/gnovm/pkg/gnolang/gotypecheck.go b/gnovm/pkg/gnolang/gotypecheck.go new file mode 100644 index 00000000000..8f86deb3dc5 --- /dev/null +++ b/gnovm/pkg/gnolang/gotypecheck.go @@ -0,0 +1,179 @@ +package gnolang + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "go/types" + "path" + "slices" + "strings" + + "github.com/gnolang/gno/gnovm" + "go.uber.org/multierr" +) + +// type checking (using go/types) + +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// [Store], separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *gnovm.MemPackage +} + +// TypeCheckMemPackage performs type validation and checking on the given +// mempkg. To retrieve dependencies, it uses getter. +// +// The syntax checking is performed entirely using Go's go/types package. +// +// If format is true, the code will be automatically updated with the +// formatted source code. +func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { + return typeCheckMemPackage(mempkg, getter, false, format) +} + +// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], +// but allows re-declarations. +// +// Note: like TypeCheckMemPackage, this function ignores tests and filetests. +func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { + return typeCheckMemPackage(mempkg, getter, true, false) +} + +func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { + var errs error + imp := &gnoImporter{ + getter: getter, + cache: map[string]gnoImporterResult{}, + cfg: &types.Config{ + Error: func(err error) { + errs = multierr.Append(errs, err) + }, + }, + allowRedefinitions: testing, + } + imp.cfg.Importer = imp + + _, err := imp.parseCheckMemPackage(mempkg, format) + // prefer to return errs instead of err: + // err will generally contain only the first error encountered. + if errs != nil { + return errs + } + return err +} + +type gnoImporterResult struct { + pkg *types.Package + err error +} + +type gnoImporter struct { + getter MemPackageGetter + cache map[string]gnoImporterResult + cfg *types.Config + + // allow symbol redefinitions? (test standard libraries) + allowRedefinitions bool +} + +// Unused, but satisfies the Importer interface. +func (g *gnoImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +type importNotFoundError string + +func (e importNotFoundError) Error() string { return "import not found: " + string(e) } + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + return pkg.pkg, pkg.err + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + err := importNotFoundError(path) + g.cache[path] = gnoImporterResult{err: err} + return nil, err + } + fmt := false + result, err := g.parseCheckMemPackage(mpkg, fmt) + g.cache[path] = gnoImporterResult{pkg: result, err: err} + return result, err +} + +func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { + // This map is used to allow for function re-definitions, which are allowed + // in Gno (testing context) but not in Go. + // This map links each function identifier with a closure to remove its + // associated declaration. + var delFunc map[string]func() + if g.allowRedefinitions { + delFunc = make(map[string]func()) + } + + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) + var errs error + for _, file := range mpkg.Files { + // Ignore non-gno files. + // TODO: support filetest type checking. (should probably handle as each its + // own separate pkg, which should also be typechecked) + if !strings.HasSuffix(file.Name, ".gno") || + strings.HasSuffix(file.Name, "_test.gno") || + strings.HasSuffix(file.Name, "_filetest.gno") { + continue + } + + const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution + f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + + if delFunc != nil { + deleteOldIdents(delFunc, f) + } + + // enforce formatting + if fmt { + var buf bytes.Buffer + err = format.Node(&buf, fset, f) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + file.Body = buf.String() + } + + files = append(files, f) + } + if errs != nil { + return nil, errs + } + + return g.cfg.Check(mpkg.Path, fset, files, nil) +} + +func deleteOldIdents(idents map[string]func(), f *ast.File) { + for _, decl := range f.Decls { + fd, ok := decl.(*ast.FuncDecl) + if !ok || fd.Recv != nil { // ignore methods + continue + } + if del := idents[fd.Name.Name]; del != nil { + del() + } + decl := decl + idents[fd.Name.Name] = func() { + // NOTE: cannot use the index as a file may contain multiple decls to be removed, + // so removing one would make all "later" indexes wrong. + f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d }) + } + } +} diff --git a/gnovm/pkg/gnolang/gotypecheck_test.go b/gnovm/pkg/gnolang/gotypecheck_test.go new file mode 100644 index 00000000000..259a1bc3e78 --- /dev/null +++ b/gnovm/pkg/gnolang/gotypecheck_test.go @@ -0,0 +1,359 @@ +package gnolang + +import ( + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" +) + +type mockPackageGetter []*gnovm.MemPackage + +func (mi mockPackageGetter) GetMemPackage(path string) *gnovm.MemPackage { + for _, pkg := range mi { + if pkg.Path == path { + return pkg + } + } + return nil +} + +type mockPackageGetterCounts struct { + mockPackageGetter + counts map[string]int +} + +func (mpg mockPackageGetterCounts) GetMemPackage(path string) *gnovm.MemPackage { + mpg.counts[path]++ + return mpg.mockPackageGetter.GetMemPackage(path) +} + +func TestTypeCheckMemPackage(t *testing.T) { + t.Parallel() + + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and + // each error in order must contain the associated string. + errContains := func(s0 string, ss ...string) func(*testing.T, error) { + return func(t *testing.T, err error) { + t.Helper() + errs := multierr.Errors(err) + if len(errs) == 0 { + t.Errorf("expected an error, got nil") + return + } + want := len(ss) + 1 + if len(errs) != want { + t.Errorf("expected %d errors, got %d", want, len(errs)) + return + } + assert.ErrorContains(t, errs[0], s0) + for idx, err := range errs[1:] { + assert.ErrorContains(t, err, ss[idx]) + } + } + } + + type testCase struct { + name string + pkg *gnovm.MemPackage + getter MemPackageGetter + check func(*testing.T, error) + } + tt := []testCase{ + { + "Simple", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + }, + }, + }, + nil, + nil, + }, + { + "WrongReturn", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + }, + }, + }, + nil, + errContains("cannot use 11"), + }, + { + "ParseError", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello! + func B() int { return 11 }`, + }, + }, + }, + nil, + errContains("found '!'"), + }, + { + "MultiError", + &gnovm.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/main", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package main + func main() { + _, _ = 11 + return 88, 88 + }`, + }, + }, + }, + nil, + errContains("assignment mismatch", "too many return values"), + }, + { + "TestsIgnored", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + func B() int { return 11 }`, + }, + { + Name: "hello_test.gno", + Body: `This is not valid Gno code, but it doesn't matter because test + files are not checked.`, + }, + }, + }, + nil, + nil, + }, + { + "ImportFailed", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{}, + errContains("import not found: std"), + }, + { + "ImportSucceeded", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "std", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package std + type Address string`, + }, + }, + }, + }, + nil, + }, + { + "ImportBadIdent", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "a_completely_different_identifier", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package a_completely_different_identifier + type Address string`, + }, + }, + }, + }, + errContains("undefined: std", "a_completely_different_identifier and not used"), + }, + } + + cacheMpg := mockPackageGetterCounts{ + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "bye", + Path: "bye", + Files: []*gnovm.MemFile{ + { + Name: "bye.gno", + Body: ` + package bye + import "std" + func Bye() std.Address { return "bye" }`, + }, + }, + }, + &gnovm.MemPackage{ + Name: "std", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package std + type Address string`, + }, + }, + }, + }, + make(map[string]int), + } + + tt = append(tt, testCase{ + "ImportWithCache", + // This test will make use of the importer's internal cache for package `std`. + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + }, + }, + }, + cacheMpg, + func(t *testing.T, err error) { + t.Helper() + require.NoError(t, err) + assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) + }, + }) + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + format := false + err := TypeCheckMemPackage(tc.pkg, tc.getter, format) + if tc.check == nil { + assert.NoError(t, err) + } else { + tc.check(t, err) + } + }) + } +} + +func TestTypeCheckMemPackage_format(t *testing.T) { + t.Parallel() + + input := ` + package hello + func Hello(name string) string {return "hello" + name +} + + + +` + + pkg := &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: input, + }, + }, + } + + mpkgGetter := mockPackageGetter{} + format := false + err := TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.Equal(t, input, pkg.Files[0].Body) // unchanged + + expected := `package hello + +func Hello(name string) string { + return "hello" + name +} +` + + format = true + err = TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.NotEqual(t, input, pkg.Files[0].Body) + assert.Equal(t, expected, pkg.Files[0].Body) +} From b3d4855050496222e565377ce2422677a7f1a961 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 14 Jan 2025 18:33:54 +0100 Subject: [PATCH 33/75] test(gnovm): move tests in values_conversions_test.go to filetests (#3490) --- gnovm/pkg/gnolang/values_conversions_test.go | 132 ------------------- gnovm/tests/files/overflow10.gno | 8 ++ gnovm/tests/files/overflow11.gno | 8 ++ gnovm/tests/files/overflow12.gno | 8 ++ gnovm/tests/files/overflow13.gno | 8 ++ gnovm/tests/files/overflow14.gno | 8 ++ gnovm/tests/files/overflow15.gno | 9 ++ gnovm/tests/files/overflow16.gno | 9 ++ gnovm/tests/files/overflow6.gno | 8 ++ gnovm/tests/files/overflow7.gno | 8 ++ gnovm/tests/files/overflow8.gno | 8 ++ gnovm/tests/files/overflow9.gno | 8 ++ 12 files changed, 90 insertions(+), 132 deletions(-) create mode 100644 gnovm/tests/files/overflow10.gno create mode 100644 gnovm/tests/files/overflow11.gno create mode 100644 gnovm/tests/files/overflow12.gno create mode 100644 gnovm/tests/files/overflow13.gno create mode 100644 gnovm/tests/files/overflow14.gno create mode 100644 gnovm/tests/files/overflow15.gno create mode 100644 gnovm/tests/files/overflow16.gno create mode 100644 gnovm/tests/files/overflow6.gno create mode 100644 gnovm/tests/files/overflow7.gno create mode 100644 gnovm/tests/files/overflow8.gno create mode 100644 gnovm/tests/files/overflow9.gno diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index 5538e973bdc..b8b06df4228 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -2,7 +2,6 @@ package gnolang import ( "math" - "strings" "testing" "github.com/cockroachdb/apd/v3" @@ -27,134 +26,3 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { require.True(t, softfloat.Feq64(dst.GetFloat64(), 0)) } - -func TestBitShiftingOverflow(t *testing.T) { - t.Parallel() - - testFunc := func(source, msg string) { - defer func() { - if len(msg) == 0 { - return - } - - r := recover() - - if r == nil { - t.Fail() - } - - err := r.(*PreprocessError) - c := strings.Contains(err.Error(), msg) - if !c { - t.Fatalf(`expected "%s", got "%s"`, msg, r) - } - }() - - m := NewMachine("test", nil) - - n := MustParseFile("main.go", source) - m.RunFiles(n) - m.RunMain() - } - - type cases struct { - source string - msg string - } - - tests := []cases{ - { - `package test - -func main() { - const a = int32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a1 = int8(1) << 8 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a2 = int16(1) << 16 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a3 = int32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a4 = int64(1) << 65 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b1 = uint8(1) << 8 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b2 = uint16(1) << 16 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b3 = uint32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b4 = uint64(1) << 65 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - - func main() { - const c1 = 1 << 128 - }`, - ``, - }, - { - `package test - - func main() { - const c1 = 1 << 128 - println(c1) - }`, - `test/main.go:5:4: bigint overflows target kind`, - }, - } - - for _, tc := range tests { - testFunc(tc.source, tc.msg) - } -} diff --git a/gnovm/tests/files/overflow10.gno b/gnovm/tests/files/overflow10.gno new file mode 100644 index 00000000000..c3224eaf471 --- /dev/null +++ b/gnovm/tests/files/overflow10.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a4 = int64(1) << 65 +} + +// Error: +// main/files/overflow10.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow11.gno b/gnovm/tests/files/overflow11.gno new file mode 100644 index 00000000000..bac883f104f --- /dev/null +++ b/gnovm/tests/files/overflow11.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b1 = uint8(1) << 8 +} + +// Error: +// main/files/overflow11.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow12.gno b/gnovm/tests/files/overflow12.gno new file mode 100644 index 00000000000..52ca8fc80d4 --- /dev/null +++ b/gnovm/tests/files/overflow12.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b2 = uint16(1) << 16 +} + +// Error: +// main/files/overflow12.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow13.gno b/gnovm/tests/files/overflow13.gno new file mode 100644 index 00000000000..40b6885b3ff --- /dev/null +++ b/gnovm/tests/files/overflow13.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b3 = uint32(1) << 33 +} + +// Error: +// main/files/overflow13.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow14.gno b/gnovm/tests/files/overflow14.gno new file mode 100644 index 00000000000..0da66bb7b9d --- /dev/null +++ b/gnovm/tests/files/overflow14.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b4 = uint64(1) << 65 +} + +// Error: +// main/files/overflow14.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow15.gno b/gnovm/tests/files/overflow15.gno new file mode 100644 index 00000000000..f41848d5d21 --- /dev/null +++ b/gnovm/tests/files/overflow15.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const c1 = 1 << 128 + println(uint64(c1 >> 65)) +} + +// Output: +// 9223372036854775808 diff --git a/gnovm/tests/files/overflow16.gno b/gnovm/tests/files/overflow16.gno new file mode 100644 index 00000000000..36cc7689529 --- /dev/null +++ b/gnovm/tests/files/overflow16.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const c1 = 1 << 128 + println(c1) +} + +// Error: +// main/files/overflow16.gno:5:2: bigint overflows target kind diff --git a/gnovm/tests/files/overflow6.gno b/gnovm/tests/files/overflow6.gno new file mode 100644 index 00000000000..b9c49027336 --- /dev/null +++ b/gnovm/tests/files/overflow6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a = int32(1) << 33 +} + +// Error: +// main/files/overflow6.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow7.gno b/gnovm/tests/files/overflow7.gno new file mode 100644 index 00000000000..28202811fca --- /dev/null +++ b/gnovm/tests/files/overflow7.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a1 = int8(1) << 8 +} + +// Error: +// main/files/overflow7.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow8.gno b/gnovm/tests/files/overflow8.gno new file mode 100644 index 00000000000..f25fb0aa833 --- /dev/null +++ b/gnovm/tests/files/overflow8.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a2 = int16(1) << 16 +} + +// Error: +// main/files/overflow8.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow9.gno b/gnovm/tests/files/overflow9.gno new file mode 100644 index 00000000000..5eae14b67aa --- /dev/null +++ b/gnovm/tests/files/overflow9.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a3 = int32(1) << 33 +} + +// Error: +// main/files/overflow9.gno:3:1: constant overflows From c79c16dc0d33d260f876b7e034531e180cc0ac86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:21:01 +0100 Subject: [PATCH 34/75] chore(deps): bump anchore/sbom-action from 0.17.8 to 0.17.9 in the actions group (#3476) Bumps the actions group with 1 update: [anchore/sbom-action](https://github.com/anchore/sbom-action). Updates `anchore/sbom-action` from 0.17.8 to 0.17.9
        Release notes

        Sourced from anchore/sbom-action's releases.

        v0.17.9

        Changes in v0.17.9

        Commits

        [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anchore/sbom-action&package-manager=github_actions&previous-version=0.17.8&new-version=0.17.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/releaser-master.yml | 2 +- .github/workflows/releaser-nightly.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 6e3eed31914..7c81789b060 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -27,7 +27,7 @@ jobs: cache: true - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.8 + - uses: anchore/sbom-action/download-syft@v0.17.9 - uses: docker/login-action@v3 with: diff --git a/.github/workflows/releaser-nightly.yml b/.github/workflows/releaser-nightly.yml index 4f6e636af1b..47b6cabb223 100644 --- a/.github/workflows/releaser-nightly.yml +++ b/.github/workflows/releaser-nightly.yml @@ -24,7 +24,7 @@ jobs: cache: true - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.8 + - uses: anchore/sbom-action/download-syft@v0.17.9 - uses: docker/login-action@v3 with: From 3adb2ac390f60467e6d77c3aa7ff4507cb37e0e8 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:29:49 +0100 Subject: [PATCH 35/75] feat(gnovm): align Gno constant handling with Go specifications (#2828) Related Issues: Closes #2628 This PR aims to align Gno's constant handling with the Go specification regarding constant expressions (see [Go Specification on Constants](https://go.dev/ref/spec#Constant_expressions)). 1. **Primitive Type Requirement** - **Should Work:** ```go const t = 1 ``` - **Should Return an Error:** ```go const t = []string{"1"} ``` **Error:** ``` (const (slice[("1" string)] []string)) (value of type []string) is not constant ``` 2. **Function Calls Disallowed** Only built-in functions should be allowed. - **Should Work:** ```go const t = len("s") ``` - **Should Return an Error:** ```go func v() string { return "" } const t = v() ``` **Error:** ``` v() (value of type string) is not constant ``` 3. **Constant Operands Requirement** Constant expressions may contain only constant operands and are evaluated at compile time. - **Should Work:** ```go const t = 1 const v = t ``` - **Should Raise an Error:** ```go t := 1 const v = t ``` **Error:** ``` t (variable of type int) is not constant ``` 4. **Type Assertion Forbidden** - This code: ```go var i interface{} = 1 const t, ok = i.(int) ``` - **Should Raise This Error:** ``` i.(int) (comma, ok expression of type int) is not constant ``` 5. **Index Expression Forbidden** - This code: ```go var i = []string{} const t, ok = i[0] ``` - **Should Return This Error:** ``` i[0] (variable of type string) is not constant ```
        Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
        --- .../r/demo/keystore/keystore_test.gno | 4 - gnovm/pkg/gnolang/preprocess.go | 1 + gnovm/pkg/gnolang/type_check.go | 145 ++++++++++++++++++ gnovm/tests/files/const23.gno | 11 ++ gnovm/tests/files/const24.gno | 82 ++++++++++ gnovm/tests/files/const25.gno | 11 ++ gnovm/tests/files/const26.gno | 15 ++ gnovm/tests/files/const27.gno | 16 ++ gnovm/tests/files/const28.gno | 12 ++ gnovm/tests/files/const29.gno | 12 ++ gnovm/tests/files/const30.gno | 15 ++ gnovm/tests/files/const31.gno | 15 ++ gnovm/tests/files/const32.gno | 11 ++ gnovm/tests/files/const33.gno | 12 ++ gnovm/tests/files/const34.gno | 10 ++ gnovm/tests/files/const35.gno | 12 ++ gnovm/tests/files/const36.gno | 13 ++ gnovm/tests/files/const37_native.gno | 10 ++ gnovm/tests/files/const37_stdlibs.gno | 10 ++ gnovm/tests/files/const38.gno | 11 ++ gnovm/tests/files/const39.gno | 14 ++ gnovm/tests/files/const40.gno | 8 + gnovm/tests/files/const41.gno | 8 + gnovm/tests/files/const42.gno | 8 + gnovm/tests/files/const43.gno | 10 ++ gnovm/tests/files/const44.gno | 10 ++ gnovm/tests/files/const45_a.gno | 14 ++ gnovm/tests/files/const45_b.gno | 14 ++ gnovm/tests/files/const46.gno | 10 ++ gnovm/tests/files/const47.gno | 10 ++ gnovm/tests/files/const48.gno | 9 ++ gnovm/tests/files/const49.gno | 16 ++ gnovm/tests/files/const50.gno | 12 ++ 33 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 gnovm/tests/files/const23.gno create mode 100644 gnovm/tests/files/const24.gno create mode 100644 gnovm/tests/files/const25.gno create mode 100644 gnovm/tests/files/const26.gno create mode 100644 gnovm/tests/files/const27.gno create mode 100644 gnovm/tests/files/const28.gno create mode 100644 gnovm/tests/files/const29.gno create mode 100644 gnovm/tests/files/const30.gno create mode 100644 gnovm/tests/files/const31.gno create mode 100644 gnovm/tests/files/const32.gno create mode 100644 gnovm/tests/files/const33.gno create mode 100644 gnovm/tests/files/const34.gno create mode 100644 gnovm/tests/files/const35.gno create mode 100644 gnovm/tests/files/const36.gno create mode 100644 gnovm/tests/files/const37_native.gno create mode 100644 gnovm/tests/files/const37_stdlibs.gno create mode 100644 gnovm/tests/files/const38.gno create mode 100644 gnovm/tests/files/const39.gno create mode 100644 gnovm/tests/files/const40.gno create mode 100644 gnovm/tests/files/const41.gno create mode 100644 gnovm/tests/files/const42.gno create mode 100644 gnovm/tests/files/const43.gno create mode 100644 gnovm/tests/files/const44.gno create mode 100644 gnovm/tests/files/const45_a.gno create mode 100644 gnovm/tests/files/const45_b.gno create mode 100644 gnovm/tests/files/const46.gno create mode 100644 gnovm/tests/files/const47.gno create mode 100644 gnovm/tests/files/const48.gno create mode 100644 gnovm/tests/files/const49.gno create mode 100644 gnovm/tests/files/const50.gno diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index 41597016ea3..9b5fafa2f95 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -11,10 +11,6 @@ import ( ) func TestRender(t *testing.T) { - // https://github.com/gnolang/gno/pull/3375 changed const -> var : - // For some reason non native functions fails on constants with - // constant overflows (code=2), this does not happens when using a variable - // TODO: check this issue after and if this PR is merged var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ddfd1851989..ffa0f518331 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2294,6 +2294,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // NOTE: may or may not be a *ConstExpr, // but if not, make one now. for i, vx := range n.Values { + assertValidConstExpr(store, last, n, vx) n.Values[i] = evalConst(store, last, vx) } } else { diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 70166116fcc..f96cb71e4b6 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,151 @@ func assertAssignableTo(n Node, xt, dt Type, autoNative bool) { } } +func assertValidConstExpr(store Store, last BlockNode, n *ValueDecl, expr Expr) { + if n.Type != nil { + nt := evalStaticType(store, last, n.Type) + if xnt, ok := nt.(*NativeType); ok { + nt = go2GnoBaseType(xnt.Type) + } + + if _, ok := baseOf(nt).(PrimitiveType); !ok { + panic(fmt.Sprintf("invalid constant type %s", nt.String())) + } + } + + nt := evalStaticTypeOf(store, last, expr) + if xnt, ok := nt.(*NativeType); ok { + nt = go2GnoBaseType(xnt.Type) + } + + if nt == nil { + panic(fmt.Sprintf("%s (variable of type nil) is not constant", expr)) + } + + if _, ok := baseOf(nt).(PrimitiveType); !ok { + panic(fmt.Sprintf("%s (variable of type %s) is not constant", expr, nt)) + } + + assertValidConstValue(store, last, expr) +} + +func assertValidConstValue(store Store, last BlockNode, currExpr Expr) { +Main: + switch currExpr := currExpr.(type) { + case *ConstExpr: + case *UnaryExpr: + // *, & is filter out previously since they are not primitive + assertValidConstValue(store, last, currExpr.X) + case *TypeAssertExpr: + ty := evalStaticTypeOf(store, last, currExpr) + if _, ok := ty.(*TypeType); ok { + ty = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (comma, ok expression of type %s) is not constant", currExpr.String(), currExpr.Type)) + case *CallExpr: + ift := evalStaticTypeOf(store, last, currExpr.Func) + switch baseOf(ift).(type) { + case *FuncType: + tup := evalStaticTypeOfRaw(store, last, currExpr).(*tupleType) + + // check for built-in functions + if cx, ok := currExpr.Func.(*ConstExpr); ok { + if fv, ok := cx.V.(*FuncValue); ok { + if fv.PkgPath == uversePkgPath { + // TODO: should support min, max, real, imag + switch { + case fv.Name == "len": + at := evalStaticTypeOf(store, last, currExpr.Args[0]) + if _, ok := baseOf(at).(*ArrayType); ok { + // ok + break Main + } + assertValidConstValue(store, last, currExpr.Args[0]) + break Main + case fv.Name == "cap": + at := evalStaticTypeOf(store, last, currExpr.Args[0]) + if _, ok := baseOf(at).(*ArrayType); ok { + // ok + break Main + } + assertValidConstValue(store, last, currExpr.Args[0]) + break Main + } + } + } + } + + switch { + case len(tup.Elts) == 0: + panic(fmt.Sprintf("%s (no value) used as value", currExpr.String())) + case len(tup.Elts) == 1: + panic(fmt.Sprintf("%s (value of type %s) is not constant", currExpr.String(), tup.Elts[0])) + default: + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", currExpr.String(), tup.Elts)) + } + case *TypeType: + for _, arg := range currExpr.Args { + assertValidConstValue(store, last, arg) + } + case *NativeType: + // Todo: should add a test after the fix of https://github.com/gnolang/gno/issues/3006 + ty := evalStaticType(store, last, currExpr.Func) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ty)) + default: + panic(fmt.Sprintf( + "unexpected func type %v (%v)", + ift, reflect.TypeOf(ift))) + } + case *BinaryExpr: + assertValidConstValue(store, last, currExpr.Left) + assertValidConstValue(store, last, currExpr.Right) + case *SelectorExpr: + xt := evalStaticTypeOf(store, last, currExpr.X) + switch xt := xt.(type) { + case *PackageType: + var pv *PackageValue + if cx, ok := currExpr.X.(*ConstExpr); ok { + // NOTE: *Machine.TestMemPackage() needs this + // to pass in an imported package as *ConstEzpr. + pv = cx.V.(*PackageValue) + } else { + // otherwise, packages can only be referred to by + // *NameExprs, and cannot be copied. + pvc := evalConst(store, last, currExpr.X) + pv_, ok := pvc.V.(*PackageValue) + if !ok { + panic(fmt.Sprintf( + "missing package in selector expr %s", + currExpr.String())) + } + pv = pv_ + } + if pv.GetBlock(store).Source.GetIsConst(store, currExpr.Sel) { + break Main + } + + tt := pv.GetBlock(store).Source.GetStaticTypeOf(store, currExpr.Sel) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), tt)) + case *PointerType, *DeclaredType, *StructType, *InterfaceType, *TypeType, *NativeType: + ty := evalStaticTypeOf(store, last, currExpr) + if _, ok := ty.(*TypeType); ok { + ty = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ty)) + default: + panic(fmt.Sprintf( + "unexpected selector expression type %v", + reflect.TypeOf(xt))) + } + default: + ift := evalStaticTypeOf(store, last, currExpr) + if _, ok := ift.(*TypeType); ok { + ift = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ift)) + } +} + // checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. func checkValDefineMismatch(n Node) { var ( diff --git a/gnovm/tests/files/const23.gno b/gnovm/tests/files/const23.gno new file mode 100644 index 00000000000..f445c3e8eb2 --- /dev/null +++ b/gnovm/tests/files/const23.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t []string = []string{} + fmt.Println(t) +} + +// Error: +// main/files/const23.gno:6:8: invalid constant type []string diff --git a/gnovm/tests/files/const24.gno b/gnovm/tests/files/const24.gno new file mode 100644 index 00000000000..d82c50c86aa --- /dev/null +++ b/gnovm/tests/files/const24.gno @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + const a int = 1_000_000 + const b byte = byte(1) + const c float64 = 1_000_000.000 + const d string = "Hello, World!" + const e rune = 'a' + const g bool = true + const h uint = 1_000 + const i int8 = 1 + const j int16 = 1 + const k int32 = 1 + const l int64 = 1 + const m uint8 = 1 + const n uint16 = 1 + const o uint32 = 1 + const p uint64 = 1 + const r float32 = 1_000_000.000 + const s = r + const t = len("s") + const u = 1 + len("s") + 3 + ars := [10]string{} + const v = len(ars) + const w = cap(ars) + const x = time.Second + const y = +len("ay") + + fmt.Println(a) + fmt.Println(b) + fmt.Println(c) + fmt.Println(d) + fmt.Println(e) + fmt.Println(g) + fmt.Println(h) + fmt.Println(i) + fmt.Println(j) + fmt.Println(k) + fmt.Println(l) + fmt.Println(m) + fmt.Println(n) + fmt.Println(o) + fmt.Println(p) + fmt.Println(r) + fmt.Println(s) + fmt.Println(t) + fmt.Println(u) + fmt.Println(v) + fmt.Println(w) + println(x) + fmt.Println(y) +} + +// Output: +// 1000000 +// 1 +// 1e+06 +// Hello, World! +// 97 +// true +// 1000 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1e+06 +// 1e+06 +// 1 +// 5 +// 10 +// 10 +// 1s +// 2 diff --git a/gnovm/tests/files/const25.gno b/gnovm/tests/files/const25.gno new file mode 100644 index 00000000000..64e0358bdef --- /dev/null +++ b/gnovm/tests/files/const25.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = []string{"1"} + fmt.Println(t) +} + +// Error: +// main/files/const25.gno:6:8: [](const-type string){(const ("1" string))} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const26.gno b/gnovm/tests/files/const26.gno new file mode 100644 index 00000000000..a1533e98c57 --- /dev/null +++ b/gnovm/tests/files/const26.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const26.gno:10:8: v() (value of type string) is not constant diff --git a/gnovm/tests/files/const27.gno b/gnovm/tests/files/const27.gno new file mode 100644 index 00000000000..4be731e16a7 --- /dev/null +++ b/gnovm/tests/files/const27.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + var i interface{} = 1 + const t, ok = i.(int) + fmt.Println(t, ok) +} + +// Error: +// main/files/const27.gno:11:8: i.((const-type int)) (comma, ok expression of type (const-type int)) is not constant diff --git a/gnovm/tests/files/const28.gno b/gnovm/tests/files/const28.gno new file mode 100644 index 00000000000..e4762c3205c --- /dev/null +++ b/gnovm/tests/files/const28.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + var s []string = []string{"1"} + const t, ok = s[0] + fmt.Println(t, ok) +} + +// Error: +// main/files/const28.gno:7:8: s[(const (0 int))] (variable of type string) is not constant diff --git a/gnovm/tests/files/const29.gno b/gnovm/tests/files/const29.gno new file mode 100644 index 00000000000..41d8d0a816d --- /dev/null +++ b/gnovm/tests/files/const29.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + s := "1" + const t = s + fmt.Println(t) +} + +// Error: +// main/files/const29.gno:7:8: s (variable of type string) is not constant diff --git a/gnovm/tests/files/const30.gno b/gnovm/tests/files/const30.gno new file mode 100644 index 00000000000..4a166013cdc --- /dev/null +++ b/gnovm/tests/files/const30.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() { + return +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const30.gno:10:8: v (no value) used as value diff --git a/gnovm/tests/files/const31.gno b/gnovm/tests/files/const31.gno new file mode 100644 index 00000000000..e37577789e6 --- /dev/null +++ b/gnovm/tests/files/const31.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() (string, string) { + return "", "" +} + +func main() { + const t, v = v() + fmt.Println(t) +} + +// Error: +// main/files/const31.gno:10:8: (const (v func()( string, string)))() (variable of type (string,string)) is not constant diff --git a/gnovm/tests/files/const32.gno b/gnovm/tests/files/const32.gno new file mode 100644 index 00000000000..83d3ae5e73c --- /dev/null +++ b/gnovm/tests/files/const32.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = 1 + 2 + len([]string{}) + fmt.Println(t) +} + +// Error: +// main/files/const32.gno:6:8: [](const-type string){} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const33.gno b/gnovm/tests/files/const33.gno new file mode 100644 index 00000000000..42652d3b458 --- /dev/null +++ b/gnovm/tests/files/const33.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 +var y = 1 + +const b = x == y + +func main() { + println("ok") +} +// Error: +// main/files/const33.gno:6:7: x (variable of type int) is not constant diff --git a/gnovm/tests/files/const34.gno b/gnovm/tests/files/const34.gno new file mode 100644 index 00000000000..9c79cf86b88 --- /dev/null +++ b/gnovm/tests/files/const34.gno @@ -0,0 +1,10 @@ +package main + +const a = func() { println("hey") } + +func main() { + println("ok") +} + +// Error: +// main/files/const34.gno:3:7: func func(){ (const (println func(xs ...interface{})()))((const ("hey" string))) } (variable of type func()()) is not constant diff --git a/gnovm/tests/files/const35.gno b/gnovm/tests/files/const35.gno new file mode 100644 index 00000000000..e85d68eb8db --- /dev/null +++ b/gnovm/tests/files/const35.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 + +const ff = +(1 << x) + +func main() { + println("ok") +} + +// Error: +// main/files/const35.gno:5:7: x (variable of type int) is not constant diff --git a/gnovm/tests/files/const36.gno b/gnovm/tests/files/const36.gno new file mode 100644 index 00000000000..8a677a35be9 --- /dev/null +++ b/gnovm/tests/files/const36.gno @@ -0,0 +1,13 @@ +package main + +type s struct { + x int +} + +func main() { + s := s{1} + const v = s.x +} + +// Error: +// main/files/const36.gno:9:8: s.x (variable of type int) is not constant diff --git a/gnovm/tests/files/const37_native.gno b/gnovm/tests/files/const37_native.gno new file mode 100644 index 00000000000..6a164328a3b --- /dev/null +++ b/gnovm/tests/files/const37_native.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_native.gno:6:8: time.UTC (variable of type *time.Location) is not constant diff --git a/gnovm/tests/files/const37_stdlibs.gno b/gnovm/tests/files/const37_stdlibs.gno new file mode 100644 index 00000000000..9ec614f2538 --- /dev/null +++ b/gnovm/tests/files/const37_stdlibs.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_stdlibs.gno:6:8: time.UTC (variable of type *time.Location) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const38.gno b/gnovm/tests/files/const38.gno new file mode 100644 index 00000000000..815e74fa76a --- /dev/null +++ b/gnovm/tests/files/const38.gno @@ -0,0 +1,11 @@ +package main + +import "std" + +func main() { + v := std.Coin{} + const c = v.Denom +} + +// Error: +// main/files/const38.gno:7:8: v.Denom (variable of type string) is not constant diff --git a/gnovm/tests/files/const39.gno b/gnovm/tests/files/const39.gno new file mode 100644 index 00000000000..68ff7dd5630 --- /dev/null +++ b/gnovm/tests/files/const39.gno @@ -0,0 +1,14 @@ +package main + +type T struct { + a int +} + +func (tv T) Mv(a int) int { return 0 } + +func main() { + const t = T.Mv +} + +// Error: +// main/files/const39.gno:10:8: T.Mv (variable of type func(tv main.T,a int)( int)) is not constant diff --git a/gnovm/tests/files/const40.gno b/gnovm/tests/files/const40.gno new file mode 100644 index 00000000000..d1dedc382e6 --- /dev/null +++ b/gnovm/tests/files/const40.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t = [0]string{} +} + +// Error: +// main/files/const40.gno:4:8: [(const (0 int))](const-type string){} (variable of type [0]string) is not constant diff --git a/gnovm/tests/files/const41.gno b/gnovm/tests/files/const41.gno new file mode 100644 index 00000000000..b4424dcef94 --- /dev/null +++ b/gnovm/tests/files/const41.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t [0]string = [0]string{} +} + +// Error: +// main/files/const41.gno:4:8: invalid constant type [0]string \ No newline at end of file diff --git a/gnovm/tests/files/const42.gno b/gnovm/tests/files/const42.gno new file mode 100644 index 00000000000..5763a2fc121 --- /dev/null +++ b/gnovm/tests/files/const42.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t int +} + +// Error: +// main/files/const42.gno:4:8: missing init expr for t \ No newline at end of file diff --git a/gnovm/tests/files/const43.gno b/gnovm/tests/files/const43.gno new file mode 100644 index 00000000000..d929e526579 --- /dev/null +++ b/gnovm/tests/files/const43.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := [2]int{1, 2} + + const b = a +} + +// Error: +// main/files/const43.gno:6:8: a (variable of type [2]int) is not constant diff --git a/gnovm/tests/files/const44.gno b/gnovm/tests/files/const44.gno new file mode 100644 index 00000000000..69924b8ea9d --- /dev/null +++ b/gnovm/tests/files/const44.gno @@ -0,0 +1,10 @@ +package main + +const a = interface{}(nil) + +func main() { + println("ok") +} + +// Error: +// main/files/const44.gno:3:7: (const (undefined)) (variable of type interface{}) is not constant diff --git a/gnovm/tests/files/const45_a.gno b/gnovm/tests/files/const45_a.gno new file mode 100644 index 00000000000..fef13e2fc68 --- /dev/null +++ b/gnovm/tests/files/const45_a.gno @@ -0,0 +1,14 @@ +package main + +type MyStruct struct { + arr [2]int +} + +const a = len(MyStruct{arr: [2]int{1, 2}}.arr) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const45_b.gno b/gnovm/tests/files/const45_b.gno new file mode 100644 index 00000000000..7da25d3268c --- /dev/null +++ b/gnovm/tests/files/const45_b.gno @@ -0,0 +1,14 @@ +package main + +type MyStruct struct { + arr []int +} + +const a = len(MyStruct{arr: []int{1, 2}}.arr) + +func main() { + println("ok") +} + +// Error: +// main/files/const45_b.gno:7:7: MyStruct{arr: [](const-type int){(const (1 int)), (const (2 int))}}.arr (variable of type []int) is not constant diff --git a/gnovm/tests/files/const46.gno b/gnovm/tests/files/const46.gno new file mode 100644 index 00000000000..4722cba294e --- /dev/null +++ b/gnovm/tests/files/const46.gno @@ -0,0 +1,10 @@ +package main + +const a = len(map[string][2]int{"arr": {1, 2}}["arr"]) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const47.gno b/gnovm/tests/files/const47.gno new file mode 100644 index 00000000000..528ba469562 --- /dev/null +++ b/gnovm/tests/files/const47.gno @@ -0,0 +1,10 @@ +package main + +const a = len(map[string][2]interface{}{"arr": {1, 2}}["arr"]) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const48.gno b/gnovm/tests/files/const48.gno new file mode 100644 index 00000000000..001fd911fa5 --- /dev/null +++ b/gnovm/tests/files/const48.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a = len(map[string][]int{"arr": {1, 2}}) + println("ok", a) +} + +// Error: +// main/files/const48.gno:4:8: map[(const-type string)] [](const-type int){(const ("arr" string)): (const-type []int){(const (1 int)), (const (2 int))}} (variable of type map[string][]int) is not constant diff --git a/gnovm/tests/files/const49.gno b/gnovm/tests/files/const49.gno new file mode 100644 index 00000000000..4b3f38e4d5e --- /dev/null +++ b/gnovm/tests/files/const49.gno @@ -0,0 +1,16 @@ +package main + +import "io" + +type ( + s [2]int + m s +) + +func main() { + const v = len(m{1, 2}) + println(v) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/const50.gno b/gnovm/tests/files/const50.gno new file mode 100644 index 00000000000..5144173bf74 --- /dev/null +++ b/gnovm/tests/files/const50.gno @@ -0,0 +1,12 @@ +package main + +var x = "a" + +const v = len(x) + +func main() { + println("ok") +} + +// Error: +// main/files/const50.gno:5:7: x (variable of type string) is not constant From bd1d104e7bd88ab8980ad2d91bac65b7eaffc354 Mon Sep 17 00:00:00 2001 From: Ursulovic <97893481+Ursulovic@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:08:43 +0100 Subject: [PATCH 36/75] feat(examples): Ivan's registry, home realm (#3354) Created registry for further development of realms, finalising home realm. --------- Co-authored-by: Ivan Ursulovic Co-authored-by: Ivan Ursulovic Co-authored-by: Morgan --- examples/gno.land/r/ursulovic/home/gno.mod | 1 + examples/gno.land/r/ursulovic/home/home.gno | 159 ++++++++++++++++++ .../gno.land/r/ursulovic/home/home_test.gno | 97 +++++++++++ .../gno.land/r/ursulovic/registry/gno.mod | 1 + .../r/ursulovic/registry/registry.gno | 59 +++++++ 5 files changed, 317 insertions(+) create mode 100644 examples/gno.land/r/ursulovic/home/gno.mod create mode 100644 examples/gno.land/r/ursulovic/home/home.gno create mode 100644 examples/gno.land/r/ursulovic/home/home_test.gno create mode 100644 examples/gno.land/r/ursulovic/registry/gno.mod create mode 100644 examples/gno.land/r/ursulovic/registry/registry.gno diff --git a/examples/gno.land/r/ursulovic/home/gno.mod b/examples/gno.land/r/ursulovic/home/gno.mod new file mode 100644 index 00000000000..78163ab2bb5 --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/ursulovic/home diff --git a/examples/gno.land/r/ursulovic/home/home.gno b/examples/gno.land/r/ursulovic/home/home.gno new file mode 100644 index 00000000000..c03d8a66868 --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/home.gno @@ -0,0 +1,159 @@ +package home + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/ownable" + "gno.land/p/moul/md" + "gno.land/r/leon/hof" + + "gno.land/r/ursulovic/registry" +) + +var ( + aboutMe string + selectedImage string + Ownable *ownable.Ownable + + githubUrl string + linkedinUrl string + connectUrl string + imageUpdatePrice int64 + + isValidUrl func(string) bool +) + +func init() { + Ownable = ownable.NewWithAddress(registry.MainAddress()) + + aboutMe = "Hi, I'm Ivan Ursulovic, a computer engineering graduate, blockchain enthusiast, and backend developer specializing in ASP.NET. I love learning new things and taking on challenges." + selectedImage = "https://i.ibb.co/W28NPkw/beograd.webp" + + githubUrl = "https://github.com/ursulovic" + linkedinUrl = "https://www.linkedin.com/in/ivan-ursulovic-953310190/" + imageUpdatePrice = 5000000 + isValidUrl = defaultURLValidation + hof.Register() +} + +func Render(s string) string { + var sb strings.Builder + sb.WriteString(renderAboutMe()) + sb.WriteString(renderSelectedImage()) + sb.WriteString(renderContactsUrl()) + return sb.String() +} + +func defaultURLValidation(url string) bool { + const urlPrefix string = "https://i.ibb.co/" + + if !strings.HasPrefix(url, urlPrefix) { + return false + } + + if !(strings.HasSuffix(url, ".jpg") || + strings.HasSuffix(url, ".png") || + strings.HasSuffix(url, ".gif") || + strings.HasSuffix(url, ".webp")) { + return false + } + + urlPath := strings.TrimPrefix(url, "https://i.ibb.co/") + parts := strings.Split(urlPath, "/") + + if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 { + return false + } + + return true +} + +func UpdateSelectedImage(url string) { + if !isValidUrl(url) { + panic("Url is not valid!") + } + + sentCoins := std.GetOrigSend() + + if len(sentCoins) != 1 && sentCoins.AmountOf("ugnot") == imageUpdatePrice { + panic("Please send exactly " + strconv.Itoa(int(imageUpdatePrice)) + " ugnot") + } + + selectedImage = url +} + +func renderSelectedImage() string { + var sb strings.Builder + + sb.WriteString(md.HorizontalRule()) + sb.WriteString("\n") + + sb.WriteString(md.H2("📸 Featured Image")) + sb.WriteString("\n") + + sb.WriteString(md.Image("", selectedImage)) + sb.WriteString("\n") + + sb.WriteString(md.H4("✨ " + md.Link("Change this image for "+strconv.Itoa(int(imageUpdatePrice/1000000))+" GNOT. To update, set a direct image URL from ImgBB.", "https://gno.studio/connect/view/gno.land/r/ursulovic/home?network=portal-loop") + " ✨")) + + return sb.String() +} + +func renderAboutMe() string { + var sb strings.Builder + + sb.WriteString(md.H1("👋 Welcome to Ivan's Homepage!")) + sb.WriteString("\n") + + sb.WriteString(md.H2("👨‍💻 About Me")) + sb.WriteString("\n") + + sb.WriteString(md.Blockquote(aboutMe)) + + return sb.String() +} + +func renderContactsUrl() string { + var sb strings.Builder + + sb.WriteString(md.HorizontalRule()) + sb.WriteString("\n") + + sb.WriteString(md.H2("🔗 Let's Connect")) + sb.WriteString("\n") + + items := []string{ + "🐙 " + md.Link("GitHub", githubUrl), + "💼 " + md.Link("LinkedIn", linkedinUrl), + } + sb.WriteString(md.BulletList(items)) + + return sb.String() +} + +func UpdateGithubUrl(url string) { + Ownable.AssertCallerIsOwner() + githubUrl = url +} + +func UpdateLinkedinUrl(url string) { + Ownable.AssertCallerIsOwner() + linkedinUrl = url +} + +func UpdateAboutMe(text string) { + Ownable.AssertCallerIsOwner() + aboutMe = text +} + +func UpdateImagePrice(newPrice int64) { + Ownable.AssertCallerIsOwner() + imageUpdatePrice = newPrice +} + +func UpdateIsValidUrlFunction(f func(string) bool) { + Ownable.AssertCallerIsOwner() + isValidUrl = f +} diff --git a/examples/gno.land/r/ursulovic/home/home_test.gno b/examples/gno.land/r/ursulovic/home/home_test.gno new file mode 100644 index 00000000000..ff3f763d62a --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/home_test.gno @@ -0,0 +1,97 @@ +package home + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestUpdateGithubUrl(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newUrl := "https://github.com/example" + + UpdateGithubUrl(newUrl) + + if githubUrl != newUrl { + t.Fatalf("GitHub url not updated properly!") + } +} + +func TestUpdateLinkedinUrl(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newUrl := "https://www.linkedin.com/in/example" + + UpdateGithubUrl(newUrl) + + if githubUrl != newUrl { + t.Fatalf("LinkedIn url not updated properly!") + } +} + +func TestUpdateAboutMe(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newAboutMe := "This is new description!" + + UpdateAboutMe(newAboutMe) + + if aboutMe != newAboutMe { + t.Fatalf("About mew not updated properly!") + } +} + +func TestUpdateSelectedImage(t *testing.T) { + var user = testutils.TestAddress("user") + std.TestSetOrigCaller(user) + + validImageUrl := "https://i.ibb.co/hLtmnX0/beautiful-rain-forest-ang-ka-nature-trail-doi-inthanon-national-park-thailand-36703721.webp" + + coinsSent := std.NewCoins(std.NewCoin("ugnot", 5000000)) // Update to match the price expected by your function + std.TestSetOrigSend(coinsSent, std.NewCoins()) + + UpdateSelectedImage(validImageUrl) + + if selectedImage != validImageUrl { + t.Fatalf("Valid image URL rejected!") + } + + invalidImageUrl := "https://ibb.co/Kb3rQNn" + + defer func() { + if r := recover(); r == nil { + t.Fatalf("Expected panic for invalid image URL, but got no panic") + } + }() + + UpdateSelectedImage(invalidImageUrl) + + invalidCoins := std.NewCoins(std.NewCoin("ugnot", 1000000)) + std.TestSetOrigSend(invalidCoins, std.NewCoins()) + + defer func() { + if r := recover(); r == nil { + t.Fatalf("Expected panic for incorrect coin denomination or amount, but got no panic") + } + }() + + UpdateSelectedImage(validImageUrl) +} + +func TestUpdateImagePrice(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + var newImageUpdatePrice int64 = 3000000 + + UpdateImagePrice(newImageUpdatePrice) + + if imageUpdatePrice != newImageUpdatePrice { + t.Fatalf("Image update price not updated properly!") + } +} diff --git a/examples/gno.land/r/ursulovic/registry/gno.mod b/examples/gno.land/r/ursulovic/registry/gno.mod new file mode 100644 index 00000000000..ee1f5d38780 --- /dev/null +++ b/examples/gno.land/r/ursulovic/registry/gno.mod @@ -0,0 +1 @@ +module gno.land/r/ursulovic/registry diff --git a/examples/gno.land/r/ursulovic/registry/registry.gno b/examples/gno.land/r/ursulovic/registry/registry.gno new file mode 100644 index 00000000000..0bbd6c80df5 --- /dev/null +++ b/examples/gno.land/r/ursulovic/registry/registry.gno @@ -0,0 +1,59 @@ +package registry + +import ( + "errors" + "std" +) + +var ( + mainAddress std.Address + backupAddress std.Address + + ErrInvalidAddr = errors.New("Ivan's registry: Invalid address") + ErrUnauthorized = errors.New("Ivan's registry: Unauthorized") +) + +func init() { + mainAddress = "g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x" + backupAddress = "g1mw2xft3eava9kfhqw3fjj3kkf3pkammty0mtv7" +} + +func MainAddress() std.Address { + return mainAddress +} + +func BackupAddress() std.Address { + return backupAddress +} + +func SetMainAddress(addr std.Address) error { + assertAuthorized() + + if !addr.IsValid() { + return ErrInvalidAddr + } + + mainAddress = addr + return nil +} + +func SetBackupAddress(addr std.Address) error { + assertAuthorized() + + if !addr.IsValid() { + return ErrInvalidAddr + } + + backupAddress = addr + return nil +} + +// It will stay here for now, might be useful later +func assertAuthorized() { + caller := std.PrevRealm().Addr() + isAuthorized := caller == mainAddress || caller == backupAddress + + if !isAuthorized { + panic(ErrUnauthorized) + } +} From 90ff3e440446a2604c32d185f0bb63d31c499a51 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:19:20 +0100 Subject: [PATCH 37/75] fix(gnovm): save object when refCount changed (#2992) This PR a fix in the gnovm to ensure that objects are saved correctly when their reference count changes. closes: #2266 #1543
        Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
        --- contribs/gnodev/pkg/dev/node_test.go | 2 +- examples/gno.land/p/demo/avl/z_0_filetest.gno | 113 - examples/gno.land/p/demo/avl/z_1_filetest.gno | 161 +- examples/gno.land/p/demo/avl/z_2_filetest.gno | 50 +- .../gno.land/r/demo/boards/z_4_filetest.gno | 19 + .../testdata/addpkg_namespace.txtar | 2 +- .../pkg/integration/testdata/ghverify.txtar | 2 +- .../pkg/integration/testdata/issue_1543.txtar | 41 + .../pkg/integration/testdata/issue_2266.txtar | 42 + .../integration/testdata/simulate_gas.txtar | 4 +- gno.land/pkg/sdk/vm/gas_test.go | 4 +- .../cmd/gno/testdata/test/realm_correct.txtar | 67 +- .../gno/testdata/test/realm_incorrect.txtar | 2 +- gnovm/cmd/gno/testdata/test/realm_sync.txtar | 65 - gnovm/pkg/gnolang/realm.go | 8 +- gnovm/pkg/gnolang/store.go | 4 +- .../more/realm_compositelit_filetest.gno | 146 -- gnovm/tests/files/heap_item_value.gno | 125 - gnovm/tests/files/heap_item_value_init.gno | 168 +- gnovm/tests/files/zrealm0.gno | 65 - gnovm/tests/files/zrealm1.gno | 156 -- gnovm/tests/files/zrealm13.gno | 77 + gnovm/tests/files/zrealm14.gno | 168 ++ gnovm/tests/files/zrealm15.gno | 178 ++ gnovm/tests/files/zrealm16.gno | 115 + gnovm/tests/files/zrealm2.gno | 192 -- gnovm/tests/files/zrealm3.gno | 186 -- gnovm/tests/files/zrealm4.gno | 120 +- gnovm/tests/files/zrealm5.gno | 120 +- gnovm/tests/files/zrealm6.gno | 141 +- gnovm/tests/files/zrealm7.gno | 236 +- gnovm/tests/files/zrealm_avl0.gno | 113 - gnovm/tests/files/zrealm_avl1.gno | 161 +- gnovm/tests/files/zrealm_crossrealm21.gno | 672 +---- gnovm/tests/files/zrealm_crossrealm22.gno | 2176 +---------------- gnovm/tests/files/zrealm_natbind0.gno | 150 -- gnovm/tests/files/zrealm_tests0.gno | 1665 +------------ 37 files changed, 1002 insertions(+), 6714 deletions(-) create mode 100644 gno.land/pkg/integration/testdata/issue_1543.txtar create mode 100644 gno.land/pkg/integration/testdata/issue_2266.txtar create mode 100644 gnovm/tests/files/zrealm13.gno create mode 100644 gnovm/tests/files/zrealm14.gno create mode 100644 gnovm/tests/files/zrealm15.gno create mode 100644 gnovm/tests/files/zrealm16.gno diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 4a4acc232b9..38fab0a3360 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -438,7 +438,7 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types txcfg := gnoclient.BaseTxCfg{ GasFee: ugnot.ValueString(1000000), // Gas fee - GasWanted: 2_000_000, // Gas wanted + GasWanted: 3_000_000, // Gas wanted } // Set Caller in the msgs diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index 2dce5e7f1ac..1db1adebd3e 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -215,116 +215,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_0.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index 97ca5ed2135..572c49333bc 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -24,6 +24,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1375f6f96a1a3f298347dc8fc0065afa36cb7f0f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "13", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { @@ -143,7 +181,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "Hash": "cafae89e4d4aaaefe7fdf0691084508d4274a981", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" // }, // "Index": "0", @@ -191,7 +229,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "Hash": "b2e446f490656c19a83c43055de29c96e92a1549", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // } // } @@ -235,7 +273,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "Hash": "4e56eeb96eb1d9b27cf603140cd03a1622b6358b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -254,7 +292,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "Hash": "7b61530859954d1d14b2f696c91c5f37d39c21e7", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // }, // "Index": "0", @@ -283,123 +321,10 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "Hash": "fedc6d430b38c985dc6a985b2fcaee97e88ba6da", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_1.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_1.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_1.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/examples/gno.land/p/demo/avl/z_2_filetest.gno b/examples/gno.land/p/demo/avl/z_2_filetest.gno index 43067c31e8f..c45088075d6 100644 --- a/examples/gno.land/p/demo/avl/z_2_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_2_filetest.gno @@ -23,6 +23,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "12", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ba7550123807b8da857e38b72f66204b1ec582a2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "14", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3cb8485664c356fcb5c88dfb96b7455133a6b022", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={ // "Fields": [ // { @@ -142,7 +180,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "849a50d6c78d65742752e3c89ad8dd556e2e63cb", +// "Hash": "db39c9c0a60e0d5b30dbaf9be6150d3fec16aa4b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" // }, // "Index": "0", @@ -190,7 +228,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "a1160b0060ad752dbfe5fe436f7734bb19136150", +// "Hash": "2e9127534f91b385426d76e8e164f50f635cc1de", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" // } // } @@ -234,7 +272,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "fd95e08763159ac529e26986d652e752e78b6325", +// "Hash": "43e03b0c877b40c34e12bc2b15560e8ecd42ae9d", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" // }, // "Index": "0", @@ -253,7 +291,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b", +// "Hash": "4b123e2424d900a427f9dee88a70ce61f3cdcf5b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // }, // "Index": "0", @@ -282,7 +320,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "63126557dba88f8556f7a0ccbbfc1d218ae7a302", +// "Hash": "76d9227e755efd6674d8fa34e12decb7a9855488", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // } // } @@ -301,7 +339,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "d31c7e797793e03ffe0bbcb72f963264f8300d22", +// "Hash": "ff46b4dd63457c3fd59801e725f65af524ec829d", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // }, // "Index": "0", diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index c6cf6397b3a..b781e94e4db 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -885,6 +885,25 @@ func main() { // "RefCount": "1" // } // } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", +// "IsEscaped": true, +// "ModTime": "127", +// "RefCount": "6" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Board" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a88a9b837af217656ee27084309f7cd02cd94cb3", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85" +// } +// } +// } // switchrealm["gno.land/r/demo/boards"] // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/users"] diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index f529c176f36..2cfd00acda4 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -73,7 +73,7 @@ stdout 'OK!' # Test gui publishing on guiland/one # gui addpkg -> gno.land/r/guiland/one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1700000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1800000 -broadcast -chainid=tendermint_test gui stdout 'OK!' # Test admin publishing on guiland/two diff --git a/gno.land/pkg/integration/testdata/ghverify.txtar b/gno.land/pkg/integration/testdata/ghverify.txtar index b53849e85b5..0f2d21f6bd5 100644 --- a/gno.land/pkg/integration/testdata/ghverify.txtar +++ b/gno.land/pkg/integration/testdata/ghverify.txtar @@ -25,7 +25,7 @@ stdout "" stderr 'invalid ingest id: a' # the agent publishes their response to the task and the verification is complete -gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5,OK' -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5,OK' -gas-fee 1000000ugnot -gas-wanted 6000000 -broadcast -chainid=tendermint_test test1 stdout OK! # get verified github handle by gno address diff --git a/gno.land/pkg/integration/testdata/issue_1543.txtar b/gno.land/pkg/integration/testdata/issue_1543.txtar new file mode 100644 index 00000000000..388f126fcda --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_1543.txtar @@ -0,0 +1,41 @@ +# test issue + +loadpkg gno.land/r/demo/realm $WORK + +# start a new node +gnoland start + + +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func UnFill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 + + +-- realm.gno -- +package main + +type A struct { + A string +} +type B struct { + A *A + B string +} + +var ( + a = &A{A: "here"} + b [2]*B +) + +func Fill(i int) { + c := B{ + A: a, + B: "", + } + b[i] = &c +} + +func UnFill(i int) { + b[i] = nil +} + diff --git a/gno.land/pkg/integration/testdata/issue_2266.txtar b/gno.land/pkg/integration/testdata/issue_2266.txtar new file mode 100644 index 00000000000..046f57802e3 --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_2266.txtar @@ -0,0 +1,42 @@ +# test issue + +loadpkg gno.land/r/demo/realm $WORK + +# start a new node +gnoland start + + +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 1 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func UnFill --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 + + +-- realm.gno -- +package main + +type A struct { + A string +} +type B struct { + A *A + B string +} + +var ( + a = &A{A: "here"} + b [2]*B +) + +func Fill(i int) { + c := B{ + A: a, + B: "", + } + b[i] = &c +} + +func UnFill() { + b[0] = nil + b[1] = nil +} + diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 57be82b75ff..0dcb9ba424b 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 99339' +stdout 'GAS USED: 99371' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 99339' # same as simulate only +stdout 'GAS USED: 99371' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 276aa9db0b0..acde3d315c6 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -74,8 +74,8 @@ func TestAddPkgDeliverTx(t *testing.T) { assert.True(t, res.IsOK()) - // NOTE: let's try to keep this bellow 100_000 :) - assert.Equal(t, int64(135365), gasDeliver) + // NOTE: let's try to keep this bellow 150_000 :) + assert.Equal(t, int64(143845), gasDeliver) } // Enough gas for a failed transaction. diff --git a/gnovm/cmd/gno/testdata/test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar index ced183bec67..ae1212133fd 100644 --- a/gnovm/cmd/gno/testdata/test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_correct.txtar @@ -18,69 +18,4 @@ func main() { } // Realm: -// switchrealm["gno.land/r/xx"] -// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3" -// }, -// "FileName": "x.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/xx", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "x.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } +// switchrealm["gno.land/r/xx"] \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar index 234d0f81e77..84f4e3438ee 100644 --- a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar @@ -7,7 +7,7 @@ stderr '=== RUN file/x_filetest.gno' stderr 'Realm diff:' stderr '--- Expected' stderr '\+\+\+ Actual' -stderr '@@ -1,2 \+1,67 @@' +stderr '@@ -1,2 \+1,2 @@' stderr '-xxx' stderr '\+switchrealm\["gno.land/r/xx"\]' stderr 'x_filetest.gno failed' diff --git a/gnovm/cmd/gno/testdata/test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar index c93e6d86e8f..65a930b2f03 100644 --- a/gnovm/cmd/gno/testdata/test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_sync.txtar @@ -33,68 +33,3 @@ func main() { // Realm: // switchrealm["gno.land/r/xx"] -// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3" -// }, -// "FileName": "x.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/xx", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "x.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 04de760037a..509fcd67a60 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -192,6 +192,9 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if co != nil { co.IncRefCount() if co.GetRefCount() > 1 { + if co.GetIsReal() { + rlm.MarkDirty(co) + } if co.GetIsEscaped() { // already escaped } else { @@ -211,6 +214,8 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if xo.GetIsReal() { rlm.MarkNewDeleted(xo) } + } else if xo.GetIsReal() { + rlm.MarkDirty(xo) } } } @@ -464,6 +469,7 @@ func (rlm *Realm) incRefCreatedDescendants(store Store, oo Object) { child.SetIsNewReal(true) } } else if rc > 1 { + rlm.MarkDirty(child) if child.GetIsEscaped() { // already escaped, do nothing. } else { @@ -537,7 +543,7 @@ func (rlm *Realm) decRefDeletedDescendants(store Store, oo Object) { if rc == 0 { rlm.decRefDeletedDescendants(store, child) } else if rc > 0 { - // do nothing + rlm.MarkDirty(child) } else { panic("deleted descendants should not have a reference count of less than zero") } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index bc56a7c6313..3a70d07381b 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -519,7 +519,7 @@ func (ds *defaultStore) SetObject(oo Object) { } ds.cacheObjects[oid] = oo // make store op log entry - if ds.opslog != nil { + if _, ok := oo.(*Block); !ok && ds.opslog != nil { var op StoreOpType if oo.GetIsNewReal() { op = StoreOpNew @@ -560,7 +560,7 @@ func (ds *defaultStore) DelObject(oo Object) { ds.baseStore.Delete([]byte(key)) } // make realm op log entry - if ds.opslog != nil { + if _, ok := oo.(*Block); !ok && ds.opslog != nil { ds.opslog = append(ds.opslog, StoreOp{Type: StoreOpDel, Object: oo}) } diff --git a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno index 45f83bade5f..8514c2676aa 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno @@ -89,149 +89,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.PrimitiveType", -// "value": "2048" -// }, -// "Methods": [], -// "Name": "word", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.word" -// }, -// "Vrd": false -// }, -// "Methods": [], -// "Name": "nat", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Int" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "3c89d875f7d6daa94113aa4c7e03432ba56202c2", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "abs", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.nat" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Int", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/assign_unnamed_type/more/realm_compositelit.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/assign_unnamed_type/more/realm_compositelit.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/heap_item_value.gno b/gnovm/tests/files/heap_item_value.gno index 80bf702bec2..7a728d2c6f9 100644 --- a/gnovm/tests/files/heap_item_value.gno +++ b/gnovm/tests/files/heap_item_value.gno @@ -51,128 +51,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "S", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/heap_item_value_init.gno b/gnovm/tests/files/heap_item_value_init.gno index 2722cce8675..6be9caed1f4 100644 --- a/gnovm/tests/files/heap_item_value_init.gno +++ b/gnovm/tests/files/heap_item_value_init.gno @@ -19,167 +19,23 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "IsEscaped": true, // "ModTime": "6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "S", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value_init.gno", -// "IsMethod": false, -// "Name": "init.3", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value_init.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value_init.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value_init.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a05e5e1e2d2a27d94408a9325a58068e60b504df", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 5685da2154b..d3bf0e79223 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -20,68 +20,3 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm0.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index bb44a93bad4..eef42cc9378 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -47,159 +47,3 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ae4e9e2d205cc0081d4ee249e1d188ebe270b220", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "InnerNode", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm1.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm13.gno b/gnovm/tests/files/zrealm13.gno new file mode 100644 index 00000000000..f43f440f731 --- /dev/null +++ b/gnovm/tests/files/zrealm13.gno @@ -0,0 +1,77 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + b [2]*B +) + + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0] = nil +} + + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, +// "Index": "1", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } diff --git a/gnovm/tests/files/zrealm14.gno b/gnovm/tests/files/zrealm14.gno new file mode 100644 index 00000000000..ffffa4883fd --- /dev/null +++ b/gnovm/tests/files/zrealm14.gno @@ -0,0 +1,168 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + b [2]*B +) + + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0] = nil + b[1] = nil +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "c" +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "c22ccb7832b422c83fec9943b751cb134fcbed0b", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "d" +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "86c916fd78da57d354cb38019923bf64c1a3471c", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "9", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "95dca2f5b12899b6367402ecdac04c7ca59a03d9", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:8] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:9] diff --git a/gnovm/tests/files/zrealm15.gno b/gnovm/tests/files/zrealm15.gno new file mode 100644 index 00000000000..4ca6ef3b03d --- /dev/null +++ b/gnovm/tests/files/zrealm15.gno @@ -0,0 +1,178 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{} + b [2]*B + s *S +) + +type S struct { + S string +} +type A struct { + A *S +} +type B struct { + A *A + B *S +} + +func init() { + s = &S{ + S: "c", + } + c := B{ + A: a, + B: s, + } + b[0] = &c + b[1] = &c + a.A = s +} + +func main() { + b[0] = nil + b[1] = nil + a.A = nil +} + + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "10", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "5bf603ab337f9f40f8b22441562319d67be402b2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "10", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "IsEscaped": true, +// "ModTime": "10", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "6e4c3c716e28df1d3a25efeb654a7b7a379ce3b0", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "10", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "e82e410f22425e48d5f6c611160084a4dd50d3d1", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:10] diff --git a/gnovm/tests/files/zrealm16.gno b/gnovm/tests/files/zrealm16.gno new file mode 100644 index 00000000000..b7bef5028b0 --- /dev/null +++ b/gnovm/tests/files/zrealm16.gno @@ -0,0 +1,115 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + a2 = &A{A: "here"} + b [2]*B +) + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0].A = a2 +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "c" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "IsEscaped": true, +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "7c9f93a63419d852f132afaf2245ddcac5e8c3fb", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "11", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "95dca2f5b12899b6367402ecdac04c7ca59a03d9", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index 7bae02e48bc..57cd8bee349 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -50,196 +50,4 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "4", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "61d4aa77a87c01e07038c6030d6aca299d0fdc1b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "InnerNode", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm2.gno", -// "IsMethod": false, -// "Name": "init.4", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm2.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm2.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm2.gno", -// "Line": "23", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index 386fd4fe470..cc9317c32fd 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -82,191 +82,5 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "8197b7c5b4f2c7bf9c12b1c614f6b4dc6e7ce8dd", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm3.gno", -// "IsMethod": false, -// "Name": "init.3", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm3.gno", -// "Line": "14", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm3.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm3.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 8c3857aa37c..f8c5502fcb1 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -78,116 +78,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm4.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm4.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm4.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm4.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d57ee28f5030eb8c612b374155fd545675633288", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index c1062d51e8d..13d6e4a6b64 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -162,116 +162,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm5.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm5.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm5.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm5.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "115779a405a1cae45446397ea897a98a4043cbb2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index 1c50baa2d15..ba162c4d468 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -163,6 +163,25 @@ func main() { // "RefCount": "1" // } // } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5d64092f4f064ca58bdeffa32f6a119545b401c8", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { @@ -213,7 +232,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "Hash": "32593d23afa555fe99d433dbca1130b3843da97a", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -228,116 +247,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "7", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ade9fce2a987ef1924040a1d75c0172410c66952", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm6.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm6.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm6.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm6.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "131993c49dced230bd7071e9bae8d95e28733b73", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index e22b90f0e0f..5b932962db4 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -164,26 +164,7 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", -// "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } -// } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -192,7 +173,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key1" +// "value": "key0" // } // }, // { @@ -202,11 +183,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value1" +// "value": "value0" // } // }, // { -// "N": "AwAAAAAAAAA=", +// "N": "AQAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -219,16 +200,6 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a4fa9bdf45caf8c6b5be7a3752704423817b3ef2", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null // } // }, // { @@ -238,27 +209,55 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// }, -// "Index": "0", -// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", // "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d7fbb234dca9f194f35fe5409a62db9daf39b0fc", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -267,7 +266,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key0" +// "value": "key1" // } // }, // { @@ -277,11 +276,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value0" +// "value": "value1" // } // }, // { -// "N": "AQAAAAAAAAA=", +// "N": "AwAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -294,6 +293,16 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "76a40dcf03d32c312c2213265c14d4de1b12a810", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null // } // }, // { @@ -303,13 +312,23 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, +// "Index": "0", +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } @@ -327,121 +346,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "f56fbd9c8db299689cc0cf806fe741b6a6e641e6", +// "Hash": "92b2f4ebab764951f64086bce480f898f755de5a", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "450aef9858564ed4ec1c418f1e8dac828079016b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm7.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm7.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm7.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm7.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index 362d04ec0f8..1db1adebd3e 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -215,116 +215,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl0.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index bfcf9f7975e..572c49333bc 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -24,6 +24,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1375f6f96a1a3f298347dc8fc0065afa36cb7f0f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "13", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { @@ -143,7 +181,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "Hash": "cafae89e4d4aaaefe7fdf0691084508d4274a981", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" // }, // "Index": "0", @@ -191,7 +229,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "Hash": "b2e446f490656c19a83c43055de29c96e92a1549", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // } // } @@ -235,7 +273,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "Hash": "4e56eeb96eb1d9b27cf603140cd03a1622b6358b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -254,7 +292,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "Hash": "7b61530859954d1d14b2f696c91c5f37d39c21e7", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // }, // "Index": "0", @@ -283,123 +321,10 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "Hash": "fedc6d430b38c985dc6a985b2fcaee97e88ba6da", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl1.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl1.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl1.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm_crossrealm21.gno b/gnovm/tests/files/zrealm_crossrealm21.gno index 634fbea13c8..e3b29f671a4 100644 --- a/gnovm/tests/files/zrealm_crossrealm21.gno +++ b/gnovm/tests/files/zrealm_crossrealm21.gno @@ -24,673 +24,25 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests/crossrealm"] -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, // "ModTime": "5", +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" // }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8222b763e9bc04b4b7805e165e9f1324a39f28b6", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// ] +// } // } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] diff --git a/gnovm/tests/files/zrealm_crossrealm22.gno b/gnovm/tests/files/zrealm_crossrealm22.gno index 18985f7719d..afb9dab6d59 100644 --- a/gnovm/tests/files/zrealm_crossrealm22.gno +++ b/gnovm/tests/files/zrealm_crossrealm22.gno @@ -38,744 +38,25 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests/crossrealm"] -// c[1712ac7adcfdc8e58a67e5615e20fb312394c4df:6]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:6", -// "ModTime": "0", -// "OwnerID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "RefCount": "1" -// }, -// "Parent": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f5a516808f8976c33939133293d598ce3bca4e8d:3" -// }, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_crossrealm22.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/crossrealm_test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:3" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ] -// } -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, -// "ModTime": "5", +// "ModTime": "6", +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8222b763e9bc04b4b7805e165e9f1324a39f28b6", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Hash": "23de97a577d573252d00394ce9b71c24b0646546", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:6" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/crossrealm_test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "28", -// "File": "files/zrealm_crossrealm22.gno", -// "Line": "13", -// "PkgPath": "gno.land/r/crossrealm_test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] +// } // } // switchrealm["gno.land/r/crossrealm_test"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] @@ -826,701 +107,26 @@ func main() { // } // } // switchrealm["gno.land/r/demo/tests/crossrealm"] -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, // "ModTime": "6", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:5" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "23", -// "File": "crossrealm.gno", -// "Line": "23", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "759fd5b507fff8ea1b18d401550d918387a63445", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// ] +// } // } -// d[1712ac7adcfdc8e58a67e5615e20fb312394c4df:6] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] @@ -1547,732 +153,6 @@ func main() { // } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] -// c[1712ac7adcfdc8e58a67e5615e20fb312394c4df:7]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:7", -// "ModTime": "0", -// "OwnerID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "RefCount": "1" -// }, -// "Parent": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:5" -// }, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "23", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "IsEscaped": true, -// "ModTime": "6", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Hash": "89352b352826005a86eee78e6c832b43ae0ab6a6", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:7" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "62", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index e6ebef6252e..6f8045107dc 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -28,153 +28,3 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:10" -// }, -// "FileName": "native.gno", -// "IsMethod": false, -// "Name": "GetChainID", -// "NativeName": "GetChainID", -// "NativePkg": "std", -// "PkgPath": "std", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "native.gno", -// "Line": "13", -// "PkgPath": "std" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_natbind0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_natbind0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_natbind0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_natbind0.gno", -// "Line": "14", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index afb7e4a7c3b..872046ef795 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -80,7 +80,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "148d314678615253ee2032d7ecff6b144b474baf", +// "Hash": "4e0d77a91ba35733bf82329317bf8c8dffa6f655", // "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12" // }, // "Index": "0", @@ -133,1644 +133,43 @@ func main() { // "RefCount": "1" // } // } -// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2]={ -// "Blank": {}, +// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12]={ // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", -// "IsEscaped": true, -// "ModTime": "16", -// "RefCount": "5" +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12", +// "ModTime": "17", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:11", +// "RefCount": "1" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests" +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f163acee63433b44deb3168fef87beb588847322", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13" // } +// } +// } +// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:15]={ +// "ObjectInfo": { +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:15", +// "ModTime": "17", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "String", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "Stringer", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// }, -// "Vrd": false -// }, -// "V": { -// "@type": "/gno.SliceValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "3c58838c5667649add1ff8ee48a1cdc187fcd2ef", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17" -// }, -// "Length": "3", -// "Maxcap": "3", -// "Offset": "0" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "str", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:3" -// }, -// "FileName": "interfaces.gno", -// "IsMethod": false, -// "Name": "AddStringer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "interfaces.gno", -// "Line": "13", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "str", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "path", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:3" -// }, -// "FileName": "interfaces.gno", -// "IsMethod": false, -// "Name": "Render", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "interfaces.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "path", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.PrimitiveType", -// "value": "2048" -// }, -// "Methods": [], -// "Name": "Word", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Word" -// }, -// "Vrd": false -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "n", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "realm_method38d.gno", -// "IsMethod": true, -// "Name": "Add", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "5", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "n", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "nat", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Int" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "49283398258e135138cd8e234142d5daaa8c661d", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "neg", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// }, -// { -// "Embedded": false, -// "Name": "abs", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "Int", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7" -// }, -// "FileName": "realm_compositelit.gno", -// "IsMethod": false, -// "Name": "GetZeroType", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_compositelit.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" -// }, -// "FileName": "realm_method38d.gno", -// "IsMethod": false, -// "Name": "GetAbs", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "9", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" -// }, -// "FileName": "realm_method38d.gno", -// "IsMethod": false, -// "Name": "AbsAdd", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IncCounter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "Counter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CurrentRealmPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "InitOrigCaller", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "26", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallAssertOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "30", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallIsOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "34", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallSubtestsAssertOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "38", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallSubtestsIsOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Field", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "tests.gno", -// "IsMethod": true, -// "Name": "Modify", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "59", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// } -// ], -// "Name": "TestRealmObject", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "5e56ba76fc0add1a3a67f7a8b6709f4f27215f93", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "ModifyTestRealmObject", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "55", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Name", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// }, -// { -// "Embedded": false, -// "Name": "Child", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "TestNode", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "InitTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "77", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "ModTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "82", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "PrintTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "90", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "GetPrevRealm", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "94", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "GetRSubtestsPrevRealm", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "98", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fn", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "Exec", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "102", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fn", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IsCallerSubPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "106", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IsCallerParentPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "110", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "HasCallerSameNamespace", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "114", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2a96c074210eb2db31b7941e31d62deb04e59937", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16" // } -// ] +// } // } // d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14] // switchrealm["gno.land/r/demo/tests_foo"] From 8cca690ae01f22f12683a6e4bf2f1265ac4cff04 Mon Sep 17 00:00:00 2001 From: Nemanja Matic <106317308+Nemanya8@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:05:02 -0500 Subject: [PATCH 38/75] feat(cmd/gno): allow gno test to default to the current directory (#3453) This PR resolves https://github.com/gnolang/gno/issues/3420 by enhancing the gno test command. The command now assumes the current directory (.) as the default path when no directory is specified, aligning its behavior with go test. Changes to CI: - Removed the no_args test. - Added new tests to cover the following scenarios: - Valid test execution. - Valid file-based test. - Test with flags. - Empty directory. - Empty test file. This PR is a repost of https://github.com/gnolang/gno/pull/3429 with a cleaner commit history. CC @notJoon @moul --------- Co-authored-by: Nemanya21 Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- gnovm/cmd/gno/test.go | 6 +- gnovm/cmd/gno/testdata/test/no_args.txtar | 6 -- .../gno/testdata/test/no_path_empty_dir.txtar | 6 ++ .../gno/testdata/test/no_path_empty_gno.txtar | 8 ++ .../gno/testdata/test/no_path_flag_run.txtar | 99 +++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) delete mode 100644 gnovm/cmd/gno/testdata/test/no_args.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index fec0de7c221..ea06b25d8e2 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -146,8 +146,9 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { } func execTest(cfg *testCfg, args []string, io commands.IO) error { - if len(args) < 1 { - return flag.ErrHelp + // Default to current directory if no args provided + if len(args) == 0 { + args = []string{"."} } // guess opts.RootDir @@ -159,6 +160,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } + if len(paths) == 0 { io.ErrPrintln("no packages to test") return nil diff --git a/gnovm/cmd/gno/testdata/test/no_args.txtar b/gnovm/cmd/gno/testdata/test/no_args.txtar deleted file mode 100644 index bd9cd4fc965..00000000000 --- a/gnovm/cmd/gno/testdata/test/no_args.txtar +++ /dev/null @@ -1,6 +0,0 @@ -# Run gno test without args - -! gno test - -! stdout .+ -stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar new file mode 100644 index 00000000000..6f8b54d7ea4 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar @@ -0,0 +1,6 @@ +# Run gno test without path argument on an empty dir + +gno test + +! stdout .+ +stderr '[no test files]' \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar new file mode 100644 index 00000000000..846ce5bbd88 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar @@ -0,0 +1,8 @@ +# Test empty gno without path argument + +gno test + +! stdout .+ +stderr '\? \. \[no test files\]' + +-- empty.gno -- \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar b/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar new file mode 100644 index 00000000000..3db2a4c9295 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar @@ -0,0 +1,99 @@ +# Run test on gno.land/p/demo/ufmt without path argument + +gno test + +gno test -v + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .* + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run NotExists + +! stdout .+ +! stderr '=== RUN TestRun' + +gno test -v -run .*/hello + +! stdout .+ +stderr '=== RUN TestRun/hello' +! stderr '=== RUN TestRun/hi_you' +! stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .*/hi + +! stdout .+ +! stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .*/NotExists + +! stdout .+ +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/.* + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/ + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/hello + +! stdout .+ +stderr '=== RUN TestRun/hello' +! stderr '=== RUN TestRun/hi_you' +! stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +-- run.gno -- +package run + +-- run_test.gno -- +package run + +import ( + "fmt" + "testing" +) + +func TestRun(t *testing.T) { + cases := []string { + "hello", + "hi you", + "hi me", + } + for _, tc := range cases { + t.Run(tc, func(t *testing.T) {}) + } +} \ No newline at end of file From ae0c9f40340b854c0a033b8552dfa78ec9650b9b Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Wed, 15 Jan 2025 19:13:21 +0900 Subject: [PATCH 39/75] fix(gnoweb): fix mobile breadcrumb (#3516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the breadcrumb bar of gnoweb on mobile. Before: Capture d’écran 2025-01-15 à 16 02 30 After: Capture d’écran 2025-01-15 à 16 03 05 --- gno.land/pkg/gnoweb/components/breadcrumb.gohtml | 6 +++--- gno.land/pkg/gnoweb/public/styles.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml index 9295b17b6f5..3824eb5894f 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml @@ -2,15 +2,15 @@
          {{- range $index, $part := .Parts }} {{- if $index }} -
        1. +
        2. {{- else }} -
        3. +
        4. {{- end }} {{ $part.Name }}
        5. {{- end }} {{- if .Args }} -
        6. +
        7. {{ .Args }}
        8. {{- end }} diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index a6771695454..4ff1a266c0c 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From f91eb4ace354b0798752d06eb228311d4b490147 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:41:27 +0100 Subject: [PATCH 40/75] feat(examples): add rolist (#3400) - [x] add `avl/rolist` - [x] add `I...` interfaces for `avl/...`'s main structs. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/avl/list/list.gno | 16 ++ examples/gno.land/p/demo/avl/rolist/gno.mod | 1 + .../gno.land/p/demo/avl/rolist/rolist.gno | 119 +++++++++++++ .../p/demo/avl/rolist/rolist_test.gno | 162 ++++++++++++++++++ .../gno.land/p/demo/avl/rotree/rotree.gno | 19 +- 5 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 examples/gno.land/p/demo/avl/rolist/gno.mod create mode 100644 examples/gno.land/p/demo/avl/rolist/rolist.gno create mode 100644 examples/gno.land/p/demo/avl/rolist/rolist_test.gno diff --git a/examples/gno.land/p/demo/avl/list/list.gno b/examples/gno.land/p/demo/avl/list/list.gno index 0875eb66e01..594f5fa2a1f 100644 --- a/examples/gno.land/p/demo/avl/list/list.gno +++ b/examples/gno.land/p/demo/avl/list/list.gno @@ -41,6 +41,22 @@ import ( "gno.land/p/demo/seqid" ) +// IList defines the interface for list operations +type IList interface { + Len() int + Append(values ...interface{}) + Get(index int) interface{} + Set(index int, value interface{}) bool + Delete(index int) (interface{}, bool) + Slice(startIndex, endIndex int) []interface{} + ForEach(fn func(index int, value interface{}) bool) + Clone() *List + DeleteRange(startIndex, endIndex int) int +} + +// Verify List implements IList interface +var _ IList = (*List)(nil) + // List represents an ordered sequence of items backed by an AVL tree type List struct { tree avl.Tree diff --git a/examples/gno.land/p/demo/avl/rolist/gno.mod b/examples/gno.land/p/demo/avl/rolist/gno.mod new file mode 100644 index 00000000000..682513c2cc3 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl/rolist diff --git a/examples/gno.land/p/demo/avl/rolist/rolist.gno b/examples/gno.land/p/demo/avl/rolist/rolist.gno new file mode 100644 index 00000000000..23a85d9c885 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/rolist.gno @@ -0,0 +1,119 @@ +// Package rolist provides a read-only wrapper for list.List with safe value transformation. +// +// It is useful when you want to expose a read-only view of a list while ensuring that +// the sensitive data cannot be modified. +// +// Example: +// +// // Define a user structure with sensitive data +// type User struct { +// Name string +// Balance int +// Internal string // sensitive field +// } +// +// // Create and populate the original list +// privateList := list.New() +// privateList.Append(&User{ +// Name: "Alice", +// Balance: 100, +// Internal: "sensitive", +// }) +// +// // Create a safe transformation function that copies the struct +// // while excluding sensitive data +// makeEntrySafeFn := func(v interface{}) interface{} { +// u := v.(*User) +// return &User{ +// Name: u.Name, +// Balance: u.Balance, +// Internal: "", // omit sensitive data +// } +// } +// +// // Create a read-only view of the list +// publicList := rolist.Wrap(list, makeEntrySafeFn) +// +// // Safely access the data +// value := publicList.Get(0) +// user := value.(*User) +// // user.Name == "Alice" +// // user.Balance == 100 +// // user.Internal == "" (sensitive data is filtered) +package rolist + +import ( + "gno.land/p/demo/avl/list" +) + +// IReadOnlyList defines the read-only operations available on a list. +type IReadOnlyList interface { + Len() int + Get(index int) interface{} + Slice(startIndex, endIndex int) []interface{} + ForEach(fn func(index int, value interface{}) bool) +} + +// ReadOnlyList wraps a list.List and provides read-only access. +type ReadOnlyList struct { + list *list.List + makeEntrySafeFn func(interface{}) interface{} +} + +// Verify interface implementations +var _ IReadOnlyList = (*ReadOnlyList)(nil) +var _ IReadOnlyList = (interface{ list.IList })(nil) // is subset of list.IList + +// Wrap creates a new ReadOnlyList from an existing list.List and a safety transformation function. +// If makeEntrySafeFn is nil, values will be returned as-is without transformation. +func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyList { + return &ReadOnlyList{ + list: list, + makeEntrySafeFn: makeEntrySafeFn, + } +} + +// getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value +func (rol *ReadOnlyList) getSafeValue(value interface{}) interface{} { + if rol.makeEntrySafeFn == nil { + return value + } + return rol.makeEntrySafeFn(value) +} + +// Len returns the number of elements in the list. +func (rol *ReadOnlyList) Len() int { + return rol.list.Len() +} + +// Get returns the value at the specified index, converted to a safe format. +// Returns nil if index is out of bounds. +func (rol *ReadOnlyList) Get(index int) interface{} { + value := rol.list.Get(index) + if value == nil { + return nil + } + return rol.getSafeValue(value) +} + +// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive), +// with all values converted to a safe format. +func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} { + values := rol.list.Slice(startIndex, endIndex) + if values == nil { + return nil + } + + result := make([]interface{}, len(values)) + for i, v := range values { + result[i] = rol.getSafeValue(v) + } + return result +} + +// ForEach iterates through all elements in the list, providing safe versions of the values. +func (rol *ReadOnlyList) ForEach(fn func(index int, value interface{}) bool) { + rol.list.ForEach(func(index int, value interface{}) bool { + return fn(index, rol.getSafeValue(value)) + }) +} diff --git a/examples/gno.land/p/demo/avl/rolist/rolist_test.gno b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno new file mode 100644 index 00000000000..03b0a8cba30 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno @@ -0,0 +1,162 @@ +package rolist + +import ( + "testing" + + "gno.land/p/demo/avl/list" +) + +func TestExample(t *testing.T) { + // User represents our internal data structure + type User struct { + ID string + Name string + Balance int + Internal string // sensitive internal data + } + + // Create and populate the original list + l := &list.List{} + l.Append( + &User{ + ID: "1", + Name: "Alice", + Balance: 100, + Internal: "sensitive_data_1", + }, + &User{ + ID: "2", + Name: "Bob", + Balance: 200, + Internal: "sensitive_data_2", + }, + ) + + // Define a makeEntrySafeFn that: + // 1. Creates a defensive copy of the User struct + // 2. Omits sensitive internal data + makeEntrySafeFn := func(v interface{}) interface{} { + originalUser := v.(*User) + return &User{ + ID: originalUser.ID, + Name: originalUser.Name, + Balance: originalUser.Balance, + Internal: "", // Omit sensitive data + } + } + + // Create a read-only view of the list + roList := Wrap(l, makeEntrySafeFn) + + // Test retrieving and verifying a user + t.Run("Get User", func(t *testing.T) { + // Get user from read-only list + value := roList.Get(0) + if value == nil { + t.Fatal("User at index 0 not found") + } + + user := value.(*User) + + // Verify user data is correct + if user.Name != "Alice" || user.Balance != 100 { + t.Errorf("Unexpected user data: got name=%s balance=%d", user.Name, user.Balance) + } + + // Verify sensitive data is not exposed + if user.Internal != "" { + t.Error("Sensitive data should not be exposed") + } + + // Verify it's a different instance than the original + originalUser := l.Get(0).(*User) + if user == originalUser { + t.Error("Read-only list should return a copy, not the original pointer") + } + }) + + // Test slice functionality + t.Run("Slice Users", func(t *testing.T) { + users := roList.Slice(0, 2) + if len(users) != 2 { + t.Fatalf("Expected 2 users, got %d", len(users)) + } + + for _, v := range users { + user := v.(*User) + if user.Internal != "" { + t.Error("Sensitive data exposed in slice") + } + } + }) + + // Test ForEach functionality + t.Run("ForEach Users", func(t *testing.T) { + count := 0 + roList.ForEach(func(index int, value interface{}) bool { + user := value.(*User) + if user.Internal != "" { + t.Error("Sensitive data exposed during iteration") + } + count++ + return false + }) + + if count != 2 { + t.Errorf("Expected 2 users, got %d", count) + } + }) +} + +func TestNilMakeEntrySafeFn(t *testing.T) { + // Create a list with some test data + l := &list.List{} + originalValue := []int{1, 2, 3} + l.Append(originalValue) + + // Create a ReadOnlyList with nil makeEntrySafeFn + roList := Wrap(l, nil) + + // Test that we get back the original value + value := roList.Get(0) + if value == nil { + t.Fatal("Value not found") + } + + // Verify it's the exact same slice (not a copy) + retrievedSlice := value.([]int) + if &retrievedSlice[0] != &originalValue[0] { + t.Error("Expected to get back the original slice reference") + } +} + +func TestReadOnlyList(t *testing.T) { + // Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation + makeEntrySafeFn := func(value interface{}) interface{} { + return value.(string) + "_readonly" + } + + l := &list.List{} + l.Append("value1", "value2", "value3") + + roList := Wrap(l, makeEntrySafeFn) + + tests := []struct { + name string + index int + expected interface{} + }{ + {"ExistingIndex0", 0, "value1_readonly"}, + {"ExistingIndex1", 1, "value2_readonly"}, + {"NonExistingIndex", 3, nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + value := roList.Get(tt.index) + if value != tt.expected { + t.Errorf("For index %d, expected %v, got %v", tt.index, tt.expected, value) + } + }) + } +} diff --git a/examples/gno.land/p/demo/avl/rotree/rotree.gno b/examples/gno.land/p/demo/avl/rotree/rotree.gno index 3e093c4d0e0..17cb4e20ced 100644 --- a/examples/gno.land/p/demo/avl/rotree/rotree.gno +++ b/examples/gno.land/p/demo/avl/rotree/rotree.gno @@ -82,8 +82,23 @@ type ReadOnlyTree struct { makeEntrySafeFn func(interface{}) interface{} } -// Verify that ReadOnlyTree implements ITree -var _ avl.ITree = (*ReadOnlyTree)(nil) +// IReadOnlyTree defines the read-only operations available on a tree. +type IReadOnlyTree interface { + Size() int + Has(key string) bool + Get(key string) (interface{}, bool) + GetByIndex(index int) (string, interface{}) + Iterate(start, end string, cb avl.IterCbFn) bool + ReverseIterate(start, end string, cb avl.IterCbFn) bool + IterateByOffset(offset int, count int, cb avl.IterCbFn) bool + ReverseIterateByOffset(offset int, count int, cb avl.IterCbFn) bool +} + +// Verify that ReadOnlyTree implements both ITree and IReadOnlyTree +var ( + _ avl.ITree = (*ReadOnlyTree)(nil) + _ IReadOnlyTree = (*ReadOnlyTree)(nil) +) // getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value func (roTree *ReadOnlyTree) getSafeValue(value interface{}) interface{} { From 1ff162bd8df47246e01ecfe973702271fde6436f Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:42:59 +0100 Subject: [PATCH 41/75] feat(examples): add p/moul/collection (#3321) Addresses #1467 Related with #3317 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../gno.land/p/demo/avl/list/list_test.gno | 64 ++ .../gno.land/p/moul/collection/collection.gno | 509 +++++++++ .../p/moul/collection/collection_test.gno | 987 ++++++++++++++++++ examples/gno.land/p/moul/collection/entry.gno | 149 +++ examples/gno.land/p/moul/collection/gno.mod | 1 + 5 files changed, 1710 insertions(+) create mode 100644 examples/gno.land/p/moul/collection/collection.gno create mode 100644 examples/gno.land/p/moul/collection/collection_test.gno create mode 100644 examples/gno.land/p/moul/collection/entry.gno create mode 100644 examples/gno.land/p/moul/collection/gno.mod diff --git a/examples/gno.land/p/demo/avl/list/list_test.gno b/examples/gno.land/p/demo/avl/list/list_test.gno index 265fbdb5eb1..0293692f660 100644 --- a/examples/gno.land/p/demo/avl/list/list_test.gno +++ b/examples/gno.land/p/demo/avl/list/list_test.gno @@ -2,6 +2,8 @@ package list import ( "testing" + + "gno.land/p/demo/ufmt" ) func TestList_Basic(t *testing.T) { @@ -395,6 +397,68 @@ func TestList_IndexConsistency(t *testing.T) { } } +func TestList_RecursiveSafety(t *testing.T) { + // Create a new list + l := &List{} + + // Add some initial values + l.Append("id1") + l.Append("id2") + l.Append("id3") + + // Test deep list traversal + found := false + l.ForEach(func(i int, v interface{}) bool { + if str, ok := v.(string); ok { + if str == "id2" { + found = true + return true // stop iteration + } + } + return false // continue iteration + }) + + if !found { + t.Error("Failed to find expected value in list") + } + + short := testing.Short() + + // Test recursive safety by performing multiple operations + for i := 0; i < 1000; i++ { + // Add new value + l.Append(ufmt.Sprintf("id%d", i+4)) + + if !short { + // Search for a value + var lastFound bool + l.ForEach(func(j int, v interface{}) bool { + if str, ok := v.(string); ok { + if str == ufmt.Sprintf("id%d", i+3) { + lastFound = true + return true + } + } + return false + }) + + if !lastFound { + t.Errorf("Failed to find value id%d after insertion", i+3) + } + } + } + + // Verify final length + expectedLen := 1003 // 3 initial + 1000 added + if l.Len() != expectedLen { + t.Errorf("Expected length %d, got %d", expectedLen, l.Len()) + } + + if short { + t.Skip("skipping extended recursive safety test in short mode") + } +} + // Helper function to compare slices func sliceEqual(a, b []interface{}) bool { if len(a) != len(b) { diff --git a/examples/gno.land/p/moul/collection/collection.gno b/examples/gno.land/p/moul/collection/collection.gno new file mode 100644 index 00000000000..f6d26e6a3ee --- /dev/null +++ b/examples/gno.land/p/moul/collection/collection.gno @@ -0,0 +1,509 @@ +// Package collection provides a generic collection implementation with support for +// multiple indexes, including unique indexes and case-insensitive indexes. +// It is designed to be used with any type and allows efficient lookups using +// different fields or computed values. +// +// Example usage: +// +// // Define a data type +// type User struct { +// Name string +// Email string +// Age int +// Username string +// Tags []string +// } +// +// // Create a new collection +// c := collection.New() +// +// // Add indexes with different options +// c.AddIndex("name", func(v interface{}) string { +// return v.(*User).Name +// }, UniqueIndex) +// +// c.AddIndex("email", func(v interface{}) string { +// return v.(*User).Email +// }, UniqueIndex|CaseInsensitiveIndex) +// +// c.AddIndex("age", func(v interface{}) string { +// return strconv.Itoa(v.(*User).Age) +// }, DefaultIndex) // Non-unique index +// +// c.AddIndex("username", func(v interface{}) string { +// return v.(*User).Username +// }, UniqueIndex|SparseIndex) // Allow empty usernames +// +// // For tags, we index all tags for the user +// c.AddIndex("tag", func(v interface{}) []string { +// return v.(*User).Tags +// }, DefaultIndex) // Non-unique to allow multiple users with same tag +// +// // Store an object +// id := c.Set(&User{ +// Name: "Alice", +// Email: "alice@example.com", +// Age: 30, +// Tags: []string{"admin", "moderator"}, // User can have multiple tags +// }) +// +// // Retrieve by any index +// entry := c.GetFirst("email", "alice@example.com") +// adminUsers := c.GetAll("tag", "admin") // Find all users with admin tag +// modUsers := c.GetAll("tag", "moderator") // Find all users with moderator tag +// +// Index options can be combined using the bitwise OR operator. +// Available options: +// - DefaultIndex: Regular index with no special behavior +// - UniqueIndex: Ensures values are unique within the index +// - CaseInsensitiveIndex: Makes string comparisons case-insensitive +// - SparseIndex: Skips indexing empty values (nil or empty string) +// +// Example: UniqueIndex|CaseInsensitiveIndex for a case-insensitive unique index +package collection + +import ( + "errors" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// New creates a new Collection instance with an initialized ID index. +// The ID index is a special unique index that is always present and +// serves as the primary key for all objects in the collection. +func New() *Collection { + c := &Collection{ + indexes: make(map[string]*Index), + idGen: seqid.ID(0), + } + // Initialize _id index + c.indexes[IDIndex] = &Index{ + options: UniqueIndex, + tree: avl.NewTree(), + } + return c +} + +// Collection represents a collection of objects with multiple indexes +type Collection struct { + indexes map[string]*Index + idGen seqid.ID +} + +const ( + // IDIndex is the reserved name for the primary key index + IDIndex = "_id" +) + +// IndexOption represents configuration options for an index using bit flags +type IndexOption uint64 + +const ( + // DefaultIndex is a basic index with no special options + DefaultIndex IndexOption = 0 + + // UniqueIndex ensures no duplicate values are allowed + UniqueIndex IndexOption = 1 << iota + + // CaseInsensitiveIndex automatically converts string values to lowercase + CaseInsensitiveIndex + + // SparseIndex only indexes non-empty values + SparseIndex +) + +// Index represents an index with its configuration and data. +// The index function can return either: +// - string: for single-value indexes +// - []string: for multi-value indexes where one object can be indexed under multiple keys +// +// The backing tree stores either a single ID or []string for multiple IDs per key. +type Index struct { + fn interface{} + options IndexOption + tree avl.ITree +} + +// AddIndex adds a new index to the collection with the specified options +// +// Parameters: +// - name: the unique name of the index (e.g., "tags") +// - indexFn: a function that extracts either a string or []string from an object +// - options: bit flags for index configuration (e.g., UniqueIndex) +func (c *Collection) AddIndex(name string, indexFn interface{}, options IndexOption) { + if name == IDIndex { + panic("_id is a reserved index name") + } + c.indexes[name] = &Index{ + fn: indexFn, + options: options, + tree: avl.NewTree(), + } +} + +// storeIndex handles how we store an ID in the index tree +func (idx *Index) store(key string, idStr string) { + stored, exists := idx.tree.Get(key) + if !exists { + // First entry for this key + idx.tree.Set(key, idStr) + return + } + + // Handle existing entries + switch existing := stored.(type) { + case string: + if existing == idStr { + return // Already stored + } + // Convert to array + idx.tree.Set(key, []string{existing, idStr}) + case []string: + // Check if ID already exists + for _, id := range existing { + if id == idStr { + return + } + } + // Append new ID + idx.tree.Set(key, append(existing, idStr)) + } +} + +// removeIndex handles how we remove an ID from the index tree +func (idx *Index) remove(key string, idStr string) { + stored, exists := idx.tree.Get(key) + if !exists { + return + } + + switch existing := stored.(type) { + case string: + if existing == idStr { + idx.tree.Remove(key) + } + case []string: + newIds := make([]string, 0, len(existing)) + for _, id := range existing { + if id != idStr { + newIds = append(newIds, id) + } + } + if len(newIds) == 0 { + idx.tree.Remove(key) + } else if len(newIds) == 1 { + idx.tree.Set(key, newIds[0]) + } else { + idx.tree.Set(key, newIds) + } + } +} + +// generateKeys extracts one or more keys from an object for a given index. +func generateKeys(idx *Index, obj interface{}) ([]string, bool) { + if obj == nil { + return nil, false + } + + switch fnTyped := idx.fn.(type) { + case func(interface{}) string: + // Single-value index + key := fnTyped(obj) + return []string{key}, true + case func(interface{}) []string: + // Multi-value index + keys := fnTyped(obj) + return keys, true + default: + panic("invalid index function type") + } +} + +// Set adds or updates an object in the collection. +// Returns a positive ID if successful. +// Returns 0 if: +// - The object is nil +// - A uniqueness constraint would be violated +// - Index generation fails for any index +func (c *Collection) Set(obj interface{}) uint64 { + if obj == nil { + return 0 + } + + // Generate new ID + id := c.idGen.Next() + idStr := id.String() + + // Check uniqueness constraints first + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + return 0 + } + + for _, key := range keys { + // Skip empty values for sparse indexes + if idx.options&SparseIndex != 0 && key == "" { + continue + } + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + // Only check uniqueness for unique + single-value indexes + // (UniqueIndex is ambiguous; skipping that scenario) + if idx.options&UniqueIndex != 0 { + if existing, exists := idx.tree.Get(key); exists && existing != nil { + return 0 + } + } + } + } + + // Store in _id index first (the actual object) + c.indexes[IDIndex].tree.Set(idStr, obj) + + // Store in all other indexes + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + // Rollback: remove from _id index + c.indexes[IDIndex].tree.Remove(idStr) + return 0 + } + + for _, key := range keys { + if idx.options&SparseIndex != 0 && key == "" { + continue + } + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + idx.store(key, idStr) + } + } + + return uint64(id) +} + +// Get retrieves entries matching the given key in the specified index. +// Returns an iterator over the matching entries. +func (c *Collection) Get(indexName string, key string) EntryIterator { + idx, exists := c.indexes[indexName] + if !exists { + return EntryIterator{err: errors.New("index not found: " + indexName)} + } + + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + + if indexName == IDIndex { + // For ID index, validate the ID format first + _, err := seqid.FromString(key) + if err != nil { + return EntryIterator{err: err} + } + } + + return EntryIterator{ + collection: c, + indexName: indexName, + key: key, + } +} + +// GetFirst returns the first matching entry or nil if none found +func (c *Collection) GetFirst(indexName, key string) *Entry { + iter := c.Get(indexName, key) + if iter.Next() { + return iter.Value() + } + return nil +} + +// Delete removes an object by its ID and returns true if something was deleted +func (c *Collection) Delete(id uint64) bool { + idStr := seqid.ID(id).String() + + // Get the object first to clean up other indexes + obj, exists := c.indexes[IDIndex].tree.Get(idStr) + if !exists { + return false + } + + // Remove from all indexes + for name, idx := range c.indexes { + if name == IDIndex { + idx.tree.Remove(idStr) + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + continue + } + for _, key := range keys { + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + idx.remove(key, idStr) + } + } + return true +} + +// Update updates an existing object and returns true if successful +// Returns true if the update was successful. +// Returns false if: +// - The object is nil +// - The ID doesn't exist +// - A uniqueness constraint would be violated +// - Index generation fails for any index +// +// If the update fails, the collection remains unchanged. +func (c *Collection) Update(id uint64, obj interface{}) bool { + if obj == nil { + return false + } + idStr := seqid.ID(id).String() + oldObj, exists := c.indexes[IDIndex].tree.Get(idStr) + if !exists { + return false + } + + // Check unique constraints + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + + if idx.options&UniqueIndex != 0 { + newKeys, newOk := generateKeys(idx, obj) + _, oldOk := generateKeys(idx, oldObj) + if !newOk || !oldOk { + return false + } + + for _, newKey := range newKeys { + if idx.options&CaseInsensitiveIndex != 0 { + newKey = strings.ToLower(newKey) + } + + found, _ := idx.tree.Get(newKey) + if found != nil { + if storedID, ok := found.(string); !ok || storedID != idStr { + return false + } + } + } + } + } + + // Store old index entries for potential rollback + oldEntries := make(map[string][]string) + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + oldKeys, ok := generateKeys(idx, oldObj) + if !ok { + continue + } + var adjusted []string + for _, okey := range oldKeys { + if idx.options&CaseInsensitiveIndex != 0 { + okey = strings.ToLower(okey) + } + // Remove the oldObj from the index right away + idx.remove(okey, idStr) + adjusted = append(adjusted, okey) + } + oldEntries[name] = adjusted + } + + // Update the object in the _id index + c.indexes[IDIndex].tree.Set(idStr, obj) + + // Add new index entries + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + newKeys, ok := generateKeys(idx, obj) + if !ok { + // Rollback: restore old object and old index entries + c.indexes[IDIndex].tree.Set(idStr, oldObj) + for idxName, keys := range oldEntries { + for _, oldKey := range keys { + c.indexes[idxName].store(oldKey, idStr) + } + } + return false + } + for _, nkey := range newKeys { + if idx.options&CaseInsensitiveIndex != 0 { + nkey = strings.ToLower(nkey) + } + idx.store(nkey, idStr) + } + } + + return true +} + +// GetAll retrieves all entries matching the given key in the specified index. +func (c *Collection) GetAll(indexName string, key string) []Entry { + idx, exists := c.indexes[indexName] + if !exists { + return nil + } + + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + + if indexName == IDIndex { + if obj, exists := idx.tree.Get(key); exists { + return []Entry{{ID: key, Obj: obj}} + } + return nil + } + + idData, exists := idx.tree.Get(key) + if !exists { + return nil + } + + // Handle both single and multi-value cases based on the actual data type + switch stored := idData.(type) { + case []string: + result := make([]Entry, 0, len(stored)) + for _, idStr := range stored { + if obj, exists := c.indexes[IDIndex].tree.Get(idStr); exists { + result = append(result, Entry{ID: idStr, Obj: obj}) + } + } + return result + case string: + if obj, exists := c.indexes[IDIndex].tree.Get(stored); exists { + return []Entry{{ID: stored, Obj: obj}} + } + } + return nil +} + +// GetIndex returns the underlying tree for an index +func (c *Collection) GetIndex(name string) avl.ITree { + idx, exists := c.indexes[name] + if !exists { + return nil + } + return idx.tree +} diff --git a/examples/gno.land/p/moul/collection/collection_test.gno b/examples/gno.land/p/moul/collection/collection_test.gno new file mode 100644 index 00000000000..3e03d222ce8 --- /dev/null +++ b/examples/gno.land/p/moul/collection/collection_test.gno @@ -0,0 +1,987 @@ +package collection + +import ( + "errors" + "strconv" + "strings" + "testing" + + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" +) + +type Person struct { + Name string + Age int + Email string + Username string + Tags []string +} + +func (p Person) String() string { + return ufmt.Sprintf("name=%s age=%d email=%s username=%s tags=%s", + p.Name, p.Age, p.Email, p.Username, strings.Join(p.Tags, ",")) +} + +// TestOperation represents a single operation in a test sequence +type TestOperation struct { + op string // "set" or "update" + person *Person + id uint64 // for updates + wantID uint64 + wantErr bool +} + +// TestCase represents a complete test case with setup and operations +type TestCase struct { + name string + setupIndex func(*Collection) + operations []TestOperation +} + +func TestBasicOperations(t *testing.T) { + c := New() + + // Add indexes + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // Test basic Set and Get + p1 := &Person{Name: "Alice", Age: 30, Email: "alice@test.com"} + id1 := c.Set(p1) + if id1 == 0 { + t.Error("Failed to set first object") + } + + // Get by ID + iter := c.Get(IDIndex, seqid.ID(id1).String()) + if !iter.Next() { + t.Error("Failed to get object by ID") + } + entry := iter.Value() + if entry.Obj.(*Person).Name != "Alice" { + t.Error("Got wrong object") + } +} + +func TestUniqueConstraints(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) uint64 + wantID bool + }{ + { + name: "First person", + setup: func(c *Collection) uint64 { + return c.Set(&Person{Name: "Alice"}) + }, + wantID: true, + }, + { + name: "Duplicate name", + setup: func(c *Collection) uint64 { + c.Set(&Person{Name: "Alice"}) + return c.Set(&Person{Name: "Alice"}) + }, + wantID: false, + }, + { + name: "Same age (non-unique index)", + setup: func(c *Collection) uint64 { + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + c.Set(&Person{Name: "Alice", Age: 30}) + return c.Set(&Person{Name: "Bob", Age: 30}) + }, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + id := tt.setup(c) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestUpdates(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("username", func(v interface{}) string { + return strings.ToLower(v.(*Person).Username) + }, UniqueIndex|CaseInsensitiveIndex) + + // Initial setup + p1 := &Person{Name: "Alice", Username: "alice123"} + p2 := &Person{Name: "Bob", Username: "bob456"} + + id1 := c.Set(p1) + id2 := c.Set(p2) + + tests := []struct { + name string + id uint64 + newPerson *Person + wantRet bool + }{ + { + name: "Update to non-conflicting values", + id: id1, + newPerson: &Person{Name: "Alice2", Username: "alice1234"}, + wantRet: true, + }, + { + name: "Update to conflicting username", + id: id1, + newPerson: &Person{Name: "Alice2", Username: "bob456"}, + wantRet: false, + }, + { + name: "Update non-existent ID", + id: 99999, + newPerson: &Person{Name: "Test", Username: "test"}, + wantRet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID := c.Update(tt.id, tt.newPerson) + if gotID != tt.wantRet { + t.Errorf("Update() got = %v, want %v", gotID, tt.wantRet) + } + }) + } +} + +func TestDelete(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + p1 := &Person{Name: "Alice"} + id1 := c.Set(p1) + + tests := []struct { + name string + id uint64 + wantRet bool + }{ + { + name: "Delete existing object", + id: id1, + wantRet: true, + }, + { + name: "Delete non-existent object", + id: 99999, + wantRet: false, + }, + { + name: "Delete already deleted object", + id: id1, + wantRet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID := c.Delete(tt.id) + if gotID != tt.wantRet { + t.Errorf("Delete() got = %v, want %v", gotID, tt.wantRet) + } + }) + } +} + +func TestEdgeCases(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + tests := []struct { + name string + operation func() bool + wantPanic bool + }{ + { + name: "Set nil object", + operation: func() bool { + return c.Set(nil) != 0 + }, + wantPanic: false, + }, + { + name: "Set wrong type", + operation: func() bool { + return c.Set("not a person") != 0 + }, + wantPanic: true, + }, + { + name: "Update with nil", + operation: func() bool { + id := c.Set(&Person{Name: "Test"}) + return c.Update(id, nil) + }, + wantPanic: false, + }, + { + name: "Get with invalid index name", + operation: func() bool { + iter := c.Get("invalid_index", "key") + if iter.Empty() { + return false + } + entry := iter.Value() + if entry == nil { + return false + } + id, err := seqid.FromString(entry.ID) + if err != nil { + return false + } + return true + }, + wantPanic: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bool + panicked := false + + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + got = tt.operation() + }() + + if panicked != tt.wantPanic { + t.Errorf("Operation panicked = %v, want panic = %v", panicked, tt.wantPanic) + } + if !panicked && got != false { + t.Errorf("Operation returned %v, want 0", got) + } + }) + } +} + +func TestIndexTypes(t *testing.T) { + c := New() + + // Test different types of indexes + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + + c.AddIndex("case_insensitive", func(v interface{}) string { + return strings.ToLower(v.(*Person).Username) + }, UniqueIndex|CaseInsensitiveIndex) + + // Test composite index + p1 := &Person{Name: "Alice", Age: 30, Username: "Alice123"} + id1 := c.Set(p1) + if id1 == 0 { + t.Error("Failed to set object with composite index") + } + + // Test case-insensitive index + p2 := &Person{Name: "Bob", Age: 25, Username: "alice123"} + id2 := c.Set(p2) + if id2 != 0 { + t.Error("Case-insensitive index failed to prevent duplicate") + } +} + +func TestIndexOptions(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) uint64 + wantID bool + wantErr bool + }{ + { + name: "Unique case-sensitive index", + setup: func(c *Collection) uint64 { + c.AddIndex("username", func(v interface{}) string { + return v.(*Person).Username + }, UniqueIndex) + + id1 := c.Set(&Person{Username: "Alice"}) + return c.Set(&Person{Username: "Alice"}) // Should fail + }, + wantID: false, + }, + { + name: "Unique case-insensitive index", + setup: func(c *Collection) uint64 { + c.AddIndex("email", func(v interface{}) string { + return v.(*Person).Email + }, UniqueIndex|CaseInsensitiveIndex) + + id1 := c.Set(&Person{Email: "test@example.com"}) + return c.Set(&Person{Email: "TEST@EXAMPLE.COM"}) // Should fail + }, + wantID: false, + }, + { + name: "Default index", + setup: func(c *Collection) uint64 { + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // First person with age 30 + id1 := c.Set(&Person{Age: 30}) + if id1 == 0 { + t.Error("Failed to set first person") + } + + // Second person with same age should succeed + return c.Set(&Person{Age: 30}) + }, + wantID: true, + }, + { + name: "Multiple options", + setup: func(c *Collection) uint64 { + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex|CaseInsensitiveIndex|SparseIndex) + + id1 := c.Set(&Person{Name: "Alice"}) + return c.Set(&Person{Name: "ALICE"}) // Should fail + }, + wantID: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() // Create new collection for each test + id := tt.setup(c) + if (id != 0) != tt.wantID { + t.Errorf("got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestConcurrentOperations(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + p1 := &Person{Name: "Alice"} + id1 := c.Set(p1) + iter := c.Get("_id", seqid.ID(id1).String()) + success := c.Update(id1, &Person{Name: "Alice2"}) + + if iter.Empty() || !success { + t.Error("Concurrent operations failed") + } +} + +func TestSparseIndexBehavior(t *testing.T) { + c := New() + c.AddIndex("optional_field", func(v interface{}) string { + return v.(*Person).Username + }, SparseIndex) + + tests := []struct { + name string + person *Person + wantID bool + }{ + { + name: "Empty optional field", + person: &Person{Name: "Alice", Email: "alice@test.com"}, + wantID: true, + }, + { + name: "Populated optional field", + person: &Person{Name: "Bob", Email: "bob@test.com", Username: "bobby"}, + wantID: true, + }, + { + name: "Multiple empty fields", + person: &Person{Name: "Charlie"}, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id := c.Set(tt.person) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestIndexKeyGeneration(t *testing.T) { + c := New() + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + + tests := []struct { + name string + person *Person + wantID bool + }{ + { + name: "Valid composite key", + person: &Person{Name: "Alice", Age: 30}, + wantID: true, + }, + { + name: "Duplicate composite key", + person: &Person{Name: "Alice", Age: 30}, + wantID: false, + }, + { + name: "Different composite key", + person: &Person{Name: "Alice", Age: 31}, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id := c.Set(tt.person) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestGetIndex(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + tests := []struct { + name string + indexName string + wantNil bool + }{ + { + name: "Get existing index", + indexName: "name", + wantNil: false, + }, + { + name: "Get _id index", + indexName: IDIndex, + wantNil: false, + }, + { + name: "Get non-existent index", + indexName: "invalid", + wantNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tree := c.GetIndex(tt.indexName) + if (tree == nil) != tt.wantNil { + t.Errorf("GetIndex() got nil = %v, want nil = %v", tree == nil, tt.wantNil) + } + }) + } +} + +func TestAddIndexPanic(t *testing.T) { + c := New() + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when adding _id index") + } + }() + + c.AddIndex(IDIndex, func(v interface{}) string { + return "" + }, DefaultIndex) +} + +func TestCaseInsensitiveOperations(t *testing.T) { + c := New() + c.AddIndex("email", func(v interface{}) string { + return v.(*Person).Email + }, UniqueIndex|CaseInsensitiveIndex) + + p := &Person{Email: "Test@Example.com"} + id := c.Set(p) + + tests := []struct { + name string + key string + wantObj bool + operation string // "get" or "getAll" + wantCount int + }{ + {"Get exact match", "Test@Example.com", true, "get", 1}, + {"Get different case", "test@example.COM", true, "get", 1}, + {"Get non-existent", "other@example.com", false, "get", 0}, + {"GetAll exact match", "Test@Example.com", true, "getAll", 1}, + {"GetAll different case", "test@example.COM", true, "getAll", 1}, + {"GetAll non-existent", "other@example.com", false, "getAll", 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.operation == "get" { + iter := c.Get("email", tt.key) + if iter.Empty() { + if tt.wantObj { + t.Error("Expected iterator to not be empty") + } + return + } + hasValue := iter.Next() + if hasValue != tt.wantObj { + t.Errorf("Get() got object = %v, want object = %v", hasValue, tt.wantObj) + } + if hasValue { + entry := iter.Value() + if entry.ID != seqid.ID(id).String() { + t.Errorf("Get() got id = %v, want id = %v", entry.ID, seqid.ID(id).String()) + } + } + } else { + entries := c.GetAll("email", tt.key) + if len(entries) != tt.wantCount { + t.Errorf("GetAll() returned %d entries, want %d", len(entries), tt.wantCount) + } + if tt.wantCount > 0 { + entry := entries[0] + if entry.ID != seqid.ID(id).String() { + t.Errorf("GetAll() returned ID %s, want %s", entry.ID, seqid.ID(id).String()) + } + } + } + }) + } +} + +func TestGetInvalidID(t *testing.T) { + c := New() + iter := c.Get(IDIndex, "not-a-valid-id") + if !iter.Empty() { + t.Errorf("Get() with invalid ID format got an entry, want nil") + } +} + +func TestGetAll(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + // Create test data + people := []*Person{ + {Name: "Alice", Age: 30, Tags: []string{"dev", "go"}}, + {Name: "Bob", Age: 30, Tags: []string{"dev", "python"}}, + {Name: "Charlie", Age: 25, Tags: []string{"dev", "rust"}}, + } + + ids := make([]uint64, len(people)) + for i, p := range people { + ids[i] = c.Set(p) + if ids[i] == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + tests := []struct { + name string + indexName string + key string + wantCount int + }{ + { + name: "Multi-value index with multiple matches", + indexName: "tags", + key: "dev", + wantCount: 3, + }, + { + name: "Multi-value index with single match", + indexName: "tags", + key: "go", + wantCount: 1, + }, + { + name: "Multi-value index with no matches", + indexName: "tags", + key: "java", + wantCount: 0, + }, + { + name: "Single-value non-unique index with multiple matches", + indexName: "age", + key: "30", + wantCount: 2, + }, + { + name: "Single-value unique index", + indexName: "name", + key: "Alice", + wantCount: 1, + }, + { + name: "Non-existent index", + indexName: "invalid", + key: "value", + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iter := c.Get(tt.indexName, tt.key) + count := 0 + for iter.Next() { + entry := iter.Value() + if entry.ID == "" { + t.Error("Got entry with empty ID") + } + if entry.Obj == nil { + t.Error("Got entry with nil Obj") + } + count++ + } + if count != tt.wantCount { + t.Errorf("Got %d entries, want %d", count, tt.wantCount) + } + }) + } +} + +func TestIndexOperations(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) (uint64, error) + verify func(*Collection, uint64) error + wantErr bool + }{ + { + name: "Basic set and get", + setup: func(c *Collection) (uint64, error) { + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + return c.Set(&Person{Name: "Alice", Age: 30}), nil + }, + verify: func(c *Collection, id uint64) error { + iter := c.Get(IDIndex, seqid.ID(id).String()) + if !iter.Next() { + return errors.New("failed to get object by ID") + } + entry := iter.Value() + if entry.Obj.(*Person).Name != "Alice" { + return errors.New("got wrong object") + } + return nil + }, + }, + { + name: "Composite index", + setup: func(c *Collection) (uint64, error) { + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + return c.Set(&Person{Name: "Alice", Age: 30}), nil + }, + verify: func(c *Collection, id uint64) error { + iter := c.Get("composite", "Alice:30") + if !iter.Next() { + return errors.New("failed to get object by composite index") + } + return nil + }, + }, + // Add more test cases combining unique scenarios from original tests + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + id, err := tt.setup(c) + if (err != nil) != tt.wantErr { + t.Errorf("setup error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil { + if err := tt.verify(c, id); err != nil { + t.Errorf("verification failed: %v", err) + } + } + }) + } +} + +func TestMultiValueIndexes(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + + tests := []struct { + name string + setup []*Person + searchTag string + wantCount int + }{ + { + name: "Multiple tags, multiple matches", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + {Name: "Charlie", Tags: []string{"dev", "rust"}}, + }, + searchTag: "dev", + wantCount: 3, + }, + { + name: "Single tag match", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + }, + searchTag: "go", + wantCount: 1, + }, + { + name: "No matches", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + }, + searchTag: "java", + wantCount: 0, + }, + { + name: "Empty tags", + setup: []*Person{ + {Name: "Alice", Tags: []string{}}, + {Name: "Bob", Tags: nil}, + }, + searchTag: "dev", + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + + // Setup test data + for _, p := range tt.setup { + if id := c.Set(p); id == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + // Test Get operation + iter := c.Get("tags", tt.searchTag) + count := 0 + for iter.Next() { + count++ + } + if count != tt.wantCount { + t.Errorf("Get() got %d matches, want %d", count, tt.wantCount) + } + }) + } +} + +func TestGetOperations(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // Setup test data + testPeople := []*Person{ + {Name: "Alice", Age: 30}, + {Name: "Bob", Age: 30}, + {Name: "Charlie", Age: 25}, + } + + ids := make([]uint64, len(testPeople)) + for i, p := range testPeople { + ids[i] = c.Set(p) + if ids[i] == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + tests := []struct { + name string + indexName string + key string + wantCount int + wantErr bool + }{ + { + name: "Get by ID", + indexName: IDIndex, + key: seqid.ID(ids[0]).String(), + wantCount: 1, + wantErr: false, + }, + { + name: "Get by unique index", + indexName: "name", + key: "Alice", + wantCount: 1, + wantErr: false, + }, + { + name: "Get by non-unique index", + indexName: "age", + key: "30", + wantCount: 2, + wantErr: false, + }, + { + name: "Get with invalid index", + indexName: "invalid_index", + key: "value", + wantCount: 0, + wantErr: true, + }, + { + name: "Get with invalid ID format", + indexName: IDIndex, + key: "not-a-valid-id", + wantCount: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iter := c.Get(tt.indexName, tt.key) + if iter.Empty() { + if !tt.wantErr { + t.Errorf("Get() returned empty iterator, wanted %d results", tt.wantCount) + } + return + } + + count := 0 + for iter.Next() { + entry := iter.Value() + if entry.ID == "" { + t.Error("Got entry with empty ID") + } + if entry.Obj == nil { + t.Error("Got entry with nil Obj") + } + count++ + } + + if count != tt.wantCount { + t.Errorf("Get() returned %d results, want %d", count, tt.wantCount) + } + }) + } +} + +func TestEntryString(t *testing.T) { + tests := []struct { + name string + entry *Entry + expected string + }{ + { + name: "Nil entry", + entry: nil, + expected: "", + }, + { + name: "Person entry", + entry: &Entry{ + ID: "123", + Obj: &Person{Name: "Alice", Age: 30}, + }, + expected: `Entry{ID: 123, Obj: name=Alice age=30 email= username= tags=}`, + }, + { + name: "Entry with nil object", + entry: &Entry{ + ID: "456", + Obj: nil, + }, + expected: `Entry{ID: 456, Obj: }`, + }, + { + name: "Entry with complete person", + entry: &Entry{ + ID: "789", + Obj: &Person{ + Name: "Bob", + Age: 25, + Email: "bob@example.com", + Username: "bobby", + Tags: []string{"dev", "go"}, + }, + }, + expected: `Entry{ID: 789, Obj: name=Bob age=25 email=bob@example.com username=bobby tags=dev,go}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.entry.String() + if got != tt.expected { + t.Errorf("Entry.String() = %q, want %q", got, tt.expected) + } + }) + } +} diff --git a/examples/gno.land/p/moul/collection/entry.gno b/examples/gno.land/p/moul/collection/entry.gno new file mode 100644 index 00000000000..8daa893b61d --- /dev/null +++ b/examples/gno.land/p/moul/collection/entry.gno @@ -0,0 +1,149 @@ +package collection + +import "gno.land/p/demo/ufmt" + +// Entry represents a single object in the collection with its ID +type Entry struct { + ID string + Obj interface{} +} + +// String returns a string representation of the Entry +func (e *Entry) String() string { + if e == nil { + return "" + } + return ufmt.Sprintf("Entry{ID: %s, Obj: %v}", e.ID, e.Obj) +} + +// EntryIterator provides iteration over collection entries +type EntryIterator struct { + collection *Collection + indexName string + key string + currentID string + currentObj interface{} + err error + closed bool + + // For multi-value cases + ids []string + currentIdx int +} + +func (ei *EntryIterator) Close() error { + ei.closed = true + ei.currentID = "" + ei.currentObj = nil + ei.ids = nil + return nil +} + +func (ei *EntryIterator) Next() bool { + if ei == nil || ei.closed || ei.err != nil { + return false + } + + // Handle ID index specially + if ei.indexName == IDIndex { + if ei.currentID != "" { // We've already returned the single value + return false + } + obj, exists := ei.collection.indexes[IDIndex].tree.Get(ei.key) + if !exists { + return false + } + ei.currentID = ei.key + ei.currentObj = obj + return true + } + + // Get the index + idx, exists := ei.collection.indexes[ei.indexName] + if !exists { + return false + } + + // Initialize ids slice if needed + if ei.ids == nil { + idData, exists := idx.tree.Get(ei.key) + if !exists { + return false + } + + switch stored := idData.(type) { + case []string: + ei.ids = stored + ei.currentIdx = -1 + case string: + ei.ids = []string{stored} + ei.currentIdx = -1 + default: + return false + } + } + + // Move to next ID + ei.currentIdx++ + if ei.currentIdx >= len(ei.ids) { + return false + } + + // Fetch the actual object + ei.currentID = ei.ids[ei.currentIdx] + obj, exists := ei.collection.indexes[IDIndex].tree.Get(ei.currentID) + if !exists { + // Skip invalid entries + return ei.Next() + } + ei.currentObj = obj + return true +} + +func (ei *EntryIterator) Error() error { + return ei.err +} + +func (ei *EntryIterator) Value() *Entry { + if ei == nil || ei.closed || ei.currentID == "" { + return nil + } + return &Entry{ + ID: ei.currentID, + Obj: ei.currentObj, + } +} + +func (ei *EntryIterator) Empty() bool { + if ei == nil || ei.closed || ei.err != nil { + return true + } + + // Handle ID index specially + if ei.indexName == IDIndex { + _, exists := ei.collection.indexes[IDIndex].tree.Get(ei.key) + return !exists + } + + // Get the index + idx, exists := ei.collection.indexes[ei.indexName] + if !exists { + return true + } + + // Check if key exists in index + idData, exists := idx.tree.Get(ei.key) + if !exists { + return true + } + + // Check if there are any valid IDs + switch stored := idData.(type) { + case []string: + return len(stored) == 0 + case string: + return stored == "" + default: + return true + } +} diff --git a/examples/gno.land/p/moul/collection/gno.mod b/examples/gno.land/p/moul/collection/gno.mod new file mode 100644 index 00000000000..a6eeca36837 --- /dev/null +++ b/examples/gno.land/p/moul/collection/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/collection From 3fa9940b85a9eeac64475cec4d5e10877c6dc14a Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:15:03 +0300 Subject: [PATCH 42/75] chore(autocounterd): fix `ShortHelp`, improve error messages (#3504) Per title. --- misc/autocounterd/cmd/cmd_start.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/autocounterd/cmd/cmd_start.go b/misc/autocounterd/cmd/cmd_start.go index ecf70f750be..e5155bf0718 100644 --- a/misc/autocounterd/cmd/cmd_start.go +++ b/misc/autocounterd/cmd/cmd_start.go @@ -44,7 +44,7 @@ func NewStartCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "start", ShortUsage: "start [flags]", - ShortHelp: "Runs the linter for the specified packages", + ShortHelp: "Increments the counter in the specified realm at regular intervals", }, cfg, func(_ context.Context, args []string) error { @@ -68,15 +68,15 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { signer, err := gnoclient.SignerFromBip39(cfg.mnemonic, cfg.chainID, "", uint32(0), uint32(0)) if err != nil { - return err + return fmt.Errorf("failed to create signer: %w", err) } if err := signer.Validate(); err != nil { - return err + return fmt.Errorf("invalid signer: %w", err) } rpcClient, err := rpcclient.NewHTTPClient(cfg.rpcURL) if err != nil { - return err + return fmt.Errorf("failed to create RPC client: %w", err) } client := gnoclient.Client{ @@ -97,7 +97,7 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { }) if err != nil { - fmt.Printf("[ERROR] Failed to call Incr on %s, %+v\n", cfg.realmPath, err.Error()) + fmt.Printf("[ERROR] Failed to call Incr on %s: %v\n", cfg.realmPath, err) } else { fmt.Println("[INFO] Counter incremented with success") } From cb2255f61c0abd0144550df223182334c6d50c51 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Thu, 16 Jan 2025 17:10:39 +0900 Subject: [PATCH 43/75] fix(gnoweb): enable table on gnoweb (#3525) --- gno.land/pkg/gnoweb/app.go | 2 ++ gno.land/pkg/gnoweb/frontend/css/input.css | 14 ++------------ gno.land/pkg/gnoweb/public/styles.css | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 1deea86644b..e99aa7ea056 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -15,6 +15,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" mdhtml "github.com/yuin/goldmark/renderer/html" ) @@ -77,6 +78,7 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { markdown.NewHighlighting( markdown.WithFormatOptions(chromaOptions...), ), + extension.Table, ), } if cfg.UnsafeHTML { diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index f86076dbe54..59e41ff4a7c 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -147,12 +147,12 @@ } .realm-content table { - @apply w-full border-collapse my-8; + @apply border-collapse my-8 block w-full max-w-full overflow-x-auto border-collapse; } .realm-content th, .realm-content td { - @apply border border-gray-300 px-4 py-2; + @apply border px-4 py-2 break-words whitespace-normal; } .realm-content th { @@ -190,16 +190,6 @@ @apply list-decimal; } - .realm-content table th:first-child, - .realm-content td:first-child { - @apply pl-0; - } - - .realm-content table th:last-child, - .realm-content td:last-child { - @apply pr-0; - } - .realm-content abbr[title] { @apply border-b border-dotted cursor-help; } diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index 4ff1a266c0c..8e8d7ed802d 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From 870cbedf24d6cc4127f0274897cd28752a33c7e5 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:37:34 +0100 Subject: [PATCH 44/75] chore(gnoweb): cleanup iteration on gnoweb (#3379) depends on #3366 This PR cleans up, documents, and reorganizes the `gnoweb` package, which was recently revamped: * Refactored the code for better readability and structure, and added enhanced comments. * Enhanced existing test cases: * Added new test cases for `assets` in `app_test.go`. * Included a new test rule in the Makefile. * Created new tests for WebHandler in `handler_test.go`. * Improved file and directory handling methods in `handler.go`. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Morgan --- gno.land/cmd/gnoweb/main.go | 14 +- gno.land/pkg/gnoweb/Makefile | 3 + gno.land/pkg/gnoweb/app.go | 80 ++++--- gno.land/pkg/gnoweb/app_test.go | 29 ++- gno.land/pkg/gnoweb/format.go | 69 ++++++ gno.land/pkg/gnoweb/formatter.go | 25 --- gno.land/pkg/gnoweb/handler.go | 304 ++++++++++++-------------- gno.land/pkg/gnoweb/handler_test.go | 112 ++++++++++ gno.land/pkg/gnoweb/markdown/toc.go | 4 +- gno.land/pkg/gnoweb/webclient.go | 136 +++--------- gno.land/pkg/gnoweb/webclient_html.go | 190 ++++++++++++++++ gno.land/pkg/gnoweb/webclient_mock.go | 91 ++++++++ 12 files changed, 710 insertions(+), 347 deletions(-) create mode 100644 gno.land/pkg/gnoweb/format.go delete mode 100644 gno.land/pkg/gnoweb/formatter.go create mode 100644 gno.land/pkg/gnoweb/handler_test.go create mode 100644 gno.land/pkg/gnoweb/webclient_html.go create mode 100644 gno.land/pkg/gnoweb/webclient_mock.go diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 6500e44fcc4..8c0df00aa35 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -144,7 +144,6 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { if cfg.verbose { level = zapcore.DebugLevel } - var zapLogger *zap.Logger if cfg.json { zapLogger = log.NewZapJSONLogger(io.Out(), level) @@ -155,23 +154,24 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger := log.ZapLoggerToSlog(zapLogger) + // Setup app appcfg := gnoweb.NewDefaultAppConfig() appcfg.ChainID = cfg.chainid appcfg.NodeRemote = cfg.remote appcfg.RemoteHelp = cfg.remoteHelp + if appcfg.RemoteHelp == "" { + appcfg.RemoteHelp = appcfg.NodeRemote + } appcfg.Analytics = cfg.analytics appcfg.UnsafeHTML = cfg.html appcfg.FaucetURL = cfg.faucetURL appcfg.AssetsDir = cfg.assetsDir - if appcfg.RemoteHelp == "" { - appcfg.RemoteHelp = appcfg.NodeRemote - } - app, err := gnoweb.NewRouter(logger, appcfg) if err != nil { return nil, fmt.Errorf("unable to start gnoweb app: %w", err) } + // Resolve binding address bindaddr, err := net.ResolveTCPAddr("tcp", cfg.bind) if err != nil { return nil, fmt.Errorf("unable to resolve listener %q: %w", cfg.bind, err) @@ -179,6 +179,7 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) + // Setup server server := &http.Server{ Handler: app, Addr: bindaddr.String(), @@ -187,10 +188,9 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { return func() error { if err := server.ListenAndServe(); err != nil { - logger.Error("HTTP server stopped", " error:", err) + logger.Error("HTTP server stopped", "error", err) return commands.ExitCodeError(1) } - return nil }, nil } diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 39c9d20ab10..8e8b6bf1a2c 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -39,6 +39,9 @@ cache_dir := .cache # Install dependencies all: generate +test: + go test -v ./... + # Generate process generate: css ts static diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index e99aa7ea056..516d3b92186 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -33,10 +33,12 @@ type AppConfig struct { ChainID string // AssetsPath is the base path to the gnoweb assets. AssetsPath string - // AssetDir, if set, will be used for assets instead of the embedded public directory + // AssetDir, if set, will be used for assets instead of the embedded public directory. AssetsDir string // FaucetURL, if specified, will be the URL to which `/faucet` redirects. FaucetURL string + // Domain is the domain used by the node. + Domain string } // NewDefaultAppConfig returns a new default [AppConfig]. The default sets @@ -44,17 +46,16 @@ type AppConfig struct { // to be served on /public/. func NewDefaultAppConfig() *AppConfig { const defaultRemote = "127.0.0.1:26657" - return &AppConfig{ - // same as Remote by default NodeRemote: defaultRemote, RemoteHelp: defaultRemote, ChainID: "dev", AssetsPath: "/public/", + Domain: "gno.land", } } -var chromaStyle = mustGetStyle("friendly") +var chromaDefaultStyle = mustGetStyle("friendly") func mustGetStyle(name string) *chroma.Style { s := styles.Get(name) @@ -64,15 +65,24 @@ func mustGetStyle(name string) *chroma.Style { return s } -// NewRouter initializes the gnoweb router, with the given logger and config. +// NewRouter initializes the gnoweb router with the specified logger and configuration. func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { + // Initialize RPC Client + client, err := client.NewHTTPClient(cfg.NodeRemote) + if err != nil { + return nil, fmt.Errorf("unable to create HTTP client: %w", err) + } + + // Configure Chroma highlighter chromaOptions := []chromahtml.Option{ chromahtml.WithLineNumbers(true), chromahtml.WithLinkableLineNumbers(true, "L"), chromahtml.WithClasses(true), chromahtml.ClassPrefix("chroma-"), } + chroma := chromahtml.New(chromaOptions...) + // Configure Goldmark markdown parser mdopts := []goldmark.Option{ goldmark.WithExtensions( markdown.NewHighlighting( @@ -84,36 +94,41 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { if cfg.UnsafeHTML { mdopts = append(mdopts, goldmark.WithRendererOptions(mdhtml.WithXHTML(), mdhtml.WithUnsafe())) } - md := goldmark.New(mdopts...) - client, err := client.NewHTTPClient(cfg.NodeRemote) - if err != nil { - return nil, fmt.Errorf("unable to create http client: %w", err) + // Configure WebClient + webcfg := HTMLWebClientConfig{ + Markdown: md, + Highlighter: NewChromaSourceHighlighter(chroma, chromaDefaultStyle), + Domain: cfg.Domain, + UnsafeHTML: cfg.UnsafeHTML, + RPCClient: client, } - webcli := NewWebClient(logger, client, md) - formatter := chromahtml.New(chromaOptions...) + webcli := NewHTMLClient(logger, &webcfg) chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") - var webConfig WebHandlerConfig - - webConfig.RenderClient = webcli - webConfig.Formatter = newFormatterWithStyle(formatter, chromaStyle) - - // Static meta - webConfig.Meta.AssetsPath = cfg.AssetsPath - webConfig.Meta.ChromaPath = chromaStylePath - webConfig.Meta.RemoteHelp = cfg.RemoteHelp - webConfig.Meta.ChainId = cfg.ChainID - webConfig.Meta.Analytics = cfg.Analytics + // Setup StaticMetadata + staticMeta := StaticMetadata{ + Domain: cfg.Domain, + AssetsPath: cfg.AssetsPath, + ChromaPath: chromaStylePath, + RemoteHelp: cfg.RemoteHelp, + ChainId: cfg.ChainID, + Analytics: cfg.Analytics, + } - // Setup main handler - webhandler := NewWebHandler(logger, webConfig) + // Configure WebHandler + webConfig := WebHandlerConfig{WebClient: webcli, Meta: staticMeta} + webhandler, err := NewWebHandler(logger, webConfig) + if err != nil { + return nil, fmt.Errorf("unable to create web handler: %w", err) + } + // Setup HTTP muxer mux := http.NewServeMux() - // Setup Webahndler along Alias Middleware + // Handle web handler with alias middleware mux.Handle("/", AliasAndRedirectMiddleware(webhandler, cfg.Analytics)) // Register faucet URL to `/faucet` if specified @@ -127,22 +142,21 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { })) } - // setup assets + // Handle Chroma CSS requests + // XXX: probably move this elsewhere mux.Handle(chromaStylePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Setup Formatter w.Header().Set("Content-Type", "text/css") - if err := formatter.WriteCSS(w, chromaStyle); err != nil { - logger.Error("unable to write css", "err", err) + if err := chroma.WriteCSS(w, chromaDefaultStyle); err != nil { + logger.Error("unable to write CSS", "err", err) http.NotFound(w, r) } })) - // Normalize assets path - assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" - // Handle assets path + // XXX: add caching + assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" if cfg.AssetsDir != "" { - logger.Debug("using assets dir instead of embed assets", "dir", cfg.AssetsDir) + logger.Debug("using assets dir instead of embedded assets", "dir", cfg.AssetsDir) mux.Handle(assetsBase, DevAssetHandler(assetsBase, cfg.AssetsDir)) } else { mux.Handle(assetsBase, AssetHandler()) diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 4fac6e0b971..9f8f87b99b1 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -24,9 +24,9 @@ func TestRoutes(t *testing.T) { status int substring string }{ - {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". + {"/", ok, "Welcome"}, // Check if / returns 200 (OK) and contains "Welcome". {"/about", ok, "blockchain"}, - {"/r/gnoland/blog", ok, ""}, // whatever content + {"/r/gnoland/blog", ok, ""}, // Any content {"/r/gnoland/blog$help", ok, "AdminSetAdminAddr"}, {"/r/gnoland/blog/", ok, "admin.gno"}, {"/r/gnoland/blog/admin.gno", ok, ">func<"}, @@ -47,12 +47,18 @@ func TestRoutes(t *testing.T) { {"/game-of-realms", found, "/contribute"}, {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, - {"/404/not/found/", notFound, ""}, + {"/r/not/found/", notFound, ""}, + {"/404/not/found", notFound, ""}, {"/아스키문자가아닌경로", notFound, ""}, {"/%ED%85%8C%EC%8A%A4%ED%8A%B8", notFound, ""}, {"/グノー", notFound, ""}, - {"/⚛️", notFound, ""}, + {"/\u269B\uFE0F", notFound, ""}, // Unicode {"/p/demo/flow/LICENSE", ok, "BSD 3-Clause"}, + // Test assets + {"/public/styles.css", ok, ""}, + {"/public/js/index.js", ok, ""}, + {"/public/_chroma/style.css", ok, ""}, + {"/public/imgs/gnoland.svg", ok, ""}, } rootdir := gnoenv.RootDir() @@ -66,8 +72,7 @@ func TestRoutes(t *testing.T) { logger := log.NewTestingLogger(t) - // set the `remoteAddr` of the client to the listening address of the - // node, which is randomly assigned. + // Initialize the router with the current node's remote address router, err := NewRouter(logger, cfg) require.NoError(t, err) @@ -85,24 +90,24 @@ func TestRoutes(t *testing.T) { func TestAnalytics(t *testing.T) { routes := []string{ - // special realms - "/", // home + // Special realms + "/", // Home "/about", "/start", - // redirects + // Redirects "/game-of-realms", "/getting-started", "/blog", "/boards", - // realm, source, help page + // Realm, source, help page "/r/gnoland/blog", "/r/gnoland/blog/admin.gno", "/r/demo/users:administrator", "/r/gnoland/blog$help", - // special pages + // Special pages "/404-not-found", } @@ -125,6 +130,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.Contains(t, response.Body.String(), "sa.gno.services") @@ -143,6 +149,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.NotContains(t, response.Body.String(), "sa.gno.services") diff --git a/gno.land/pkg/gnoweb/format.go b/gno.land/pkg/gnoweb/format.go new file mode 100644 index 00000000000..67911bfa985 --- /dev/null +++ b/gno.land/pkg/gnoweb/format.go @@ -0,0 +1,69 @@ +package gnoweb + +import ( + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" +) + +// FormatSource defines the interface for formatting source code. +type FormatSource interface { + Format(w io.Writer, fileName string, file []byte) error +} + +// ChromaSourceHighlighter implements the Highlighter interface using the Chroma library. +type ChromaSourceHighlighter struct { + *html.Formatter + style *chroma.Style +} + +// NewChromaSourceHighlighter constructs a new ChromaHighlighter with the given formatter and style. +func NewChromaSourceHighlighter(formatter *html.Formatter, style *chroma.Style) FormatSource { + return &ChromaSourceHighlighter{Formatter: formatter, style: style} +} + +// Format applies syntax highlighting to the source code using Chroma. +func (f *ChromaSourceHighlighter) Format(w io.Writer, fileName string, src []byte) error { + var lexer chroma.Lexer + + // Determine the lexer to be used based on the file extension. + switch strings.ToLower(filepath.Ext(fileName)) { + case ".gno": + lexer = lexers.Get("go") + case ".md": + lexer = lexers.Get("markdown") + case ".mod": + lexer = lexers.Get("gomod") + default: + lexer = lexers.Get("txt") // Unsupported file type, default to plain text. + } + + if lexer == nil { + return fmt.Errorf("unsupported lexer for file %q", fileName) + } + + iterator, err := lexer.Tokenise(nil, string(src)) + if err != nil { + return fmt.Errorf("unable to tokenise %q: %w", fileName, err) + } + + if err := f.Formatter.Format(w, f.style, iterator); err != nil { + return fmt.Errorf("unable to format source file %q: %w", fileName, err) + } + + return nil +} + +// noopFormat is a no-operation highlighter that writes the source code as-is. +type noopFormat struct{} + +// Format writes the source code to the writer without any formatting. +func (f *noopFormat) Format(w io.Writer, fileName string, src []byte) error { + _, err := w.Write(src) + return err +} diff --git a/gno.land/pkg/gnoweb/formatter.go b/gno.land/pkg/gnoweb/formatter.go deleted file mode 100644 index e172afe9e21..00000000000 --- a/gno.land/pkg/gnoweb/formatter.go +++ /dev/null @@ -1,25 +0,0 @@ -package gnoweb - -import ( - "io" - - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" -) - -type Formatter interface { - Format(w io.Writer, iterator chroma.Iterator) error -} - -type formatterWithStyle struct { - *html.Formatter - style *chroma.Style -} - -func newFormatterWithStyle(formater *html.Formatter, style *chroma.Style) Formatter { - return &formatterWithStyle{Formatter: formater, style: style} -} - -func (f *formatterWithStyle) Format(w io.Writer, iterator chroma.Iterator) error { - return f.Formatter.Format(w, f.style, iterator) -} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 20f19e4405a..2dc51d64029 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -9,19 +9,16 @@ import ( "log/slog" "net/http" "path/filepath" - "slices" "strings" "time" - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/lexers" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // For error types ) -const DefaultChainDomain = "gno.land" - +// StaticMetadata holds static configuration for a web handler. type StaticMetadata struct { + Domain string AssetsPath string ChromaPath string RemoteHelp string @@ -29,35 +26,43 @@ type StaticMetadata struct { Analytics bool } +// WebHandlerConfig configures a WebHandler. type WebHandlerConfig struct { - Meta StaticMetadata - RenderClient *WebClient - Formatter Formatter + Meta StaticMetadata + WebClient WebClient } -type WebHandler struct { - formatter Formatter +// validate checks if the WebHandlerConfig is valid. +func (cfg WebHandlerConfig) validate() error { + if cfg.WebClient == nil { + return errors.New("no `WebClient` configured") + } + return nil +} - logger *slog.Logger - static StaticMetadata - webcli *WebClient +// WebHandler processes HTTP requests. +type WebHandler struct { + Logger *slog.Logger + Static StaticMetadata + Client WebClient } -func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) *WebHandler { - if cfg.RenderClient == nil { - logger.Error("no renderer has been defined") +// NewWebHandler creates a new WebHandler. +func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) (*WebHandler, error) { + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("config validate error: %w", err) } return &WebHandler{ - formatter: cfg.Formatter, - webcli: cfg.RenderClient, - logger: logger, - static: cfg.Meta, - } + Client: cfg.WebClient, + Static: cfg.Meta, + Logger: logger, + }, nil } +// ServeHTTP handles HTTP requests. func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) + h.Logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) @@ -67,47 +72,29 @@ func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Get(w, r) } +// Get processes a GET HTTP request. func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { var body bytes.Buffer start := time.Now() defer func() { - h.logger.Debug("request completed", + h.Logger.Debug("request completed", "url", r.URL.String(), "elapsed", time.Since(start).String()) }() - var indexData components.IndexData - indexData.HeadData.AssetsPath = h.static.AssetsPath - indexData.HeadData.ChromaPath = h.static.ChromaPath - indexData.FooterData.Analytics = h.static.Analytics - indexData.FooterData.AssetsPath = h.static.AssetsPath - - // Render the page body into the buffer - var status int - gnourl, err := ParseGnoURL(r.URL) - if err != nil { - h.logger.Warn("page not found", "path", r.URL.Path, "err", err) - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } else { - // TODO: real data (title & description) - indexData.HeadData.Title = "gno.land - " + gnourl.Path - - // Header - indexData.HeaderData.RealmPath = gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape) - indexData.HeaderData.Breadcrumb = generateBreadcrumbPaths(gnourl) - indexData.HeaderData.WebQuery = gnourl.WebQuery - - // Render - switch { - case gnourl.IsRealm(), gnourl.IsPure(): - status, err = h.renderPackage(&body, gnourl) - default: - h.logger.Debug("invalid path: path is neither a pure package or a realm") - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } + indexData := components.IndexData{ + HeadData: components.HeadData{ + AssetsPath: h.Static.AssetsPath, + ChromaPath: h.Static.ChromaPath, + }, + FooterData: components.FooterData{ + Analytics: h.Static.Analytics, + AssetsPath: h.Static.AssetsPath, + }, } + status, err := h.renderPage(&body, r, &indexData) if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) return @@ -120,75 +107,99 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { // Render the final page with the rendered body if err = components.RenderIndexComponent(w, indexData); err != nil { - h.logger.Error("failed to render index component", "err", err) + h.Logger.Error("failed to render index component", "err", err) } +} - return +// renderPage renders the page into the given buffer and prepares the index data. +func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData *components.IndexData) (int, error) { + gnourl, err := ParseGnoURL(r.URL) + if err != nil { + h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "err", err) + return http.StatusNotFound, components.RenderStatusComponent(body, "invalid path") + } + + breadcrumb := generateBreadcrumbPaths(gnourl) + indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path + indexData.HeaderData = components.HeaderData{ + RealmPath: gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape), + Breadcrumb: breadcrumb, + WebQuery: gnourl.WebQuery, + } + + switch { + case gnourl.IsRealm(), gnourl.IsPure(): + return h.GetPackagePage(body, gnourl) + default: + h.Logger.Debug("invalid path: path is neither a pure package or a realm") + return http.StatusBadRequest, components.RenderStatusComponent(body, "invalid path") + } } -func (h *WebHandler) renderPackage(w io.Writer, gnourl *GnoURL) (status int, err error) { - h.logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) +// GetPackagePage handles package pages. +func (h *WebHandler) GetPackagePage(w io.Writer, gnourl *GnoURL) (int, error) { + h.Logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - // Display realm help page? + // Handle Help page if gnourl.WebQuery.Has("help") { - return h.renderRealmHelp(w, gnourl) + return h.GetHelpPage(w, gnourl) } - // Display package source page? - switch { - case gnourl.WebQuery.Has("source"): - return h.renderRealmSource(w, gnourl) - case gnourl.IsFile(): - // Fill webquery with file infos - return h.renderRealmSource(w, gnourl) - case gnourl.IsDir(), gnourl.IsPure(): - return h.renderRealmDirectory(w, gnourl) + // Handle Source page + if gnourl.WebQuery.Has("source") || gnourl.IsFile() { + return h.GetSourcePage(w, gnourl) + } + + // Handle Source page + if gnourl.IsDir() || gnourl.IsPure() { + return h.GetDirectoryPage(w, gnourl) } - // Render content into the content buffer + // Ultimately render realm content + return h.renderRealmContent(w, gnourl) +} + +// renderRealmContent renders the content of a realm. +func (h *WebHandler) renderRealmContent(w io.Writer, gnourl *GnoURL) (int, error) { var content bytes.Buffer - meta, err := h.webcli.Render(&content, gnourl.Path, gnourl.EncodeArgs()) + meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { - if errors.Is(err, vm.InvalidPkgPathError{}) { - return http.StatusNotFound, components.RenderStatusComponent(w, "not found") - } - - h.logger.Error("unable to render markdown", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to render realm", "err", err, "path", gnourl.EncodeArgs()) + return renderClientErrorStatusPage(w, gnourl, err) } err = components.RenderRealmComponent(w, components.RealmData{ TocItems: &components.RealmTOCData{ - Items: meta.Items, + Items: meta.Toc.Items, }, - // NOTE: `content` should have already been escaped by + // NOTE: `RenderRealm` should ensure that HTML content is + // sanitized before rendering Content: template.HTML(content.String()), //nolint:gosec }) if err != nil { - h.logger.Error("unable to render template", "err", err) + h.Logger.Error("unable to render template", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } - // Write the rendered content to the response writer return http.StatusOK, nil } -func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, err error) { - fsigs, err := h.webcli.Functions(gnourl.Path) +// GetHelpPage renders the help page. +func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { + fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { - h.logger.Error("unable to fetch path functions", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to fetch path functions", "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } - var selArgs map[string]string - var selFn string - if selFn = gnourl.WebQuery.Get("func"); selFn != "" { + selArgs := make(map[string]string) + selFn := gnourl.WebQuery.Get("func") + if selFn != "" { for _, fn := range fsigs { if selFn != fn.FuncName { continue } - selArgs = make(map[string]string) for _, param := range fn.Params { selArgs[param.Name] = gnourl.WebQuery.Get(param.Name) } @@ -198,102 +209,87 @@ func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, e } } - // Catch last name of the path - // XXX: we should probably add a helper within the template realmName := filepath.Base(gnourl.Path) err = components.RenderHelpComponent(w, components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, - ChainId: h.static.ChainId, + ChainId: h.Static.ChainId, // TODO: get chain domain and use that. - PkgPath: filepath.Join(DefaultChainDomain, gnourl.Path), - Remote: h.static.RemoteHelp, + PkgPath: filepath.Join(h.Static.Domain, gnourl.Path), + Remote: h.Static.RemoteHelp, Functions: fsigs, }) if err != nil { - h.logger.Error("unable to render helper", "err", err) + h.Logger.Error("unable to render helper", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } return http.StatusOK, nil } -func (h *WebHandler) renderRealmSource(w io.Writer, gnourl *GnoURL) (status int, err error) { +// GetSource renders the source page. +func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { pkgPath := gnourl.Path - - files, err := h.webcli.Sources(pkgPath) + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) + h.Logger.Debug("no files available", "path", gnourl.Path) return http.StatusOK, components.RenderStatusComponent(w, "no files available") } - file := gnourl.WebQuery.Get("file") // webquery override file - if file == "" { - file = gnourl.File - } - var fileName string - if file == "" { - fileName = files[0] // Default to the first file if none specified - } else if slices.Contains(files, file) { - fileName = file // Use specified file if it exists - } else { - h.logger.Error("unable to render source", "file", file, "err", "file does not exist") - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if gnourl.IsFile() { // check path file from path first + fileName = gnourl.File + } else if file := gnourl.WebQuery.Get("file"); file != "" { + fileName = file } - source, err := h.webcli.SourceFile(pkgPath, fileName) - if err != nil { - h.logger.Error("unable to get source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if fileName == "" { + fileName = files[0] // fallback on the first file if } - // XXX: we should either do this on the front or in the markdown parsing side - fileLines := strings.Count(string(source), "\n") - fileSizeKb := float64(len(source)) / 1024.0 - fileSizeStr := fmt.Sprintf("%.2f Kb", fileSizeKb) - - // Highlight code source - hsource, err := h.highlightSource(fileName, source) + var source bytes.Buffer + meta, err := h.Client.SourceFile(&source, pkgPath, fileName) if err != nil { - h.logger.Error("unable to highlight source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to get source file", "file", fileName, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } + fileSizeStr := fmt.Sprintf("%.2f Kb", meta.SizeKb) err = components.RenderSourceComponent(w, components.SourceData{ PkgPath: gnourl.Path, Files: files, FileName: fileName, FileCounter: len(files), - FileLines: fileLines, + FileLines: meta.Lines, FileSize: fileSizeStr, - FileSource: template.HTML(hsource), //nolint:gosec + FileSource: template.HTML(source.String()), //nolint:gosec }) if err != nil { - h.logger.Error("unable to render helper", "err", err) + h.Logger.Error("unable to render helper", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } return http.StatusOK, nil } -func (h *WebHandler) renderRealmDirectory(w io.Writer, gnourl *GnoURL) (status int, err error) { - pkgPath := gnourl.Path +// GetDirectoryPage renders the directory page. +func (h *WebHandler) GetDirectoryPage(w io.Writer, gnourl *GnoURL) (int, error) { + pkgPath := strings.TrimSuffix(gnourl.Path, "/") - files, err := h.webcli.Sources(pkgPath) + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) + h.Logger.Debug("no files available", "path", gnourl.Path) return http.StatusOK, components.RenderStatusComponent(w, "no files available") } @@ -303,40 +299,28 @@ func (h *WebHandler) renderRealmDirectory(w io.Writer, gnourl *GnoURL) (status i FileCounter: len(files), }) if err != nil { - h.logger.Error("unable to render directory", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to render directory", "err", err) + return http.StatusInternalServerError, components.RenderStatusComponent(w, "not found") } return http.StatusOK, nil } -func (h *WebHandler) highlightSource(fileName string, src []byte) ([]byte, error) { - var lexer chroma.Lexer - - switch strings.ToLower(filepath.Ext(fileName)) { - case ".gno": - lexer = lexers.Get("go") - case ".md": - lexer = lexers.Get("markdown") - default: - lexer = lexers.Get("txt") // file kind not supported, fallback on `.txt` +func renderClientErrorStatusPage(w io.Writer, _ *GnoURL, err error) (int, error) { + if err == nil { + return http.StatusOK, nil } - if lexer == nil { - return nil, fmt.Errorf("unsupported lexer for file %q", fileName) - } - - iterator, err := lexer.Tokenise(nil, string(src)) - if err != nil { - h.logger.Error("unable to ", "fileName", fileName, "err", err) - } - - var buff bytes.Buffer - if err := h.formatter.Format(&buff, iterator); err != nil { - return nil, fmt.Errorf("unable to format source file %q: %w", fileName, err) + switch { + case errors.Is(err, ErrClientPathNotFound): + return http.StatusNotFound, components.RenderStatusComponent(w, err.Error()) + case errors.Is(err, ErrClientBadRequest): + return http.StatusInternalServerError, components.RenderStatusComponent(w, "bad request") + case errors.Is(err, ErrClientResponse): + fallthrough // XXX: for now fallback as internal error + default: + return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } - - return buff.Bytes(), nil } func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go new file mode 100644 index 00000000000..624e3390a97 --- /dev/null +++ b/gno.land/pkg/gnoweb/handler_test.go @@ -0,0 +1,112 @@ +package gnoweb_test + +import ( + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testingLogger struct { + *testing.T +} + +func (t *testingLogger) Write(b []byte) (n int, err error) { + t.T.Log(strings.TrimSpace(string(b))) + return len(b), nil +} + +// TestWebHandler_Get tests the Get method of WebHandler using table-driven tests. +func TestWebHandler_Get(t *testing.T) { + // Set up a mock package with some files and functions + mockPackage := &gnoweb.MockPackage{ + Domain: "example.com", + Path: "/r/mock/path", + Files: map[string]string{ + "render.gno": `package main; func Render(path string) { return "one more time" }`, + "gno.mod": `module example.com/r/mock/path`, + "LicEnse": `my super license`, + }, + Functions: []vm.FunctionSignature{ + {FuncName: "SuperRenderFunction", Params: []vm.NamedType{ + {Name: "my_super_arg", Type: "string"}, + }}, + }, + } + + // Create a mock web client with the mock package + webclient := gnoweb.NewMockWebClient(mockPackage) + + // Create a WebHandlerConfig with the mock web client and static metadata + config := gnoweb.WebHandlerConfig{ + WebClient: webclient, + } + + // Define test cases + cases := []struct { + Path string + Status int + Contain string // optional + Contains []string // optional + }{ + // Found + {Path: "/r/mock/path", Status: http.StatusOK, Contain: "[example.com]/r/mock/path"}, + + // Source page + {Path: "/r/mock/path/", Status: http.StatusOK, Contain: "Directory"}, + {Path: "/r/mock/path/render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path/LicEnse", Status: http.StatusOK, Contain: "my super license"}, + {Path: "/r/mock/path$source&file=render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path$source", Status: http.StatusOK, Contain: "module"}, // `gno.mod` by default + {Path: "/r/mock/path/license", Status: http.StatusNotFound}, + + // Help page + {Path: "/r/mock/path$help", Status: http.StatusOK, Contains: []string{ + "my_super_arg", + "SuperRenderFunction", + }}, + + // Package not found + {Path: "/r/invalid/path", Status: http.StatusNotFound, Contain: "not found"}, + + // Invalid path + {Path: "/r", Status: http.StatusBadRequest, Contain: "invalid path"}, + {Path: "/r/~!1337", Status: http.StatusNotFound, Contain: "invalid path"}, + } + + for _, tc := range cases { + t.Run(strings.TrimPrefix(tc.Path, "/"), func(t *testing.T) { + t.Logf("input: %+v", tc) + + // Initialize testing logger + logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{})) + + // Create a new WebHandler + handler, err := gnoweb.NewWebHandler(logger, config) + require.NoError(t, err) + + // Create a new HTTP request for each test case + req, err := http.NewRequest(http.MethodGet, tc.Path, nil) + require.NoError(t, err) + + // Create a ResponseRecorder to capture the response + rr := httptest.NewRecorder() + + // Invoke serve method + handler.ServeHTTP(rr, req) + + // Assert result + assert.Equal(t, tc.Status, rr.Code) + assert.Containsf(t, rr.Body.String(), tc.Contain, "rendered body should contain: %q", tc.Contain) + for _, contain := range tc.Contains { + assert.Containsf(t, rr.Body.String(), contain, "rendered body should contain: %q", contain) + } + }) + } +} diff --git a/gno.land/pkg/gnoweb/markdown/toc.go b/gno.land/pkg/gnoweb/markdown/toc.go index 59d4941fabf..ceafbd7cc96 100644 --- a/gno.land/pkg/gnoweb/markdown/toc.go +++ b/gno.land/pkg/gnoweb/markdown/toc.go @@ -45,7 +45,7 @@ type TocOptions struct { MinDepth, MaxDepth int } -func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { +func TocInspect(n ast.Node, src []byte, opts TocOptions) (Toc, error) { // Appends an empty subitem to the given node // and returns a reference to it. appendChild := func(n *TocItem) *TocItem { @@ -114,7 +114,7 @@ func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { root.Items = compactItems(root.Items) - return &Toc{Items: root.Items}, err + return Toc{Items: root.Items}, err } // compactItems removes items with no titles diff --git a/gno.land/pkg/gnoweb/webclient.go b/gno.land/pkg/gnoweb/webclient.go index a1005baa0a5..de44303f352 100644 --- a/gno.land/pkg/gnoweb/webclient.go +++ b/gno.land/pkg/gnoweb/webclient.go @@ -2,126 +2,44 @@ package gnoweb import ( "errors" - "fmt" "io" - "log/slog" - "path/filepath" - "strings" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" ) -type WebClient struct { - logger *slog.Logger - client *client.RPCClient - md goldmark.Markdown -} - -func NewWebClient(log *slog.Logger, cl *client.RPCClient, m goldmark.Markdown) *WebClient { - m.Parser().AddOptions(parser.WithAutoHeadingID()) - return &WebClient{ - logger: log, - client: cl, - md: m, - } -} - -func (s *WebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { - const qpath = "vm/qfuncs" - - args := fmt.Sprintf("gno.land/%s", strings.Trim(pkgPath, "/")) - res, err := s.query(qpath, []byte(args)) - if err != nil { - return nil, fmt.Errorf("unable query funcs list: %w", err) - } - - var fsigs vm.FunctionSignatures - if err := amino.UnmarshalJSON(res, &fsigs); err != nil { - s.logger.Warn("unable to unmarshal fsigs, client is probably outdated ?") - return nil, fmt.Errorf("unable to unamarshal fsigs: %w", err) - } - - return fsigs, nil -} - -func (s *WebClient) SourceFile(path, fileName string) ([]byte, error) { - const qpath = "vm/qfile" - - fileName = strings.TrimSpace(fileName) // sanitize filename - if fileName == "" { - return nil, errors.New("empty filename given") // XXX -> ErrXXX - } - - // XXX: move this into gnoclient ? - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - path = filepath.Join(path, fileName) - return s.query(qpath, []byte(path)) -} - -func (s *WebClient) Sources(path string) ([]string, error) { - const qpath = "vm/qfile" - - // XXX: move this into gnoclient - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - res, err := s.query(qpath, []byte(path)) - if err != nil { - return nil, err - } +var ( + ErrClientPathNotFound = errors.New("package not found") + ErrClientBadRequest = errors.New("bad request") + ErrClientResponse = errors.New("node response error") +) - files := strings.Split(string(res), "\n") - return files, nil +type FileMeta struct { + Lines int + SizeKb float64 } -type Metadata struct { - *md.Toc +type RealmMeta struct { + Toc md.Toc } -func (s *WebClient) Render(w io.Writer, pkgPath string, args string) (*Metadata, error) { - const qpath = "vm/qrender" - - data := []byte(gnoPath(pkgPath, args)) - rawres, err := s.query(qpath, data) - if err != nil { - return nil, err - } - - doc := s.md.Parser().Parse(text.NewReader(rawres)) - if err := s.md.Renderer().Render(w, rawres, doc); err != nil { - return nil, fmt.Errorf("unable render real %q: %w", data, err) - } +// WebClient is an interface for interacting with package and node ressources. +type WebClient interface { + // RenderRealm renders the content of a realm from a given path and + // arguments into the giver `writer`. The method should ensures the rendered + // content is safely handled and formatted. + RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) - var meta Metadata - meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) - if err != nil { - s.logger.Warn("unable to inspect for toc elements", "err", err) - } + // SourceFile fetches and writes the source file from a given + // package path and file name. The method should ensures the source + // file's content is safely handled and formatted. + SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) - return &meta, nil -} - -func (s *WebClient) query(qpath string, data []byte) ([]byte, error) { - s.logger.Info("query", "qpath", qpath, "data", string(data)) - - qres, err := s.client.ABCIQuery(qpath, data) - if err != nil { - s.logger.Error("request error", "path", qpath, "data", string(data), "error", err) - return nil, fmt.Errorf("unable to query path %q: %w", qpath, err) - } - if qres.Response.Error != nil { - s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) - return nil, qres.Response.Error - } - - return qres.Response.Data, nil -} + // Functions retrieves a list of function signatures from a + // specified package path. + Functions(path string) ([]vm.FunctionSignature, error) -func gnoPath(pkgPath, args string) string { - pkgPath = strings.Trim(pkgPath, "/") - return fmt.Sprintf("gno.land/%s:%s", pkgPath, args) + // Sources lists all source files available in a specified + // package path. + Sources(path string) ([]string, error) } diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go new file mode 100644 index 00000000000..ffe2238df98 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -0,0 +1,190 @@ +package gnoweb + +import ( + "errors" + "fmt" + "io" + "log/slog" + "path/filepath" + "strings" + + md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +type HTMLWebClientConfig struct { + Domain string + UnsafeHTML bool + RPCClient *client.RPCClient + Highlighter FormatSource + Markdown goldmark.Markdown +} + +// NewDefaultHTMLWebClientConfig initializes a WebClientConfig with default settings. +// It sets up goldmark Markdown parsing options and default domain and highlighter. +func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfig { + mdopts := []goldmark.Option{goldmark.WithParserOptions(parser.WithAutoHeadingID())} + return &HTMLWebClientConfig{ + Domain: "gno.land", + Highlighter: &noopFormat{}, + Markdown: goldmark.New(mdopts...), + RPCClient: client, + } +} + +type HTMLWebClient struct { + domain string + logger *slog.Logger + client *client.RPCClient + md goldmark.Markdown + highlighter FormatSource +} + +// NewHTMLClient creates a new instance of WebClient. +// It requires a configured logger and WebClientConfig. +func NewHTMLClient(log *slog.Logger, cfg *HTMLWebClientConfig) *HTMLWebClient { + return &HTMLWebClient{ + logger: log, + domain: cfg.Domain, + client: cfg.RPCClient, + md: cfg.Markdown, + highlighter: cfg.Highlighter, + } +} + +// Functions retrieves a list of function signatures from a +// specified package path. +func (s *HTMLWebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { + const qpath = "vm/qfuncs" + + args := fmt.Sprintf("%s/%s", s.domain, strings.Trim(pkgPath, "/")) + res, err := s.query(qpath, []byte(args)) + if err != nil { + return nil, fmt.Errorf("unable to query func list: %w", err) + } + + var fsigs vm.FunctionSignatures + if err := amino.UnmarshalJSON(res, &fsigs); err != nil { + s.logger.Warn("unable to unmarshal function signatures, client is probably outdated") + return nil, fmt.Errorf("unable to unmarshal function signatures: %w", err) + } + + return fsigs, nil +} + +// SourceFile fetches and writes the source file from a given +// package path and file name to the provided writer. It uses +// Chroma for syntax highlighting source. +func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMeta, error) { + const qpath = "vm/qfile" + + fileName = strings.TrimSpace(fileName) + if fileName == "" { + return nil, errors.New("empty filename given") // XXX: Consider creating a specific error variable + } + + // XXX: Consider moving this into gnoclient + fullPath := filepath.Join(s.domain, strings.Trim(path, "/"), fileName) + + source, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + fileMeta := FileMeta{ + Lines: strings.Count(string(source), "\n"), + SizeKb: float64(len(source)) / 1024.0, + } + + // Use Chroma for syntax highlighting + if err := s.highlighter.Format(w, fileName, source); err != nil { + return nil, err + } + + return &fileMeta, nil +} + +// Sources lists all source files available in a specified +// package path by querying the RPC client. +func (s *HTMLWebClient) Sources(path string) ([]string, error) { + const qpath = "vm/qfile" + + // XXX: Consider moving this into gnoclient + pkgPath := strings.Trim(path, "/") + fullPath := fmt.Sprintf("%s/%s", s.domain, pkgPath) + res, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + files := strings.Split(strings.TrimSpace(string(res)), "\n") + return files, nil +} + +// RenderRealm renders the content of a realm from a given path +// and arguments into the provided writer. It uses Goldmark for +// Markdown processing to generate HTML content. +func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*RealmMeta, error) { + const qpath = "vm/qrender" + + pkgPath = strings.Trim(pkgPath, "/") + data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args) + rawres, err := s.query(qpath, []byte(data)) + if err != nil { + return nil, err + } + + // Use Goldmark for Markdown parsing + doc := s.md.Parser().Parse(text.NewReader(rawres)) + if err := s.md.Renderer().Render(w, rawres, doc); err != nil { + return nil, fmt.Errorf("unable to render realm %q: %w", data, err) + } + + var meta RealmMeta + meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) + if err != nil { + s.logger.Warn("unable to inspect for TOC elements", "error", err) + } + + return &meta, nil +} + +// query sends a query to the RPC client and returns the response +// data. +func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) { + s.logger.Info("query", "path", qpath, "data", string(data)) + + qres, err := s.client.ABCIQuery(qpath, data) + if err != nil { + s.logger.Debug("request error", "path", qpath, "data", string(data), "error", err) + return nil, fmt.Errorf("%w: %s", ErrClientBadRequest, err.Error()) + } + + if err = qres.Response.Error; err != nil { + if errors.Is(err, vm.InvalidPkgPathError{}) { + return nil, ErrClientPathNotFound + } + + s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) + return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error()) + } + + return qres.Response.Data, nil +} diff --git a/gno.land/pkg/gnoweb/webclient_mock.go b/gno.land/pkg/gnoweb/webclient_mock.go new file mode 100644 index 00000000000..451f5e237c3 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_mock.go @@ -0,0 +1,91 @@ +package gnoweb + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" +) + +// MockPackage represents a mock package with files and function signatures. +type MockPackage struct { + Path string + Domain string + Files map[string]string // filename -> body + Functions []vm.FunctionSignature +} + +// MockWebClient is a mock implementation of the Client interface. +type MockWebClient struct { + Packages map[string]*MockPackage // path -> package +} + +func NewMockWebClient(pkgs ...*MockPackage) *MockWebClient { + mpkgs := make(map[string]*MockPackage) + for _, pkg := range pkgs { + mpkgs[pkg.Path] = pkg + } + + return &MockWebClient{Packages: mpkgs} +} + +// Render simulates rendering a package by writing its content to the writer. +func (m *MockWebClient) RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + fmt.Fprintf(w, "[%s]%s:", pkg.Domain, pkg.Path) + + // Return a dummy RealmMeta for simplicity + return &RealmMeta{}, nil +} + +// SourceFile simulates retrieving a source file's metadata. +func (m *MockWebClient) SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) { + pkg, exists := m.Packages[pkgPath] + if !exists { + return nil, ErrClientPathNotFound + } + + if body, ok := pkg.Files[fileName]; ok { + w.Write([]byte(body)) + return &FileMeta{ + Lines: len(bytes.Split([]byte(body), []byte("\n"))), + SizeKb: float64(len(body)) / 1024.0, + }, nil + } + + return nil, ErrClientPathNotFound +} + +// Functions simulates retrieving function signatures from a package. +func (m *MockWebClient) Functions(path string) ([]vm.FunctionSignature, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + return pkg.Functions, nil +} + +// Sources simulates listing all source files in a package. +func (m *MockWebClient) Sources(path string) ([]string, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + fileNames := make([]string, 0, len(pkg.Files)) + for file := range pkg.Files { + fileNames = append(fileNames, file) + } + + // Sort for consistency + sort.Strings(fileNames) + + return fileNames, nil +} From 1d306d6ae64b553642ec7a8780c934c04daf6eb8 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Fri, 17 Jan 2025 17:45:26 +0900 Subject: [PATCH 45/75] fix(gnoweb): font style italic (#3537) --- gno.land/pkg/gnoweb/frontend/css/input.css | 2 +- gno.land/pkg/gnoweb/public/styles.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index 59e41ff4a7c..fb6a3dcd099 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -305,7 +305,7 @@ @layer utilities { .italic-subtle { - font-style: oblique 10deg; + font-style: oblique 14deg; } .quotes { diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index 8e8d7ed802d..a4c02629111 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 14deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From d2813f87533d69409201c2ee8e87c9bcff6c1719 Mon Sep 17 00:00:00 2001 From: Antoine Eddi <5222525+aeddi@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:20:48 +0100 Subject: [PATCH 46/75] ci(bot): disable bot if not configured on fork (#3542) --- .github/workflows/bot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index add800fe2bf..19a9e3022eb 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -39,10 +39,12 @@ jobs: define-prs-matrix: name: Define PRs matrix # Skip this workflow if: + # - the bot is not configured on this repo/fork # - the bot is retriggering itself # - the event is emitted by codecov # - the event is a review on a pull request from a fork (see save-pr-number job below) if: | + vars.GH_BOT_LOGIN != '' && github.actor != vars.GH_BOT_LOGIN && github.actor != 'codecov[bot]' && (github.event_name != 'pull_request_review' || github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name) From 7a397a22ec95ebcb7051cc17abfdd187d8020114 Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:40:32 +0100 Subject: [PATCH 47/75] fix(txtar): ignore filetests when loading packages (#3550) ## Description Addresses: #3546 --- gno.land/pkg/integration/pkgloader.go | 2 +- gno.land/pkg/integration/testdata/issue_3546.txtar | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 gno.land/pkg/integration/testdata/issue_3546.txtar diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 71b1491b2a8..5968ce2b4ef 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -134,7 +134,7 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { if err != nil { return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) } - imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest) for _, imp := range imports { if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { continue diff --git a/gno.land/pkg/integration/testdata/issue_3546.txtar b/gno.land/pkg/integration/testdata/issue_3546.txtar new file mode 100644 index 00000000000..6d0704df879 --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_3546.txtar @@ -0,0 +1,3 @@ +loadpkg gno.land/r/gov/dao/v2 + +gnoland start \ No newline at end of file From 4135ffe88369ab23b01945ab73989553d043c6d9 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Mon, 20 Jan 2025 00:42:33 -0800 Subject: [PATCH 48/75] fix(gnodev/pkg/emitter): use html/template not text/template for HTML generation (#3545) This change uses html/template instead of text/template for HTML generation and also locks in tests to detect such subtle regressions and thus help prevent future cross-side scripting (XSS) attacks if later the scripts evolve and take in user input. Fixes #3544 --- contribs/gnodev/pkg/emitter/middleware.go | 2 +- .../gnodev/pkg/emitter/middleware_test.go | 43 +++++++++++++++++++ .../gnodev/pkg/emitter/static/hotreload.js | 4 +- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 contribs/gnodev/pkg/emitter/middleware_test.go diff --git a/contribs/gnodev/pkg/emitter/middleware.go b/contribs/gnodev/pkg/emitter/middleware.go index 9c53cfe158e..e4def43f919 100644 --- a/contribs/gnodev/pkg/emitter/middleware.go +++ b/contribs/gnodev/pkg/emitter/middleware.go @@ -5,10 +5,10 @@ import ( _ "embed" "encoding/json" "fmt" + "html/template" "net/http" "strings" "sync" - "text/template" "github.com/gnolang/gno/contribs/gnodev/pkg/events" ) diff --git a/contribs/gnodev/pkg/emitter/middleware_test.go b/contribs/gnodev/pkg/emitter/middleware_test.go new file mode 100644 index 00000000000..bed7ddd0e75 --- /dev/null +++ b/contribs/gnodev/pkg/emitter/middleware_test.go @@ -0,0 +1,43 @@ +package emitter + +import ( + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMiddlewareUsesHTMLTemplate(t *testing.T) { + tests := []struct { + name string + remote string + want string + }{ + {"normal remote", "localhost:9999", "const ws = new WebSocket('ws://localhost:9999');"}, + {"xss'd remote", `localhost:9999');alert('pwned`, "const ws = new WebSocket('ws://localhost:9999');alert('pwned');"}, + } + + // As the code revolves, add more search patterns here. + reWebsocket := regexp.MustCompile("const ws = new WebSocket[^\n]+") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := httptest.NewRecorder() + mdw := NewMiddleware(tt.remote, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "text/html") + fmt.Fprintf(rw, "") + })) + rec.Header().Set("Content-Type", "text/html") + req := httptest.NewRequest("GET", "https://gno.land/example", nil) + mdw.ServeHTTP(rec, req) + + targets := reWebsocket.FindAllString(rec.Body.String(), -1) + require.True(t, len(targets) > 0) + body := targets[0] + require.Equal(t, body, tt.want) + }) + } +} diff --git a/contribs/gnodev/pkg/emitter/static/hotreload.js b/contribs/gnodev/pkg/emitter/static/hotreload.js index aabad4f341c..28e47c1ea15 100644 --- a/contribs/gnodev/pkg/emitter/static/hotreload.js +++ b/contribs/gnodev/pkg/emitter/static/hotreload.js @@ -1,6 +1,8 @@ (function() { // Define the events that will trigger a page reload - const eventsReload = {{ .ReloadEvents | json }}; + const eventsReload = [ + {{range .ReloadEvents}}'{{.}}',{{end}} + ]; // Establish the WebSocket connection to the event server const ws = new WebSocket('ws://{{- .Remote -}}'); From 7e21e23e8d496c6731b355ca5c39201578c23b3c Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 20 Jan 2025 09:49:13 +0100 Subject: [PATCH 49/75] chore(github-bot): avoid references from bot comment to meta issue (#3551) --- contribs/github-bot/internal/check/comment.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/github-bot/internal/check/comment.tmpl b/contribs/github-bot/internal/check/comment.tmpl index d9b633a69d5..acdb2681b92 100644 --- a/contribs/github-bot/internal/check/comment.tmpl +++ b/contribs/github-bot/internal/check/comment.tmpl @@ -27,7 +27,7 @@ 1. Complete manual checks for the PR, including the guidelines and additional checks if applicable. ##### 📚 Resources: -- [Report a bug with the bot](https://github.com/gnolang/gno/issues/3238). +- [Report a bug with the bot](https://www.github.com/gnolang/gno/issues/3238). - [View the bot’s configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go). {{ if or .AutoRules .ManualRules }}
          Debug
          From 5448754ad3fa442a1040d76e2694b07fc4a67f80 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:28:47 +0100 Subject: [PATCH 50/75] feat: add r/demo/atomicswap (#2510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [x] implement - [x] unit test / txtar - [x] question: ugnot or grc20 or both? Maybe it’s time to encourage using `wugnot`. **-> both, with a callback mechanism.** - [x] question: p+r or just r? **-> just `r`, and a single file. let's do it more!** - [x] make the API gnokey compatible + add Render. Depends on #3397 (cherry-picked) Depends on #2529 (cherry-picked) Depends on #2549 (cherry-picked) Depends on #2551 Closes #2549 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../gno.land/r/demo/atomicswap/atomicswap.gno | 175 +++++++ .../r/demo/atomicswap/atomicswap_test.gno | 434 ++++++++++++++++++ examples/gno.land/r/demo/atomicswap/gno.mod | 1 + examples/gno.land/r/demo/atomicswap/swap.gno | 98 ++++ .../pkg/integration/testdata/atomicswap.txtar | 41 ++ 5 files changed, 749 insertions(+) create mode 100644 examples/gno.land/r/demo/atomicswap/atomicswap.gno create mode 100644 examples/gno.land/r/demo/atomicswap/atomicswap_test.gno create mode 100644 examples/gno.land/r/demo/atomicswap/gno.mod create mode 100644 examples/gno.land/r/demo/atomicswap/swap.gno create mode 100644 gno.land/pkg/integration/testdata/atomicswap.txtar diff --git a/examples/gno.land/r/demo/atomicswap/atomicswap.gno b/examples/gno.land/r/demo/atomicswap/atomicswap.gno new file mode 100644 index 00000000000..8862feb1bed --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/atomicswap.gno @@ -0,0 +1,175 @@ +// Package atomicswap implements a hash time-locked contract (HTLC) for atomic swaps +// between native coins (ugnot) or GRC20 tokens. +// +// An atomic swap allows two parties to exchange assets in a trustless way, where +// either both transfers happen or neither does. The process works as follows: +// +// 1. Alice wants to swap with Bob. She generates a secret and creates a swap with +// Bob's address and the hash of the secret (hashlock). +// +// 2. Bob can claim the assets by providing the correct secret before the timelock expires. +// The secret proves Bob knows the preimage of the hashlock. +// +// 3. If Bob doesn't claim in time, Alice can refund the assets back to herself. +// +// Example usage for native coins: +// +// // Alice creates a swap with 1000ugnot for Bob +// secret := "mysecret" +// hashlock := hex.EncodeToString(sha256.Sum256([]byte(secret))) +// id, _ := atomicswap.NewCoinSwap(bobAddr, hashlock) // -send 1000ugnot +// +// // Bob claims the swap by providing the secret +// atomicswap.Claim(id, "mysecret") +// +// Example usage for GRC20 tokens: +// +// // Alice approves the swap contract to spend her tokens +// token.Approve(swapAddr, 1000) +// +// // Alice creates a swap with 1000 tokens for Bob +// id, _ := atomicswap.NewGRC20Swap(bobAddr, hashlock, "gno.land/r/demo/token") +// +// // Bob claims the swap by providing the secret +// atomicswap.Claim(id, "mysecret") +// +// If Bob doesn't claim in time (default 1 week), Alice can refund: +// +// atomicswap.Refund(id) +package atomicswap + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/grc20reg" +) + +const defaultTimelockDuration = 7 * 24 * time.Hour // 1w + +var ( + swaps avl.Tree // id -> *Swap + counter int +) + +// NewCoinSwap creates a new atomic swap contract for native coins. +// It uses a default timelock duration. +func NewCoinSwap(recipient std.Address, hashlock string) (int, *Swap) { + timelock := time.Now().Add(defaultTimelockDuration) + return NewCustomCoinSwap(recipient, hashlock, timelock) +} + +// NewGRC20Swap creates a new atomic swap contract for grc20 tokens. +// It uses gno.land/r/demo/grc20reg to lookup for a registered token. +func NewGRC20Swap(recipient std.Address, hashlock string, tokenRegistryKey string) (int, *Swap) { + timelock := time.Now().Add(defaultTimelockDuration) + tokenGetter := grc20reg.MustGet(tokenRegistryKey) + token := tokenGetter() + return NewCustomGRC20Swap(recipient, hashlock, timelock, token) +} + +// NewCoinSwapWithTimelock creates a new atomic swap contract for native coin. +// It allows specifying a custom timelock duration. +// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`. +func NewCustomCoinSwap(recipient std.Address, hashlock string, timelock time.Time) (int, *Swap) { + sender := std.PrevRealm().Addr() + sent := std.GetOrigSend() + require(len(sent) != 0, "at least one coin needs to be sent") + + // Create the swap + sendFn := func(to std.Address) { + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + banker.SendCoins(pkgAddr, to, sent) + } + amountStr := sent.String() + swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn) + + counter++ + id := strconv.Itoa(counter) + swaps.Set(id, swap) + return counter, swap +} + +// NewCustomGRC20Swap creates a new atomic swap contract for grc20 tokens. +// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`. +func NewCustomGRC20Swap(recipient std.Address, hashlock string, timelock time.Time, token *grc20.Token) (int, *Swap) { + sender := std.PrevRealm().Addr() + curAddr := std.CurrentRealm().Addr() + + allowance := token.Allowance(sender, curAddr) + require(allowance > 0, "no allowance") + + userTeller := token.CallerTeller() + err := userTeller.TransferFrom(sender, curAddr, allowance) + require(err == nil, "cannot retrieve tokens from allowance") + + amountStr := ufmt.Sprintf("%d%s", allowance, token.GetSymbol()) + sendFn := func(to std.Address) { + err := userTeller.Transfer(to, allowance) + require(err == nil, "cannot transfer tokens") + } + + swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn) + + counter++ + id := strconv.Itoa(counter) + swaps.Set(id, swap) + + return counter, swap +} + +// Claim loads a registered swap and tries to claim it. +func Claim(id int, secret string) { + swap := mustGet(id) + swap.Claim(secret) +} + +// Refund loads a registered swap and tries to refund it. +func Refund(id int) { + swap := mustGet(id) + swap.Refund() +} + +// Render returns a list of swaps (simplified) for the homepage, and swap details when specifying a swap ID. +func Render(path string) string { + if path == "" { // home + output := "" + size := swaps.Size() + max := 10 + swaps.ReverseIterateByOffset(size-max, max, func(key string, value interface{}) bool { + swap := value.(*Swap) + output += ufmt.Sprintf("- %s: %s -(%s)> %s - %s\n", + key, swap.sender, swap.amountStr, swap.recipient, swap.Status()) + return false + }) + return output + } else { // by id + swap, ok := swaps.Get(path) + if !ok { + return "404" + } + return swap.(*Swap).String() + } +} + +// require checks a condition and panics with a message if the condition is false. +func require(check bool, msg string) { + if !check { + panic(msg) + } +} + +// mustGet retrieves a swap by its id or panics. +func mustGet(id int) *Swap { + key := strconv.Itoa(id) + swap, ok := swaps.Get(key) + if !ok { + panic("unknown swap ID") + } + return swap.(*Swap) +} diff --git a/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno b/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno new file mode 100644 index 00000000000..0bcf6a1342d --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno @@ -0,0 +1,434 @@ +package atomicswap + +import ( + "crypto/sha256" + "encoding/hex" + "std" + "testing" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/r/demo/tests/test20" +) + +var testRun bool + +func TestNewCustomCoinSwap_Claim(t *testing.T) { + t.Skip("skipping due to bad support for unit-test driven banker") + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender1") + recipient := testutils.TestAddress("recipient1") + amount := std.Coins{{Denom: "ugnot", Amount: 1}} + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + std.TestSetOrigSend(amount, nil) + id, swap := NewCustomCoinSwap(recipient, hashlockHex, timelock) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wgc47h6lta047h6lta047h6l56jtjc +- recipient: g1wfjkx6tsd9jkuap3ta047h6lta047h6lkk20gv +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wgc47h6lta047h6lta047h6l56jtjc +- recipient: g1wfjkx6tsd9jkuap3ta047h6lta047h6lkk20gv +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomCoinSwap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender2") + recipient := testutils.TestAddress("recipient2") + amount := std.Coins{{Denom: "ugnot", Amount: 1}} + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + std.TestSetOrigSend(amount, nil) + id, swap := NewCustomCoinSwap(recipient, hashlockHex, timelock) // Create a new swap + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wge97h6lta047h6lta047h6ltfacad +- recipient: g1wfjkx6tsd9jkuapjta047h6lta047h6lducc3v +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOrigPkgAddr(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + expected = `- status: refunded +- sender: g1wdjkuer9wge97h6lta047h6lta047h6ltfacad +- recipient: g1wfjkx6tsd9jkuapjta047h6lta047h6lducc3v +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomGRC20Swap_Claim(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender3") + recipient := testutils.TestAddress("recipient3") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewCustomGRC20Swap(recipient, hashlockHex, timelock, test20.Token) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wge47h6lta047h6lta047h6l5rk38l +- recipient: g1wfjkx6tsd9jkuapnta047h6lta047h6ly6k4pv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(70_000)) + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wge47h6lta047h6lta047h6l5rk38l +- recipient: g1wfjkx6tsd9jkuapnta047h6lta047h6ly6k4pv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomGRC20Swap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender5") + recipient := testutils.TestAddress("recipient5") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewCustomGRC20Swap(recipient, hashlockHex, timelock, test20.Token) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wg647h6lta047h6lta047h6l5p6k3k +- recipient: g1wfjkx6tsd9jkuap4ta047h6lta047h6lmwmj6v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOrigPkgAddr(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(100_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + expected = `- status: refunded +- sender: g1wdjkuer9wg647h6lta047h6lta047h6l5p6k3k +- recipient: g1wfjkx6tsd9jkuap4ta047h6lta047h6lmwmj6v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewGRC20Swap_Claim(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender4") + recipient := testutils.TestAddress("recipient4") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(defaultTimelockDuration) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewGRC20Swap(recipient, hashlockHex, "gno.land/r/demo/tests/test20") + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wg697h6lta047h6lta047h6ltt3lty +- recipient: g1wfjkx6tsd9jkuap5ta047h6lta047h6ljg4l2v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(70_000)) + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wg697h6lta047h6lta047h6ltt3lty +- recipient: g1wfjkx6tsd9jkuap5ta047h6lta047h6ljg4l2v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewGRC20Swap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender6") + recipient := testutils.TestAddress("recipient6") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(defaultTimelockDuration) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewGRC20Swap(recipient, hashlockHex, "gno.land/r/demo/tests/test20") + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wgm97h6lta047h6lta047h6ltj497r +- recipient: g1wfjkx6tsd9jkuapkta047h6lta047h6lqyf9rv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOrigPkgAddr(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(100_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + expected = `- status: refunded +- sender: g1wdjkuer9wgm97h6lta047h6lta047h6ltj497r +- recipient: g1wfjkx6tsd9jkuapkta047h6lta047h6lqyf9rv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestRender(t *testing.T) { + defer resetTestState() + + // Setup + alice := testutils.TestAddress("alice") + bob := testutils.TestAddress("bob") + charly := testutils.TestAddress("charly") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(alice, 100_000) + std.TestSetRealm(std.NewUserRealm(alice)) + + userTeller := test20.Token.CallerTeller() + userTeller.Approve(rlm, 10_000) + _, bobSwap := NewCustomGRC20Swap(bob, hashlockHex, timelock, test20.Token) + + userTeller.Approve(rlm, 20_000) + _, _ = NewCustomGRC20Swap(charly, hashlockHex, timelock, test20.Token) + + std.TestSetRealm(std.NewUserRealm(bob)) + bobSwap.Claim("secret") + + expected := `- 2: g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh -(20000TST)> g1vd5xzunv09047h6lta047h6lta047h6lhsyveh - active +- 1: g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh -(10000TST)> g1vfhkyh6lta047h6lta047h6lta047h6l03vdhu - claimed +` + uassert.Equal(t, expected, Render("")) +} + +func resetTestState() { + swaps = avl.Tree{} + counter = 0 +} diff --git a/examples/gno.land/r/demo/atomicswap/gno.mod b/examples/gno.land/r/demo/atomicswap/gno.mod new file mode 100644 index 00000000000..1d6580c51e8 --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/atomicswap diff --git a/examples/gno.land/r/demo/atomicswap/swap.gno b/examples/gno.land/r/demo/atomicswap/swap.gno new file mode 100644 index 00000000000..da40805221e --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/swap.gno @@ -0,0 +1,98 @@ +package atomicswap + +import ( + "crypto/sha256" + "encoding/hex" + "std" + "time" + + "gno.land/p/demo/ufmt" +) + +// Swap represents an atomic swap contract. +type Swap struct { + sender std.Address + recipient std.Address + hashlock string + timelock time.Time + claimed bool + refunded bool + amountStr string + sendFn func(to std.Address) +} + +func newSwap( + sender std.Address, + recipient std.Address, + hashlock string, + timelock time.Time, + amountStr string, + sendFn func(std.Address), +) *Swap { + require(time.Now().Before(timelock), "timelock must be in the future") + require(hashlock != "", "hashlock must not be empty") + return &Swap{ + recipient: recipient, + sender: sender, + hashlock: hashlock, + timelock: timelock, + claimed: false, + refunded: false, + sendFn: sendFn, + amountStr: amountStr, + } +} + +// Claim allows the recipient to claim the funds if they provide the correct preimage. +func (s *Swap) Claim(preimage string) { + require(!s.claimed, "already claimed") + require(!s.refunded, "already refunded") + require(std.PrevRealm().Addr() == s.recipient, "unauthorized") + + hashlock := sha256.Sum256([]byte(preimage)) + hashlockHex := hex.EncodeToString(hashlock[:]) + require(hashlockHex == s.hashlock, "invalid preimage") + + s.claimed = true + s.sendFn(s.recipient) +} + +// Refund allows the sender to refund the funds after the timelock has expired. +func (s *Swap) Refund() { + require(!s.claimed, "already claimed") + require(!s.refunded, "already refunded") + require(std.PrevRealm().Addr() == s.sender, "unauthorized") + require(time.Now().After(s.timelock), "timelock not expired") + + s.refunded = true + s.sendFn(s.sender) +} + +func (s Swap) Status() string { + switch { + case s.refunded: + return "refunded" + case s.claimed: + return "claimed" + case s.TimeRemaining() < 0: + return "expired" + default: + return "active" + } +} + +func (s Swap) TimeRemaining() time.Duration { + remaining := time.Until(s.timelock) + if remaining < 0 { + return 0 + } + return remaining +} + +// String returns the current state of the swap. +func (s Swap) String() string { + return ufmt.Sprintf( + "- status: %s\n- sender: %s\n- recipient: %s\n- amount: %s\n- hashlock: %s\n- timelock: %s\n- remaining: %s", + s.Status(), s.sender, s.recipient, s.amountStr, s.hashlock, s.timelock.Format(time.RFC3339), s.TimeRemaining().String(), + ) +} diff --git a/gno.land/pkg/integration/testdata/atomicswap.txtar b/gno.land/pkg/integration/testdata/atomicswap.txtar new file mode 100644 index 00000000000..6d0f89a6dc8 --- /dev/null +++ b/gno.land/pkg/integration/testdata/atomicswap.txtar @@ -0,0 +1,41 @@ +loadpkg gno.land/r/demo/atomicswap +# XXX: would be nice to specify an artibrary initial balance as an "adduser" argument. cc @gfanton +adduser test2 +adduser test3 + +gnoland start + +gnokey maketx send -send 1000000000ugnot -to $test2_user_addr -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +gnokey maketx send -send 1000000000ugnot -to $test3_user_addr -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1010000000ugnot' + +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010000000ugnot' + +# To generate the hash for "secret", use a hashing tool or library of your choice. For example: +# In Unix-based systems, you can use: +# echo -n "secret" | sha256sum +# This will produce a hashlock string like "2bb808d537b1da3e38bd30361aa85586dbbeacdd7126fef6a25ef97b5f27a25b". +# Replace the hashlock argument in the command below with the generated hash. +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func NewCoinSwap -gas-fee 1000000ugnot -send 12345ugnot -gas-wanted 10000000 -args $test3_user_addr -args '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b' -broadcast -chainid=tendermint_test test2 +stdout '(1 int)' +stdout ".*$test2_user_addr.*$test3_user_addr.*12345ugnot.*" +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func Render -gas-fee 1000000ugnot -gas-wanted 10000000 -args '' -broadcast -chainid=tendermint_test test2 +stdout 'OK!' + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1007987655ugnot' +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010000000ugnot' + +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func Claim -gas-fee 1ugnot -gas-wanted 10000000 -args '1' -args 'secret' -broadcast -chainid=tendermint_test test3 +stdout 'OK!' + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1007987655ugnot' +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010012344ugnot' From a52b8b28ec56c98082759537935ee291d6d1571a Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:14:47 +0100 Subject: [PATCH 51/75] chore: cleanup gno files (#3527) This PR mostly removes some unused variables and type assertions that should have been identified by `golang` as error at compile time. These errors were flagged by `gnopls`, and I'm unsure why they are not currently detected as error by our current `gno` tools suite. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: Morgan Bazalgette --- examples/gno.land/p/demo/btree/btree_test.gno | 134 +++++++++--------- .../gno.land/p/demo/context/context_test.gno | 2 +- .../grc/grc1155/basic_grc1155_token_test.gno | 2 +- .../p/demo/grc/grc721/basic_nft_test.gno | 4 +- .../p/demo/grc/grc721/grc721_royalty_test.gno | 2 +- .../gno.land/p/demo/grc/grc777/dummy_test.gno | 2 +- .../subscription/recurring/recurring_test.gno | 2 +- .../p/moul/typeutil/typeutil_test.gno | 4 - examples/gno.land/p/n2p5/loci/loci_test.gno | 5 - .../p/wyhaines/rand/isaac/isaac_test.gno | 2 +- .../p/wyhaines/rand/isaac64/isaac64_test.gno | 2 +- .../gno.land/r/demo/foo1155/foo1155_test.gno | 3 +- examples/gno.land/r/demo/foo20/foo20_test.gno | 1 - .../gno.land/r/demo/foo721/foo721_test.gno | 2 +- .../games/dice_roller/dice_roller_test.gno | 2 +- .../r/demo/keystore/keystore_test.gno | 2 +- .../gno.land/r/gnoland/blog/gnoblog_test.gno | 3 - examples/gno.land/r/ursulovic/home/home.gno | 1 - 18 files changed, 81 insertions(+), 94 deletions(-) diff --git a/examples/gno.land/p/demo/btree/btree_test.gno b/examples/gno.land/p/demo/btree/btree_test.gno index a0f7c1c55ca..871e8c25e1d 100644 --- a/examples/gno.land/p/demo/btree/btree_test.gno +++ b/examples/gno.land/p/demo/btree/btree_test.gno @@ -138,7 +138,7 @@ func TestHas(t *testing.T) { } func TestMin(t *testing.T) { - min := Content(genericSeeding(New(WithDegree(10)), 53).Min()) + min := genericSeeding(New(WithDegree(10)), 53).Min().(Content) if min.Key != 0 { t.Errorf("Minimum should have been 0, but it was reported as %d.", min) @@ -146,24 +146,24 @@ func TestMin(t *testing.T) { } func TestMax(t *testing.T) { - max := Content(genericSeeding(New(WithDegree(10)), 53).Min()) + max := genericSeeding(New(WithDegree(10)), 53).Max().(Content) - if max.Key != 0 { - t.Errorf("Minimum should have been 0, but it was reported as %d.", max) + if max.Key != 52 { + t.Errorf("Maximum should have been 52, but it was reported as %d.", max) } } func TestGet(t *testing.T) { tree := genericSeeding(New(WithDegree(10)), 40) - if Content(tree.Get(Content{Key: 7})).Value != "Value_7" { - t.Errorf("Get(7) should have returned 'Value_7', but it returned %v.", tree.Get(Content{Key: 7})) + if val := tree.Get(Content{Key: 7}); val != nil && val.(Content).Value != "Value_7" { + t.Errorf("Get(7) should have returned 'Value_7', but it returned %v.", val) } - if Content(tree.Get(Content{Key: 39})).Value != "Value_39" { - t.Errorf("Get(40) should have returnd 'Value_39', but it returned %v.", tree.Get(Content{Key: 39})) + if val := tree.Get(Content{Key: 39}); val != nil && val.(Content).Value != "Value_39" { + t.Errorf("Get(39) should have returned 'Value_39', but it returned %v.", val) } - if tree.Get(Content{Key: 1111}) != nil { - t.Errorf("Get(1111) returned %v, but it should be nil.", Content(tree.Get(Content{Key: 1111}))) + if val := tree.Get(Content{Key: 1111}); val != nil { + t.Errorf("Get(1111) returned %v, but it should be nil.", val) } } @@ -174,8 +174,8 @@ func TestDescend(t *testing.T) { found := []int{} tree.Descend(func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -191,8 +191,8 @@ func TestDescendGreaterThan(t *testing.T) { found := []int{} tree.DescendGreaterThan(Content{Key: 4}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -208,8 +208,8 @@ func TestDescendLessOrEqual(t *testing.T) { found := []int{} tree.DescendLessOrEqual(Content{Key: 4}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -225,8 +225,8 @@ func TestDescendRange(t *testing.T) { found := []int{} tree.DescendRange(Content{Key: 6}, Content{Key: 1}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -242,8 +242,8 @@ func TestAscend(t *testing.T) { found := []int{} tree.Ascend(func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -259,8 +259,8 @@ func TestAscendGreaterOrEqual(t *testing.T) { found := []int{} tree.AscendGreaterOrEqual(Content{Key: 5}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -276,13 +276,13 @@ func TestAscendLessThan(t *testing.T) { found := []int{} tree.AscendLessThan(Content{Key: 5}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) if intSlicesCompare(expected, found) != 0 { - t.Errorf("DescendLessOrEqual returned the wrong sequence. Expected %v, but got %v.", expected, found) + t.Errorf("AscendLessThan returned the wrong sequence. Expected %v, but got %v.", expected, found) } } @@ -293,13 +293,13 @@ func TestAscendRange(t *testing.T) { found := []int{} tree.AscendRange(Content{Key: 2}, Content{Key: 7}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) if intSlicesCompare(expected, found) != 0 { - t.Errorf("DescendRange returned the wrong sequence. Expected %v, but got %v.", expected, found) + t.Errorf("AscendRange returned the wrong sequence. Expected %v, but got %v.", expected, found) } } @@ -309,11 +309,11 @@ func TestDeleteMin(t *testing.T) { expected := []int{0, 1, 2, 3, 4} found := []int{} - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) @@ -326,11 +326,11 @@ func TestShift(t *testing.T) { expected := []int{0, 1, 2, 3, 4} found := []int{} - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { t.Errorf("5 rounds of Shift returned the wrong elements. Expected %v, but got %v.", expected, found) @@ -343,14 +343,14 @@ func TestDeleteMax(t *testing.T) { expected := []int{99, 98, 97, 96, 95} found := []int{} - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { - t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) + t.Errorf("5 rounds of DeleteMax returned the wrong elements. Expected %v, but got %v.", expected, found) } } @@ -360,14 +360,14 @@ func TestPop(t *testing.T) { expected := []int{99, 98, 97, 96, 95} found := []int{} - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { - t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) + t.Errorf("5 rounds of Pop returned the wrong elements. Expected %v, but got %v.", expected, found) } } @@ -383,13 +383,15 @@ func TestInsertGet(t *testing.T) { } for count := 0; count < 20; count++ { - if tree.Get(Content{Key: count}) != expected[count] { - t.Errorf("Insert/Get doesn't appear to be working. Expected to retrieve %v with key %d, but got %v.", expected[count], count, tree.Get(Content{Key: count})) + val := tree.Get(Content{Key: count}) + if val == nil || val.(Content) != expected[count] { + t.Errorf("Insert/Get doesn't appear to be working. Expected to retrieve %v with key %d, but got %v.", expected[count], count, val) } } } func TestClone(t *testing.T) { + // Implement the clone test } // ***** The following tests are functional or stress testing type tests. @@ -440,18 +442,18 @@ func TestBTree(t *testing.T) { val := tree.Get(test.test) if test.expected { - if val != nil && Content(val).Value == test.test.Value { + if val != nil && val.(Content).Value == test.test.Value { t.Logf("Found expected key:value %v:%v", test.test.Key, test.test.Value) } else { if val == nil { t.Logf("Didn't find %v, but expected", test.test.Key) } else { - t.Errorf("Expected key %v:%v, but found %v:%v.", test.test.Key, test.test.Value, Content(val).Key, Content(val).Value) + t.Errorf("Expected key %v:%v, but found %v:%v.", test.test.Key, test.test.Value, val.(Content).Key, val.(Content).Value) } } } else { if val != nil { - t.Errorf("Did not expect key %v, but found key:value %v:%v", test.test.Key, Content(val).Key, Content(val).Value) + t.Errorf("Did not expect key %v, but found key:value %v:%v", test.test.Key, val.(Content).Key, val.(Content).Value) } else { t.Logf("Didn't find %v, but wasn't expected", test.test.Key) } @@ -474,7 +476,7 @@ func TestBTree(t *testing.T) { t.Logf("Tree Length: %d", tree.Len()) tree.Ascend(func(_record Record) bool { - record := Content(_record) + record := _record.(Content) t.Logf("Key:Value == %v:%v", record.Key, record.Value) if record.Key != sortedInsertData[pos].Key { t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key) @@ -487,7 +489,7 @@ func TestBTree(t *testing.T) { pos = len(sortedInsertData) - 1 tree.Descend(func(_record Record) bool { - record := Content(_record) + record := _record.(Content) t.Logf("Key:Value == %v:%v", record.Key, record.Value) if record.Key != sortedInsertData[pos].Key { t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key) @@ -497,10 +499,10 @@ func TestBTree(t *testing.T) { }) deleteTests := []Content{ - Content{Key: 10, Value: "Value_10"}, - Content{Key: 15, Value: ""}, - Content{Key: "banana", Value: "Fruit_banana"}, - Content{Key: "kiwi", Value: ""}, + {Key: 10, Value: "Value_10"}, + {Key: 15, Value: ""}, + {Key: "banana", Value: "Fruit_banana"}, + {Key: "kiwi", Value: ""}, } for _, test := range deleteTests { fmt.Printf("\nDeleting %+v\n", test) @@ -514,7 +516,7 @@ func TestBTree(t *testing.T) { for _, test := range deleteTests { val := tree.Get(test) if val != nil { - t.Errorf("Did not expect key %v, but found key:value %v:%v", test.Key, Content(val).Key, Content(val).Value) + t.Errorf("Did not expect key %v, but found key:value %v:%v", test.Key, val.(Content).Key, val.(Content).Value) } else { t.Logf("Didn't find %v, but wasn't expected", test.Key) } @@ -558,7 +560,7 @@ func TestStress(t *testing.T) { val := tree.Get(content) if i%2 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -643,7 +645,7 @@ func TestBTreeCloneIsolation(t *testing.T) { if i%3 == 0 || i%2 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -654,7 +656,7 @@ func TestBTreeCloneIsolation(t *testing.T) { val = clone.Get(content) if i%2 != 0 || i%3 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -665,7 +667,7 @@ func TestBTreeCloneIsolation(t *testing.T) { val = clone2.Get(content) if i%2 != 0 || (i-2)%3 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { diff --git a/examples/gno.land/p/demo/context/context_test.gno b/examples/gno.land/p/demo/context/context_test.gno index 0059f0d2a25..d8e2069363d 100644 --- a/examples/gno.land/p/demo/context/context_test.gno +++ b/examples/gno.land/p/demo/context/context_test.gno @@ -9,7 +9,7 @@ func TestContextExample(t *testing.T) { ctx := WithValue(Empty(), k, "Gno") if v := ctx.Value(k); v != nil { - if string(v) != "Gno" { + if v.(string) != "Gno" { t.Errorf("language value should be Gno, but is %s", v) } } else { diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno index 2fef3431b43..930ae03fa04 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno @@ -30,7 +30,7 @@ func TestBalanceOf(t *testing.T) { tid1 := TokenID("1") tid2 := TokenID("2") - balanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1) + _, err := dummy.BalanceOf(zeroAddress, tid1) uassert.Error(t, err, "should result in error") balanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1) diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno index 6375b0307a8..0481bf6b268 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -128,7 +128,7 @@ func TestGetApproved(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + _, err := dummy.GetApproved(TokenID("invalid")) uassert.Error(t, err, "should result in error") } @@ -247,7 +247,7 @@ func TestBurn(t *testing.T) { uassert.NoError(t, err, "should not result in error") // Check Owner of Token id - owner, err := dummy.OwnerOf(TokenID("1")) + _, err = dummy.OwnerOf(TokenID("1")) uassert.Error(t, err, "should result in error") } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno index 7893453a1c6..af5b4d3b239 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -32,7 +32,7 @@ func TestSetTokenRoyalty(t *testing.T) { uassert.NoError(t, derr, "Should not result in error") // Test case: Invalid token ID - err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + _ = dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) diff --git a/examples/gno.land/p/demo/grc/grc777/dummy_test.gno b/examples/gno.land/p/demo/grc/grc777/dummy_test.gno index 5f8ac3598d1..33415fc689a 100644 --- a/examples/gno.land/p/demo/grc/grc777/dummy_test.gno +++ b/examples/gno.land/p/demo/grc/grc777/dummy_test.gno @@ -11,7 +11,7 @@ type dummyImpl struct{} var _ IGRC777 = (*dummyImpl)(nil) func TestInterface(t *testing.T) { - var dummy IGRC777 = &dummyImpl{} + var _ IGRC777 = &dummyImpl{} } func (impl *dummyImpl) GetName() string { panic("not implemented") } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno index e8bca15c0bf..0b458b716ec 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -26,7 +26,7 @@ func TestRecurringSubscription(t *testing.T) { err = rs.HasValidSubscription(std.PrevRealm().Addr()) uassert.NoError(t, err, "Expected Alice to have access") - expiration, err := rs.GetExpiration(std.PrevRealm().Addr()) + _, err = rs.GetExpiration(std.PrevRealm().Addr()) uassert.NoError(t, err, "Expected to get expiration for Alice") } diff --git a/examples/gno.land/p/moul/typeutil/typeutil_test.gno b/examples/gno.land/p/moul/typeutil/typeutil_test.gno index 543ea1deec4..2aaf1b0e93d 100644 --- a/examples/gno.land/p/moul/typeutil/typeutil_test.gno +++ b/examples/gno.land/p/moul/typeutil/typeutil_test.gno @@ -278,10 +278,6 @@ func TestIsZero(t *testing.T) { } func TestToInterfaceSlice(t *testing.T) { - now := time.Now() - addr := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - str := testStringer{value: "hello"} - tests := []struct { name string input interface{} diff --git a/examples/gno.land/p/n2p5/loci/loci_test.gno b/examples/gno.land/p/n2p5/loci/loci_test.gno index bb216a8539e..6df6d4f9b2d 100644 --- a/examples/gno.land/p/n2p5/loci/loci_test.gno +++ b/examples/gno.land/p/n2p5/loci/loci_test.gno @@ -8,11 +8,6 @@ import ( ) func TestLociStore(t *testing.T) { - t.Parallel() - - u1 := testutils.TestAddress("u1") - u2 := testutils.TestAddress("u1") - t.Run("TestSet", func(t *testing.T) { t.Parallel() store := New() diff --git a/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno b/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno index b08621e271c..fdf633bb543 100644 --- a/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno +++ b/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno @@ -14,7 +14,7 @@ type OpenISAAC struct { } func TestISAACSeeding(t *testing.T) { - isaac := New() + _ = New() } func TestISAACRand(t *testing.T) { diff --git a/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno b/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno index 239e7f818fb..74abf2c13ca 100644 --- a/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno +++ b/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno @@ -14,7 +14,7 @@ type OpenISAAC struct { } func TestISAACSeeding(t *testing.T) { - isaac := New() + _ = New() } func TestISAACRand(t *testing.T) { diff --git a/examples/gno.land/r/demo/foo1155/foo1155_test.gno b/examples/gno.land/r/demo/foo1155/foo1155_test.gno index 64d4bc1256f..8ea722939f2 100644 --- a/examples/gno.land/r/demo/foo1155/foo1155_test.gno +++ b/examples/gno.land/r/demo/foo1155/foo1155_test.gno @@ -11,9 +11,8 @@ func TestFoo721(t *testing.T) { admin := users.AddressOrName("g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530") bob := users.AddressOrName("g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw") tid1 := grc1155.TokenID("1") - tid2 := grc1155.TokenID("2") - for i, tc := range []struct { + for _, tc := range []struct { name string expected interface{} fn func() interface{} diff --git a/examples/gno.land/r/demo/foo20/foo20_test.gno b/examples/gno.land/r/demo/foo20/foo20_test.gno index b9e80fbb476..dbafe2d6c1d 100644 --- a/examples/gno.land/r/demo/foo20/foo20_test.gno +++ b/examples/gno.land/r/demo/foo20/foo20_test.gno @@ -61,7 +61,6 @@ func TestReadOnlyPublicMethods(t *testing.T) { func TestErrConditions(t *testing.T) { var ( admin = pusers.AddressOrName("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - alice = pusers.AddressOrName(testutils.TestAddress("alice")) empty = pusers.AddressOrName("") ) diff --git a/examples/gno.land/r/demo/foo721/foo721_test.gno b/examples/gno.land/r/demo/foo721/foo721_test.gno index 2477ca34fde..fab39e561d1 100644 --- a/examples/gno.land/r/demo/foo721/foo721_test.gno +++ b/examples/gno.land/r/demo/foo721/foo721_test.gno @@ -13,7 +13,7 @@ func TestFoo721(t *testing.T) { admin := pusers.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") hariom := pusers.AddressOrName("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - for i, tc := range []struct { + for _, tc := range []struct { name string expected interface{} fn func() interface{} diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno index 2f6770a366f..4697b03bf66 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno @@ -128,7 +128,7 @@ func TestPlayBeyondGameEnd(t *testing.T) { Play(gameID) // Check if the game is over - g, err := getGame(gameID) + _, err := getGame(gameID) urequire.NoError(t, err) // Attempt to play more should fail diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index 9b5fafa2f95..03b61e79663 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -56,7 +56,7 @@ func TestRender(t *testing.T) { p := "" if len(tc.ps) > 0 { p = tc.owner.String() - for i, psv := range tc.ps { + for _, psv := range tc.ps { p += ":" + psv } } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno index b4658db4fb5..d046eb80f42 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno @@ -8,9 +8,6 @@ import ( func TestPackage(t *testing.T) { std.TestSetOrigCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) - - author := std.GetOrigCaller() - // by default, no posts. { got := Render("") diff --git a/examples/gno.land/r/ursulovic/home/home.gno b/examples/gno.land/r/ursulovic/home/home.gno index c03d8a66868..cc420df5e6e 100644 --- a/examples/gno.land/r/ursulovic/home/home.gno +++ b/examples/gno.land/r/ursulovic/home/home.gno @@ -19,7 +19,6 @@ var ( githubUrl string linkedinUrl string - connectUrl string imageUpdatePrice int64 isValidUrl func(string) bool From b1352ac42d753d7acca11297419fcef559561125 Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Mon, 20 Jan 2025 12:58:54 +0100 Subject: [PATCH 52/75] fix: global var dependencies (#2077) fixes [this](https://github.com/gnolang/gno/issues/1463) by adding missing logic in in `findUndefined2` --------- Co-authored-by: jaekwon --- gnovm/pkg/gnolang/nodes.go | 1 + gnovm/pkg/gnolang/preprocess.go | 570 ++++++++++++++++++++++++++++++-- gnovm/tests/files/closure.gno | 16 + gnovm/tests/files/var27.gno | 80 +++++ gnovm/tests/files/var28.gno | 18 + 5 files changed, 649 insertions(+), 36 deletions(-) create mode 100644 gnovm/tests/files/closure.gno create mode 100644 gnovm/tests/files/var27.gno create mode 100644 gnovm/tests/files/var28.gno diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index b85d1ac7026..0f1f4388623 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -157,6 +157,7 @@ const ( ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" + ATTR_GLOBAL GnoAttribute = "ATTR_GLOBAL" ) type Attributes struct { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ffa0f518331..96e09642cd8 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -129,6 +129,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { continue } + d.SetAttribute(ATTR_GLOBAL, true) // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) @@ -516,6 +517,17 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if cd, ok := d.(*ValueDecl); ok { checkValDefineMismatch(cd) } + + isGlobal := true + + for i := len(ns) - 1; i > 0; i-- { + if _, ok := ns[i].(*FuncDecl); ok { + isGlobal = false + } + } + + d.SetAttribute(ATTR_GLOBAL, isGlobal) + // recursively predefine dependencies. d2, ppd := predefineNow(store, last, d) if ppd { @@ -3779,16 +3791,486 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, // type expressions, which must get preprocessed for inner // composite type eliding to work. func findUndefined(store Store, last BlockNode, x Expr) (un Name) { - return findUndefined2(store, last, x, nil) + return findUndefined2(store, last, x, nil, true) +} + +// finds the next undefined identifier and returns it if it is global +func findUndefined2SkipLocals(store Store, last BlockNode, x Expr, t Type) Name { + name := findUndefinedGlobal(store, last, x, t) + + if name == "" { + return "" + } + + existsLocal := func(name Name, bn BlockNode) bool { + curr := bn + for { + currNames := curr.GetBlockNames() + + for _, currName := range currNames { + if currName == name { + return true + } + } + + newcurr := bn.GetStaticBlock().GetParentNode(store) + + if curr == newcurr { + return false + } + + curr = newcurr + + if curr == nil { + return false + } + + _, isFile := curr.(*FileNode) + + if isFile { + return false + } + } + } + + pkg := packageOf(last) + + if _, _, ok := pkg.FileSet.GetDeclForSafe(name); !ok { + return "" + } + + isLocal := existsLocal(name, last) + + if isLocal { + return "" + } + + return name +} + +func findUndefinedStmt(store Store, last BlockNode, stmt Stmt, t Type) Name { + switch s := stmt.(type) { + case *TypeDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + case *ValueDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + for _, rh := range s.Values { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *DeclStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, last, rh, t) + + if un != "" { + return un + } + } + case *IncDecStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + + if un != "" { + return un + } + case *PanicStmt: + un := findUndefined2SkipLocals(store, last, s.Exception, t) + + if un != "" { + return un + } + case *BlockStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, s, rh, t) + + if un != "" { + return un + } + } + case *DeferStmt: + un := findUndefined2SkipLocals(store, last, s.Call.Func, t) + + if un != "" { + return un + } + + for _, rh := range s.Call.Args { + un = findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *SwitchStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + for _, b := range s.Clauses { + b := b + un = findUndefinedStmt(store, s, &b, t) + + if un != "" { + return un + } + } + case *SwitchClauseStmt: + for _, rh := range s.Cases { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + + case *ExprStmt: + return findUndefined2SkipLocals(store, last, s.X, t) + case *AssignStmt: + for _, rh := range s.Rhs { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *IfStmt: + un := findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, last, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Else, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Then, t) + if un != "" { + return un + } + case *IfCaseStmt: + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + case *ReturnStmt: + for _, b := range s.Results { + un := findUndefined2SkipLocals(store, last, b, t) + if un != "" { + return un + } + } + case *RangeStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *ForStmt: + un := findUndefinedStmt(store, s, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, s, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, s, s.Post, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *BranchStmt: + case nil: + return "" + default: + panic(fmt.Sprintf("findUndefinedStmt: %T not supported", s)) + } + return "" +} + +func getGlobalValueRef(sb BlockNode, store Store, n Name) *TypedValue { + sbb := sb.GetStaticBlock() + idx, ok := sb.GetLocalIndex(n) + bb := &sb.GetStaticBlock().Block + bp := sb.GetParentNode(store) + + for { + if ok && sbb.Types[idx] != nil && (bp == nil || bp.GetParentNode(store) == nil) { + return bb.GetPointerToInt(store, int(idx)).TV + } else if bp != nil { + idx, ok = bp.GetLocalIndex(n) + sbb = bp.GetStaticBlock() + bb = sbb.GetBlock() + bp = bp.GetParentNode(store) + } else { + return nil + } + } +} + +func findUndefinedGlobal(store Store, last BlockNode, x Expr, t Type) (un Name) { + if x == nil { + return + } + switch cx := x.(type) { + case *NameExpr: + if tv := getGlobalValueRef(last, store, cx.Name); tv != nil { + return + } + + if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { + // XXX NOTE even if the name is shadowed by a file + // level declaration, it is fine to return here as it + // will be predefined later. + return + } + + return cx.Name + case *BasicLitExpr: + return + case *BinaryExpr: + un = findUndefinedGlobal(store, last, cx.Left, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Right, nil) + if un != "" { + return + } + case *SelectorExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *SliceExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + if cx.Low != nil { + un = findUndefinedGlobal(store, last, cx.Low, nil) + if un != "" { + return + } + } + if cx.High != nil { + un = findUndefinedGlobal(store, last, cx.High, nil) + if un != "" { + return + } + } + if cx.Max != nil { + un = findUndefinedGlobal(store, last, cx.Max, nil) + if un != "" { + return + } + } + case *StarExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *RefExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *TypeAssertExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + return findUndefinedGlobal(store, last, cx.Type, nil) + case *UnaryExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *CompositeLitExpr: + var ct Type + if cx.Type == nil { + if t == nil { + panic("cannot elide unknown composite type") + } + ct = t + cx.Type = constType(cx, t) + } else { + un = findUndefinedGlobal(store, last, cx.Type, nil) + if un != "" { + return + } + // preprocess now for eliding purposes. + // TODO recursive preprocessing here is hacky, find a better + // way. This cannot be done asynchronously, cuz undefined + // names ought to be returned immediately to let the caller + // predefine it. + cx.Type = Preprocess(store, last, cx.Type).(Expr) // recursive + ct = evalStaticType(store, last, cx.Type) + // elide composite lit element (nested) composite types. + elideCompositeElements(cx, ct) + } + switch ct.Kind() { + case ArrayKind, SliceKind, MapKind: + for _, kvx := range cx.Elts { + un = findUndefinedGlobal(store, last, kvx.Key, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, kvx.Value, ct.Elem()) + if un != "" { + return + } + } + case StructKind: + for _, kvx := range cx.Elts { + un = findUndefinedGlobal(store, last, kvx.Value, nil) + if un != "" { + return + } + } + default: + panic(fmt.Sprintf( + "unexpected composite lit type %s", + ct.String())) + } + case *FuncLitExpr: + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + return findUndefinedGlobal(store, last, &cx.Type, nil) + case *FieldTypeExpr: + return findUndefinedGlobal(store, last, cx.Type, nil) + case *ArrayTypeExpr: + if cx.Len != nil { + un = findUndefinedGlobal(store, last, cx.Len, nil) + if un != "" { + return + } + } + return findUndefinedGlobal(store, last, cx.Elt, nil) + case *SliceTypeExpr: + return findUndefinedGlobal(store, last, cx.Elt, nil) + case *InterfaceTypeExpr: + for i := range cx.Methods { + un = findUndefinedGlobal(store, last, &cx.Methods[i], nil) + if un != "" { + return + } + } + case *ChanTypeExpr: + return findUndefinedGlobal(store, last, cx.Value, nil) + case *FuncTypeExpr: + for i := range cx.Params { + un = findUndefinedGlobal(store, last, &cx.Params[i], nil) + if un != "" { + return + } + } + for i := range cx.Results { + un = findUndefinedGlobal(store, last, &cx.Results[i], nil) + if un != "" { + return + } + } + case *MapTypeExpr: + un = findUndefinedGlobal(store, last, cx.Key, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Value, nil) + if un != "" { + return + } + case *StructTypeExpr: + for i := range cx.Fields { + un = findUndefinedGlobal(store, last, &cx.Fields[i], nil) + if un != "" { + return + } + } + case *MaybeNativeTypeExpr: + un = findUndefinedGlobal(store, last, cx.Type, nil) + if un != "" { + return + } + case *CallExpr: + un = findUndefinedGlobal(store, last, cx.Func, nil) + if un != "" { + return + } + for i := range cx.Args { + un = findUndefinedGlobal(store, last, cx.Args[i], nil) + if un != "" { + return + } + } + case *IndexExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Index, nil) + if un != "" { + return + } + case *constTypeExpr: + return + case *ConstExpr: + return + default: + panic(fmt.Sprintf( + "unexpected expr: %v (%v)", + x, reflect.TypeOf(x))) + } + return } -func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { +func findUndefined2(store Store, last BlockNode, x Expr, t Type, skipPredefined bool) (un Name) { if x == nil { return } switch cx := x.(type) { case *NameExpr: - if tv := last.GetValueRef(store, cx.Name, true); tv != nil { + if tv := last.GetValueRef(store, cx.Name, skipPredefined); tv != nil { return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { @@ -3801,51 +4283,51 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { case *BasicLitExpr: return case *BinaryExpr: - un = findUndefined(store, last, cx.Left) + un = findUndefined2(store, last, cx.Left, nil, skipPredefined) if un != "" { return } - un = findUndefined(store, last, cx.Right) + un = findUndefined2(store, last, cx.Right, nil, skipPredefined) if un != "" { return } case *SelectorExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *SliceExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } if cx.Low != nil { - un = findUndefined(store, last, cx.Low) + un = findUndefined2(store, last, cx.Low, nil, skipPredefined) if un != "" { return } } if cx.High != nil { - un = findUndefined(store, last, cx.High) + un = findUndefined2(store, last, cx.High, nil, skipPredefined) if un != "" { return } } if cx.Max != nil { - un = findUndefined(store, last, cx.Max) + un = findUndefined2(store, last, cx.Max, nil, skipPredefined) if un != "" { return } } case *StarExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *RefExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *TypeAssertExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } - return findUndefined(store, last, cx.Type) + return findUndefined2(store, last, cx.Type, nil, skipPredefined) case *UnaryExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *CompositeLitExpr: var ct Type if cx.Type == nil { @@ -3855,7 +4337,7 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct = t cx.Type = constType(cx, t) } else { - un = findUndefined(store, last, cx.Type) + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) if un != "" { return } @@ -3872,18 +4354,18 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { switch ct.Kind() { case ArrayKind, SliceKind, MapKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Key) + un = findUndefined2(store, last, kvx.Key, nil, skipPredefined) if un != "" { return } - un = findUndefined2(store, last, kvx.Value, ct.Elem()) + un = findUndefined2(store, last, kvx.Value, ct.Elem(), skipPredefined) if un != "" { return } } case StructKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Value) + un = findUndefined2(store, last, kvx.Value, nil, skipPredefined) if un != "" { return } @@ -3894,43 +4376,53 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct.String())) } case *FuncLitExpr: - return findUndefined(store, last, &cx.Type) + if cx.GetAttribute(ATTR_GLOBAL) == true { + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + } + + return findUndefined2(store, last, &cx.Type, nil, skipPredefined) case *FieldTypeExpr: - return findUndefined(store, last, cx.Type) + return findUndefined2(store, last, cx.Type, nil, skipPredefined) case *ArrayTypeExpr: if cx.Len != nil { - un = findUndefined(store, last, cx.Len) + un = findUndefined2(store, last, cx.Len, nil, skipPredefined) if un != "" { return } } - return findUndefined(store, last, cx.Elt) + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) case *SliceTypeExpr: - return findUndefined(store, last, cx.Elt) + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) case *InterfaceTypeExpr: for i := range cx.Methods { - un = findUndefined(store, last, &cx.Methods[i]) + un = findUndefined2(store, last, &cx.Methods[i], nil, skipPredefined) if un != "" { return } } case *ChanTypeExpr: - return findUndefined(store, last, cx.Value) + return findUndefined2(store, last, cx.Value, nil, skipPredefined) case *FuncTypeExpr: for i := range cx.Params { - un = findUndefined(store, last, &cx.Params[i]) + un = findUndefined2(store, last, &cx.Params[i], nil, skipPredefined) if un != "" { return } } for i := range cx.Results { - un = findUndefined(store, last, &cx.Results[i]) + un = findUndefined2(store, last, &cx.Results[i], nil, skipPredefined) if un != "" { return } } case *MapTypeExpr: - un = findUndefined(store, last, cx.Key) + un = findUndefined2(store, last, cx.Key, nil, skipPredefined) if un != "" { return } @@ -3940,33 +4432,34 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { } case *StructTypeExpr: for i := range cx.Fields { - un = findUndefined(store, last, &cx.Fields[i]) + un = findUndefined2(store, last, &cx.Fields[i], nil, skipPredefined) if un != "" { return } } case *MaybeNativeTypeExpr: - un = findUndefined(store, last, cx.Type) + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) if un != "" { return } case *CallExpr: - un = findUndefined(store, last, cx.Func) + cx.Func.SetAttribute(ATTR_GLOBAL, cx.GetAttribute(ATTR_GLOBAL)) + un = findUndefined2(store, last, cx.Func, nil, skipPredefined) if un != "" { return } for i := range cx.Args { - un = findUndefined(store, last, cx.Args[i]) + un = findUndefined2(store, last, cx.Args[i], nil, skipPredefined) if un != "" { return } } case *IndexExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } - un = findUndefined(store, last, cx.Index) + un = findUndefined2(store, last, cx.Index, nil, skipPredefined) if un != "" { return } @@ -4082,8 +4575,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo if !file.IsInitialized() { panic("all types from files in file-set should have already been predefined") } + + declaration := *decl + declaration.SetAttribute(ATTR_GLOBAL, true) + // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, stack) + *decl, _ = predefineNow2(store, file, declaration, stack) } else { break } @@ -4241,6 +4738,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } for _, vx := range d.Values { + vx.SetAttribute(ATTR_GLOBAL, d.GetAttribute(ATTR_GLOBAL)) un = findUndefined(store, last, vx) if un != "" { return diff --git a/gnovm/tests/files/closure.gno b/gnovm/tests/files/closure.gno new file mode 100644 index 00000000000..ea05c025954 --- /dev/null +++ b/gnovm/tests/files/closure.gno @@ -0,0 +1,16 @@ +package main + +func main() { + +} + +var a = func() { + b() +} + +var b = func() { + a() +} + +// Error: +// main/files/closure.gno:7:5: constant definition loop with a diff --git a/gnovm/tests/files/var27.gno b/gnovm/tests/files/var27.gno new file mode 100644 index 00000000000..65ab9307b9b --- /dev/null +++ b/gnovm/tests/files/var27.gno @@ -0,0 +1,80 @@ +package main + +func main() {} + +var myDep string + +var myVar1 = func() { + a := myDep1 +} + +var myDep1 string + +var myVar2 = func() { + aaa := "" + + switch myDep { + case aaa: + println(myDep2) + } +} + +var myDep2 string + +var myVar3 = func() { + for _, c := range myDep3 { + println(c) + } +} + +var myDep3 string + +var v1 = func() int { + v2 := 11 + return v2 +}() + +var v2 = func() int { + return v1 +}() + +var v3 = func() int { + return func() int { + v4 := 11 + return v4 + }() +}() + +var v4 = func() int { + return v3 +}() + +var v5 = func() int { + v6 := 11 + return func() int { + return v6 + }() +}() + +var v6 = func() int { + return v5 +}() + +var other = func() { + if true { + something := 2 + print(something) // 2 + } else { + print(something) // a string, but single shared 'st' masks the outer/global reference. + } +} +var something = "a string" + +var other1 = func() { + if true { + something1 := 2 + print(something1) // 2 + } + print(something1) // a string, but single shared 'st' masks the outer/global reference. +} +var something1 = "a string" \ No newline at end of file diff --git a/gnovm/tests/files/var28.gno b/gnovm/tests/files/var28.gno new file mode 100644 index 00000000000..279f60168a4 --- /dev/null +++ b/gnovm/tests/files/var28.gno @@ -0,0 +1,18 @@ +package main + +func main() {} + +var foo = func() (bool, bool) { + return true, true +} + +var x = func() bool { return a }() +var a, b = foo() + +var a1 = func() int { + type B1 b1 + x := B1(1) + return int(x) +} + +type b1 int \ No newline at end of file From 09764ad0e5c6e7bc9671d27ead7ab8214b67e6c8 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Mon, 20 Jan 2025 04:00:57 -0800 Subject: [PATCH 53/75] fix(tm2/pkg/bft,os,p2p): close leaking resources (#3556) This change closes some leaking resources that were identified in a preliminary code read. Fixes #3029 Fixes #3030 Fixes #3031 --- tm2/pkg/bft/privval/utils.go | 9 ++++++++- tm2/pkg/os/tempfile.go | 11 +++++++---- tm2/pkg/p2p/transport.go | 8 +++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tm2/pkg/bft/privval/utils.go b/tm2/pkg/bft/privval/utils.go index c759d9dde9d..a98a36ef04a 100644 --- a/tm2/pkg/bft/privval/utils.go +++ b/tm2/pkg/bft/privval/utils.go @@ -25,7 +25,7 @@ func IsConnTimeout(err error) bool { } // NewSignerListener creates a new SignerListenerEndpoint using the corresponding listen address -func NewSignerListener(listenAddr string, logger *slog.Logger) (*SignerListenerEndpoint, error) { +func NewSignerListener(listenAddr string, logger *slog.Logger) (_ *SignerListenerEndpoint, rerr error) { var listener net.Listener protocol, address := osm.ProtocolAndAddress(listenAddr) @@ -33,6 +33,13 @@ func NewSignerListener(listenAddr string, logger *slog.Logger) (*SignerListenerE if err != nil { return nil, err } + + defer func() { + if rerr != nil { + ln.Close() + } + }() + switch protocol { case "unix": listener = NewUnixListener(ln) diff --git a/tm2/pkg/os/tempfile.go b/tm2/pkg/os/tempfile.go index 277813051fd..46d97739b80 100644 --- a/tm2/pkg/os/tempfile.go +++ b/tm2/pkg/os/tempfile.go @@ -107,14 +107,17 @@ func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) } break } + + // Clean up in any case. + defer func() { + f.Close() + os.Remove(f.Name()) + }() + if i == atomicWriteFileMaxNumWriteAttempts { return fmt.Errorf("could not create atomic write file after %d attempts", i) } - // Clean up in any case. Defer stacking order is last-in-first-out. - defer os.Remove(f.Name()) - defer f.Close() - if n, err := f.Write(data); err != nil { return err } else if n < len(data) { diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 9edef9a15e5..150072ad5eb 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -147,13 +147,19 @@ func (mt *MultiplexTransport) Close() error { } // Listen starts an active process of listening for incoming connections [NON-BLOCKING] -func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { +func (mt *MultiplexTransport) Listen(addr types.NetAddress) (rerr error) { // Reserve a port, and start listening ln, err := net.Listen("tcp", addr.DialString()) if err != nil { return fmt.Errorf("unable to listen on address, %w", err) } + defer func() { + if rerr != nil { + ln.Close() + } + }() + if addr.Port == 0 { // net.Listen on port 0 means the kernel will auto-allocate a port // - find out which one has been given to us. From 52ddd005ef5f18a8f053e0b5284c8bbe6dfe3129 Mon Sep 17 00:00:00 2001 From: ltzmaxwell Date: Mon, 20 Jan 2025 20:52:59 +0800 Subject: [PATCH 54/75] fix(gnovm): correct map key persistence (#3127) # update: closes: https://github.com/gnolang/gno/issues/2060 and some other issues, by: The main issue is that after the pointer value is retrieved from the store, the `TV` field is not correctly filled, it leads to inconsistent map key calculation. this PR solves this problem. =========================================================== ~~closes: https://github.com/gnolang/gno/issues/2060 and some other issues, by:~~ ~~- correct map key computation when it's a pointer;~~ ~~- make key object owned by map value.~~ ~~The root cause of the issue lies in how the map key is calculated when using a pointer type. Currently, the memory address of the pointer value is used to generate the map key. However, this approach is inconsistent between the VM space and storage space. As a result, if a map key derived from a pointer is persisted and later restored, the two keys do not match.~~ ~~The proposed solution is to derive an immutable identifier for pointer values. This involves calculating an absolute path for the variables that the pointer value references, ensuring a corresponding and consistent `origin`.~~ ~~The absolute path consists of `pkgId:blockId:[index]`, providing a globally unique identifier for the pointer.~~ ~~For example: In the code below, the map key `i`, which is a pointer value, has an origin represented as: `15bc39c5756bbda67fd0d56bc9e86b29d7b444fc:1:[1]`~~ ~~// PKGPATH: gno.land/r/ptr_map~~ ~~package ptr_map~~ ~~var ( m = map[*int]int{} i = new(int) )~~ ~~func AddToMap(value int) { m[i] = value }~~ ~~func GetFromMap() int { return m[i] }~~ ~~func init() { *i = 1 AddToMap(5) }~~ ~~// ----above is initialized and persisted before main is executed.~~ ~~func main() { ~~r := GetFromMap() println(r == 5)~~ *i = 2 // this changes TV, also Base of a pointer value r = GetFromMap() println(r == 5)~~ }~~ ~~// Output:~~ ~~// true~~ ~~// true~~ --- .../pkg/integration/testdata/ptr_mapkey.txtar | 31 ++++++++++++++ gnovm/Makefile | 3 +- gnovm/pkg/gnolang/values.go | 1 + gnovm/tests/files/ptrmap_1.gno | 27 ++++++++++++ gnovm/tests/files/ptrmap_10.gno | 21 ++++++++++ gnovm/tests/files/ptrmap_11.gno | 22 ++++++++++ gnovm/tests/files/ptrmap_12.gno | 21 ++++++++++ gnovm/tests/files/ptrmap_13.gno | 22 ++++++++++ gnovm/tests/files/ptrmap_14.gno | 20 +++++++++ gnovm/tests/files/ptrmap_15.gno | 23 +++++++++++ gnovm/tests/files/ptrmap_16.gno | 24 +++++++++++ gnovm/tests/files/ptrmap_17.gno | 23 +++++++++++ gnovm/tests/files/ptrmap_18.gno | 33 +++++++++++++++ gnovm/tests/files/ptrmap_19.gno | 37 +++++++++++++++++ gnovm/tests/files/ptrmap_2.gno | 35 ++++++++++++++++ gnovm/tests/files/ptrmap_20.gno | 38 +++++++++++++++++ gnovm/tests/files/ptrmap_22.gno | 30 ++++++++++++++ gnovm/tests/files/ptrmap_23.gno | 17 ++++++++ gnovm/tests/files/ptrmap_24.gno | 28 +++++++++++++ gnovm/tests/files/ptrmap_25.gno | 34 +++++++++++++++ gnovm/tests/files/ptrmap_26.gno | 33 +++++++++++++++ gnovm/tests/files/ptrmap_3.gno | 34 +++++++++++++++ gnovm/tests/files/ptrmap_4.gno | 30 ++++++++++++++ gnovm/tests/files/ptrmap_5.gno | 28 +++++++++++++ gnovm/tests/files/ptrmap_6.gno | 37 +++++++++++++++++ gnovm/tests/files/ptrmap_7.gno | 41 +++++++++++++++++++ gnovm/tests/files/ptrmap_8.gno | 26 ++++++++++++ gnovm/tests/files/ptrmap_9.gno | 27 ++++++++++++ 28 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 gno.land/pkg/integration/testdata/ptr_mapkey.txtar create mode 100644 gnovm/tests/files/ptrmap_1.gno create mode 100644 gnovm/tests/files/ptrmap_10.gno create mode 100644 gnovm/tests/files/ptrmap_11.gno create mode 100644 gnovm/tests/files/ptrmap_12.gno create mode 100644 gnovm/tests/files/ptrmap_13.gno create mode 100644 gnovm/tests/files/ptrmap_14.gno create mode 100644 gnovm/tests/files/ptrmap_15.gno create mode 100644 gnovm/tests/files/ptrmap_16.gno create mode 100644 gnovm/tests/files/ptrmap_17.gno create mode 100644 gnovm/tests/files/ptrmap_18.gno create mode 100644 gnovm/tests/files/ptrmap_19.gno create mode 100644 gnovm/tests/files/ptrmap_2.gno create mode 100644 gnovm/tests/files/ptrmap_20.gno create mode 100644 gnovm/tests/files/ptrmap_22.gno create mode 100644 gnovm/tests/files/ptrmap_23.gno create mode 100644 gnovm/tests/files/ptrmap_24.gno create mode 100644 gnovm/tests/files/ptrmap_25.gno create mode 100644 gnovm/tests/files/ptrmap_26.gno create mode 100644 gnovm/tests/files/ptrmap_3.gno create mode 100644 gnovm/tests/files/ptrmap_4.gno create mode 100644 gnovm/tests/files/ptrmap_5.gno create mode 100644 gnovm/tests/files/ptrmap_6.gno create mode 100644 gnovm/tests/files/ptrmap_7.gno create mode 100644 gnovm/tests/files/ptrmap_8.gno create mode 100644 gnovm/tests/files/ptrmap_9.gno diff --git a/gno.land/pkg/integration/testdata/ptr_mapkey.txtar b/gno.land/pkg/integration/testdata/ptr_mapkey.txtar new file mode 100644 index 00000000000..77f0f845c12 --- /dev/null +++ b/gno.land/pkg/integration/testdata/ptr_mapkey.txtar @@ -0,0 +1,31 @@ +gnoland start + +# add contract +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/ptrmap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/demo/ptrmap -func AddToMap -args 5 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/demo/ptrmap -func GetFromMap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(5 int)' +stdout OK! + +-- gno.mod -- +module gno.land/r/demo/ptrmap + +-- realm.gno -- +package ptrmap + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} \ No newline at end of file diff --git a/gnovm/Makefile b/gnovm/Makefile index d724ffbb6a2..babb7ad74ca 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -84,7 +84,7 @@ run.bench.storage: build.bench.storage ######################################## # Test suite .PHONY: test -test: _test.cmd _test.pkg _test.stdlibs +test: _test.filetest _test.cmd _test.pkg _test.stdlibs .PHONY: _test.cmd _test.cmd: @@ -116,6 +116,7 @@ _test.pkg: _test.stdlibs: go run ./cmd/gno test -v ./stdlibs/... +.PHONY: _test.filetest _test.filetest:; go test pkg/gnolang/files_test.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index da887764c8e..3bfd7c0286f 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -1563,6 +1563,7 @@ func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey { pbz := tv.PrimitiveBytes() bz = append(bz, pbz...) case *PointerType: + fillValueTV(store, tv) ptr := uintptr(unsafe.Pointer(tv.V.(PointerValue).TV)) bz = append(bz, uintptrToBytes(&ptr)...) case FieldType: diff --git a/gnovm/tests/files/ptrmap_1.gno b/gnovm/tests/files/ptrmap_1.gno new file mode 100644 index 00000000000..4e496a82f39 --- /dev/null +++ b/gnovm/tests/files/ptrmap_1.gno @@ -0,0 +1,27 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[int]int{} + i = 0 +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) +} + +// Output: +// true diff --git a/gnovm/tests/files/ptrmap_10.gno b/gnovm/tests/files/ptrmap_10.gno new file mode 100644 index 00000000000..92f61f557cb --- /dev/null +++ b/gnovm/tests/files/ptrmap_10.gno @@ -0,0 +1,21 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + arr = [2]*int{&a, &b} +) + +func init() { + m[arr[0]] = "first key" +} + +func main() { + println(m[arr[0]]) // Output: first key + println(m[arr[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_11.gno b/gnovm/tests/files/ptrmap_11.gno new file mode 100644 index 00000000000..6ffeed62f0b --- /dev/null +++ b/gnovm/tests/files/ptrmap_11.gno @@ -0,0 +1,22 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + S = []*int{&a, &b} // slice + index = 0 +) + +func init() { + m[S[index]] = "first key" +} + +func main() { + println(m[S[index]]) // Output: first key + println(m[S[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_12.gno b/gnovm/tests/files/ptrmap_12.gno new file mode 100644 index 00000000000..b0360fb5cef --- /dev/null +++ b/gnovm/tests/files/ptrmap_12.gno @@ -0,0 +1,21 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + S = []*int{&a, &b} +) + +func init() { + m[S[0]] = "first key" +} + +func main() { + println(m[S[0]]) // Output: first key + println(m[S[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_13.gno b/gnovm/tests/files/ptrmap_13.gno new file mode 100644 index 00000000000..0e0d24c6865 --- /dev/null +++ b/gnovm/tests/files/ptrmap_13.gno @@ -0,0 +1,22 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Index int +} + +var m = make(map[*int]string) +var a, b = 1, 2 +var s = []*int{&a, &b} +var myStruct = MyStruct{Index: 0} + +func init() { + m[s[myStruct.Index]] = "a" +} + +func main() { + println(m[s[myStruct.Index]]) +} + +// Output: +// a diff --git a/gnovm/tests/files/ptrmap_14.gno b/gnovm/tests/files/ptrmap_14.gno new file mode 100644 index 00000000000..5eb4c436def --- /dev/null +++ b/gnovm/tests/files/ptrmap_14.gno @@ -0,0 +1,20 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a = 0 + ptr *int = &a // A pointer to an int + i1 **int = &ptr +) + +func init() { + m[*i1] = "first key" +} + +func main() { + println(m[*i1]) // Output: first key +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_15.gno b/gnovm/tests/files/ptrmap_15.gno new file mode 100644 index 00000000000..b900fc4741f --- /dev/null +++ b/gnovm/tests/files/ptrmap_15.gno @@ -0,0 +1,23 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Key *int +} + +var ( + m = map[*int]string{} + i1 = MyStruct{Key: new(int)} +) + +func init() { + *i1.Key = 1 // Set the value of the pointer + m[i1.Key] = "first key" +} + +func main() { + println(m[i1.Key]) // Output: first key +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_16.gno b/gnovm/tests/files/ptrmap_16.gno new file mode 100644 index 00000000000..40682a2c70e --- /dev/null +++ b/gnovm/tests/files/ptrmap_16.gno @@ -0,0 +1,24 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +var ( + arr = [3]Foo{Foo{"a"}, Foo{"b"}, Foo{"c"}} + m = map[*Foo]string{} +) + +func init() { + m[&arr[0]] = "first key" +} + +func main() { + println(m[&arr[0]]) + println(m[&arr[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_17.gno b/gnovm/tests/files/ptrmap_17.gno new file mode 100644 index 00000000000..4838ae988be --- /dev/null +++ b/gnovm/tests/files/ptrmap_17.gno @@ -0,0 +1,23 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +var ( + arr = [3]Foo{Foo{"a"}, Foo{"b"}, Foo{"c"}} + m = map[*Foo]string{} + index = 0 +) + +func init() { + m[&arr[index]] = "first key" +} + +func main() { + println(m[&arr[index]]) +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_18.gno b/gnovm/tests/files/ptrmap_18.gno new file mode 100644 index 00000000000..03c2d431d91 --- /dev/null +++ b/gnovm/tests/files/ptrmap_18.gno @@ -0,0 +1,33 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = 0 +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + return m[&i] +} + +func init() { + i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_19.gno b/gnovm/tests/files/ptrmap_19.gno new file mode 100644 index 00000000000..2a3e6b5240f --- /dev/null +++ b/gnovm/tests/files/ptrmap_19.gno @@ -0,0 +1,37 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = 0 +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + i := 0 + { + { + return m[&i] + } + } +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// false +// false diff --git a/gnovm/tests/files/ptrmap_2.gno b/gnovm/tests/files/ptrmap_2.gno new file mode 100644 index 00000000000..006de1274b7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_2.gno @@ -0,0 +1,35 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +// ----above is initialized and persisted before main is executed. + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 // this changes TV, also Base of a pointer value + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_20.gno b/gnovm/tests/files/ptrmap_20.gno new file mode 100644 index 00000000000..5efe9afaac0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_20.gno @@ -0,0 +1,38 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[**int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + return m[&i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + var j = 0 + j1 := &j + println(m[&j1]) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// 0 +// true \ No newline at end of file diff --git a/gnovm/tests/files/ptrmap_22.gno b/gnovm/tests/files/ptrmap_22.gno new file mode 100644 index 00000000000..653a6f2ffe8 --- /dev/null +++ b/gnovm/tests/files/ptrmap_22.gno @@ -0,0 +1,30 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Name string + Age int +} + +var ( + m = map[*MyStruct]string{} + i1 = &MyStruct{Name: "alice", Age: 2} +) + +func init() { + m[i1] = "first key" + println(m[i1]) +} + +func main() { + i2 := *i1 + println(m[&i2] == "") + + i1.Age = 3 + println(m[i1]) +} + +// Output: +// first key +// true +// first key diff --git a/gnovm/tests/files/ptrmap_23.gno b/gnovm/tests/files/ptrmap_23.gno new file mode 100644 index 00000000000..a587d924bc7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_23.gno @@ -0,0 +1,17 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var arr = [2]int{1, 2} +var m = map[*[2]int]string{} + +func init() { + m[&arr] = "ok" +} + +func main() { + + println(m[&arr]) // Output: example +} + +// Output: +// ok diff --git a/gnovm/tests/files/ptrmap_24.gno b/gnovm/tests/files/ptrmap_24.gno new file mode 100644 index 00000000000..2c0890f47d0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_24.gno @@ -0,0 +1,28 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +type MyStruct struct { + Name string + Age int + key Foo +} + +var ( + m = map[*Foo]string{} + i1 = MyStruct{Name: "alice", Age: 2, key: Foo{name: "bob"}} +) + +func init() { + m[&i1.key] = "first key" +} + +func main() { + println(m[&i1.key]) +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_25.gno b/gnovm/tests/files/ptrmap_25.gno new file mode 100644 index 00000000000..ebfa1940ce9 --- /dev/null +++ b/gnovm/tests/files/ptrmap_25.gno @@ -0,0 +1,34 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + a, b, c = 1, 2, 3 + s = []*int{&a, &b, &c} +) + +func AddToMap(value int) { + m[&*s[0]] = value +} + +func GetFromMap() int { + return m[&*s[0]] +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + a = 0 + + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_26.gno b/gnovm/tests/files/ptrmap_26.gno new file mode 100644 index 00000000000..8b8faf97a2b --- /dev/null +++ b/gnovm/tests/files/ptrmap_26.gno @@ -0,0 +1,33 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[&*i] = value +} + +func GetFromMap() int { + return m[&*i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_3.gno b/gnovm/tests/files/ptrmap_3.gno new file mode 100644 index 00000000000..d89c696e2f0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_3.gno @@ -0,0 +1,34 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + j := *i + return m[&j] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// false +// false diff --git a/gnovm/tests/files/ptrmap_4.gno b/gnovm/tests/files/ptrmap_4.gno new file mode 100644 index 00000000000..e8f6f2bfdb1 --- /dev/null +++ b/gnovm/tests/files/ptrmap_4.gno @@ -0,0 +1,30 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = make([]*S, 0, 4) // Use a slice of pointers + +func init() { + // Append pointers to the slice + sArr = append(sArr, &S{1}, &S{2}, &S{3}) + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) // same base array + + // Compare pointers directly + println(m[sArr[1]] == m[newArr[1]]) + println(m[sArr[1]] == "") + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_5.gno b/gnovm/tests/files/ptrmap_5.gno new file mode 100644 index 00000000000..a38817867e5 --- /dev/null +++ b/gnovm/tests/files/ptrmap_5.gno @@ -0,0 +1,28 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = []*S{&S{1}, &S{2}, &S{3}} // Use a slice of pointers + +func init() { + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) // same base array + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_6.gno b/gnovm/tests/files/ptrmap_6.gno new file mode 100644 index 00000000000..1af7442e09f --- /dev/null +++ b/gnovm/tests/files/ptrmap_6.gno @@ -0,0 +1,37 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = make([]*S, 0, 4) // Use a slice of pointers + +func init() { + a := S{1} + // Append pointers to the slice + sArr = append(sArr, &a, &S{2}, &S{3}) + println(&a == sArr[0]) + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) + + newArr = append(newArr, &S{4}) + newArr = append(newArr, &S{5}) + newArr = append(newArr, &S{6}) // reallocate array + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_7.gno b/gnovm/tests/files/ptrmap_7.gno new file mode 100644 index 00000000000..443a112c6d8 --- /dev/null +++ b/gnovm/tests/files/ptrmap_7.gno @@ -0,0 +1,41 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +import "fmt" + +type S struct { + i int +} + +var m = make(map[*byte]string) // Initialize the map +var sArr = make([]*byte, 0, 4) // Use a slice of pointers +var a, b, c = byte('a'), byte('b'), byte('c') +var d, e, f = byte('d'), byte('e'), byte('f') + +func init() { + // Append pointers to the slice + sArr = append(sArr, &a, &b, &c) + m[sArr[1]] = "ok" + println(&b == sArr[1]) +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) + + newArr = append(newArr, &d) + newArr = append(newArr, &e) + // a, c, d, e, f + newArr = append(newArr, &f) // reallocation + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") // underlying base array changed +} + +// Output: +// true +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_8.gno b/gnovm/tests/files/ptrmap_8.gno new file mode 100644 index 00000000000..0985644e9d9 --- /dev/null +++ b/gnovm/tests/files/ptrmap_8.gno @@ -0,0 +1,26 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var m = make(map[*int]string) +var m2 = make(map[int]int) + +var a, b, c = 1, 2, 3 +var s = []*int{&a, &b, &c} +var s2 []*int + +func init() { + m2[0] = 0 + m[s[m2[0]]] = "a" + s2 = append(s[:1], s[1:]...) +} + +func main() { + println(m[s[m2[0]]]) + println(s[m2[0]] == s2[m2[0]]) + println(m[s[m2[0]]] == m[s2[m2[0]]]) +} + +// Output: +// a +// true +// true diff --git a/gnovm/tests/files/ptrmap_9.gno b/gnovm/tests/files/ptrmap_9.gno new file mode 100644 index 00000000000..2756ad9acc7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_9.gno @@ -0,0 +1,27 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var m = make(map[*int]string) +var m2 = make(map[int]int) + +var s []*int +var s2 []*int + +func init() { + s = append(s, new(int)) + m2[0] = 0 + // s[m2[0]] is pointer value, + m[s[m2[0]]] = "a" + s2 = append(s[:1], s[1:]...) +} + +func main() { + println(m[s[m2[0]]]) + println(s[m2[0]] == s2[m2[0]]) + println(m[s[m2[0]]] == m[s2[m2[0]]]) +} + +// Output: +// a +// true +// true From 238d5d8d09e2dfdd19f983fffef020b09b3be5fc Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Mon, 20 Jan 2025 23:24:02 +0900 Subject: [PATCH 55/75] fix(avl): partial revert from #1881 (#3534) # Description closes #3533 Changed only the `node.gno` files to before #1881, but kept the implementation of the `TraverseInRange` function that was modified in #3393. --- examples/gno.land/p/demo/avl/node.gno | 223 ++++++++++----------- examples/gno.land/p/demo/avl/node_test.gno | 178 +++++++++++++++- 2 files changed, 279 insertions(+), 122 deletions(-) diff --git a/examples/gno.land/p/demo/avl/node.gno b/examples/gno.land/p/demo/avl/node.gno index 7d4ddffff02..0ad8b0cecbf 100644 --- a/examples/gno.land/p/demo/avl/node.gno +++ b/examples/gno.land/p/demo/avl/node.gno @@ -46,7 +46,6 @@ func (node *Node) Value() interface{} { return node.value } -// _copy creates a copy of the node (excluding value). func (node *Node) _copy() *Node { if node.height == 0 { panic("Why are you copying a value node?") @@ -70,11 +69,13 @@ func (node *Node) Has(key string) (has bool) { } if node.height == 0 { return false + } else { + if key < node.key { + return node.getLeftNode().Has(key) + } else { + return node.getRightNode().Has(key) + } } - if key < node.key { - return node.getLeftNode().Has(key) - } - return node.getRightNode().Has(key) } // Get searches for a node with the given key in the subtree rooted at the node @@ -87,21 +88,21 @@ func (node *Node) Get(key string) (index int, value interface{}, exists bool) { if node.height == 0 { if node.key == key { return 0, node.value, true - } - if node.key < key { + } else if node.key < key { return 1, nil, false + } else { + return 0, nil, false + } + } else { + if key < node.key { + return node.getLeftNode().Get(key) + } else { + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists } - return 0, nil, false - } - - if key < node.key { - return node.getLeftNode().Get(key) } - - rightNode := node.getRightNode() - index, value, exists = rightNode.Get(key) - index += node.size - rightNode.size - return index, value, exists } // GetByIndex retrieves the key-value pair of the node at the given index @@ -110,15 +111,18 @@ func (node *Node) GetByIndex(index int) (key string, value interface{}) { if node.height == 0 { if index == 0 { return node.key, node.value + } else { + panic("GetByIndex asked for invalid index") + } + } else { + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } else { + return node.getRightNode().GetByIndex(index - leftNode.size) } - panic("GetByIndex asked for invalid index") - } - // TODO: could improve this by storing the sizes - leftNode := node.getLeftNode() - if index < leftNode.size { - return leftNode.GetByIndex(index) } - return node.getRightNode().GetByIndex(index - leftNode.size) } // Set inserts a new node with the given key-value pair into the subtree rooted at the node, @@ -129,50 +133,40 @@ func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated boo if node == nil { return NewNode(key, value), false } - if node.height == 0 { - return node.setLeaf(key, value) - } - - node = node._copy() - if key < node.key { - node.leftNode, updated = node.getLeftNode().Set(key, value) + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } else if key == node.key { + return NewNode(key, value), true + } else { + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false + } } else { - node.rightNode, updated = node.getRightNode().Set(key, value) - } - - if updated { - return node, updated - } - - node.calcHeightAndSize() - return node.balance(), updated -} - -// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, -// and returns the new root of the subtree and whether an existing node was updated. -func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { - if key == node.key { - return NewNode(key, value), true - } - - if key < node.key { - return &Node{ - key: node.key, - height: 1, - size: 2, - leftNode: NewNode(key, value), - rightNode: node, - }, false + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + if updated { + return node, updated + } else { + node.calcHeightAndSize() + return node.balance(), updated + } } - - return &Node{ - key: key, - height: 1, - size: 2, - leftNode: node, - rightNode: NewNode(key, value), - }, false } // Remove deletes the node with the given key from the subtree rooted at the node. @@ -187,49 +181,47 @@ func (node *Node) Remove(key string) ( if node.height == 0 { if key == node.key { return nil, "", node.value, true + } else { + return node, "", nil, false } - return node, "", nil, false - } - if key < node.key { - var newLeftNode *Node - newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) - if !removed { - return node, "", value, false - } - if newLeftNode == nil { // left node held value, was removed - return node.rightNode, node.key, value, true + } else { + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } else if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } else { + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } else if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true } - node = node._copy() - node.leftNode = newLeftNode - node.calcHeightAndSize() - node = node.balance() - return node, newKey, value, true - } - - var newRightNode *Node - newRightNode, newKey, value, removed = node.getRightNode().Remove(key) - if !removed { - return node, "", value, false } - if newRightNode == nil { // right node held value, was removed - return node.leftNode, "", value, true - } - node = node._copy() - node.rightNode = newRightNode - if newKey != "" { - node.key = newKey - } - node.calcHeightAndSize() - node = node.balance() - return node, "", value, true } -// getLeftNode returns the left child of the node. func (node *Node) getLeftNode() *Node { return node.leftNode } -// getRightNode returns the right child of the node. func (node *Node) getRightNode() *Node { return node.rightNode } @@ -287,29 +279,30 @@ func (node *Node) calcBalance() int { // TODO: optimize balance & rotate func (node *Node) balance() (newSelf *Node) { balance := node.calcBalance() - if balance >= -1 { - return node - } if balance > 1 { if node.getLeftNode().calcBalance() >= 0 { // Left Left Case return node.rotateRight() + } else { + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() } - // Left Right Case - left := node.getLeftNode() - node.leftNode = left.rotateLeft() - return node.rotateRight() } - - if node.getRightNode().calcBalance() <= 0 { - // Right Right Case - return node.rotateLeft() + if balance < -1 { + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } else { + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() + } } - - // Right Left Case - right := node.getRightNode() - node.rightNode = right.rotateRight() - return node.rotateLeft() + // Nothing changed + return node } // Shortcut for TraverseInRange. diff --git a/examples/gno.land/p/demo/avl/node_test.gno b/examples/gno.land/p/demo/avl/node_test.gno index 3682cbc7c80..10c742d8cc6 100644 --- a/examples/gno.land/p/demo/avl/node_test.gno +++ b/examples/gno.land/p/demo/avl/node_test.gno @@ -4,6 +4,8 @@ import ( "sort" "strings" "testing" + + "gno.land/p/demo/ufmt" ) func TestTraverseByOffset(t *testing.T) { @@ -550,6 +552,175 @@ func TestRotateAndBalance(t *testing.T) { } } +func TestRemoveFromEmptyTree(t *testing.T) { + var tree *Node + newTree, _, val, removed := tree.Remove("NonExistent") + if newTree != nil { + t.Errorf("Removing from an empty tree should still be nil tree.") + } + if val != nil || removed { + t.Errorf("Expected no value and removed=false when removing from empty tree.") + } +} + +func TestBalanceAfterRemoval(t *testing.T) { + tests := []struct { + name string + insertKeys []string + removeKey string + expectedBalance int + }{ + { + name: "balance after removing right node", + insertKeys: []string{"B", "A", "D", "C", "E"}, + removeKey: "E", + expectedBalance: 0, + }, + { + name: "balance after removing left node", + insertKeys: []string{"D", "B", "E", "A", "C"}, + removeKey: "A", + expectedBalance: 0, + }, + { + name: "ensure no lean after removal", + insertKeys: []string{"C", "B", "E", "A", "D", "F"}, + removeKey: "F", + expectedBalance: -1, + }, + { + name: "descending order insert, remove middle node", + insertKeys: []string{"E", "D", "C", "B", "A"}, + removeKey: "C", + expectedBalance: 0, + }, + { + name: "ascending order insert, remove middle node", + insertKeys: []string{"A", "B", "C", "D", "E"}, + removeKey: "C", + expectedBalance: 0, + }, + { + name: "duplicate key insert, remove the duplicated key", + insertKeys: []string{"C", "B", "C", "A", "D"}, + removeKey: "C", + expectedBalance: 1, + }, + { + name: "complex rotation case", + insertKeys: []string{"H", "B", "A", "C", "E", "D", "F", "G"}, + removeKey: "B", + expectedBalance: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.insertKeys { + tree, _ = tree.Set(key, nil) + } + + tree, _, _, _ = tree.Remove(tt.removeKey) + + balance := tree.calcBalance() + if balance != tt.expectedBalance { + t.Errorf("Expected balance factor %d, got %d", tt.expectedBalance, balance) + } + + if balance < -1 || balance > 1 { + t.Errorf("Tree is unbalanced with factor %d", balance) + } + + if errMsg := checkSubtreeBalance(t, tree); errMsg != "" { + t.Errorf("AVL property violation after removal: %s", errMsg) + } + }) + } +} + +func TestBSTProperty(t *testing.T) { + var tree *Node + keys := []string{"D", "B", "F", "A", "C", "E", "G"} + for _, key := range keys { + tree, _ = tree.Set(key, nil) + } + + var result []string + inorderTraversal(t, tree, &result) + + for i := 1; i < len(result); i++ { + if result[i] < result[i-1] { + t.Errorf("BST property violated: %s < %s (index %d)", + result[i], result[i-1], i) + } + } +} + +// inorderTraversal performs an inorder traversal of the tree and returns the keys in a list. +func inorderTraversal(t *testing.T, node *Node, result *[]string) { + t.Helper() + + if node == nil { + return + } + // leaf + if node.height == 0 { + *result = append(*result, node.key) + return + } + inorderTraversal(t, node.leftNode, result) + inorderTraversal(t, node.rightNode, result) +} + +// checkSubtreeBalance checks if all nodes under the given node satisfy the AVL tree conditions. +// The balance factor of all nodes must be ∈ [-1, +1] +func checkSubtreeBalance(t *testing.T, node *Node) string { + t.Helper() + + if node == nil { + return "" + } + + if node.IsLeaf() { + // leaf node must be height=0, size=1 + if node.height != 0 { + return ufmt.Sprintf("Leaf node %s has height %d, expected 0", node.Key(), node.height) + } + if node.size != 1 { + return ufmt.Sprintf("Leaf node %s has size %d, expected 1", node.Key(), node.size) + } + return "" + } + + // check balance factor for current node + balanceFactor := node.calcBalance() + if balanceFactor < -1 || balanceFactor > 1 { + return ufmt.Sprintf("Node %s is unbalanced: balanceFactor=%d", node.Key(), balanceFactor) + } + + // check height / size relationship for children + left, right := node.getLeftNode(), node.getRightNode() + expectedHeight := maxInt8(left.height, right.height) + 1 + if node.height != expectedHeight { + return ufmt.Sprintf("Node %s has incorrect height %d, expected %d", node.Key(), node.height, expectedHeight) + } + expectedSize := left.Size() + right.Size() + if node.size != expectedSize { + return ufmt.Sprintf("Node %s has incorrect size %d, expected %d", node.Key(), node.size, expectedSize) + } + + // recursively check the left/right subtree + if errMsg := checkSubtreeBalance(t, left); errMsg != "" { + return errMsg + } + if errMsg := checkSubtreeBalance(t, right); errMsg != "" { + return errMsg + } + + return "" +} + func slicesEqual(w1, w2 []string) bool { if len(w1) != len(w2) { return false @@ -562,13 +733,6 @@ func slicesEqual(w1, w2 []string) bool { return true } -func maxint8(a, b int8) int8 { - if a > b { - return a - } - return b -} - func reverseSlice(ss []string) { for i := 0; i < len(ss)/2; i++ { j := len(ss) - 1 - i From fc40183ada9d970d25b8bbf0017fcecb6ad82458 Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Mon, 20 Jan 2025 16:56:02 +0100 Subject: [PATCH 56/75] fix: addressability (#3198) Fixes https://github.com/gnolang/gno/issues/2299 and https://github.com/gnolang/gno/issues/2840 --------- Co-authored-by: deelawn Co-authored-by: ltzmaxwell --- gnovm/pkg/gnolang/go2gno.go | 2 + gnovm/pkg/gnolang/nodes.go | 241 ++++++++++++++---- gnovm/pkg/gnolang/op_types.go | 5 +- gnovm/pkg/gnolang/preprocess.go | 86 ++++++- gnovm/pkg/gnolang/types.go | 2 + gnovm/stdlibs/crypto/sha256/sha256_test.gno | 3 +- gnovm/tests/files/addressable_1.gno | 38 +++ gnovm/tests/files/addressable_10.gno | 12 + gnovm/tests/files/addressable_10a_err.gno | 14 + gnovm/tests/files/addressable_10b_err.gno | 12 + gnovm/tests/files/addressable_11.gno | 39 +++ gnovm/tests/files/addressable_1a_err.gno | 8 + gnovm/tests/files/addressable_1b_err.gno | 8 + gnovm/tests/files/addressable_1c_err.gno | 13 + gnovm/tests/files/addressable_1d_err.gno | 12 + gnovm/tests/files/addressable_2.gno | 40 +++ gnovm/tests/files/addressable_2a_err.gno | 8 + gnovm/tests/files/addressable_2b_err.gno | 12 + gnovm/tests/files/addressable_3.gno | 105 ++++++++ gnovm/tests/files/addressable_3a_err.gno | 14 + gnovm/tests/files/addressable_3b_err.gno | 16 ++ gnovm/tests/files/addressable_3c_err.gno | 17 ++ gnovm/tests/files/addressable_3d_err.gno | 8 + gnovm/tests/files/addressable_4.gno | 23 ++ gnovm/tests/files/addressable_4a_err.gno | 9 + gnovm/tests/files/addressable_5.gno | 11 + .../files/addressable_5a_err_stdlibs.gno | 11 + .../files/addressable_5b_err_stdlibs.gno | 11 + gnovm/tests/files/addressable_5c_err.gno | 10 + gnovm/tests/files/addressable_5d_err.gno | 10 + gnovm/tests/files/addressable_6.gno | 27 ++ gnovm/tests/files/addressable_6a_err.gno | 10 + gnovm/tests/files/addressable_6b_err.gno | 14 + gnovm/tests/files/addressable_6c_err.gno | 10 + gnovm/tests/files/addressable_6d_err.gno | 12 + gnovm/tests/files/addressable_7a_err.gno | 12 + gnovm/tests/files/addressable_7b_err.gno | 8 + gnovm/tests/files/addressable_8.gno | 9 + gnovm/tests/files/addressable_8a_err.gno | 8 + gnovm/tests/files/addressable_9.gno | 32 +++ gnovm/tests/files/addressable_9a_err.gno | 10 + gnovm/tests/files/addressable_9b_err.gno | 15 ++ 42 files changed, 907 insertions(+), 60 deletions(-) create mode 100644 gnovm/tests/files/addressable_1.gno create mode 100644 gnovm/tests/files/addressable_10.gno create mode 100644 gnovm/tests/files/addressable_10a_err.gno create mode 100644 gnovm/tests/files/addressable_10b_err.gno create mode 100644 gnovm/tests/files/addressable_11.gno create mode 100644 gnovm/tests/files/addressable_1a_err.gno create mode 100644 gnovm/tests/files/addressable_1b_err.gno create mode 100644 gnovm/tests/files/addressable_1c_err.gno create mode 100644 gnovm/tests/files/addressable_1d_err.gno create mode 100644 gnovm/tests/files/addressable_2.gno create mode 100644 gnovm/tests/files/addressable_2a_err.gno create mode 100644 gnovm/tests/files/addressable_2b_err.gno create mode 100644 gnovm/tests/files/addressable_3.gno create mode 100644 gnovm/tests/files/addressable_3a_err.gno create mode 100644 gnovm/tests/files/addressable_3b_err.gno create mode 100644 gnovm/tests/files/addressable_3c_err.gno create mode 100644 gnovm/tests/files/addressable_3d_err.gno create mode 100644 gnovm/tests/files/addressable_4.gno create mode 100644 gnovm/tests/files/addressable_4a_err.gno create mode 100644 gnovm/tests/files/addressable_5.gno create mode 100644 gnovm/tests/files/addressable_5a_err_stdlibs.gno create mode 100644 gnovm/tests/files/addressable_5b_err_stdlibs.gno create mode 100644 gnovm/tests/files/addressable_5c_err.gno create mode 100644 gnovm/tests/files/addressable_5d_err.gno create mode 100644 gnovm/tests/files/addressable_6.gno create mode 100644 gnovm/tests/files/addressable_6a_err.gno create mode 100644 gnovm/tests/files/addressable_6b_err.gno create mode 100644 gnovm/tests/files/addressable_6c_err.gno create mode 100644 gnovm/tests/files/addressable_6d_err.gno create mode 100644 gnovm/tests/files/addressable_7a_err.gno create mode 100644 gnovm/tests/files/addressable_7b_err.gno create mode 100644 gnovm/tests/files/addressable_8.gno create mode 100644 gnovm/tests/files/addressable_8a_err.gno create mode 100644 gnovm/tests/files/addressable_9.gno create mode 100644 gnovm/tests/files/addressable_9a_err.gno create mode 100644 gnovm/tests/files/addressable_9b_err.gno diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 4c9de87a6a7..34686dc4cc1 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -225,6 +225,8 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } case *ast.FuncLit: type_ := Go2Gno(fs, gon.Type).(*FuncTypeExpr) + type_.IsClosure = true + return &FuncLitExpr{ Type: *type_, Body: toBody(fs, gon.Body), diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 0f1f4388623..445968a2c9c 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -352,28 +352,11 @@ var ( type Expr interface { Node - assertExpr() + addressability() addressabilityStatus } type Exprs []Expr -// non-pointer receiver to help make immutable. -func (*NameExpr) assertExpr() {} -func (*BasicLitExpr) assertExpr() {} -func (*BinaryExpr) assertExpr() {} -func (*CallExpr) assertExpr() {} -func (*IndexExpr) assertExpr() {} -func (*SelectorExpr) assertExpr() {} -func (*SliceExpr) assertExpr() {} -func (*StarExpr) assertExpr() {} -func (*RefExpr) assertExpr() {} -func (*TypeAssertExpr) assertExpr() {} -func (*UnaryExpr) assertExpr() {} -func (*CompositeLitExpr) assertExpr() {} -func (*KeyValueExpr) assertExpr() {} -func (*FuncLitExpr) assertExpr() {} -func (*ConstExpr) assertExpr() {} - var ( _ Expr = &NameExpr{} _ Expr = &BasicLitExpr{} @@ -410,6 +393,10 @@ type NameExpr struct { Type NameExprType } +func (x *NameExpr) addressability() addressabilityStatus { + return addressabilityStatusSatisfied +} + type NameExprs []NameExpr type BasicLitExpr struct { @@ -421,6 +408,10 @@ type BasicLitExpr struct { Value string } +func (x *BasicLitExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + type BinaryExpr struct { // (Left Op Right) Attributes Left Expr // left operand @@ -428,26 +419,54 @@ type BinaryExpr struct { // (Left Op Right) Right Expr // right operand } +func (x *BinaryExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + type CallExpr struct { // Func(Args) Attributes - Func Expr // function expression - Args Exprs // function arguments, if any. - Varg bool // if true, final arg is variadic. - NumArgs int // len(Args) or len(Args[0].Results) + Func Expr // function expression + Args Exprs // function arguments, if any. + Varg bool // if true, final arg is variadic. + NumArgs int // len(Args) or len(Args[0].Results) + Addressability addressabilityStatus +} + +func (x *CallExpr) addressability() addressabilityStatus { + return x.Addressability } type IndexExpr struct { // X[Index] Attributes - X Expr // expression - Index Expr // index expression - HasOK bool // if true, is form: `value, ok := []` + X Expr // expression + Index Expr // index expression + HasOK bool // if true, is form: `value, ok := [] + Addressability addressabilityStatus +} + +func (x *IndexExpr) addressability() addressabilityStatus { + // If not set in TRANS_LEAVE, defer to the the child expression's addressability. + if x.Addressability == addressabilityStatusNotApplicable { + return x.X.addressability() + } + + return x.Addressability } type SelectorExpr struct { // X.Sel Attributes - X Expr // expression - Path ValuePath // set by preprocessor. - Sel Name // field selector + X Expr // expression + Path ValuePath // set by preprocessor. + Sel Name // field selector + IsAddressable bool // true if X is a pointer +} + +func (x *SelectorExpr) addressability() addressabilityStatus { + if x.IsAddressable || x.X.addressability() == addressabilityStatusSatisfied { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } type SliceExpr struct { // X[Low:High:Max] @@ -458,6 +477,10 @@ type SliceExpr struct { // X[Low:High:Max] Max Expr // maximum capacity of slice; or nil; added in Go 1.2 } +func (x *SliceExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // A StarExpr node represents an expression of the form // "*" Expression. Semantically it could be a unary "*" // expression, or a pointer type. @@ -466,16 +489,33 @@ type StarExpr struct { // *X X Expr // operand } +func (x *StarExpr) addressability() addressabilityStatus { + return addressabilityStatusSatisfied +} + type RefExpr struct { // &X Attributes X Expr // operand } +func (x *RefExpr) addressability() addressabilityStatus { + return x.X.addressability() +} + type TypeAssertExpr struct { // X.(Type) Attributes - X Expr // expression. - Type Expr // asserted type, never nil. - HasOK bool // if true, is form: `_, ok := .()`. + X Expr // expression. + Type Expr // asserted type, never nil. + HasOK bool // if true, is form: `_, ok := .()`. + IsAddressable bool +} + +func (x *TypeAssertExpr) addressability() addressabilityStatus { + if x.IsAddressable { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } // A UnaryExpr node represents a unary expression. Unary @@ -488,12 +528,25 @@ type UnaryExpr struct { // (Op X) Op Word // operator } +func (x *UnaryExpr) addressability() addressabilityStatus { + return x.X.addressability() +} + // MyType{:} struct, array, slice, and map // expressions. type CompositeLitExpr struct { Attributes - Type Expr // literal type; or nil - Elts KeyValueExprs // list of struct fields; if any + Type Expr // literal type; or nil + Elts KeyValueExprs // list of struct fields; if any + IsAddressable bool +} + +func (x *CompositeLitExpr) addressability() addressabilityStatus { + if x.IsAddressable { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } // Returns true if any elements are keyed. @@ -526,6 +579,10 @@ type KeyValueExpr struct { Value Expr // never nil } +func (x *KeyValueExpr) addressability() addressabilityStatus { + return addressabilityStatusNotApplicable +} + type KeyValueExprs []KeyValueExpr // A FuncLitExpr node represents a function literal. Here one @@ -539,6 +596,10 @@ type FuncLitExpr struct { HeapCaptures NameExprs // filled in findLoopUses1 } +func (x *FuncLitExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // The preprocessor replaces const expressions // with *ConstExpr nodes. type ConstExpr struct { @@ -547,6 +608,10 @@ type ConstExpr struct { TypedValue } +func (x *ConstExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // ---------------------------------------- // Type(Expressions) // @@ -565,6 +630,8 @@ type TypeExpr interface { assertTypeExpr() } +const typeExprAddressability = "the addressability method should not be called on Type Expressions" + // non-pointer receiver to help make immutable. func (x *FieldTypeExpr) assertTypeExpr() {} func (x *ArrayTypeExpr) assertTypeExpr() {} @@ -577,17 +644,6 @@ func (x *StructTypeExpr) assertTypeExpr() {} func (x *constTypeExpr) assertTypeExpr() {} func (x *MaybeNativeTypeExpr) assertTypeExpr() {} -func (x *FieldTypeExpr) assertExpr() {} -func (x *ArrayTypeExpr) assertExpr() {} -func (x *SliceTypeExpr) assertExpr() {} -func (x *InterfaceTypeExpr) assertExpr() {} -func (x *ChanTypeExpr) assertExpr() {} -func (x *FuncTypeExpr) assertExpr() {} -func (x *MapTypeExpr) assertExpr() {} -func (x *StructTypeExpr) assertExpr() {} -func (x *constTypeExpr) assertExpr() {} -func (x *MaybeNativeTypeExpr) assertExpr() {} - var ( _ TypeExpr = &FieldTypeExpr{} _ TypeExpr = &ArrayTypeExpr{} @@ -611,6 +667,10 @@ type FieldTypeExpr struct { Tag Expr } +func (x *FieldTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type FieldTypeExprs []FieldTypeExpr func (ftxz FieldTypeExprs) IsNamed() bool { @@ -639,18 +699,30 @@ type ArrayTypeExpr struct { Elt Expr // element type } +func (x *ArrayTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type SliceTypeExpr struct { Attributes Elt Expr // element type Vrd bool // variadic arg expression } +func (x *SliceTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type InterfaceTypeExpr struct { Attributes Methods FieldTypeExprs // list of methods Generic Name // for uverse generics } +func (x *InterfaceTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type ChanDir int const ( @@ -668,10 +740,19 @@ type ChanTypeExpr struct { Value Expr // value type } +func (x *ChanTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type FuncTypeExpr struct { Attributes - Params FieldTypeExprs // (incoming) parameters, if any. - Results FieldTypeExprs // (outgoing) results, if any. + Params FieldTypeExprs // (incoming) parameters, if any. + Results FieldTypeExprs // (outgoing) results, if any. + IsClosure bool +} + +func (x *FuncTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) } type MapTypeExpr struct { @@ -680,11 +761,19 @@ type MapTypeExpr struct { Value Expr // value type } +func (x *MapTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type StructTypeExpr struct { Attributes Fields FieldTypeExprs // list of field declarations } +func (x *StructTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // Like ConstExpr but for types. type constTypeExpr struct { Attributes @@ -692,12 +781,20 @@ type constTypeExpr struct { Type Type } +func (x *constTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // Only used for native func arguments type MaybeNativeTypeExpr struct { Attributes Type Expr } +func (x *MaybeNativeTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // ---------------------------------------- // Stmt // @@ -1531,6 +1628,7 @@ type BlockNode interface { GetPathForName(Store, Name) ValuePath GetBlockNodeForPath(Store, ValuePath) BlockNode GetIsConst(Store, Name) bool + GetIsConstAt(Store, ValuePath) bool GetLocalIndex(Name) (uint16, bool) GetValueRef(Store, Name, bool) *TypedValue GetStaticTypeOf(Store, Name) Type @@ -1548,12 +1646,13 @@ type BlockNode interface { // Embed in node to make it a BlockNode. type StaticBlock struct { Block - Types []Type - NumNames uint16 - Names []Name - Consts []Name // TODO consider merging with Names. - Externs []Name - Loc Location + Types []Type + NumNames uint16 + Names []Name + UnassignableNames []Name + Consts []Name // TODO consider merging with Names. + Externs []Name + Loc Location // temporary storage for rolling back redefinitions. oldValues []oldValue @@ -1738,6 +1837,10 @@ func (sb *StaticBlock) GetIsConst(store Store, n Name) bool { } } +func (sb *StaticBlock) GetIsConstAt(store Store, path ValuePath) bool { + return sb.GetBlockNodeForPath(store, path).GetStaticBlock().getLocalIsConst(path.Name) +} + // Returns true iff n is a local const defined name. func (sb *StaticBlock) getLocalIsConst(n Name) bool { for _, name := range sb.Consts { @@ -1748,6 +1851,32 @@ func (sb *StaticBlock) getLocalIsConst(n Name) bool { return false } +func (sb *StaticBlock) IsAssignable(store Store, n Name) bool { + _, ok := sb.GetLocalIndex(n) + bp := sb.GetParentNode(store) + un := sb.UnassignableNames + + for { + if ok { + for _, uname := range un { + if n == uname { + return false + } + } + + return true + } else if bp != nil { + _, ok = bp.GetLocalIndex(n) + un = bp.GetStaticBlock().UnassignableNames + bp = bp.GetParentNode(store) + } else if _, ok := UverseNode().GetLocalIndex(n); ok { + return false + } else { + return true + } + } +} + // Implements BlockNode. // XXX XXX what about uverse? func (sb *StaticBlock) GetStaticTypeOf(store Store, n Name) Type { @@ -2187,3 +2316,11 @@ func isHiddenResultVariable(name string) bool { } return false } + +type addressabilityStatus int + +const ( + addressabilityStatusNotApplicable addressabilityStatus = iota + addressabilityStatusSatisfied + addressabilityStatusUnsatisfied +) diff --git a/gnovm/pkg/gnolang/op_types.go b/gnovm/pkg/gnolang/op_types.go index 37b549fe14c..f7f3f75f91d 100644 --- a/gnovm/pkg/gnolang/op_types.go +++ b/gnovm/pkg/gnolang/op_types.go @@ -85,8 +85,9 @@ func (m *Machine) doOpFuncType() { } // Push func type. ft := &FuncType{ - Params: params, - Results: results, + Params: params, + Results: results, + IsClosure: x.IsClosure, } m.PushValue(TypedValue{ T: gTypeType, diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 96e09642cd8..ca5834aa44e 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -234,6 +234,7 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) { nx := &n.NameExpr nx.Type = NameExprTypeDefine pkg.Predefine(false, n.Name) + pkg.UnassignableNames = append(pkg.UnassignableNames, n.Name) } case *FuncTypeExpr: for i := range n.Params { @@ -1442,6 +1443,8 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if cx, ok := n.Func.(*ConstExpr); ok { fv := cx.GetFunc() if fv.PkgPath == uversePkgPath && fv.Name == "append" { + // append returns a slice and slices are always addressable. + n.Addressability = addressabilityStatusSatisfied if n.Varg && len(n.Args) == 2 { // If the second argument is a string, // convert to byteslice. @@ -1488,9 +1491,23 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { n.Args[1] = args1 } } + } else if fv.PkgPath == uversePkgPath && fv.Name == "new" { + // The pointer value returned is not addressable, but maybe some selector + // will make it addressable. For now mark it as not addressable. + n.Addressability = addressabilityStatusUnsatisfied } } + // If addressability is not satisfied at this point and the function call returns only one + // result, then mark addressability as unsatisfied. Otherwise, this expression has already + // been explicitly marked as satisfied, or the function returns multiple results, rendering + // addressability NotApplicable for this situation -- it should fallback to the error produced + // when trying to take a reference or slice the result of a call expression that returns + // multiple values. + if n.Addressability != addressabilityStatusSatisfied && len(ft.Results) == 1 { + n.Addressability = addressabilityStatusUnsatisfied + } + // Continue with general case. hasVarg := ft.HasVarg() isVarg := n.Varg @@ -1636,9 +1653,23 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // Replace const index with int *ConstExpr, // or if not const, assert integer type.. checkOrConvertIntegerKind(store, last, n, n.Index) + + // Addressability of this index expression can only be known for slice and + // strings, explanations below in the respective blocks. If this is an index + // on an array, do nothing. This will defer to the array's addresability when + // the `addressability` method is called on this index expression. + if dt.Kind() == SliceKind { + // A value at a slice index is always addressable because the underlying + // array is addressable. + n.Addressability = addressabilityStatusSatisfied + } else if dt.Kind() == StringKind { + // Special case; string indexes are never addressable. + n.Addressability = addressabilityStatusUnsatisfied + } case MapKind: mt := baseOf(gnoTypeOf(store, dt)).(*MapType) checkOrConvertType(store, last, n, &n.Index, mt.Key, false) + n.Addressability = addressabilityStatusUnsatisfied default: panic(fmt.Sprintf( "unexpected index base kind for type %s", @@ -1653,8 +1684,14 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { checkOrConvertIntegerKind(store, last, n, n.High) checkOrConvertIntegerKind(store, last, n, n.Max) - // if n.X is untyped, convert to corresponding type t := evalStaticTypeOf(store, last, n.X) + if t.Kind() == ArrayKind { + if n.X.addressability() == addressabilityStatusUnsatisfied { + panic(fmt.Sprintf("cannot take address of %s", n.X.String())) + } + } + + // if n.X is untyped, convert to corresponding type if isUntyped(t) { dt := defaultTypeOf(t) checkOrConvertType(store, last, n, &n.X, dt, false) @@ -1693,8 +1730,6 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { ) } - evalStaticType(store, last, n.Type) - // TRANS_LEAVE ----------------------- case *UnaryExpr: xt := evalStaticTypeOf(store, last, n.X) @@ -1754,6 +1789,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertType(store, last, n, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false) } + + // Slices are always addressable because the underlying array + // is added to the heap during initialization. + n.IsAddressable = true case *MapType: for i := 0; i < len(n.Elts); i++ { checkOrConvertType(store, last, n, &n.Elts[i].Key, cclt.Key, false) @@ -1793,6 +1832,16 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } } + // If ftype is TRANS_REF_X, then this composite literal being created looks + // something like this in the code: `&MyStruct{}`. It is marked as addressable here + // because on TRANS_LEAVE for a RefExpr, it defers to the addressability of the + // expression it is referencing. When a composite literal is created with a preceding + // '&', it means the value is assigned to an address and that address is returned, + // so the value is addressable. + if ftype == TRANS_REF_X { + n.IsAddressable = true + } + // TRANS_LEAVE ----------------------- case *KeyValueExpr: // NOTE: For simplicity we just @@ -1809,6 +1858,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *SelectorExpr: xt := evalStaticTypeOf(store, last, n.X) + if xt.Kind() == PointerKind { + n.IsAddressable = true + } // Set selector path based on xt's type. switch cxt := xt.(type) { @@ -1821,6 +1873,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { panic(fmt.Sprintf("missing field %s in %s", n.Sel, cxt.String())) } + if len(tr) > 1 { // (the last vp, tr[len(tr)-1], is for n.Sel) if debug { @@ -1923,10 +1976,13 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // packages may contain constant vars, // so check and evaluate if so. tt := pn.GetStaticTypeOfAt(store, n.Path) - if isUntyped(tt) { + + // Produce a constant expression for both typed and untyped constants. + if isUntyped(tt) || pn.GetIsConstAt(store, n.Path) { cx := evalConst(store, last, n) return cx, TRANS_CONTINUE } + case *TypeType: // unbound method xt := evalStaticType(store, last, n.X) @@ -2001,6 +2057,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *AssignStmt: n.AssertCompatible(store, last) + if n.Op == ASSIGN { + for _, lh := range n.Lhs { + if ne, ok := lh.(*NameExpr); ok { + if !last.GetStaticBlock().IsAssignable(store, ne.Name) { + panic("not assignable") + } + } + } + } // NOTE: keep DEFINE and ASSIGN in sync. if n.Op == DEFINE { @@ -2378,9 +2443,20 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // We need to replace all references of the new // Type with old Type, including in attributes. n.Type.SetAttribute(ATTR_TYPE_VALUE, dst) - // Replace the type with *constTypeExpr{}, + // Replace the type with *{}, // otherwise methods would be un at runtime. n.Type = constType(n.Type, dst) + + case *RefExpr: + // If n.X is a RefExpr, then this expression is something like: + // &(&value). The resulting pointer value of the first reference is not + // addressable. Otherwise fall back to the target expression's addressability. + _, xIsRef := n.X.(*RefExpr) + tt := evalStaticTypeOf(store, last, n.X) + + if ft, is_func := tt.(*FuncType); (is_func && !ft.IsClosure) || xIsRef || n.X.addressability() == addressabilityStatusUnsatisfied { + panic(fmt.Sprintf("cannot take address of %s", n.X.String())) + } } // end type switch statement // END TRANS_LEAVE ----------------------- diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 18774fcc462..374ac6d9150 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1132,6 +1132,8 @@ type FuncType struct { typeid TypeID bound *FuncType + + IsClosure bool } // true for predefined func types that are not filled in yet. diff --git a/gnovm/stdlibs/crypto/sha256/sha256_test.gno b/gnovm/stdlibs/crypto/sha256/sha256_test.gno index 26d96cd547e..809f826f007 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256_test.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256_test.gno @@ -7,7 +7,8 @@ import ( ) func TestSha256Sum(t *testing.T) { - got := sha256.Sum256([]byte("sha256 this string"))[:] + result := sha256.Sum256([]byte("sha256 this string")) + got := result[:] expected := "1af1dfa857bf1d8814fe1af8983c18080019922e557f15a8a0d3db739d77aacb" if hex.EncodeToString(got) != expected { diff --git a/gnovm/tests/files/addressable_1.gno b/gnovm/tests/files/addressable_1.gno new file mode 100644 index 00000000000..30616145733 --- /dev/null +++ b/gnovm/tests/files/addressable_1.gno @@ -0,0 +1,38 @@ +package main + +func main() { + // Array pointers are addressable. + println(&getArrPtr1()[0]) + println(&getArrPtr2()[0]) + println(&getArrPtr3()[0]) + println(&new([1]int)[0]) + + // Array pointers are slicable. + println(getArrPtr1()[:]) + println(getArrPtr2()[:]) + println(getArrPtr3()[:]) + println(new([1]int)[:]) +} + +func getArrPtr1() *[1]int { + return &[1]int{1} +} + +func getArrPtr2() *[1]int { + a := [1]int{2} + return &a +} + +func getArrPtr3() *[1]int { + return new([1]int) +} + +// Output: +// &(1 int) +// &(2 int) +// &(0 int) +// &(0 int) +// slice[(1 int)] +// slice[(2 int)] +// slice[(0 int)] +// slice[(0 int)] diff --git a/gnovm/tests/files/addressable_10.gno b/gnovm/tests/files/addressable_10.gno new file mode 100644 index 00000000000..9052c172973 --- /dev/null +++ b/gnovm/tests/files/addressable_10.gno @@ -0,0 +1,12 @@ +package main + +type S struct { + i int +} + +func main() { + println(&new(S).i) +} + +// Output: +// &(0 int) diff --git a/gnovm/tests/files/addressable_10a_err.gno b/gnovm/tests/files/addressable_10a_err.gno new file mode 100644 index 00000000000..6781c6c67e5 --- /dev/null +++ b/gnovm/tests/files/addressable_10a_err.gno @@ -0,0 +1,14 @@ +package main + +func main() { + println(&getPtr()) +} + +type S struct{} + +func getPtr() *S { + return &S{} +} + +// Error: +// main/files/addressable_10a_err.gno:4:10: cannot take address of getPtr() diff --git a/gnovm/tests/files/addressable_10b_err.gno b/gnovm/tests/files/addressable_10b_err.gno new file mode 100644 index 00000000000..30048dfa8f6 --- /dev/null +++ b/gnovm/tests/files/addressable_10b_err.gno @@ -0,0 +1,12 @@ +package main + +type S struct { + i int +} + +func main() { + println(&new(S)) +} + +// Error: +// main/files/addressable_10b_err.gno:8:10: cannot take address of (const (new func(t type{})( *main.S)))(S) diff --git a/gnovm/tests/files/addressable_11.gno b/gnovm/tests/files/addressable_11.gno new file mode 100644 index 00000000000..607c155abfe --- /dev/null +++ b/gnovm/tests/files/addressable_11.gno @@ -0,0 +1,39 @@ +package main + +func main() { + var ii **int + i := new(int) + ii = &i + println(&(*ii)) + println(&ii) + println(i) + println(ii) + println(&i) + + j := new(int) + println(&(*j)) + + println(&(*getPtr())) + + derefTypeAssert() +} + +func getPtr() *int { + return new(int) +} + +func derefTypeAssert() { + var i interface{} + i = new(int) + println(&(*(i.(*int)))) +} + +// Output: +// &(&(0 int) *int) +// &(&(&(0 int) *int) **int) +// &(0 int) +// &(&(0 int) *int) +// &(&(0 int) *int) +// &(0 int) +// &(0 int) +// &(0 int) diff --git a/gnovm/tests/files/addressable_1a_err.gno b/gnovm/tests/files/addressable_1a_err.gno new file mode 100644 index 00000000000..a8c8c3b3c77 --- /dev/null +++ b/gnovm/tests/files/addressable_1a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &[1]int{1}[0] +} + +// Error: +// main/files/addressable_1a_err.gno:4:6: cannot take address of [(const (1 int))](const-type int){(const (1 int))}[(const (0 int))] diff --git a/gnovm/tests/files/addressable_1b_err.gno b/gnovm/tests/files/addressable_1b_err.gno new file mode 100644 index 00000000000..9d8a7d12e4b --- /dev/null +++ b/gnovm/tests/files/addressable_1b_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = [1]int{1}[:] +} + +// Error: +// main/files/addressable_1b_err.gno:4:6: cannot take address of [(const (1 int))](const-type int){(const (1 int))} diff --git a/gnovm/tests/files/addressable_1c_err.gno b/gnovm/tests/files/addressable_1c_err.gno new file mode 100644 index 00000000000..420531187df --- /dev/null +++ b/gnovm/tests/files/addressable_1c_err.gno @@ -0,0 +1,13 @@ +package main + +func main() { + _ = &getArr()[0] +} + +func getArr() [1]int { + arr := [1]int{1} + return arr +} + +// Error: +// main/files/addressable_1c_err.gno:4:6: cannot take address of getArr()[(const (0 int))] diff --git a/gnovm/tests/files/addressable_1d_err.gno b/gnovm/tests/files/addressable_1d_err.gno new file mode 100644 index 00000000000..56bb81c881f --- /dev/null +++ b/gnovm/tests/files/addressable_1d_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = getArr()[:] +} + +func getArr() [1]int { + return [1]int{1} +} + +// Error: +// main/files/addressable_1d_err.gno:4:6: cannot take address of getArr() diff --git a/gnovm/tests/files/addressable_2.gno b/gnovm/tests/files/addressable_2.gno new file mode 100644 index 00000000000..1baf7f29e65 --- /dev/null +++ b/gnovm/tests/files/addressable_2.gno @@ -0,0 +1,40 @@ +package main + +func main() { + // Slices are addressable because the underlying array is addressable + // after slice initialization. + println(&[]int{1}[0]) + println(&getSlice1()[0]) + println(&getSlice2()[0]) + println(&[]int{1}) + + a := []int{1} + println(&append(a, 1, 2, 3, 4, 5)[5]) + + println([]int{1}[:]) + println(getSlice1()[:]) + println(getSlice2()[:]) + + b := []int{1} + println(append(b, 1, 2, 3, 4, 5)[:]) +} + +func getSlice1() []int { + return []int{2} +} + +func getSlice2() []int { + s := []int{3} + return s +} + +// Output: +// &(1 int) +// &(2 int) +// &(3 int) +// &(slice[(1 int)] []int) +// &(5 int) +// slice[(1 int)] +// slice[(2 int)] +// slice[(3 int)] +// slice[(1 int),(1 int),(2 int),(3 int),(4 int),(5 int)] diff --git a/gnovm/tests/files/addressable_2a_err.gno b/gnovm/tests/files/addressable_2a_err.gno new file mode 100644 index 00000000000..363fa3d2817 --- /dev/null +++ b/gnovm/tests/files/addressable_2a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &[]int{1}[:] +} + +// Error: +// main/files/addressable_2a_err.gno:4:6: cannot take address of [](const-type int){(const (1 int))}[:] diff --git a/gnovm/tests/files/addressable_2b_err.gno b/gnovm/tests/files/addressable_2b_err.gno new file mode 100644 index 00000000000..eef7fd37ca5 --- /dev/null +++ b/gnovm/tests/files/addressable_2b_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = &getSlice() +} + +func getSlice() []int { + return []int{1} +} + +// Error: +// main/files/addressable_2b_err.gno:4:6: cannot take address of getSlice() diff --git a/gnovm/tests/files/addressable_3.gno b/gnovm/tests/files/addressable_3.gno new file mode 100644 index 00000000000..34e9a2e1edc --- /dev/null +++ b/gnovm/tests/files/addressable_3.gno @@ -0,0 +1,105 @@ +package main + +type S struct { + i int + ip *int + a [1]int + ap *[1]int + s []int +} + +func main() { + intPtr := new(int) + *intPtr = 9 + + v1 := S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } + + // v1 and all members are addressable. + println(&v1) + println(&v1.i) + println(*v1.ip) + println(&v1.a[0]) + println(&v1.ap[0]) + println(&v1.s[0]) + println("") + + // Defining a struct as a pointer also makes a member addressable. + println(&(&S{i: 4}).i) + println("") + + // Print only the members that are addressable when S is not addressable. + println(*S{ip: intPtr}.ip) + println(&S{ap: new([1]int)}.ap[0]) + println(&S{s: []int{6}}.s[0]) + println("") + + // A struct value returned by a function is not addressable. + // Only certain members are addressable. + println(*getStruct().ip) + println(&getStruct().ap[0]) + println(&getStruct().s[0]) + println("") + + // A struct pointer value returned by a function has all members addressable. + println(&getStructPtr().i) + println(*getStructPtr().ip) + println(&getStructPtr().a[0]) + println(&getStructPtr().ap[0]) + println(&getStructPtr().s[0]) +} + +func getStruct() S { + intPtr := new(int) + *intPtr = 9 + + return S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } +} + +func getStructPtr() *S { + intPtr := new(int) + *intPtr = 9 + + return &S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } +} + +// Output: +// &(struct{(4 int),(&(9 int) *int),(array[(5 int)] [1]int),(&(array[(0 int)] [1]int) *[1]int),(slice[(6 int)] []int)} main.S) +// &(4 int) +// 9 +// &(5 int) +// &(0 int) +// &(6 int) +// +// &(4 int) +// +// 9 +// &(0 int) +// &(6 int) +// +// 9 +// &(0 int) +// &(6 int) +// +// &(4 int) +// 9 +// &(5 int) +// &(0 int) +// &(6 int) diff --git a/gnovm/tests/files/addressable_3a_err.gno b/gnovm/tests/files/addressable_3a_err.gno new file mode 100644 index 00000000000..dcf3883e79b --- /dev/null +++ b/gnovm/tests/files/addressable_3a_err.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + i int +} + +func main() { + // Can't take the address of non-addressable member of + // a non-addressable struct. + _ = &S{i: 4}.i +} + +// Error: +// main/files/addressable_3a_err.gno:10:6: cannot take address of S{i: (const (4 int))}.i diff --git a/gnovm/tests/files/addressable_3b_err.gno b/gnovm/tests/files/addressable_3b_err.gno new file mode 100644 index 00000000000..db4e810155a --- /dev/null +++ b/gnovm/tests/files/addressable_3b_err.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + i int +} + +func main() { + _ = &getStruct().i +} + +func getStruct() S { + return S{i: 9} +} + +// Error: +// main/files/addressable_3b_err.gno:8:6: cannot take address of getStruct().i diff --git a/gnovm/tests/files/addressable_3c_err.gno b/gnovm/tests/files/addressable_3c_err.gno new file mode 100644 index 00000000000..536b5f77475 --- /dev/null +++ b/gnovm/tests/files/addressable_3c_err.gno @@ -0,0 +1,17 @@ +package main + +type MyStruct struct { + Mp *int +} + +func makeT() MyStruct { + x := 10 + return MyStruct{Mp: &x} +} + +func main() { + _ = &makeT().Mp +} + +// Error: +// main/files/addressable_3c_err.gno:13:6: cannot take address of makeT().Mp diff --git a/gnovm/tests/files/addressable_3d_err.gno b/gnovm/tests/files/addressable_3d_err.gno new file mode 100644 index 00000000000..00d75f80402 --- /dev/null +++ b/gnovm/tests/files/addressable_3d_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &(&struct{}{}) +} + +// Error: +// main/files/addressable_3d_err.gno:4:6: cannot take address of &(struct { }{}) diff --git a/gnovm/tests/files/addressable_4.gno b/gnovm/tests/files/addressable_4.gno new file mode 100644 index 00000000000..322cf8eabb3 --- /dev/null +++ b/gnovm/tests/files/addressable_4.gno @@ -0,0 +1,23 @@ +package main + +type S struct { + s string +} + +func main() { + // Strings are special; they are slicable even if not addressable. + println("hello"[:]) + println("hello"[0]) + println("hello"[2:4]) + println(S{s: "hello"}.s[:]) + println(S{s: "hello"}.s[0]) + println(S{s: "hello"}.s[2:4]) +} + +// Output: +// hello +// 104 +// ll +// hello +// 104 +// ll diff --git a/gnovm/tests/files/addressable_4a_err.gno b/gnovm/tests/files/addressable_4a_err.gno new file mode 100644 index 00000000000..17481976f3a --- /dev/null +++ b/gnovm/tests/files/addressable_4a_err.gno @@ -0,0 +1,9 @@ +package main + +func main() { + greeting := "hello" + _ = &greeting[2] +} + +// Error: +// main/files/addressable_4a_err.gno:5:6: cannot take address of greeting[(const (2 int))] diff --git a/gnovm/tests/files/addressable_5.gno b/gnovm/tests/files/addressable_5.gno new file mode 100644 index 00000000000..800cc744458 --- /dev/null +++ b/gnovm/tests/files/addressable_5.gno @@ -0,0 +1,11 @@ +package main + +import "encoding/binary" + +func main() { + // Verify that addressable results of expressions are + // still addressable when accessed via a selector. + var b []byte + le := &binary.LittleEndian + println(&le.AppendUint16(b, 0)[0]) +} diff --git a/gnovm/tests/files/addressable_5a_err_stdlibs.gno b/gnovm/tests/files/addressable_5a_err_stdlibs.gno new file mode 100644 index 00000000000..bc6318f511d --- /dev/null +++ b/gnovm/tests/files/addressable_5a_err_stdlibs.gno @@ -0,0 +1,11 @@ +package main + +import "math" + +func main() { + // Untyped constants are not addressable. + _ = &math.MaxUint8 +} + +// Error: +// main/files/addressable_5a_err_stdlibs.gno:7:6: cannot take address of (const (255 bigint)) diff --git a/gnovm/tests/files/addressable_5b_err_stdlibs.gno b/gnovm/tests/files/addressable_5b_err_stdlibs.gno new file mode 100644 index 00000000000..e2028a22c9e --- /dev/null +++ b/gnovm/tests/files/addressable_5b_err_stdlibs.gno @@ -0,0 +1,11 @@ +package main + +import "std" + +func main() { + // Type constants are not addressable. + _ = &std.BankerTypeReadonly +} + +// Error: +// main/files/addressable_5b_err_stdlibs.gno:7:6: cannot take address of (const (0 std.BankerType)) diff --git a/gnovm/tests/files/addressable_5c_err.gno b/gnovm/tests/files/addressable_5c_err.gno new file mode 100644 index 00000000000..92de1aeb30a --- /dev/null +++ b/gnovm/tests/files/addressable_5c_err.gno @@ -0,0 +1,10 @@ +package main + +const a = 1 + +func main() { + _ = &a +} + +// Error: +// main/files/addressable_5c_err.gno:6:6: cannot take address of (const (1 bigint)) diff --git a/gnovm/tests/files/addressable_5d_err.gno b/gnovm/tests/files/addressable_5d_err.gno new file mode 100644 index 00000000000..fe78ed36681 --- /dev/null +++ b/gnovm/tests/files/addressable_5d_err.gno @@ -0,0 +1,10 @@ +package main + +const a int = 1 + +func main() { + _ = &a +} + +// Error: +// main/files/addressable_5d_err.gno:6:6: cannot take address of (const (1 int)) diff --git a/gnovm/tests/files/addressable_6.gno b/gnovm/tests/files/addressable_6.gno new file mode 100644 index 00000000000..9fc0616f980 --- /dev/null +++ b/gnovm/tests/files/addressable_6.gno @@ -0,0 +1,27 @@ +package main + +type S struct { + a int +} + +type Alias []int + +func main() { + // Type assertions copy the value being asserted, so only pointers and + // slices are addressable. Slices are addressable because a copy of a slice + // maintains a reference to the same underlying array. + var i interface{} + i = []int{1} + println(&i.([]int)[0]) + + i = &S{} + println(&i.(*S).a) + + i = Alias{4} + println(&i.(Alias)[0]) +} + +// Output: +// &(1 int) +// &(0 int) +// &(4 int) diff --git a/gnovm/tests/files/addressable_6a_err.gno b/gnovm/tests/files/addressable_6a_err.gno new file mode 100644 index 00000000000..f27432fa81b --- /dev/null +++ b/gnovm/tests/files/addressable_6a_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var i interface{} + i = 5 + println(&i.(int)) +} + +// Error: +// main/files/addressable_6a_err.gno:6:10: cannot take address of i.((const-type int)) diff --git a/gnovm/tests/files/addressable_6b_err.gno b/gnovm/tests/files/addressable_6b_err.gno new file mode 100644 index 00000000000..ea2e19aa3c4 --- /dev/null +++ b/gnovm/tests/files/addressable_6b_err.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + a int +} + +func main() { + var i interface{} + i = S{a: 9} + println(&i.(S).a) +} + +// Error: +// main/files/addressable_6b_err.gno:10:10: cannot take address of i.(S).a diff --git a/gnovm/tests/files/addressable_6c_err.gno b/gnovm/tests/files/addressable_6c_err.gno new file mode 100644 index 00000000000..03666922eee --- /dev/null +++ b/gnovm/tests/files/addressable_6c_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var i interface{} + i = [1]int{1} + println(&i.([1]int)[0]) +} + +// Error: +// main/files/addressable_6c_err.gno:6:10: cannot take address of i.([(const (1 int))](const-type int))[(const (0 int))] diff --git a/gnovm/tests/files/addressable_6d_err.gno b/gnovm/tests/files/addressable_6d_err.gno new file mode 100644 index 00000000000..b6058b7c024 --- /dev/null +++ b/gnovm/tests/files/addressable_6d_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = &getSlice().([]int) +} + +func getSlice() interface{} { + return []int{1} +} + +// Error: +// main/files/addressable_6d_err.gno:4:6: cannot take address of getSlice().([](const-type int)) diff --git a/gnovm/tests/files/addressable_7a_err.gno b/gnovm/tests/files/addressable_7a_err.gno new file mode 100644 index 00000000000..f3648b6b990 --- /dev/null +++ b/gnovm/tests/files/addressable_7a_err.gno @@ -0,0 +1,12 @@ +package main + +func foo() ([]int, []string) { + return []int{1, 2, 3}, []string{"a", "b", "c"} +} + +func main() { + _ = &foo() +} + +// Error: +// main/files/addressable_7a_err.gno:8:2: getTypeOf() only supports *CallExpr with 1 result, got ([]int,[]string) diff --git a/gnovm/tests/files/addressable_7b_err.gno b/gnovm/tests/files/addressable_7b_err.gno new file mode 100644 index 00000000000..a621d688ea4 --- /dev/null +++ b/gnovm/tests/files/addressable_7b_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _, _ = &int(9) +} + +// Error: +// main/files/addressable_7b_err.gno:4:9: cannot take address of (const (9 int)) diff --git a/gnovm/tests/files/addressable_8.gno b/gnovm/tests/files/addressable_8.gno new file mode 100644 index 00000000000..3fde1018185 --- /dev/null +++ b/gnovm/tests/files/addressable_8.gno @@ -0,0 +1,9 @@ +package main + +func main() { + f := func() { println("Hello, World!") } + println(&f) +} + +// Output: +// &(func()(){...} func()()) diff --git a/gnovm/tests/files/addressable_8a_err.gno b/gnovm/tests/files/addressable_8a_err.gno new file mode 100644 index 00000000000..6e3395da773 --- /dev/null +++ b/gnovm/tests/files/addressable_8a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &func() { println("Hello, World!") } +} + +// Error: +// main/files/addressable_8a_err.gno:4:6: cannot take address of func func(){ (const (println func(xs ...interface{})()))((const ("Hello, World!" string))) } diff --git a/gnovm/tests/files/addressable_9.gno b/gnovm/tests/files/addressable_9.gno new file mode 100644 index 00000000000..3b0b832f781 --- /dev/null +++ b/gnovm/tests/files/addressable_9.gno @@ -0,0 +1,32 @@ +package main + +type S struct { + i int + t *T +} + +type T struct { + i int +} + +func main() { + m := map[int]*S{} + s := &S{i: 4} + m[5] = s + println(&m[5].i) + + mm := map[int]S{} + ss := S{t: new(T)} + mm[8] = ss + println(&mm[8].t.i) + + mmm := map[int]map[int]*S{} + mmm[3] = map[int]*S{} + mmm[3][3] = &S{i: 7} + println(&mmm[3][3].i) +} + +// Output: +// &(4 int) +// &(0 int) +// &(7 int) diff --git a/gnovm/tests/files/addressable_9a_err.gno b/gnovm/tests/files/addressable_9a_err.gno new file mode 100644 index 00000000000..038990e1c01 --- /dev/null +++ b/gnovm/tests/files/addressable_9a_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + m := map[int]int{} + m[4] = 5 + println(&m[4]) +} + +// Error: +// main/files/addressable_9a_err.gno:6:10: cannot take address of m[(const (4 int))] diff --git a/gnovm/tests/files/addressable_9b_err.gno b/gnovm/tests/files/addressable_9b_err.gno new file mode 100644 index 00000000000..92db1fbeefc --- /dev/null +++ b/gnovm/tests/files/addressable_9b_err.gno @@ -0,0 +1,15 @@ +package main + +type S struct { + i int +} + +func main() { + mmm := map[int]map[int]S{} + mmm[3] = map[int]S{} + mmm[3][3] = S{i: 7} + println(&mmm[3][3].i) +} + +// Error: +// main/files/addressable_9b_err.gno:11:10: cannot take address of mmm[(const (3 int))][(const (3 int))].i From d95ee0493f0d12ab538de19eb97d3b175ca9c246 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 20 Jan 2025 17:26:21 +0100 Subject: [PATCH 57/75] test: fix TestDownloadDeps after #3534 (#3565) --- gnovm/cmd/gno/download_deps_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gnovm/cmd/gno/download_deps_test.go b/gnovm/cmd/gno/download_deps_test.go index 0828e9b2245..15c344cbd28 100644 --- a/gnovm/cmd/gno/download_deps_test.go +++ b/gnovm/cmd/gno/download_deps_test.go @@ -46,9 +46,10 @@ func TestDownloadDeps(t *testing.T) { }, }, }, - requirements: []string{"avl"}, + requirements: []string{"avl", "ufmt"}, ioErrContains: []string{ "gno: downloading gno.land/p/demo/avl", + "gno: downloading gno.land/p/demo/ufmt", }, }, { desc: "fetch_gno.land/p/demo/blog6", @@ -80,9 +81,10 @@ func TestDownloadDeps(t *testing.T) { New: module.Version{Path: "gno.land/p/demo/avl"}, }}, }, - requirements: []string{"avl"}, + requirements: []string{"avl", "ufmt"}, ioErrContains: []string{ "gno: downloading gno.land/p/demo/avl", + "gno: downloading gno.land/p/demo/ufmt", }, }, { desc: "fetch_replace_local", From 8702da906f177cd10e12133286844018e15a0307 Mon Sep 17 00:00:00 2001 From: Antoine Eddi <5222525+aeddi@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:26:37 +0100 Subject: [PATCH 58/75] chore: fix gnohealth command/subcommand help (#3555) This PR fixes a detail, the help descriptions of the gnohealth command that are incorrect. --- contribs/gnohealth/health.go | 24 ------------------- .../gnohealth/internal/timestamp/timestamp.go | 2 +- contribs/gnohealth/main.go | 15 +++++++++++- 3 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 contribs/gnohealth/health.go diff --git a/contribs/gnohealth/health.go b/contribs/gnohealth/health.go deleted file mode 100644 index 5118cac5fa5..00000000000 --- a/contribs/gnohealth/health.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "github.com/gnolang/gno/contribs/gnohealth/internal/timestamp" - "github.com/gnolang/gno/tm2/pkg/commands" -) - -func newHealthCmd(io commands.IO) *commands.Command { - cmd := commands.NewCommand( - commands.Metadata{ - ShortUsage: " [flags] [...]", - ShortHelp: "gno health check suite", - LongHelp: "Gno health check suite, to verify that different parts of Gno are working correctly", - }, - commands.NewEmptyConfig(), - commands.HelpExec, - ) - - cmd.AddSubCommands( - timestamp.NewTimestampCmd(io), - ) - - return cmd -} diff --git a/contribs/gnohealth/internal/timestamp/timestamp.go b/contribs/gnohealth/internal/timestamp/timestamp.go index 50521b9130f..cb5dab44f91 100644 --- a/contribs/gnohealth/internal/timestamp/timestamp.go +++ b/contribs/gnohealth/internal/timestamp/timestamp.go @@ -35,7 +35,7 @@ func NewTimestampCmd(io commands.IO) *commands.Command { return commands.NewCommand( commands.Metadata{ Name: "timestamp", - ShortUsage: "[flags]", + ShortUsage: "timestamp [flags]", ShortHelp: "check if block timestamps are drifting", LongHelp: "This command checks if block timestamps are drifting on a blockchain by connecting to a specified node via RPC.", }, diff --git a/contribs/gnohealth/main.go b/contribs/gnohealth/main.go index 4325c657976..a4a23369710 100644 --- a/contribs/gnohealth/main.go +++ b/contribs/gnohealth/main.go @@ -4,11 +4,24 @@ import ( "context" "os" + "github.com/gnolang/gno/contribs/gnohealth/internal/timestamp" "github.com/gnolang/gno/tm2/pkg/commands" ) func main() { - cmd := newHealthCmd(commands.NewDefaultIO()) + cmd := commands.NewCommand( + commands.Metadata{ + ShortUsage: " [flags]", + LongHelp: "Gno health check suite, to verify that different parts of Gno are working correctly", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + io := commands.NewDefaultIO() + cmd.AddSubCommands( + timestamp.NewTimestampCmd(io), + ) cmd.Execute(context.Background(), os.Args[1:]) } From 6f37334052741ec38ad8fa097dd95b140a605fa2 Mon Sep 17 00:00:00 2001 From: Stefan Nikolic Date: Tue, 21 Jan 2025 11:17:16 +0100 Subject: [PATCH 59/75] =?UTF-8?q?fix(grc721):=20Make=20basic=20`metadataNF?= =?UTF-8?q?T`=20implementation=20usable=20from=20external=C2=A0packages=20?= =?UTF-8?q?(#3495)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current implementation of `grc721_metadata.gno` has a critical usability issue where external packages can't access `basic_nft.gno` methods when using `NewNFTWithMetadata()`. This is because: 1. The `metadataNFT` struct in `grc721_metadata.gno` embeds `*basicNFT` which is unexported 2. While embedding promotes methods within the same package, unexported methods are not accessible from external packages 3. External packages could only access the metadata-specific methods (`SetTokenMetadata`, `TokenMetadata`) Changes made: - Add forwarding methods in `metadataNFT` for all `basicNFT` functionality (`Name`, `Symbol`, `BalanceOf`, etc.) - Remove owner check in `SetTokenMetadata`, since this is something that should be left for NFT collection creator to choose who should be able to use this method These changes ensure that external packages can properly use the metadata NFT implementation with full NFT functionality while maintaining proper encapsulation of the underlying `basicNFT` struct interface. An example of a working usage of this updated `grc721_metadata.gno` can be found in [this file](https://github.com/gnolang/gno/pull/3344/files#diff-b5a138a5b86103af5764def3e87ec3814c9822e5b6da0c705b732273edbde57b). --------- Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- .../p/demo/grc/grc721/grc721_metadata.gno | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 05fad41be18..2b408a206ed 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -29,16 +29,6 @@ func NewNFTWithMetadata(name string, symbol string) *metadataNFT { // SetTokenMetadata sets metadata for a given token ID. func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { - // Check if the caller is the owner of the token - owner, err := s.basicNFT.OwnerOf(tid) - if err != nil { - return err - } - caller := std.PrevRealm().Addr() - if caller != owner { - return ErrCallerIsNotOwner - } - // Set the metadata for the token ID in the extensions AVL tree s.extensions.Set(string(tid), metadata) return nil @@ -55,44 +45,72 @@ func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { return metadata.(Metadata), nil } -// mint mints a new token and assigns it to the specified address. -func (s *metadataNFT) mint(to std.Address, tid TokenID) error { - // Check if the address is valid - if err := isValidAddress(to); err != nil { - return err - } +// Basic NFT methods forwarded to embedded basicNFT - // Check if the token ID already exists - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } +func (s *metadataNFT) Name() string { + return s.basicNFT.Name() +} - s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) +func (s *metadataNFT) Symbol() string { + return s.basicNFT.Symbol() +} - // Check if the token ID was minted by beforeTokenTransfer - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } +func (s *metadataNFT) TokenCount() uint64 { + return s.basicNFT.TokenCount() +} - // Increment balance of the recipient address - toBalance, err := s.basicNFT.BalanceOf(to) - if err != nil { - return err - } - toBalance += 1 - s.basicNFT.balances.Set(to.String(), toBalance) +func (s *metadataNFT) BalanceOf(addr std.Address) (uint64, error) { + return s.basicNFT.BalanceOf(addr) +} + +func (s *metadataNFT) OwnerOf(tid TokenID) (std.Address, error) { + return s.basicNFT.OwnerOf(tid) +} - // Set owner of the token ID to the recipient address - s.basicNFT.owners.Set(string(tid), to) +func (s *metadataNFT) TokenURI(tid TokenID) (string, error) { + return s.basicNFT.TokenURI(tid) +} - std.Emit( - TransferEvent, - "from", string(zeroAddress), - "to", string(to), - "tokenId", string(tid), - ) +func (s *metadataNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + return s.basicNFT.SetTokenURI(tid, tURI) +} - s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) +func (s *metadataNFT) IsApprovedForAll(owner, operator std.Address) bool { + return s.basicNFT.IsApprovedForAll(owner, operator) +} - return nil +func (s *metadataNFT) Approve(to std.Address, tid TokenID) error { + return s.basicNFT.Approve(to, tid) +} + +func (s *metadataNFT) GetApproved(tid TokenID) (std.Address, error) { + return s.basicNFT.GetApproved(tid) +} + +func (s *metadataNFT) SetApprovalForAll(operator std.Address, approved bool) error { + return s.basicNFT.SetApprovalForAll(operator, approved) +} + +func (s *metadataNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + return s.basicNFT.SafeTransferFrom(from, to, tid) +} + +func (s *metadataNFT) TransferFrom(from, to std.Address, tid TokenID) error { + return s.basicNFT.TransferFrom(from, to, tid) +} + +func (s *metadataNFT) Mint(to std.Address, tid TokenID) error { + return s.basicNFT.Mint(to, tid) +} + +func (s *metadataNFT) SafeMint(to std.Address, tid TokenID) error { + return s.basicNFT.SafeMint(to, tid) +} + +func (s *metadataNFT) Burn(tid TokenID) error { + return s.basicNFT.Burn(tid) +} + +func (s *metadataNFT) RenderHome() string { + return s.basicNFT.RenderHome() } From bcfc002c725ffe2d61a68cb03e3065f6c6312938 Mon Sep 17 00:00:00 2001 From: Michelle <117160070+michelleellen@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:06:12 +0100 Subject: [PATCH 60/75] chore(r/gnoland/pages): add bounties repo link to `page_contribute.gno` (#3570) --- examples/gno.land/r/gnoland/pages/page_contribute.gno | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/gno.land/r/gnoland/pages/page_contribute.gno b/examples/gno.land/r/gnoland/pages/page_contribute.gno index 0855dc327cd..3448383226e 100644 --- a/examples/gno.land/r/gnoland/pages/page_contribute.gno +++ b/examples/gno.land/r/gnoland/pages/page_contribute.gno @@ -19,9 +19,7 @@ A good place where to start are the issues tagged ["good first issue"](https://g ## Gno Bounties -Additionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms. - -The Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the ["bounty" label](https://github.com/gnolang/gno/labels/bounty). +Additionally, you can look out to help on specific issues labeled as bounties. The Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the ["bounty" label](https://github.com/gnolang/gno/labels/bounty). For more detals on the categories and types of bounties, we have a [bounties README](https://github.com/gnolang/bounties). Recommendations on participating in the gno.land Bounty Program: From c74fb5781b6377d14f17534fcc793a8b50518851 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 21 Jan 2025 13:08:35 +0100 Subject: [PATCH 61/75] feat(github-bot): support review team flows (#3530) Fixes #3072, Fixes #3093. Should add support for the required workflows on the GitHub bot, for the review team. cc @Kouteki @jefft0 --- contribs/github-bot/Makefile | 16 ++++ .../github-bot/internal/conditions/author.go | 21 +++++ .../internal/conditions/author_test.go | 27 +++++++ .../github-bot/internal/conditions/draft.go | 2 +- contribs/github-bot/internal/config/config.go | 13 ++++ .../internal/requirements/boolean.go | 66 ++++++++++++++++ .../internal/requirements/boolean_test.go | 77 +++++++++++++++++++ .../github-bot/internal/requirements/draft.go | 21 +++++ .../internal/requirements/draft_test.go | 34 ++++++++ .../github-bot/internal/requirements/label.go | 76 +++++++++++++----- .../internal/requirements/label_test.go | 71 +++++++++++++++-- .../internal/requirements/reviewer.go | 56 ++++++++++++-- .../internal/requirements/reviewer_test.go | 65 ++++++++++++++++ contribs/github-bot/internal/utils/testing.go | 7 +- 14 files changed, 516 insertions(+), 36 deletions(-) create mode 100644 contribs/github-bot/Makefile create mode 100644 contribs/github-bot/internal/requirements/draft.go create mode 100644 contribs/github-bot/internal/requirements/draft_test.go diff --git a/contribs/github-bot/Makefile b/contribs/github-bot/Makefile new file mode 100644 index 00000000000..aee43149b7a --- /dev/null +++ b/contribs/github-bot/Makefile @@ -0,0 +1,16 @@ +GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../) + +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + +.PHONY: install +install: + go install . + +.PHONY: lint +lint: + $(golangci_lint) --config ../../.github/golangci.yml run ./... + +.PHONY: test +test: + go test $(GOTEST_FLAGS) -v ./... diff --git a/contribs/github-bot/internal/conditions/author.go b/contribs/github-bot/internal/conditions/author.go index 9052f781bd5..19be8298849 100644 --- a/contribs/github-bot/internal/conditions/author.go +++ b/contribs/github-bot/internal/conditions/author.go @@ -58,3 +58,24 @@ func (a *authorInTeam) IsMet(pr *github.PullRequest, details treeprint.Tree) boo func AuthorInTeam(gh *client.GitHub, team string) Condition { return &authorInTeam{gh: gh, team: team} } + +type authorAssociationIs struct { + assoc string +} + +var _ Condition = &authorAssociationIs{} + +func (a *authorAssociationIs) IsMet(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("Pull request author has author_association: %q", a.assoc) + + return utils.AddStatusNode(pr.GetAuthorAssociation() == a.assoc, detail, details) +} + +// AuthorAssociationIs asserts that the author of the PR has the given value for +// the GitHub "author association" field, on the PR. +// +// See https://docs.github.com/en/graphql/reference/enums#commentauthorassociation +// for a list of possible values and descriptions. +func AuthorAssociationIs(association string) Condition { + return &authorAssociationIs{assoc: association} +} diff --git a/contribs/github-bot/internal/conditions/author_test.go b/contribs/github-bot/internal/conditions/author_test.go index c5836f1ea76..24d3c51859e 100644 --- a/contribs/github-bot/internal/conditions/author_test.go +++ b/contribs/github-bot/internal/conditions/author_test.go @@ -91,3 +91,30 @@ func TestAuthorInTeam(t *testing.T) { }) } } + +func TestAuthorAssociationIs(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + association string + associationWant string + isMet bool + }{ + {"has", "MEMBER", "MEMBER", true}, + {"hasNot", "COLLABORATOR", "MEMBER", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{ + AuthorAssociation: github.String(testCase.association), + } + details := treeprint.New() + condition := AuthorAssociationIs(testCase.associationWant) + + assert.Equal(t, condition.IsMet(pr, details), testCase.isMet, fmt.Sprintf("condition should have a met status: %t", testCase.isMet)) + assert.True(t, utils.TestLastNodeStatus(t, testCase.isMet, details), fmt.Sprintf("condition details should have a status: %t", testCase.isMet)) + }) + } +} diff --git a/contribs/github-bot/internal/conditions/draft.go b/contribs/github-bot/internal/conditions/draft.go index 2c263f2ae75..5d554b4b484 100644 --- a/contribs/github-bot/internal/conditions/draft.go +++ b/contribs/github-bot/internal/conditions/draft.go @@ -10,7 +10,7 @@ import ( // Draft Condition. type draft struct{} -var _ Condition = &baseBranch{} +var _ Condition = &draft{} func (*draft) IsMet(pr *github.PullRequest, details treeprint.Tree) bool { return utils.AddStatusNode(pr.GetDraft(), "This pull request is a draft", details) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index f80fc86cb11..83219c04d1d 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -54,6 +54,19 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { If: c.Label("don't merge"), Then: r.Never(), }, + { + Description: "Pending initial approval by a review team member (and label matches review triage state)", + If: c.Not(c.AuthorInTeam(gh, "tech-staff")), + Then: r. + If(r.Or(r.ReviewByOrgMembers(gh, 1), r.Draft())). + // Either there was a first approval from a member, and we + // assert that the label for triage-pending is removed... + Then(r.Not(r.Label(gh, "review/triage-pending", r.LabelRemove))). + // Or there was not, and we apply the triage pending label. + // The requirement should always fail, to mark the PR is not + // ready to be merged. + Else(r.And(r.Label(gh, "review/triage-pending", r.LabelApply), r.Never())), + }, } manual := []ManualCheck{ diff --git a/contribs/github-bot/internal/requirements/boolean.go b/contribs/github-bot/internal/requirements/boolean.go index 6b441c92f80..7d9eea2f0d9 100644 --- a/contribs/github-bot/internal/requirements/boolean.go +++ b/contribs/github-bot/internal/requirements/boolean.go @@ -96,3 +96,69 @@ func (n *not) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { func Not(req Requirement) Requirement { return ¬{req} } + +// IfCondition executes the condition, and based on the result then runs Then +// or Else. +type IfCondition struct { + cond Requirement + then Requirement + els Requirement +} + +var _ Requirement = &IfCondition{} + +func (i *IfCondition) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + if i.then == nil { + i.then = Always() + } + ifBranch := details.AddBranch("") + condBranch := ifBranch.AddBranch("") + + var ( + target Requirement + targetName string + ) + + if i.cond.IsSatisfied(pr, condBranch) { + condBranch.SetValue(fmt.Sprintf("%s Condition", utils.Success)) + target, targetName = i.then, "Then" + } else { + condBranch.SetValue(fmt.Sprintf("%s Condition", utils.Fail)) + target, targetName = i.els, "Else" + } + + targBranch := ifBranch.AddBranch("") + if target == nil || target.IsSatisfied(pr, targBranch) { + ifBranch.SetValue(fmt.Sprintf("%s If", utils.Success)) + targBranch.SetValue(fmt.Sprintf("%s %s", utils.Success, targetName)) + return true + } else { + ifBranch.SetValue(fmt.Sprintf("%s If", utils.Fail)) + targBranch.SetValue(fmt.Sprintf("%s %s", utils.Fail, targetName)) + return false + } +} + +// If returns a conditional requirement, which runs Then if cond evaluates +// successfully, or Else otherwise. +// +// Then / Else are optional, and always evaluate to true by default. +func If(cond Requirement) *IfCondition { + return &IfCondition{cond: cond} +} + +func (i *IfCondition) Then(then Requirement) *IfCondition { + if i.then != nil { + panic("'Then' is already set") + } + i.then = then + return i +} + +func (i *IfCondition) Else(els Requirement) *IfCondition { + if i.els != nil { + panic("'Else' is already set") + } + i.els = els + return i +} diff --git a/contribs/github-bot/internal/requirements/boolean_test.go b/contribs/github-bot/internal/requirements/boolean_test.go index 0043a44985c..dfa829ede61 100644 --- a/contribs/github-bot/internal/requirements/boolean_test.go +++ b/contribs/github-bot/internal/requirements/boolean_test.go @@ -94,3 +94,80 @@ func TestNot(t *testing.T) { }) } } + +func TestIfCond(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + req Requirement + isSatisfied bool + }{ + {"if always", If(Always()), true}, + {"if never", If(Never()), true}, + {"if always then always", If(Always()).Then(Always()), true}, + {"if never else always", If(Never()).Else(Always()), true}, + {"if always then never", If(Always()).Then(Never()), false}, + {"if never else never", If(Never()).Else(Never()), false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{} + details := treeprint.New() + + actual := testCase.req.IsSatisfied(pr, details) + assert.Equal(t, testCase.isSatisfied, actual, + "requirement should have a satisfied status: %t", testCase.isSatisfied) + assert.True(t, + utils.TestNodeStatus(t, testCase.isSatisfied, details.(*treeprint.Node).Nodes[0]), + "requirement details should have a status: %t", testCase.isSatisfied) + }) + } +} + +type reqFunc func(*github.PullRequest, treeprint.Tree) bool + +func (r reqFunc) IsSatisfied(gh *github.PullRequest, details treeprint.Tree) bool { + return r(gh, details) +} + +func TestIfCond_ConditionalExecution(t *testing.T) { + t.Run("executeThen", func(t *testing.T) { + thenExec, elseExec := 0, 0 + If(Always()). + Then(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + thenExec++ + return true + })). + Else(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + elseExec++ + return true + })).IsSatisfied(nil, treeprint.New()) + assert.Equal(t, 1, thenExec, "Then should be executed 1 time") + assert.Equal(t, 0, elseExec, "Else should be executed 0 time") + }) + t.Run("executeElse", func(t *testing.T) { + thenExec, elseExec := 0, 0 + If(Never()). + Then(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + thenExec++ + return true + })). + Else(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + elseExec++ + return true + })).IsSatisfied(nil, treeprint.New()) + assert.Equal(t, 0, thenExec, "Then should be executed 0 time") + assert.Equal(t, 1, elseExec, "Else should be executed 1 time") + }) +} + +func TestIfCond_NoRepeats(t *testing.T) { + assert.Panics(t, func() { + If(Always()).Then(Always()).Then(Always()) + }, "two Then should panic") + assert.Panics(t, func() { + If(Always()).Else(Always()).Else(Always()) + }, "two Else should panic") +} diff --git a/contribs/github-bot/internal/requirements/draft.go b/contribs/github-bot/internal/requirements/draft.go new file mode 100644 index 00000000000..675ffa02090 --- /dev/null +++ b/contribs/github-bot/internal/requirements/draft.go @@ -0,0 +1,21 @@ +package requirements + +import ( + "github.com/gnolang/gno/contribs/github-bot/internal/utils" + + "github.com/google/go-github/v64/github" + "github.com/xlab/treeprint" +) + +// Draft Condition. +type draft struct{} + +var _ Requirement = &draft{} + +func (*draft) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + return utils.AddStatusNode(pr.GetDraft(), "This pull request is a draft", details) +} + +func Draft() Requirement { + return &draft{} +} diff --git a/contribs/github-bot/internal/requirements/draft_test.go b/contribs/github-bot/internal/requirements/draft_test.go new file mode 100644 index 00000000000..ded4917b808 --- /dev/null +++ b/contribs/github-bot/internal/requirements/draft_test.go @@ -0,0 +1,34 @@ +package requirements + +import ( + "fmt" + "testing" + + "github.com/gnolang/gno/contribs/github-bot/internal/utils" + "github.com/google/go-github/v64/github" + "github.com/stretchr/testify/assert" + "github.com/xlab/treeprint" +) + +func TestDraft(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + isMet bool + }{ + {"draft is true", true}, + {"draft is false", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{Draft: &testCase.isMet} + details := treeprint.New() + req := Draft() + + assert.Equal(t, req.IsSatisfied(pr, details), testCase.isMet, fmt.Sprintf("condition should have a met status: %t", testCase.isMet)) + assert.True(t, utils.TestLastNodeStatus(t, testCase.isMet, details), fmt.Sprintf("condition details should have a status: %t", testCase.isMet)) + }) + } +} diff --git a/contribs/github-bot/internal/requirements/label.go b/contribs/github-bot/internal/requirements/label.go index d1ee475db92..783c7a94ae8 100644 --- a/contribs/github-bot/internal/requirements/label.go +++ b/contribs/github-bot/internal/requirements/label.go @@ -10,10 +10,23 @@ import ( "github.com/xlab/treeprint" ) +// LabelAction controls what to do with the given label. +type LabelAction byte + +const ( + // LabelApply will place the label on the PR if it doesn't exist. + LabelApply = iota + // LabelRemove will remove the label from the PR if it exists. + LabelRemove + // LabelIgnore always leaves the label on the PR as-is, without modifying it. + LabelIgnore +) + // Label Requirement. type label struct { - gh *client.GitHub - name string + gh *client.GitHub + name string + action LabelAction } var _ Requirement = &label{} @@ -21,33 +34,58 @@ var _ Requirement = &label{} func (l *label) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { detail := fmt.Sprintf("This label is applied to pull request: %s", l.name) + found := false // Check if label was already applied to PR. for _, label := range pr.Labels { if l.name == label.GetName() { - return utils.AddStatusNode(true, detail, details) + found = true + break } } - // If in a dry run, skip applying the label. - if l.gh.DryRun { - return utils.AddStatusNode(false, detail, details) + // If in a dry run, or no action expected, skip applying the label. + if l.gh.DryRun || + l.action == LabelIgnore || + (l.action == LabelApply && found) || + (l.action == LabelRemove && !found) { + return utils.AddStatusNode(found, detail, details) } - // If label not already applied, apply it. - if _, _, err := l.gh.Client.Issues.AddLabelsToIssue( - l.gh.Ctx, - l.gh.Owner, - l.gh.Repo, - pr.GetNumber(), - []string{l.name}, - ); err != nil { - l.gh.Logger.Errorf("Unable to add label %s to PR %d: %v", l.name, pr.GetNumber(), err) + switch l.action { + case LabelApply: + // If label not already applied, apply it. + if _, _, err := l.gh.Client.Issues.AddLabelsToIssue( + l.gh.Ctx, + l.gh.Owner, + l.gh.Repo, + pr.GetNumber(), + []string{l.name}, + ); err != nil { + l.gh.Logger.Errorf("Unable to add label %s to PR %d: %v", l.name, pr.GetNumber(), err) + return utils.AddStatusNode(false, detail, details) + } + return utils.AddStatusNode(true, detail, details) + case LabelRemove: + // If label not already applied, apply it. + if _, err := l.gh.Client.Issues.RemoveLabelForIssue( + l.gh.Ctx, + l.gh.Owner, + l.gh.Repo, + pr.GetNumber(), + l.name, + ); err != nil { + l.gh.Logger.Errorf("Unable to remove label %s from PR %d: %v", l.name, pr.GetNumber(), err) + return utils.AddStatusNode(true, detail, details) + } return utils.AddStatusNode(false, detail, details) + default: + panic(fmt.Sprintf("invalid LabelAction value: %d", l.action)) } - - return utils.AddStatusNode(true, detail, details) } -func Label(gh *client.GitHub, name string) Requirement { - return &label{gh, name} +// Label asserts that the label with the given name is not applied to the PR. +// +// If it's not a dry run, the label will be applied to the PR. +func Label(gh *client.GitHub, name string, action LabelAction) Requirement { + return &label{gh, name, action} } diff --git a/contribs/github-bot/internal/requirements/label_test.go b/contribs/github-bot/internal/requirements/label_test.go index 631bff9e64b..e59fa821dc3 100644 --- a/contribs/github-bot/internal/requirements/label_test.go +++ b/contribs/github-bot/internal/requirements/label_test.go @@ -25,11 +25,11 @@ func TestLabel(t *testing.T) { } for _, testCase := range []struct { - name string - pattern string - labels []*github.Label - dryRun bool - exists bool + name string + labelName string + prLabels []*github.Label + dryRun bool + exists bool }{ {"empty label list", "label", []*github.Label{}, false, false}, {"empty label list with dry-run", "user", []*github.Label{}, true, false}, @@ -60,9 +60,9 @@ func TestLabel(t *testing.T) { DryRun: testCase.dryRun, } - pr := &github.PullRequest{Labels: testCase.labels} + pr := &github.PullRequest{Labels: testCase.prLabels} details := treeprint.New() - requirement := Label(gh, testCase.pattern) + requirement := Label(gh, testCase.labelName, LabelApply) assert.True(t, requirement.IsSatisfied(pr, details) || testCase.dryRun, "requirement should have a satisfied status: true") assert.True(t, utils.TestLastNodeStatus(t, true, details) || testCase.dryRun, "requirement details should have a status: true") @@ -70,3 +70,60 @@ func TestLabel(t *testing.T) { }) } } + +func TestLabel_LabelRemove(t *testing.T) { + t.Parallel() + + labels := []*github.Label{ + {Name: github.String("notTheRightOne")}, + {Name: github.String("label")}, + {Name: github.String("anotherOne")}, + } + + for _, testCase := range []struct { + name string + labelName string + prLabels []*github.Label + dryRun bool + shouldRequest bool + result bool + }{ + {"empty label list", "label", []*github.Label{}, false, false, false}, + {"empty label list with dry-run", "label", []*github.Label{}, true, false, false}, + {"label list contains label", "label", labels, false, true, false}, + {"label list contains label with dry-run", "label", labels, true, false, true}, + {"label list doesn't contain label", "label2", labels, false, false, false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + requested := false + mockedHTTPClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/repos/issues/0/labels/label", + Method: "GET", + }, + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + requested = true + }), + ), + ) + + gh := &client.GitHub{ + Client: github.NewClient(mockedHTTPClient), + Ctx: context.Background(), + Logger: logger.NewNoopLogger(), + DryRun: testCase.dryRun, + } + + pr := &github.PullRequest{Labels: testCase.prLabels} + details := treeprint.New() + requirement := Label(gh, testCase.labelName, LabelRemove) + + assert.Equal(t, testCase.result, requirement.IsSatisfied(pr, details), "result of IsSatisfied should match expectation") + assert.True(t, utils.TestLastNodeStatus(t, testCase.result, details), "requirement details should have a status: %t", testCase.result) + assert.Equal(t, testCase.shouldRequest, requested, "IsSatisfied should have requested to delete item") + }) + } +} diff --git a/contribs/github-bot/internal/requirements/reviewer.go b/contribs/github-bot/internal/requirements/reviewer.go index aa3914d4c4a..3c0d4f98510 100644 --- a/contribs/github-bot/internal/requirements/reviewer.go +++ b/contribs/github-bot/internal/requirements/reviewer.go @@ -16,6 +16,8 @@ type reviewByUser struct { user string } +const approvedState = "APPROVED" + var _ Requirement = &reviewByUser{} func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { @@ -65,7 +67,7 @@ func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tre for _, review := range reviews { if review.GetUser().GetLogin() == r.user { r.gh.Logger.Debugf("User %s already reviewed PR %d with state %s", r.user, pr.GetNumber(), review.GetState()) - return utils.AddStatusNode(review.GetState() == "APPROVED", detail, details) + return utils.AddStatusNode(review.GetState() == approvedState, detail, details) } } r.gh.Logger.Debugf("User %s has not reviewed PR %d yet", r.user, pr.GetNumber()) @@ -131,16 +133,16 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr return utils.AddStatusNode(false, detail, details) } - for _, review := range reviews { - teamMembers, err := r.gh.ListTeamMembers(r.team) - if err != nil { - r.gh.Logger.Errorf(err.Error()) - continue - } + teamMembers, err := r.gh.ListTeamMembers(r.team) + if err != nil { + r.gh.Logger.Errorf(err.Error()) + return utils.AddStatusNode(false, detail, details) + } + for _, review := range reviews { for _, member := range teamMembers { if review.GetUser().GetLogin() == member.GetLogin() { - if review.GetState() == "APPROVED" { + if review.GetState() == approvedState { approved += 1 } r.gh.Logger.Debugf("Member %s from team %s already reviewed PR %d with state %s (%d/%d required approval(s))", member.GetLogin(), r.team, pr.GetNumber(), review.GetState(), approved, r.count) @@ -154,3 +156,41 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr func ReviewByTeamMembers(gh *client.GitHub, team string, count uint) Requirement { return &reviewByTeamMembers{gh, team, count} } + +type reviewByOrgMembers struct { + gh *client.GitHub + count uint +} + +var _ Requirement = &reviewByOrgMembers{} + +func (r *reviewByOrgMembers) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("At least %d user(s) of the organization approved the pull request", r.count) + + // Check how many members of this team already approved this PR. + approved := uint(0) + reviews, err := r.gh.ListPRReviews(pr.GetNumber()) + if err != nil { + r.gh.Logger.Errorf("unable to check number of reviews on this PR: %v", err) + return utils.AddStatusNode(false, detail, details) + } + + for _, review := range reviews { + if review.GetAuthorAssociation() == "MEMBER" { + if review.GetState() == approvedState { + approved++ + } + r.gh.Logger.Debugf( + "Member %s already reviewed PR %d with state %s (%d/%d required approval(s))", + review.GetUser().GetLogin(), pr.GetNumber(), review.GetState(), + approved, r.count, + ) + } + } + + return utils.AddStatusNode(approved >= r.count, detail, details) +} + +func ReviewByOrgMembers(gh *client.GitHub, count uint) Requirement { + return &reviewByOrgMembers{gh, count} +} diff --git a/contribs/github-bot/internal/requirements/reviewer_test.go b/contribs/github-bot/internal/requirements/reviewer_test.go index 16c50e13743..98f68384d8f 100644 --- a/contribs/github-bot/internal/requirements/reviewer_test.go +++ b/contribs/github-bot/internal/requirements/reviewer_test.go @@ -213,3 +213,68 @@ func TestReviewByTeamMembers(t *testing.T) { }) } } + +func TestReviewByOrgMembers(t *testing.T) { + t.Parallel() + + reviews := []*github.PullRequestReview{ + { + User: &github.User{Login: github.String("user1")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("MEMBER"), + }, { + User: &github.User{Login: github.String("user2")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("COLLABORATOR"), + }, { + User: &github.User{Login: github.String("user3")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("MEMBER"), + }, { + User: &github.User{Login: github.String("user4")}, + State: github.String("REQUEST_CHANGES"), + AuthorAssociation: github.String("MEMBER"), + }, { + User: &github.User{Login: github.String("user5")}, + State: github.String("REQUEST_CHANGES"), + AuthorAssociation: github.String("NONE"), + }, + } + + for _, testCase := range []struct { + name string + count uint + isSatisfied bool + }{ + {"2/3 org members approved", 3, false}, + {"2/2 org members approved", 2, true}, + {"2/1 org members approved", 1, true}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + mockedHTTPClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchPages( + mock.EndpointPattern{ + Pattern: "/repos/pulls/0/reviews", + Method: "GET", + }, + reviews, + ), + ) + + gh := &client.GitHub{ + Client: github.NewClient(mockedHTTPClient), + Ctx: context.Background(), + Logger: logger.NewNoopLogger(), + } + + pr := &github.PullRequest{} + details := treeprint.New() + requirement := ReviewByOrgMembers(gh, testCase.count) + + assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) + assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) + }) + } +} diff --git a/contribs/github-bot/internal/utils/testing.go b/contribs/github-bot/internal/utils/testing.go index 3c7f7bfef88..839bde831dd 100644 --- a/contribs/github-bot/internal/utils/testing.go +++ b/contribs/github-bot/internal/utils/testing.go @@ -9,8 +9,13 @@ import ( func TestLastNodeStatus(t *testing.T, success bool, details treeprint.Tree) bool { t.Helper() + return TestNodeStatus(t, success, details.FindLastNode()) +} + +func TestNodeStatus(t *testing.T, success bool, details treeprint.Tree) bool { + t.Helper() - detail := details.FindLastNode().(*treeprint.Node).Value.(string) + detail := details.(*treeprint.Node).Value.(string) status := Fail if success { From 1d72733a158a01a5982ffec60f120bcd3b2ef477 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Tue, 21 Jan 2025 22:38:57 +0900 Subject: [PATCH 62/75] fix(gnoweb): titles missing id and toc anchors (#3538) Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoweb/app.go | 58 +++------------ gno.land/pkg/gnoweb/app_test.go | 2 + gno.land/pkg/gnoweb/format.go | 69 ------------------ gno.land/pkg/gnoweb/webclient_html.go | 100 +++++++++++++++++++++----- 4 files changed, 94 insertions(+), 135 deletions(-) delete mode 100644 gno.land/pkg/gnoweb/format.go diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 516d3b92186..455a9aafaf1 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -7,15 +7,9 @@ import ( "path" "strings" - markdown "github.com/yuin/goldmark-highlighting/v2" - - "github.com/alecthomas/chroma/v2" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/styles" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" mdhtml "github.com/yuin/goldmark/renderer/html" ) @@ -55,16 +49,6 @@ func NewDefaultAppConfig() *AppConfig { } } -var chromaDefaultStyle = mustGetStyle("friendly") - -func mustGetStyle(name string) *chroma.Style { - s := styles.Get(name) - if s == nil { - panic("unable to get chroma style") - } - return s -} - // NewRouter initializes the gnoweb router with the specified logger and configuration. func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { // Initialize RPC Client @@ -73,42 +57,18 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { return nil, fmt.Errorf("unable to create HTTP client: %w", err) } - // Configure Chroma highlighter - chromaOptions := []chromahtml.Option{ - chromahtml.WithLineNumbers(true), - chromahtml.WithLinkableLineNumbers(true, "L"), - chromahtml.WithClasses(true), - chromahtml.ClassPrefix("chroma-"), - } - chroma := chromahtml.New(chromaOptions...) - - // Configure Goldmark markdown parser - mdopts := []goldmark.Option{ - goldmark.WithExtensions( - markdown.NewHighlighting( - markdown.WithFormatOptions(chromaOptions...), - ), - extension.Table, - ), - } + // Setup web client HTML + webcfg := NewDefaultHTMLWebClientConfig(client) + webcfg.Domain = cfg.Domain if cfg.UnsafeHTML { - mdopts = append(mdopts, goldmark.WithRendererOptions(mdhtml.WithXHTML(), mdhtml.WithUnsafe())) - } - md := goldmark.New(mdopts...) - - // Configure WebClient - webcfg := HTMLWebClientConfig{ - Markdown: md, - Highlighter: NewChromaSourceHighlighter(chroma, chromaDefaultStyle), - Domain: cfg.Domain, - UnsafeHTML: cfg.UnsafeHTML, - RPCClient: client, + webcfg.GoldmarkOptions = append(webcfg.GoldmarkOptions, goldmark.WithRendererOptions( + mdhtml.WithXHTML(), mdhtml.WithUnsafe(), + )) } - - webcli := NewHTMLClient(logger, &webcfg) - chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") + webcli := NewHTMLClient(logger, webcfg) // Setup StaticMetadata + chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") staticMeta := StaticMetadata{ Domain: cfg.Domain, AssetsPath: cfg.AssetsPath, @@ -146,7 +106,7 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { // XXX: probably move this elsewhere mux.Handle(chromaStylePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css") - if err := chroma.WriteCSS(w, chromaDefaultStyle); err != nil { + if err := webcli.WriteFormatterCSS(w); err != nil { logger.Error("unable to write CSS", "err", err) http.NotFound(w, r) } diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 9f8f87b99b1..6fb69c6d984 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -59,6 +59,8 @@ func TestRoutes(t *testing.T) { {"/public/js/index.js", ok, ""}, {"/public/_chroma/style.css", ok, ""}, {"/public/imgs/gnoland.svg", ok, ""}, + // Test Toc + {"/", ok, `href="#learn-about-gnoland"`}, } rootdir := gnoenv.RootDir() diff --git a/gno.land/pkg/gnoweb/format.go b/gno.land/pkg/gnoweb/format.go deleted file mode 100644 index 67911bfa985..00000000000 --- a/gno.land/pkg/gnoweb/format.go +++ /dev/null @@ -1,69 +0,0 @@ -package gnoweb - -import ( - "fmt" - "io" - "path/filepath" - "strings" - - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/lexers" -) - -// FormatSource defines the interface for formatting source code. -type FormatSource interface { - Format(w io.Writer, fileName string, file []byte) error -} - -// ChromaSourceHighlighter implements the Highlighter interface using the Chroma library. -type ChromaSourceHighlighter struct { - *html.Formatter - style *chroma.Style -} - -// NewChromaSourceHighlighter constructs a new ChromaHighlighter with the given formatter and style. -func NewChromaSourceHighlighter(formatter *html.Formatter, style *chroma.Style) FormatSource { - return &ChromaSourceHighlighter{Formatter: formatter, style: style} -} - -// Format applies syntax highlighting to the source code using Chroma. -func (f *ChromaSourceHighlighter) Format(w io.Writer, fileName string, src []byte) error { - var lexer chroma.Lexer - - // Determine the lexer to be used based on the file extension. - switch strings.ToLower(filepath.Ext(fileName)) { - case ".gno": - lexer = lexers.Get("go") - case ".md": - lexer = lexers.Get("markdown") - case ".mod": - lexer = lexers.Get("gomod") - default: - lexer = lexers.Get("txt") // Unsupported file type, default to plain text. - } - - if lexer == nil { - return fmt.Errorf("unsupported lexer for file %q", fileName) - } - - iterator, err := lexer.Tokenise(nil, string(src)) - if err != nil { - return fmt.Errorf("unable to tokenise %q: %w", fileName, err) - } - - if err := f.Formatter.Format(w, f.style, iterator); err != nil { - return fmt.Errorf("unable to format source file %q: %w", fileName, err) - } - - return nil -} - -// noopFormat is a no-operation highlighter that writes the source code as-is. -type noopFormat struct{} - -// Format writes the source code to the writer without any formatting. -func (f *noopFormat) Format(w io.Writer, fileName string, src []byte) error { - _, err := w.Write(src) - return err -} diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go index ffe2238df98..d856c6f87a0 100644 --- a/gno.land/pkg/gnoweb/webclient_html.go +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -8,52 +8,83 @@ import ( "path/filepath" "strings" + "github.com/alecthomas/chroma/v2" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" + markdown "github.com/yuin/goldmark-highlighting/v2" + "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" ) +var chromaDefaultStyle = styles.Get("friendly") + type HTMLWebClientConfig struct { - Domain string - UnsafeHTML bool - RPCClient *client.RPCClient - Highlighter FormatSource - Markdown goldmark.Markdown + Domain string + RPCClient *client.RPCClient + ChromaStyle *chroma.Style + ChromaHTMLOptions []chromahtml.Option + GoldmarkOptions []goldmark.Option } // NewDefaultHTMLWebClientConfig initializes a WebClientConfig with default settings. // It sets up goldmark Markdown parsing options and default domain and highlighter. func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfig { - mdopts := []goldmark.Option{goldmark.WithParserOptions(parser.WithAutoHeadingID())} + chromaOptions := []chromahtml.Option{ + chromahtml.WithLineNumbers(true), + chromahtml.WithLinkableLineNumbers(true, "L"), + chromahtml.WithClasses(true), + chromahtml.ClassPrefix("chroma-"), + } + + goldmarkOptions := []goldmark.Option{ + goldmark.WithParserOptions(parser.WithAutoHeadingID()), + goldmark.WithExtensions( + markdown.NewHighlighting( + markdown.WithFormatOptions(chromaOptions...), + ), + extension.Table, + ), + } + return &HTMLWebClientConfig{ - Domain: "gno.land", - Highlighter: &noopFormat{}, - Markdown: goldmark.New(mdopts...), - RPCClient: client, + Domain: "gno.land", + GoldmarkOptions: goldmarkOptions, + ChromaHTMLOptions: chromaOptions, + ChromaStyle: chromaDefaultStyle, + RPCClient: client, } } type HTMLWebClient struct { + Markdown goldmark.Markdown + Formatter *chromahtml.Formatter + domain string logger *slog.Logger client *client.RPCClient - md goldmark.Markdown - highlighter FormatSource + chromaStyle *chroma.Style } // NewHTMLClient creates a new instance of WebClient. // It requires a configured logger and WebClientConfig. func NewHTMLClient(log *slog.Logger, cfg *HTMLWebClientConfig) *HTMLWebClient { return &HTMLWebClient{ + // XXX: Possibly consider exporting this in a single interface logic. + // For now it's easier to manager all this in one place + Markdown: goldmark.New(cfg.GoldmarkOptions...), + Formatter: chromahtml.New(cfg.ChromaHTMLOptions...), + logger: log, domain: cfg.Domain, client: cfg.RPCClient, - md: cfg.Markdown, - highlighter: cfg.Highlighter, + chromaStyle: cfg.ChromaStyle, } } @@ -108,7 +139,7 @@ func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMet } // Use Chroma for syntax highlighting - if err := s.highlighter.Format(w, fileName, source); err != nil { + if err := s.FormatSource(w, fileName, source); err != nil { return nil, err } @@ -152,8 +183,8 @@ func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (* } // Use Goldmark for Markdown parsing - doc := s.md.Parser().Parse(text.NewReader(rawres)) - if err := s.md.Renderer().Render(w, rawres, doc); err != nil { + doc := s.Markdown.Parser().Parse(text.NewReader(rawres)) + if err := s.Markdown.Renderer().Render(w, rawres, doc); err != nil { return nil, fmt.Errorf("unable to render realm %q: %w", data, err) } @@ -188,3 +219,38 @@ func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) { return qres.Response.Data, nil } + +func (s *HTMLWebClient) FormatSource(w io.Writer, fileName string, src []byte) error { + var lexer chroma.Lexer + + // Determine the lexer to be used based on the file extension. + switch strings.ToLower(filepath.Ext(fileName)) { + case ".gno": + lexer = lexers.Get("go") + case ".md": + lexer = lexers.Get("markdown") + case ".mod": + lexer = lexers.Get("gomod") + default: + lexer = lexers.Get("txt") // Unsupported file type, default to plain text. + } + + if lexer == nil { + return fmt.Errorf("unsupported lexer for file %q", fileName) + } + + iterator, err := lexer.Tokenise(nil, string(src)) + if err != nil { + return fmt.Errorf("unable to tokenise %q: %w", fileName, err) + } + + if err := s.Formatter.Format(w, s.chromaStyle, iterator); err != nil { + return fmt.Errorf("unable to format source file %q: %w", fileName, err) + } + + return nil +} + +func (s *HTMLWebClient) WriteFormatterCSS(w io.Writer) error { + return s.Formatter.WriteCSS(w, s.chromaStyle) +} From 349ea1f1143b64a7eeb2184beb13570d26bb3bfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:44:52 +0100 Subject: [PATCH 63/75] chore(deps): bump the everything-else group across 1 directory with 18 updates (#3553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the everything-else group with 10 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.14.0` | `2.15.0` | | [github.com/cosmos/ledger-cosmos-go](https://github.com/cosmos/ledger-cosmos-go) | `0.13.3` | `0.14.0` | | [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) | `1.12.0` | `1.13.1` | | [github.com/stretchr/testify](https://github.com/stretchr/testify) | `1.9.0` | `1.10.0` | | [github.com/yuin/goldmark](https://github.com/yuin/goldmark) | `1.7.2` | `1.7.8` | | [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.29.0` | `1.34.0` | | [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.29.0` | `1.34.0` | | [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) | `1.29.0` | `1.34.0` | | [golang.org/x/mod](https://github.com/golang/mod) | `0.20.0` | `0.22.0` | | [golang.org/x/tools](https://github.com/golang/tools) | `0.24.0` | `0.29.0` | Updates `github.com/alecthomas/chroma/v2` from 2.14.0 to 2.15.0
          Release notes

          Sourced from github.com/alecthomas/chroma/v2's releases.

          v2.15.0

          What's Changed

          New Contributors

          ... (truncated)

          Commits

          Updates `github.com/cosmos/ledger-cosmos-go` from 0.13.3 to 0.14.0
          Release notes

          Sourced from github.com/cosmos/ledger-cosmos-go's releases.

          v0.14.0

          What's Changed

          Full Changelog: https://github.com/cosmos/ledger-cosmos-go/compare/v0.13.3...v0.14.0

          Commits

          Updates `github.com/rogpeppe/go-internal` from 1.12.0 to 1.13.1
          Release notes

          Sourced from github.com/rogpeppe/go-internal's releases.

          v1.13.1

          What's Changed

          New Contributors

          Full Changelog: https://github.com/rogpeppe/go-internal/compare/v1.12.0...v1.13.1

          Commits
          • e67a4aa README: update the package list
          • 4794549 take advantage of Go 1.22
          • 5ce929e drop Go 1.21, add Go 1.23
          • 3a65db1 dirhash: forward to golang.org/x/mod/sumdb/dirhash
          • 361e7d2 goproxytest: add test wrapper API
          • ccf4b43 testscript: ignore result when interrupting background processes
          • 66960b6 Fix typos discovered by codespell
          • 5556500 cmd/testscript: do not create an extra temporary directory (#259)
          • b143f3f testscript: add Config.Files (#258)
          • 8300480 cmd/testscript: remove redundant use of Failed (#257)
          • Additional commits viewable in compare view

          Updates `github.com/stretchr/testify` from 1.9.0 to 1.10.0
          Release notes

          Sourced from github.com/stretchr/testify's releases.

          v1.10.0

          What's Changed

          Functional Changes

          Fixes

          Documantation, Build & CI

          New Contributors

          ... (truncated)

          Commits
          • 89cbdd9 Merge pull request #1626 from arjun-1/fix-functional-options-diff-indirect-calls
          • 07bac60 Merge pull request #1667 from sikehish/flaky
          • 716de8d Increase timeouts in Test_Mock_Called_blocks to reduce flakiness in CI
          • 118fb83 NotSame should fail if args are not pointers #1661 (#1664)
          • 7d99b2b attempt 2
          • 05f87c0 more similar
          • ea7129e better fmt
          • a1b9c9e Merge pull request #1663 from ybrustin/master
          • 8302de9 Merge branch 'master' into master
          • 89352f7 Merge pull request #1518 from hendrywiranto/adjust-readme-remove-v2
          • Additional commits viewable in compare view

          Updates `github.com/yuin/goldmark` from 1.7.2 to 1.7.8
          Commits

          Updates `go.opentelemetry.io/otel` from 1.29.0 to 1.34.0
          Changelog

          Sourced from go.opentelemetry.io/otel's changelog.

          [1.34.0/0.56.0/0.10.0] 2025-01-17

          Changed

          • Remove the notices from Logger to make the whole Logs API user-facing in go.opentelemetry.io/otel/log. (#6167)

          Fixed

          • Relax minimum Go version to 1.22.0 in various modules. (#6073)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc client is corrected from otlphttpgrpc to otlptracegrpc. (#6143)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlphttpgrpc client is corrected from otlphttphttp to otlptracehttp. (#6143)

          [1.33.0/0.55.0/0.9.0/0.0.12] 2024-12-12

          Added

          • Add Reset method to SpanRecorder in go.opentelemetry.io/otel/sdk/trace/tracetest. (#5994)
          • Add EnabledInstrument interface in go.opentelemetry.io/otel/sdk/metric/internal/x. This is an experimental interface that is implemented by synchronous instruments provided by go.opentelemetry.io/otel/sdk/metric. Users can use it to avoid performing computationally expensive operations when recording measurements. It does not fall within the scope of the OpenTelemetry Go versioning and stability policy and it may be changed in backwards incompatible ways or removed in feature releases. (#6016)

          Changed

          • The default global API now supports full auto-instrumentation from the go.opentelemetry.io/auto package. See that package for more information. (#5920)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5929)
          • Performance improvements for attribute value AsStringSlice, AsFloat64Slice, AsInt64Slice, AsBoolSlice. (#6011)
          • Change EnabledParameters to have a Severity field instead of a getter and setter in go.opentelemetry.io/otel/log. (#6009)

          Fixed

          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5954)
          • Fix invalid exemplar keys in go.opentelemetry.io/otel/exporters/prometheus. (#5995)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/trace. (#5997)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/log. (#6032)

          [1.32.0/0.54.0/0.8.0/0.0.11] 2024-11-08

          Added

          • Add go.opentelemetry.io/otel/sdk/metric/exemplar.AlwaysOffFilter, which can be used to disable exemplar recording. (#5850)
          • Add go.opentelemetry.io/otel/sdk/metric.WithExemplarFilter, which can be used to configure the exemplar filter used by the metrics SDK. (#5850)
          • Add ExemplarReservoirProviderSelector and DefaultExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric, which defines the exemplar reservoir to use based on the aggregation of the metric. (#5861)
          • Add ExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric.Stream to allow using views to configure the exemplar reservoir to use for a metric. (#5861)
          • Add ReservoirProvider, HistogramReservoirProvider and FixedSizeReservoirProvider to go.opentelemetry.io/otel/sdk/metric/exemplar to make it convenient to use providers of Reservoirs. (#5861)

          ... (truncated)

          Commits
          • edc378f Release v1.34.0/v0.56.0/v0.10.0 (#6174)
          • e18299f log: Make whole Logs API user-facing (#6167)
          • cbc3b6a fix(deps): update module google.golang.org/protobuf to v1.36.3 (#6166)
          • 2dcb9b3 chore(deps): update module github.com/protonmail/go-crypto to v1.1.5 (#6165)
          • 764ae10 fix(deps): update googleapis to 1a7da9e (#6164)
          • f87cced chore(deps): update module github.com/ldez/exptostd to v0.4.0 (#6163)
          • be76ebf chore(deps): update module github.com/crocmagnon/fatcontext to v0.6.0 (#6162)
          • 3e60bd4 fix(deps): update module golang.org/x/vuln to v1.1.4 (#6161)
          • 79b1fc1 Fix demo links (#6160)
          • 4a87cfe fix(deps): update module google.golang.org/grpc to v1.69.4 (#6159)
          • Additional commits viewable in compare view

          Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` from 1.29.0 to 1.34.0
          Changelog

          Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc's changelog.

          [1.34.0/0.56.0/0.10.0] 2025-01-17

          Changed

          • Remove the notices from Logger to make the whole Logs API user-facing in go.opentelemetry.io/otel/log. (#6167)

          Fixed

          • Relax minimum Go version to 1.22.0 in various modules. (#6073)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc client is corrected from otlphttpgrpc to otlptracegrpc. (#6143)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlphttpgrpc client is corrected from otlphttphttp to otlptracehttp. (#6143)

          [1.33.0/0.55.0/0.9.0/0.0.12] 2024-12-12

          Added

          • Add Reset method to SpanRecorder in go.opentelemetry.io/otel/sdk/trace/tracetest. (#5994)
          • Add EnabledInstrument interface in go.opentelemetry.io/otel/sdk/metric/internal/x. This is an experimental interface that is implemented by synchronous instruments provided by go.opentelemetry.io/otel/sdk/metric. Users can use it to avoid performing computationally expensive operations when recording measurements. It does not fall within the scope of the OpenTelemetry Go versioning and stability policy and it may be changed in backwards incompatible ways or removed in feature releases. (#6016)

          Changed

          • The default global API now supports full auto-instrumentation from the go.opentelemetry.io/auto package. See that package for more information. (#5920)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5929)
          • Performance improvements for attribute value AsStringSlice, AsFloat64Slice, AsInt64Slice, AsBoolSlice. (#6011)
          • Change EnabledParameters to have a Severity field instead of a getter and setter in go.opentelemetry.io/otel/log. (#6009)

          Fixed

          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5954)
          • Fix invalid exemplar keys in go.opentelemetry.io/otel/exporters/prometheus. (#5995)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/trace. (#5997)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/log. (#6032)

          [1.32.0/0.54.0/0.8.0/0.0.11] 2024-11-08

          Added

          • Add go.opentelemetry.io/otel/sdk/metric/exemplar.AlwaysOffFilter, which can be used to disable exemplar recording. (#5850)
          • Add go.opentelemetry.io/otel/sdk/metric.WithExemplarFilter, which can be used to configure the exemplar filter used by the metrics SDK. (#5850)
          • Add ExemplarReservoirProviderSelector and DefaultExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric, which defines the exemplar reservoir to use based on the aggregation of the metric. (#5861)
          • Add ExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric.Stream to allow using views to configure the exemplar reservoir to use for a metric. (#5861)
          • Add ReservoirProvider, HistogramReservoirProvider and FixedSizeReservoirProvider to go.opentelemetry.io/otel/sdk/metric/exemplar to make it convenient to use providers of Reservoirs. (#5861)

          ... (truncated)

          Commits
          • edc378f Release v1.34.0/v0.56.0/v0.10.0 (#6174)
          • e18299f log: Make whole Logs API user-facing (#6167)
          • cbc3b6a fix(deps): update module google.golang.org/protobuf to v1.36.3 (#6166)
          • 2dcb9b3 chore(deps): update module github.com/protonmail/go-crypto to v1.1.5 (#6165)
          • 764ae10 fix(deps): update googleapis to 1a7da9e (#6164)
          • f87cced chore(deps): update module github.com/ldez/exptostd to v0.4.0 (#6163)
          • be76ebf chore(deps): update module github.com/crocmagnon/fatcontext to v0.6.0 (#6162)
          • 3e60bd4 fix(deps): update module golang.org/x/vuln to v1.1.4 (#6161)
          • 79b1fc1 Fix demo links (#6160)
          • 4a87cfe fix(deps): update module google.golang.org/grpc to v1.69.4 (#6159)
          • Additional commits viewable in compare view

          Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` from 1.29.0 to 1.34.0
          Changelog

          Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's changelog.

          [1.34.0/0.56.0/0.10.0] 2025-01-17

          Changed

          • Remove the notices from Logger to make the whole Logs API user-facing in go.opentelemetry.io/otel/log. (#6167)

          Fixed

          • Relax minimum Go version to 1.22.0 in various modules. (#6073)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc client is corrected from otlphttpgrpc to otlptracegrpc. (#6143)
          • The Type name logged for the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlphttpgrpc client is corrected from otlphttphttp to otlptracehttp. (#6143)

          [1.33.0/0.55.0/0.9.0/0.0.12] 2024-12-12

          Added

          • Add Reset method to SpanRecorder in go.opentelemetry.io/otel/sdk/trace/tracetest. (#5994)
          • Add EnabledInstrument interface in go.opentelemetry.io/otel/sdk/metric/internal/x. This is an experimental interface that is implemented by synchronous instruments provided by go.opentelemetry.io/otel/sdk/metric. Users can use it to avoid performing computationally expensive operations when recording measurements. It does not fall within the scope of the OpenTelemetry Go versioning and stability policy and it may be changed in backwards incompatible ways or removed in feature releases. (#6016)

          Changed

          • The default global API now supports full auto-instrumentation from the go.opentelemetry.io/auto package. See that package for more information. (#5920)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5929)
          • Propagate non-retryable error messages to client in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5929)
          • Performance improvements for attribute value AsStringSlice, AsFloat64Slice, AsInt64Slice, AsBoolSlice. (#6011)
          • Change EnabledParameters to have a Severity field instead of a getter and setter in go.opentelemetry.io/otel/log. (#6009)

          Fixed

          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#5954)
          • Fix inconsistent request body closing in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#5954)
          • Fix invalid exemplar keys in go.opentelemetry.io/otel/exporters/prometheus. (#5995)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/trace. (#5997)
          • Fix attribute value truncation in go.opentelemetry.io/otel/sdk/log. (#6032)

          [1.32.0/0.54.0/0.8.0/0.0.11] 2024-11-08

          Added

          • Add go.opentelemetry.io/otel/sdk/metric/exemplar.AlwaysOffFilter, which can be used to disable exemplar recording. (#5850)
          • Add go.opentelemetry.io/otel/sdk/metric.WithExemplarFilter, which can be used to configure the exemplar filter used by the metrics SDK. (#5850)
          • Add ExemplarReservoirProviderSelector and DefaultExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric, which defines the exemplar reservoir to use based on the aggregation of the metric. (#5861)
          • Add ExemplarReservoirProviderSelector to go.opentelemetry.io/otel/sdk/metric.Stream to allow using views to configure the exemplar reservoir to use for a metric. (#5861)
          • Add ReservoirProvider, HistogramReservoirProvider and FixedSizeReservoirProvider to go.opentelemetry.io/otel/sdk/metric/exemplar to make it convenient to use providers of Reservoirs. (#5861)

          ... (truncated)

          Commits
          • edc378f Release v1.34.0/v0.56.0/v0.10.0 (#6174)
          • e18299f log: Make whole Logs API user-facing (#6167)
          • cbc3b6a fix(deps): update module google.golang.org/protobuf to v1.36.3 (#6166)
          • 2dcb9b3 chore(deps): update module github.com/protonmail/go-crypto to v1.1.5 (#6165)
          • 764ae10 fix(deps): update googleapis to 1a7da9e (#6164)
          • f87cced chore(deps): update module github.com/ldez/exptostd to v0.4.0 (#6163)
          • be76ebf chore(deps): update module github.com/crocmagnon/fatcontext to... _Description has been truncated_ --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Morgan Bazalgette --- contribs/github-bot/go.mod | 10 ++-- contribs/github-bot/go.sum | 12 ++-- contribs/gnodev/go.mod | 59 +++++++++---------- contribs/gnodev/go.sum | 114 +++++++++++++++++++------------------ contribs/gnofaucet/go.mod | 47 +++++++-------- contribs/gnofaucet/go.sum | 92 ++++++++++++++++-------------- contribs/gnogenesis/go.mod | 49 ++++++++-------- contribs/gnogenesis/go.sum | 94 +++++++++++++++--------------- contribs/gnohealth/go.mod | 43 +++++++------- contribs/gnohealth/go.sum | 92 ++++++++++++++++-------------- contribs/gnokeykc/go.mod | 49 ++++++++-------- contribs/gnokeykc/go.sum | 94 +++++++++++++++--------------- contribs/gnomigrate/go.mod | 47 ++++++++------- contribs/gnomigrate/go.sum | 94 +++++++++++++++--------------- go.mod | 59 +++++++++---------- go.sum | 114 +++++++++++++++++++------------------ misc/autocounterd/go.mod | 49 ++++++++-------- misc/autocounterd/go.sum | 98 +++++++++++++++---------------- misc/docs-linter/go.mod | 12 ++-- misc/docs-linter/go.sum | 16 +++--- misc/loop/go.mod | 47 +++++++-------- misc/loop/go.sum | 94 +++++++++++++++--------------- 22 files changed, 710 insertions(+), 675 deletions(-) diff --git a/contribs/github-bot/go.mod b/contribs/github-bot/go.mod index 8df55e3f282..f8914819d54 100644 --- a/contribs/github-bot/go.mod +++ b/contribs/github-bot/go.mod @@ -1,8 +1,8 @@ module github.com/gnolang/gno/contribs/github-bot -go 1.22 +go 1.22.0 -toolchain go1.22.2 +toolchain go1.23.2 replace github.com/gnolang/gno => ../.. @@ -11,7 +11,7 @@ require ( github.com/google/go-github/v64 v64.0.0 github.com/migueleliasweb/go-github-mock v1.0.1 github.com/sethvargo/go-githubactions v1.3.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/xlab/treeprint v1.2.0 ) @@ -21,8 +21,8 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/time v0.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/github-bot/go.sum b/contribs/github-bot/go.sum index 2dae4e83e72..e55a59cc6eb 100644 --- a/contribs/github-bot/go.sum +++ b/contribs/github-bot/go.sum @@ -20,14 +20,14 @@ github.com/sethvargo/go-githubactions v1.3.0 h1:Kg633LIUV2IrJsqy2MfveiED/Ouo+H2P github.com/sethvargo/go-githubactions v1.3.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 92d8494fa40..a4c106a24ee 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,8 +1,8 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 replace github.com/gnolang/gno => ../.. @@ -21,15 +21,15 @@ require ( github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 github.com/sahilm/fuzzy v0.1.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.23.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.28.0 ) require ( dario.cat/mergo v1.0.1 // indirect - github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/alecthomas/chroma/v2 v2.15.0 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -46,11 +46,11 @@ require ( github.com/charmbracelet/x/termios v0.1.0 // indirect github.com/charmbracelet/x/windows v0.1.2 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/creack/pty v1.1.21 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -59,7 +59,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -74,38 +74,39 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yuin/goldmark v1.7.2 // indirect + github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.24.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index 3f22e4f2f00..e87c2de6441 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -1,11 +1,11 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= +github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -78,8 +78,8 @@ github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelh github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -95,8 +95,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -137,8 +137,8 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -205,8 +205,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -222,16 +222,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= -github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= @@ -242,22 +242,24 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -269,22 +271,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -296,35 +298,35 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 3d1e5f54c54..32d2e322098 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,13 +1,13 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 require ( github.com/gnolang/faucet v0.3.2 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 golang.org/x/time v0.5.0 ) @@ -25,34 +25,35 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 10e2c19b408..5b3cfdc3289 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -63,6 +63,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU 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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -77,8 +79,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -105,8 +107,8 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -120,28 +122,30 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -153,22 +157,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -178,34 +182,34 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 3056af1d4cc..5b28c8774c8 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -1,10 +1,12 @@ module github.com/gnolang/contribs/gnogenesis -go 1.22 +go 1.22.0 + +toolchain go1.22.10 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) replace github.com/gnolang/gno => ../.. @@ -15,7 +17,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -24,7 +26,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -37,26 +39,27 @@ require ( github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index 7e4a683cad1..dcd853e9148 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,8 +129,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -139,43 +139,45 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,33 +187,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod index 4a3f6392804..203dac360b7 100644 --- a/contribs/gnohealth/go.mod +++ b/contribs/gnohealth/go.mod @@ -16,32 +16,33 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/crypto v0.26.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnohealth/go.sum b/contribs/gnohealth/go.sum index 02e8893406a..e51cadf1564 100644 --- a/contribs/gnohealth/go.sum +++ b/contribs/gnohealth/go.sum @@ -57,6 +57,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU 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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -71,8 +73,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -99,8 +101,8 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= @@ -112,47 +114,49 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -162,32 +166,32 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 157b5585828..73e51f6b25e 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,8 +1,8 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 replace github.com/gnolang/gno => ../.. @@ -17,7 +17,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect @@ -28,7 +28,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -36,30 +36,31 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 7aac05b84a0..7a058c85750 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -34,8 +34,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -84,8 +84,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -120,8 +120,8 @@ 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= @@ -135,8 +135,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= @@ -147,43 +147,45 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -193,33 +195,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index 49f40eb79af..83d88c354e7 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -1,10 +1,12 @@ module github.com/gnolang/gnomigrate -go 1.22 +go 1.22.0 + +toolchain go1.22.10 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) replace github.com/gnolang/gno => ../.. @@ -22,7 +24,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -32,26 +34,27 @@ require ( github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index 7e4a683cad1..dcd853e9148 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,8 +129,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -139,43 +139,45 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,33 +187,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/go.mod b/go.mod index cd038e2ae65..ce58b8f7998 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/gnolang/gno -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 require ( dario.cat/mergo v1.0.1 - github.com/alecthomas/chroma/v2 v2.14.0 + github.com/alecthomas/chroma/v2 v2.15.0 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/cockroachdb/apd/v3 v3.2.1 - github.com/cosmos/ledger-cosmos-go v0.13.3 + github.com/cosmos/ledger-cosmos-go v0.14.0 github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/fortytw2/leaktest v1.3.0 @@ -21,53 +21,54 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pmezard/go-difflib v1.0.0 - github.com/rogpeppe/go-internal v1.12.0 + github.com/rogpeppe/go-internal v1.13.1 github.com/rs/cors v1.11.1 github.com/rs/xid v1.6.0 github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/yuin/goldmark v1.7.2 + github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.etcd.io/bbolt v1.3.11 - go.opentelemetry.io/otel v1.29.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 - go.opentelemetry.io/otel/metric v1.29.0 - go.opentelemetry.io/otel/sdk v1.29.0 - go.opentelemetry.io/otel/sdk/metric v1.29.0 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.3.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.32.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.20.0 - golang.org/x/net v0.28.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.23.0 - golang.org/x/tools v0.24.0 - google.golang.org/protobuf v1.35.1 + golang.org/x/mod v0.22.0 + golang.org/x/net v0.34.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.28.0 + golang.org/x/tools v0.29.0 + google.golang.org/protobuf v1.36.3 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9c4d20dbad6..046d9c8c75a 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= +github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -40,8 +40,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -55,8 +55,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -91,8 +91,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -129,8 +129,8 @@ 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -144,13 +144,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= -github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -159,22 +159,24 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -186,22 +188,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -212,35 +214,35 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 82e8b0081ce..730a3d901b7 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,8 +1,8 @@ module autocounterd -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 require github.com/gnolang/gno v0.0.0-00010101000000-000000000000 @@ -11,7 +11,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -20,38 +20,39 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // 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/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index bd88dd5d08c..3d0eae7661b 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,8 +129,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -139,43 +139,45 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,35 +187,35 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index e27b82ef2f5..2951bcbef54 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,13 +1,13 @@ module linter -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.23.2 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.8.0 + github.com/stretchr/testify v1.10.0 + golang.org/x/sync v0.10.0 mvdan.cc/xurls/v2 v2.5.0 ) @@ -17,7 +17,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/misc/docs-linter/go.sum b/misc/docs-linter/go.sum index 4957bd0cc88..02d08e952f0 100644 --- a/misc/docs-linter/go.sum +++ b/misc/docs-linter/go.sum @@ -4,14 +4,14 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index fc2c5daac59..669a431e3b3 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,8 +1,8 @@ module loop -go 1.22 +go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.10 require ( github.com/docker/docker v24.0.7+incompatible @@ -36,7 +36,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/moby/term v0.5.0 // indirect @@ -53,31 +53,32 @@ require ( github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 1ed786fb82d..cdaee678e71 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -40,8 +40,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -100,8 +100,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -156,8 +156,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -173,8 +173,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -185,36 +185,38 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.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-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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -223,15 +225,15 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL 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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -243,15 +245,15 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w 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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -263,20 +265,20 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From f0223bee0747f9d12990421c1945e3cafcb3987b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:04:07 +0100 Subject: [PATCH 64/75] chore(deps): bump the go_modules group across 2 directories with 2 updates (#3571) Bumps the go_modules group with 1 update in the /contribs/gnomd directory: [golang.org/x/net](https://github.com/golang/net). Bumps the go_modules group with 1 update in the /misc/loop directory: [github.com/docker/docker](https://github.com/docker/docker). Updates `golang.org/x/net` from 0.23.0 to 0.33.0
            Commits
            • dfc720d go.mod: update golang.org/x dependencies
            • 8e66b04 html: use strings.EqualFold instead of lowering ourselves
            • b935f7b html: avoid endless loop on error token
            • 9af49ef route: remove unused sizeof* consts
            • 6705db9 quic: clean up crypto streams when dropping packet protection keys
            • 4ef7588 quic: handle ACK frame in packet which drops number space
            • 552d8ac Revert "route: change from syscall to x/sys/unix"
            • 13a7c01 Revert "route: remove unused sizeof* consts on freebsd"
            • 285e1cf go.mod: update golang.org/x dependencies
            • d0a1049 route: remove unused sizeof* consts on freebsd
            • Additional commits viewable in compare view

            Updates `github.com/docker/docker` from 24.0.7+incompatible to 25.0.6+incompatible
            Release notes

            Sourced from github.com/docker/docker's releases.

            v25.0.6

            25.0.6

            For a full list of pull requests and changes in this release, refer to the relevant GitHub milestones:

            Security

            This release contains a fix for CVE-2024-41110 / GHSA-v23v-6jw2-98fq that impacted setups using authorization plugins (AuthZ) for access control.

            Bug fixes and enhancements

            • [25.0] remove erroneous platform from image config OCI descriptor in docker save output. moby/moby#47695
            • [25.0 backport] Fix a nil dereference when getting image history for images having layers without the Created value set. moby/moby#47759
            • [25.0 backport] apparmor: Allow confined runc to kill containers. moby/moby#47830
            • [25.0 backport] Fix an issue where rapidly promoting a Swarm node after another node was demoted could cause the promoted node to fail its promotion. moby/moby#47869
            • [25.0 backport] don't depend on containerd platform.Parse to return a typed error. moby/moby#47890
            • [25.0 backport] builder/mobyexporter: Add missing nil check moby/moby#47987

            Packaging updates

            Full Changelog: https://github.com/moby/moby/compare/v25.0.5...v25.0.6

            v25.0.5

            25.0.5

            For a full list of pull requests and changes in this release, refer to the relevant GitHub milestones:

            Security

            This release contains a security fix for CVE-2024-29018, a potential data exfiltration from 'internal' networks via authoritative DNS servers.

            Bug fixes and enhancements

            • CVE-2024-29018: Do not forward requests to external DNS servers for a container that is only connected to an 'internal' network. Previously, requests were forwarded if the host's DNS server was running on a loopback address, like systemd's 127.0.0.53. moby/moby#47589

            • plugin: fix mounting /etc/hosts when running in UserNS. moby/moby#47588

            • rootless: fix open /etc/docker/plugins: permission denied. moby/moby#47587

            • Fix multiple parallel docker build runs leaking disk space. moby/moby#47527

            ... (truncated)

            Commits
            • b08a51f Merge pull request #48231 from austinvazquez/backport-vendor-otel-v0.46.1-to-...
            • d151b0f vendor: OTEL v0.46.1 / v1.21.0
            • c6ba9a5 Merge pull request #48225 from austinvazquez/backport-workflow-artifact-reten...
            • 4673a3c Merge pull request #48227 from austinvazquez/backport-backport-branch-check-t...
            • 30f8908 github/ci: Check if backport is opened against the expected branch
            • 7454d6a ci: update workflow artifacts retention
            • 65cc597 Merge commit from fork
            • b722836 Merge pull request #48199 from austinvazquez/update-containerd-binary-to-1.7.20
            • e8ecb9c update containerd binary to v1.7.20
            • e6cae1f update containerd binary to v1.7.19
            • Additional commits viewable in compare view

            Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/gnolang/gno/network/alerts).
            --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Morgan Bazalgette --- contribs/gnomd/go.mod | 4 ++-- contribs/gnomd/go.sum | 8 ++++---- misc/loop/go.mod | 7 +++++-- misc/loop/go.sum | 16 ++++++++++++---- misc/stdlib_diff/go.mod | 4 +++- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 57c07621324..511af13886d 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -22,6 +22,6 @@ require ( github.com/mattn/go-runewidth v0.0.12 // indirect github.com/rivo/uniseg v0.1.0 // indirect golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/contribs/gnomd/go.sum b/contribs/gnomd/go.sum index 3d4666530b1..94a0e4048bf 100644 --- a/contribs/gnomd/go.sum +++ b/contribs/gnomd/go.sum @@ -59,14 +59,14 @@ golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 669a431e3b3..c72101c7c1e 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -5,7 +5,7 @@ go 1.22.0 toolchain go1.22.10 require ( - github.com/docker/docker v24.0.7+incompatible + github.com/docker/docker v25.0.6+incompatible github.com/docker/go-connections v0.4.0 github.com/gnolang/gno v0.1.0-nightly.20240627 github.com/gnolang/tx-archive v0.4.2 @@ -24,11 +24,12 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -57,9 +58,11 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index cdaee678e71..634dbdac082 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -40,6 +40,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -55,14 +57,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= +github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -187,12 +189,18 @@ go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= diff --git a/misc/stdlib_diff/go.mod b/misc/stdlib_diff/go.mod index 4e200f56ebb..f2ddffba2b2 100644 --- a/misc/stdlib_diff/go.mod +++ b/misc/stdlib_diff/go.mod @@ -1,5 +1,7 @@ module github.com/gnolang/gno/misc/stdlib_diff -go 1.21.0 +go 1.22.0 + +toolchain go1.22.10 require github.com/hexops/gotextdiff v1.0.3 From 92c41eb8ebb027a8a32cbcd7dfd95155345dac60 Mon Sep 17 00:00:00 2001 From: Nathan Toups <612924+n2p5@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:10:33 -0700 Subject: [PATCH 65/75] fix(contribs/gnomd): patch dependencies, add linter, remove ioutils (#3575) This is a PR primarily focused on updating direct and transitive dependencies to address security patching for this tool, while this doesn't currently include tests, I did add `make lint` in the same pattern used by other contribs. I also used the opportunity to remove `ioutil` in favor of the more modern `io` and `os` functions that are supported in go stdLib moving forward. --- contribs/gnomd/Makefile | 7 +++++- contribs/gnomd/go.mod | 27 +++++++++++----------- contribs/gnomd/go.sum | 50 ++++++++++++++++++++++++----------------- contribs/gnomd/main.go | 6 ++--- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/contribs/gnomd/Makefile b/contribs/gnomd/Makefile index f953631b529..984c809502a 100644 --- a/contribs/gnomd/Makefile +++ b/contribs/gnomd/Makefile @@ -1,3 +1,8 @@ +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + + +.PHONY: install test lint install: go install . @@ -5,4 +10,4 @@ test: @echo "XXX: add tests" lint: - @echo "XXX: add lint" + $(golangci_lint) --config ../../.github/golangci.yml run ./... diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 511af13886d..d8f01bf82e3 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -8,20 +8,19 @@ require github.com/MichaelMure/go-term-markdown v0.1.4 require ( github.com/MichaelMure/go-term-text v0.3.1 // indirect - github.com/alecthomas/chroma v0.7.1 // indirect - github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/disintegration/imaging v1.6.2 // indirect - github.com/dlclark/regexp2 v1.1.6 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 // indirect - github.com/fatih/color v1.9.0 // indirect - github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 // indirect - github.com/kyokomi/emoji/v2 v2.2.8 // indirect - github.com/lucasb-eyer/go-colorful v1.0.3 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.11 // indirect - github.com/mattn/go-runewidth v0.0.12 // indirect - github.com/rivo/uniseg v0.1.0 // indirect - golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 // indirect + github.com/kyokomi/emoji/v2 v2.2.13 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/image v0.23.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/contribs/gnomd/go.sum b/contribs/gnomd/go.sum index 94a0e4048bf..cfa2672ae06 100644 --- a/contribs/gnomd/go.sum +++ b/contribs/gnomd/go.sum @@ -2,50 +2,57 @@ github.com/MichaelMure/go-term-markdown v0.1.4 h1:Ir3kBXDUtOX7dEv0EaQV8CNPpH+T7A github.com/MichaelMure/go-term-markdown v0.1.4/go.mod h1:EhcA3+pKYnlUsxYKBJ5Sn1cTQmmBMjeNlpV8nRb+JxA= github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0= github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ= github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 h1:vbix8DDQ/rfatfFr/8cf/sJfIL69i4BcZfjrVOxsMqk= github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75/go.mod h1:0gZuvTO1ikSA5LtTI6E13LEOdWQNjIo5MTQOvrV0eFg= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 h1:Qxs3bNRWe8GTcKMxYOSXm0jx6j0de8XUtb/fsP3GZ0I= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= -github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= +github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g= +github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U= +github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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= @@ -56,17 +63,18 @@ golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90te golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/contribs/gnomd/main.go b/contribs/gnomd/main.go index 2d3b7266af5..85dc67547f3 100644 --- a/contribs/gnomd/main.go +++ b/contribs/gnomd/main.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "os" markdown "github.com/MichaelMure/go-term-markdown" @@ -11,14 +11,14 @@ import ( func main() { // If no arguments are provided, read from stdin if len(os.Args) <= 1 { - fileContent, err := ioutil.ReadAll(os.Stdin) + fileContent, err := io.ReadAll(os.Stdin) checkErr(err) renderMarkdown("stdin.gno", fileContent) } // Iterate through command-line arguments (file paths) for _, filePath := range os.Args[1:] { - fileContent, err := ioutil.ReadFile(filePath) + fileContent, err := os.ReadFile(filePath) checkErr(err) renderMarkdown(filePath, fileContent) } From 2ec6a15fe8d3af992f27b8812ad4f39cfe2df42c Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Thu, 23 Jan 2025 02:10:31 +0900 Subject: [PATCH 66/75] refactor(gnoweb): improve layouts and reduce code duplication (#3569) This PR aims to improve the HTML composability of gnoweb and avoid DRY issues across templates and layouts. The changes include: - Refactoring HTML components for better reusability. - Reducing code duplication in views. - Removing static data from the layout - Enhancing code maintainability and readability. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoweb/Makefile | 4 +- gno.land/pkg/gnoweb/alias.go | 4 +- gno.land/pkg/gnoweb/app.go | 4 +- .../pkg/gnoweb/components/breadcrumb.gohtml | 18 -- gno.land/pkg/gnoweb/components/component.go | 35 ++++ gno.land/pkg/gnoweb/components/directory.go | 15 -- gno.land/pkg/gnoweb/components/help.go | 51 ------ gno.land/pkg/gnoweb/components/help.gohtml | 110 ------------ gno.land/pkg/gnoweb/components/index.go | 47 ------ gno.land/pkg/gnoweb/components/index.gohtml | 159 ------------------ .../pkg/gnoweb/components/layout_footer.go | 50 ++++++ .../pkg/gnoweb/components/layout_header.go | 60 +++++++ .../pkg/gnoweb/components/layout_index.go | 67 ++++++++ .../gnoweb/components/layouts/analytics.html | 7 + .../gnoweb/components/layouts/article.html | 3 + .../pkg/gnoweb/components/layouts/aside.html | 10 ++ .../pkg/gnoweb/components/layouts/footer.html | 23 +++ .../pkg/gnoweb/components/layouts/head.html | 44 +++++ .../pkg/gnoweb/components/layouts/header.html | 24 +++ .../pkg/gnoweb/components/layouts/index.html | 29 ++++ gno.land/pkg/gnoweb/components/logosvg.gohtml | 21 --- gno.land/pkg/gnoweb/components/realm.go | 32 ---- gno.land/pkg/gnoweb/components/realm.gohtml | 41 ----- gno.land/pkg/gnoweb/components/redirect.go | 12 -- gno.land/pkg/gnoweb/components/source.go | 20 --- gno.land/pkg/gnoweb/components/source.gohtml | 57 ------- .../pkg/gnoweb/components/spritesvg.gohtml | 125 -------------- gno.land/pkg/gnoweb/components/status.gohtml | 12 -- gno.land/pkg/gnoweb/components/template.go | 75 +++------ .../pkg/gnoweb/components/ui/breadcrumb.html | 19 +++ .../pkg/gnoweb/components/ui/btn_copy.html | 6 + .../gnoweb/components/ui/code_wrapper.html | 3 + .../gnoweb/components/ui/expend_label.html | 14 ++ .../pkg/gnoweb/components/ui/header_link.html | 14 ++ .../gnoweb/components/ui/help_function.html | 43 +++++ gno.land/pkg/gnoweb/components/ui/icons.html | 124 ++++++++++++++ gno.land/pkg/gnoweb/components/ui/logo.html | 51 ++++++ .../pkg/gnoweb/components/ui/toc_generic.html | 14 ++ .../pkg/gnoweb/components/ui/toc_realm.html | 10 ++ .../{breadcrumb.go => ui_breadcrumb.go} | 0 gno.land/pkg/gnoweb/components/view.go | 32 ++++ .../pkg/gnoweb/components/view_directory.go | 13 ++ gno.land/pkg/gnoweb/components/view_help.go | 83 +++++++++ gno.land/pkg/gnoweb/components/view_realm.go | 38 +++++ .../pkg/gnoweb/components/view_redirect.go | 12 ++ gno.land/pkg/gnoweb/components/view_source.go | 66 ++++++++ gno.land/pkg/gnoweb/components/view_status.go | 11 ++ .../directory.html} | 6 - .../pkg/gnoweb/components/views/help.html | 38 +++++ .../pkg/gnoweb/components/views/realm.html | 8 + .../{redirect.gohtml => views/redirect.html} | 4 +- .../pkg/gnoweb/components/views/source.html | 26 +++ .../pkg/gnoweb/components/views/status.html | 8 + gno.land/pkg/gnoweb/frontend/css/input.css | 152 +++++++++-------- gno.land/pkg/gnoweb/frontend/css/tx.config.js | 3 +- gno.land/pkg/gnoweb/frontend/js/copy.ts | 2 +- gno.land/pkg/gnoweb/frontend/js/index.ts | 6 +- gno.land/pkg/gnoweb/frontend/js/realmhelp.ts | 2 +- gno.land/pkg/gnoweb/frontend/js/searchbar.ts | 2 +- gno.land/pkg/gnoweb/handler.go | 144 ++++++---------- gno.land/pkg/gnoweb/public/js/copy.js | 2 +- gno.land/pkg/gnoweb/public/js/index.js | 2 +- gno.land/pkg/gnoweb/public/js/realmhelp.js | 2 +- gno.land/pkg/gnoweb/public/js/searchbar.js | 2 +- gno.land/pkg/gnoweb/public/styles.css | 2 +- 65 files changed, 1171 insertions(+), 952 deletions(-) delete mode 100644 gno.land/pkg/gnoweb/components/breadcrumb.gohtml create mode 100644 gno.land/pkg/gnoweb/components/component.go delete mode 100644 gno.land/pkg/gnoweb/components/directory.go delete mode 100644 gno.land/pkg/gnoweb/components/help.go delete mode 100644 gno.land/pkg/gnoweb/components/help.gohtml delete mode 100644 gno.land/pkg/gnoweb/components/index.go delete mode 100644 gno.land/pkg/gnoweb/components/index.gohtml create mode 100644 gno.land/pkg/gnoweb/components/layout_footer.go create mode 100644 gno.land/pkg/gnoweb/components/layout_header.go create mode 100644 gno.land/pkg/gnoweb/components/layout_index.go create mode 100644 gno.land/pkg/gnoweb/components/layouts/analytics.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/article.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/aside.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/footer.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/head.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/header.html create mode 100644 gno.land/pkg/gnoweb/components/layouts/index.html delete mode 100644 gno.land/pkg/gnoweb/components/logosvg.gohtml delete mode 100644 gno.land/pkg/gnoweb/components/realm.go delete mode 100644 gno.land/pkg/gnoweb/components/realm.gohtml delete mode 100644 gno.land/pkg/gnoweb/components/redirect.go delete mode 100644 gno.land/pkg/gnoweb/components/source.go delete mode 100644 gno.land/pkg/gnoweb/components/source.gohtml delete mode 100644 gno.land/pkg/gnoweb/components/spritesvg.gohtml delete mode 100644 gno.land/pkg/gnoweb/components/status.gohtml create mode 100644 gno.land/pkg/gnoweb/components/ui/breadcrumb.html create mode 100644 gno.land/pkg/gnoweb/components/ui/btn_copy.html create mode 100644 gno.land/pkg/gnoweb/components/ui/code_wrapper.html create mode 100644 gno.land/pkg/gnoweb/components/ui/expend_label.html create mode 100644 gno.land/pkg/gnoweb/components/ui/header_link.html create mode 100644 gno.land/pkg/gnoweb/components/ui/help_function.html create mode 100644 gno.land/pkg/gnoweb/components/ui/icons.html create mode 100644 gno.land/pkg/gnoweb/components/ui/logo.html create mode 100644 gno.land/pkg/gnoweb/components/ui/toc_generic.html create mode 100644 gno.land/pkg/gnoweb/components/ui/toc_realm.html rename gno.land/pkg/gnoweb/components/{breadcrumb.go => ui_breadcrumb.go} (100%) create mode 100644 gno.land/pkg/gnoweb/components/view.go create mode 100644 gno.land/pkg/gnoweb/components/view_directory.go create mode 100644 gno.land/pkg/gnoweb/components/view_help.go create mode 100644 gno.land/pkg/gnoweb/components/view_realm.go create mode 100644 gno.land/pkg/gnoweb/components/view_redirect.go create mode 100644 gno.land/pkg/gnoweb/components/view_source.go create mode 100644 gno.land/pkg/gnoweb/components/view_status.go rename gno.land/pkg/gnoweb/components/{directory.gohtml => views/directory.html} (85%) create mode 100644 gno.land/pkg/gnoweb/components/views/help.html create mode 100644 gno.land/pkg/gnoweb/components/views/realm.html rename gno.land/pkg/gnoweb/components/{redirect.gohtml => views/redirect.html} (79%) create mode 100644 gno.land/pkg/gnoweb/components/views/source.html create mode 100644 gno.land/pkg/gnoweb/components/views/status.html diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 8e8b6bf1a2c..c8d662ec3b5 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -13,7 +13,7 @@ input_css := frontend/css/input.css output_css := $(PUBLIC_DIR)/styles.css tw_version := 3.4.14 tw_config_path := frontend/css/tx.config.js -templates_files := $(shell find . -iname '*.gohtml') +templates_files := $(shell find . -iname '*.html') # static config src_dir_static := frontend/static @@ -79,7 +79,7 @@ dev: # Go server in development mode dev.gnoweb: generate - $(run_reflex) -s -r '.*\.go(html)?' -- \ + $(run_reflex) -s -r '.*\.(go|html)' -- \ go run ../../cmd/gnoweb -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ 2>&1 | $(run_logname) gnoweb diff --git a/gno.land/pkg/gnoweb/alias.go b/gno.land/pkg/gnoweb/alias.go index 06bb3941e41..a837a2dcb49 100644 --- a/gno.land/pkg/gnoweb/alias.go +++ b/gno.land/pkg/gnoweb/alias.go @@ -39,10 +39,10 @@ func AliasAndRedirectMiddleware(next http.Handler, analytics bool) http.Handler // Check if the request path matches a redirect if newPath, ok := Redirects[r.URL.Path]; ok { http.Redirect(w, r, newPath, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: newPath, WithAnalytics: analytics, - }) + }).Render(w) return } diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 455a9aafaf1..61773ef39af 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -95,10 +95,10 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { if cfg.FaucetURL != "" { mux.Handle("/faucet", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, cfg.FaucetURL, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: cfg.FaucetURL, WithAnalytics: cfg.Analytics, - }) + }).Render(w) })) } diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml deleted file mode 100644 index 3824eb5894f..00000000000 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ /dev/null @@ -1,18 +0,0 @@ -{{ define "breadcrumb" }} -
              - {{- range $index, $part := .Parts }} - {{- if $index }} -
            1. - {{- else }} -
            2. - {{- end }} - {{ $part.Name }} -
            3. - {{- end }} - {{- if .Args }} -
            4. - {{ .Args }} -
            5. - {{- end }} -
            -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/component.go b/gno.land/pkg/gnoweb/components/component.go new file mode 100644 index 00000000000..7a7c8a3d160 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/component.go @@ -0,0 +1,35 @@ +package components + +import ( + "io" +) + +type Component interface { + Render(w io.Writer) error +} + +type TemplateComponent struct { + name string + data any +} + +func (c *TemplateComponent) Render(w io.Writer) error { + return tmpl.ExecuteTemplate(w, c.name, c.data) +} + +func NewTemplateComponent(name string, data any) Component { + return &TemplateComponent{name: name, data: data} +} + +type readerComponent struct { + io.Reader +} + +func NewReaderComponent(reader io.Reader) Component { + return &readerComponent{reader} +} + +func (c *readerComponent) Render(w io.Writer) (err error) { + _, err = io.Copy(w, c) + return err +} diff --git a/gno.land/pkg/gnoweb/components/directory.go b/gno.land/pkg/gnoweb/components/directory.go deleted file mode 100644 index 6e47db3b2c4..00000000000 --- a/gno.land/pkg/gnoweb/components/directory.go +++ /dev/null @@ -1,15 +0,0 @@ -package components - -import ( - "io" -) - -type DirData struct { - PkgPath string - Files []string - FileCounter int -} - -func RenderDirectoryComponent(w io.Writer, data DirData) error { - return tmpl.ExecuteTemplate(w, "renderDir", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.go b/gno.land/pkg/gnoweb/components/help.go deleted file mode 100644 index e819705006b..00000000000 --- a/gno.land/pkg/gnoweb/components/help.go +++ /dev/null @@ -1,51 +0,0 @@ -package components - -import ( - "html/template" - "io" - "strings" - - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types -) - -type HelpData struct { - // Selected function - SelectedFunc string - SelectedArgs map[string]string - - RealmName string - Functions []vm.FunctionSignature - ChainId string - Remote string - PkgPath string -} - -func registerHelpFuncs(funcs template.FuncMap) { - funcs["helpFuncSignature"] = func(fsig vm.FunctionSignature) (string, error) { - var fsigStr strings.Builder - - fsigStr.WriteString(fsig.FuncName) - fsigStr.WriteRune('(') - for i, param := range fsig.Params { - if i > 0 { - fsigStr.WriteString(", ") - } - fsigStr.WriteString(param.Name) - } - fsigStr.WriteRune(')') - - return fsigStr.String(), nil - } - - funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { - if data.SelectedArgs == nil { - return "", nil - } - - return data.SelectedArgs[param.Name], nil - } -} - -func RenderHelpComponent(w io.Writer, data HelpData) error { - return tmpl.ExecuteTemplate(w, "renderHelp", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.gohtml b/gno.land/pkg/gnoweb/components/help.gohtml deleted file mode 100644 index 535cb56e9d6..00000000000 --- a/gno.land/pkg/gnoweb/components/help.gohtml +++ /dev/null @@ -1,110 +0,0 @@ -{{ define "renderHelp" }} - {{ $data := . }} -
            -
            -
            -
            -

            {{ .RealmName }}

            -
            -
            -
            - - - - -
            -
            - - -
            -
            -
            - -
            - - {{ range .Functions }} -
            -

            {{ .FuncName }}

            -
            -
            -

            Params

            -
            - {{ $funcName := .FuncName }} - {{ range .Params }} -
            -
            - - -
            -
            - {{ end }} -
            -
            -
            -
            -

            Command

            -
            - -
            gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESS
            -
            -
            -
            - {{ end }} - -
            -
            -
            -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/index.go b/gno.land/pkg/gnoweb/components/index.go deleted file mode 100644 index 0cc020ae261..00000000000 --- a/gno.land/pkg/gnoweb/components/index.go +++ /dev/null @@ -1,47 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - "net/url" -) - -type HeadData struct { - Title string - Description string - Canonical string - Image string - URL string - ChromaPath string - AssetsPath string - Analytics bool -} - -type HeaderData struct { - RealmPath string - Breadcrumb BreadcrumbData - WebQuery url.Values -} - -type FooterData struct { - Analytics bool - AssetsPath string -} - -type IndexData struct { - HeadData - HeaderData - FooterData - Body template.HTML -} - -func IndexComponent(data IndexData) Component { - return func(ctx context.Context, tmpl *template.Template, w io.Writer) error { - return tmpl.ExecuteTemplate(w, "index", data) - } -} - -func RenderIndexComponent(w io.Writer, data IndexData) error { - return tmpl.ExecuteTemplate(w, "index", data) -} diff --git a/gno.land/pkg/gnoweb/components/index.gohtml b/gno.land/pkg/gnoweb/components/index.gohtml deleted file mode 100644 index a87decc14bf..00000000000 --- a/gno.land/pkg/gnoweb/components/index.gohtml +++ /dev/null @@ -1,159 +0,0 @@ -{{ define "index" }} - - - {{ template "head" .HeadData }} - - {{ template "spritesvg" }} - - - {{ template "header" .HeaderData }} - - - {{ template "main" .Body }} - - - {{ template "footer" .FooterData }} - - -{{ end }} - -{{ define "head" }} - - - - {{ .Title }} - - - - - - - - - - - {{ if .Canonical }} - - {{ end }} - - - - - - - - - - - - - - - - - - - - - - - - - - -{{ end }} - -{{ define "header" }} -
            - -
            -{{ end }} - -{{ define "main" }} - {{ . }} -{{ end }} - -{{ define "footer" }} - - -{{- if .Analytics -}} {{- template "analytics" }} {{- end -}} - -{{- end }} - -{{- define "analytics" -}} - - - -{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layout_footer.go b/gno.land/pkg/gnoweb/components/layout_footer.go new file mode 100644 index 00000000000..05c83ba130b --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_footer.go @@ -0,0 +1,50 @@ +package components + +type FooterData struct { + Analytics bool + AssetsPath string + Sections []FooterSection +} + +type FooterLink struct { + Label string + URL string +} + +type FooterSection struct { + Title string + Links []FooterLink +} + +func EnrichFooterData(data FooterData) FooterData { + data.Sections = []FooterSection{ + { + Title: "Footer navigation", + Links: []FooterLink{ + {Label: "About", URL: "/about"}, + {Label: "Docs", URL: "https://docs.gno.land/"}, + {Label: "Faucet", URL: "https://faucet.gno.land/"}, + {Label: "Blog", URL: "https://gno.land/r/gnoland/blog"}, + {Label: "Status", URL: "https://status.gnoteam.com/"}, + }, + }, + { + Title: "Social media", + Links: []FooterLink{ + {Label: "GitHub", URL: "https://github.com/gnolang/gno"}, + {Label: "X", URL: "https://twitter.com/_gnoland"}, + {Label: "Discord", URL: "https://discord.gg/S8nKUqwkPn"}, + {Label: "YouTube", URL: "https://www.youtube.com/@_gnoland"}, + }, + }, + { + Title: "Legal", + Links: []FooterLink{ + {Label: "Terms", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + {Label: "Privacy", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + }, + }, + } + + return data +} diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go new file mode 100644 index 00000000000..b85efde5f85 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_header.go @@ -0,0 +1,60 @@ +package components + +import ( + "net/url" +) + +type HeaderLink struct { + Label string + URL string + Icon string + IsActive bool +} + +type HeaderData struct { + RealmPath string + Breadcrumb BreadcrumbData + WebQuery url.Values + Links []HeaderLink +} + +func StaticHeaderLinks(realmPath string, webQuery url.Values) []HeaderLink { + return []HeaderLink{ + { + Label: "Content", + URL: realmPath, + Icon: "ico-info", + IsActive: isActive(webQuery, "Content"), + }, + { + Label: "Source", + URL: realmPath + "$source", + Icon: "ico-code", + IsActive: isActive(webQuery, "Source"), + }, + { + Label: "Docs", + URL: realmPath + "$help", + Icon: "ico-docs", + IsActive: isActive(webQuery, "Docs"), + }, + } +} + +func EnrichHeaderData(data HeaderData) HeaderData { + data.Links = StaticHeaderLinks(data.RealmPath, data.WebQuery) + return data +} + +func isActive(webQuery url.Values, label string) bool { + switch label { + case "Content": + return !(webQuery.Has("source") || webQuery.Has("help")) + case "Source": + return webQuery.Has("source") + case "Docs": + return webQuery.Has("help") + default: + return false + } +} diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go new file mode 100644 index 00000000000..8b49e8f8ada --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_index.go @@ -0,0 +1,67 @@ +package components + +// Layout +const ( + SidebarLayout = "sidebar" + FullLayout = "full" +) + +type HeadData struct { + Title string + Description string + Canonical string + Image string + URL string + ChromaPath string + AssetsPath string + Analytics bool +} + +type IndexData struct { + HeadData + HeaderData + FooterData + BodyView *View +} + +type indexLayoutParams struct { + IndexData + + // Additional data + IsDevmodView bool + Layout string + ViewType string +} + +func IndexLayout(data IndexData) Component { + data.FooterData = EnrichFooterData(data.FooterData) + data.HeaderData = EnrichHeaderData(data.HeaderData) + + dataLayout := indexLayoutParams{ + IndexData: data, + // Set default value + Layout: FullLayout, + ViewType: data.BodyView.String(), + } + + switch data.BodyView.Type { + case RealmViewType: + dataLayout.Layout = SidebarLayout + + case HelpViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case SourceViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case DirectoryViewType: + dataLayout.IsDevmodView = true + + case StatusViewType: + dataLayout.IsDevmodView = true + } + + return NewTemplateComponent("index", dataLayout) +} diff --git a/gno.land/pkg/gnoweb/components/layouts/analytics.html b/gno.land/pkg/gnoweb/components/layouts/analytics.html new file mode 100644 index 00000000000..8782c80d7ae --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/analytics.html @@ -0,0 +1,7 @@ +{{- define "layout/analytics" -}} + + + +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layouts/article.html b/gno.land/pkg/gnoweb/components/layouts/article.html new file mode 100644 index 00000000000..63862e9a4dc --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/article.html @@ -0,0 +1,3 @@ +{{ define "layout/article" }} +
            {{ render .ComponentContent }}
            +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/aside.html b/gno.land/pkg/gnoweb/components/layouts/aside.html new file mode 100644 index 00000000000..2b33d7a8e2b --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/aside.html @@ -0,0 +1,10 @@ +{{- define "layout/aside" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/footer.html b/gno.land/pkg/gnoweb/components/layouts/footer.html new file mode 100644 index 00000000000..1ae51add995 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/footer.html @@ -0,0 +1,23 @@ +{{ define "layouts/footer" }} + + + + + +{{- if .Analytics -}} {{- template "layout/analytics" }}{{- end -}} {{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/layouts/head.html b/gno.land/pkg/gnoweb/components/layouts/head.html new file mode 100644 index 00000000000..1b78eeeb324 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/head.html @@ -0,0 +1,44 @@ +{{ define "layouts/head" }} + + + + {{ .Title }} + + + + + + + + + + {{ if .Canonical }} + + {{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html new file mode 100644 index 00000000000..8a1433ccd1c --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -0,0 +1,24 @@ +{{ define "layouts/header" }} +
            + +
            +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/index.html b/gno.land/pkg/gnoweb/components/layouts/index.html new file mode 100644 index 00000000000..c42a868e6fa --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/index.html @@ -0,0 +1,29 @@ +{{ define "index" -}} + + + + {{ template "layouts/head" .IndexData.HeadData -}} + + {{ template "ui/icons" -}} + + + {{ template "layouts/header" .IndexData.HeaderData -}} + + +
            +
            + {{ render .IndexData.BodyView -}} +
            +
            + + + {{ template "layouts/footer" .FooterData -}} + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/logosvg.gohtml b/gno.land/pkg/gnoweb/components/logosvg.gohtml deleted file mode 100644 index 5ebe6460ee3..00000000000 --- a/gno.land/pkg/gnoweb/components/logosvg.gohtml +++ /dev/null @@ -1,21 +0,0 @@ -{{ define "logosvg" }} - - - - - - - - - - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/realm.go b/gno.land/pkg/gnoweb/components/realm.go deleted file mode 100644 index 027760bb382..00000000000 --- a/gno.land/pkg/gnoweb/components/realm.go +++ /dev/null @@ -1,32 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - - "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" -) - -type RealmTOCData struct { - Items []*markdown.TocItem -} - -func RealmTOCComponent(data *RealmTOCData) Component { - return func(ctx context.Context, tmpl *template.Template, w io.Writer) error { - return tmpl.ExecuteTemplate(w, "renderRealmToc", data) - } -} - -func RenderRealmTOCComponent(w io.Writer, data *RealmTOCData) error { - return tmpl.ExecuteTemplate(w, "renderRealmToc", data) -} - -type RealmData struct { - Content template.HTML - TocItems *RealmTOCData -} - -func RenderRealmComponent(w io.Writer, data RealmData) error { - return tmpl.ExecuteTemplate(w, "renderRealm", data) -} diff --git a/gno.land/pkg/gnoweb/components/realm.gohtml b/gno.land/pkg/gnoweb/components/realm.gohtml deleted file mode 100644 index 55f39ef36d7..00000000000 --- a/gno.land/pkg/gnoweb/components/realm.gohtml +++ /dev/null @@ -1,41 +0,0 @@ -{{ define "renderRealmToc" }} - -{{ end }} - -{{ define "renderRealm" }} -
            -
            - -
            - - {{ .Content }} -
            -
            -
            -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.go b/gno.land/pkg/gnoweb/components/redirect.go deleted file mode 100644 index 873ddf56ff5..00000000000 --- a/gno.land/pkg/gnoweb/components/redirect.go +++ /dev/null @@ -1,12 +0,0 @@ -package components - -import "io" - -type RedirectData struct { - To string - WithAnalytics bool -} - -func RenderRedirectComponent(w io.Writer, data RedirectData) error { - return tmpl.ExecuteTemplate(w, "renderRedirect", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.go b/gno.land/pkg/gnoweb/components/source.go deleted file mode 100644 index 23170776657..00000000000 --- a/gno.land/pkg/gnoweb/components/source.go +++ /dev/null @@ -1,20 +0,0 @@ -package components - -import ( - "html/template" - "io" -) - -type SourceData struct { - PkgPath string - Files []string - FileName string - FileSize string - FileLines int - FileCounter int - FileSource template.HTML -} - -func RenderSourceComponent(w io.Writer, data SourceData) error { - return tmpl.ExecuteTemplate(w, "renderSource", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.gohtml b/gno.land/pkg/gnoweb/components/source.gohtml deleted file mode 100644 index cb2430b504a..00000000000 --- a/gno.land/pkg/gnoweb/components/source.gohtml +++ /dev/null @@ -1,57 +0,0 @@ -{{ define "renderSource" }} -
            -
            -
            -
            -

            {{ .FileName }}

            -
            -
            - {{ .FileSize }} · {{ .FileLines }} lines - -
            -
            - - -
            -
            - {{ .FileSource }} -
            -
            -
            -
            -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/spritesvg.gohtml b/gno.land/pkg/gnoweb/components/spritesvg.gohtml deleted file mode 100644 index c061e97bf58..00000000000 --- a/gno.land/pkg/gnoweb/components/spritesvg.gohtml +++ /dev/null @@ -1,125 +0,0 @@ -{{ define "spritesvg" }} - - - Search - - - - - - - Apps - - - - Documentation - - - - Source - - - - Content - - - - File - - - - Folder - - - - - - - - - - - Download - - - - Copy - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/status.gohtml b/gno.land/pkg/gnoweb/components/status.gohtml deleted file mode 100644 index 2321d1110bd..00000000000 --- a/gno.land/pkg/gnoweb/components/status.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{ define "status" }} -
            -
            -
            - gno land -

            Error: {{ .Message }}

            -

            Something went wrong. Let’s find our way back!

            - Go Back Home -
            -
            -
            -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/template.go b/gno.land/pkg/gnoweb/components/template.go index 9c08703f460..ee2605d1436 100644 --- a/gno.land/pkg/gnoweb/components/template.go +++ b/gno.land/pkg/gnoweb/components/template.go @@ -2,76 +2,57 @@ package components import ( "bytes" - "context" "embed" + "fmt" "html/template" - "io" "net/url" ) -//go:embed *.gohtml -var gohtml embed.FS +//go:embed ui/*.html views/*.html layouts/*.html +var html embed.FS -var funcMap = template.FuncMap{ +var funcMap = template.FuncMap{} + +var tmpl = template.New("web") + +func registerCommonFuncs(funcs template.FuncMap) { // NOTE: this method does NOT escape HTML, use with caution - "noescape_string": func(in string) template.HTML { + funcs["noescape_string"] = func(in string) template.HTML { return template.HTML(in) //nolint:gosec - }, + } // NOTE: this method does NOT escape HTML, use with caution - "noescape_bytes": func(in []byte) template.HTML { + funcs["noescape_bytes"] = func(in []byte) template.HTML { return template.HTML(in) //nolint:gosec - }, - "queryHas": func(vals url.Values, key string) bool { + } + // NOTE: this method does NOT escape HTML, use with caution + // Render Component element into raw html element + funcs["render"] = func(comp Component) (template.HTML, error) { + var buf bytes.Buffer + if err := comp.Render(&buf); err != nil { + return "", fmt.Errorf("unable to render component: %w", err) + } + + return template.HTML(buf.String()), nil //nolint:gosec + } + funcs["queryHas"] = func(vals url.Values, key string) bool { if vals == nil { return false } return vals.Has(key) - }, + } } -var tmpl = template.New("web").Funcs(funcMap) - func init() { + // Register templates functions + registerCommonFuncs(funcMap) registerHelpFuncs(funcMap) tmpl.Funcs(funcMap) + // Parse templates var err error - tmpl, err = tmpl.ParseFS(gohtml, "*.gohtml") + tmpl, err = tmpl.ParseFS(html, "layouts/*.html", "ui/*.html", "views/*.html") if err != nil { panic("unable to parse embed tempalates: " + err.Error()) } } - -type Component func(ctx context.Context, tmpl *template.Template, w io.Writer) error - -func (c Component) Render(ctx context.Context, w io.Writer) error { - return RenderComponent(ctx, w, c) -} - -func RenderComponent(ctx context.Context, w io.Writer, c Component) error { - var render *template.Template - funcmap := template.FuncMap{ - "render": func(cf Component) (string, error) { - var buf bytes.Buffer - if err := cf(ctx, render, &buf); err != nil { - return "", err - } - - return buf.String(), nil - }, - } - - render = tmpl.Funcs(funcmap) - return c(ctx, render, w) -} - -type StatusData struct { - Message string -} - -func RenderStatusComponent(w io.Writer, message string) error { - return tmpl.ExecuteTemplate(w, "status", StatusData{ - Message: message, - }) -} diff --git a/gno.land/pkg/gnoweb/components/ui/breadcrumb.html b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html new file mode 100644 index 00000000000..42f3186a476 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html @@ -0,0 +1,19 @@ +{{ define "ui/breadcrumb" }} +
              + {{- range $index, $part := .Parts }} {{- if $index }} +
            1. {{- else }}
            2. + +
            3. + {{- end }} + {{ $part.Name }} +
            4. + {{- end }} {{- if .Args }} +
            5. + {{ .Args }} +
            6. + {{- end }} +
            +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/btn_copy.html b/gno.land/pkg/gnoweb/components/ui/btn_copy.html new file mode 100644 index 00000000000..5f6e7c6f7a8 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/btn_copy.html @@ -0,0 +1,6 @@ +{{ define "ui/copy" }} + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/code_wrapper.html b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html new file mode 100644 index 00000000000..b05adb0532d --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html @@ -0,0 +1,3 @@ +{{ define "ui/code_wrapper" }} +
            {{ render . }}
            +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/expend_label.html b/gno.land/pkg/gnoweb/components/ui/expend_label.html new file mode 100644 index 00000000000..53b92721c07 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/expend_label.html @@ -0,0 +1,14 @@ +{{ define "ui/expend_label" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/header_link.html b/gno.land/pkg/gnoweb/components/ui/header_link.html new file mode 100644 index 00000000000..e70c6a7f733 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/header_link.html @@ -0,0 +1,14 @@ + +{{ define "ui/header_link" }} + +
            + + + + + +
            +
            +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/help_function.html b/gno.land/pkg/gnoweb/components/ui/help_function.html new file mode 100644 index 00000000000..59c5286b093 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/help_function.html @@ -0,0 +1,43 @@ +{{ define "ui/help_function" }} +{{ $data := . }} +{{ range .Functions }} +
            +

            {{ .FuncName }}

            +
            +
            +

            Params

            +
            + {{ $funcName := .FuncName }} {{ range .Params }} +
            +
            + + +
            +
            + {{ end }} +
            +
            +
            +
            +

            Command

            +
            + +
            gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESS
            +
            +
            +
            +{{ end }} +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/icons.html b/gno.land/pkg/gnoweb/components/ui/icons.html new file mode 100644 index 00000000000..feef8226be7 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/icons.html @@ -0,0 +1,124 @@ +{{ define "ui/icons" }} + + + Search + + + + + + + Apps + + + + Documentation + + + + Source + + + + Content + + + + File + + + + Folder + + + + + + + + + + + Download + + + + Copy + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/logo.html b/gno.land/pkg/gnoweb/components/ui/logo.html new file mode 100644 index 00000000000..a61beafd3cd --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/logo.html @@ -0,0 +1,51 @@ +{{ define "ui/logo" }} + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_generic.html b/gno.land/pkg/gnoweb/components/ui/toc_generic.html new file mode 100644 index 00000000000..3cd027f2a0c --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_generic.html @@ -0,0 +1,14 @@ +{{- define "ui/toc_generic" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_realm.html b/gno.land/pkg/gnoweb/components/ui/toc_realm.html new file mode 100644 index 00000000000..dc09db4d0e6 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_realm.html @@ -0,0 +1,10 @@ +{{ define "ui/toc_realm" }} + +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.go b/gno.land/pkg/gnoweb/components/ui_breadcrumb.go similarity index 100% rename from gno.land/pkg/gnoweb/components/breadcrumb.go rename to gno.land/pkg/gnoweb/components/ui_breadcrumb.go diff --git a/gno.land/pkg/gnoweb/components/view.go b/gno.land/pkg/gnoweb/components/view.go new file mode 100644 index 00000000000..f2da05b4b90 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view.go @@ -0,0 +1,32 @@ +package components + +import ( + "fmt" + "io" +) + +type ViewType string + +type View struct { + Type ViewType + Component +} + +func (v *View) String() string { + return string(v.Type) +} + +func (v *View) Render(w io.Writer) error { + if err := v.Component.Render(w); err != nil { + return fmt.Errorf("view %q error: %w", string(v.Type), err) + } + + return nil +} + +func NewTemplateView(typ ViewType, name string, data any) *View { + return &View{ + Type: typ, + Component: NewTemplateComponent(name, data), + } +} diff --git a/gno.land/pkg/gnoweb/components/view_directory.go b/gno.land/pkg/gnoweb/components/view_directory.go new file mode 100644 index 00000000000..a105291a4dd --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_directory.go @@ -0,0 +1,13 @@ +package components + +const DirectoryViewType ViewType = "dir-view" + +type DirData struct { + PkgPath string + Files []string + FileCounter int +} + +func DirectoryView(data DirData) *View { + return NewTemplateView(DirectoryViewType, "renderDir", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_help.go b/gno.land/pkg/gnoweb/components/view_help.go new file mode 100644 index 00000000000..3473f5a3f42 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_help.go @@ -0,0 +1,83 @@ +package components + +import ( + "html/template" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types +) + +const HelpViewType ViewType = "help-view" + +type HelpData struct { + // Selected function + SelectedFunc string + SelectedArgs map[string]string + + RealmName string + Functions []vm.FunctionSignature + ChainId string + Remote string + PkgPath string +} + +type HelpTocData struct { + Icon string + Items []HelpTocItem +} + +type HelpTocItem struct { + Link string + Text string +} + +type helpViewParams struct { + HelpData + Article ArticleData + ComponentTOC Component +} + +func registerHelpFuncs(funcs template.FuncMap) { + funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { + if data.SelectedArgs == nil { + return "", nil + } + + return data.SelectedArgs[param.Name], nil + } +} + +func HelpView(data HelpData) *View { + tocData := HelpTocData{ + Icon: "code", + Items: make([]HelpTocItem, len(data.Functions)), + } + + for i, fn := range data.Functions { + sig := fn.FuncName + "(" + for j, param := range fn.Params { + if j > 0 { + sig += ", " + } + sig += param.Name + } + sig += ")" + + tocData.Items[i] = HelpTocItem{ + Link: "#func-" + fn.FuncName, + Text: sig, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/help_function", data) + viewData := helpViewParams{ + HelpData: data, + Article: ArticleData{ + ComponentContent: content, + Classes: "", + }, + ComponentTOC: toc, + } + + return NewTemplateView(HelpViewType, "renderHelp", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_realm.go b/gno.land/pkg/gnoweb/components/view_realm.go new file mode 100644 index 00000000000..49372244fd4 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_realm.go @@ -0,0 +1,38 @@ +package components + +import ( + "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" +) + +const RealmViewType ViewType = "realm-view" + +type RealmTOCData struct { + Items []*markdown.TocItem +} + +type RealmData struct { + ComponentContent Component + TocItems *RealmTOCData +} + +type ArticleData struct { + ComponentContent Component + Classes string +} + +type realmViewParams struct { + Article ArticleData + ComponentTOC Component +} + +func RealmView(data RealmData) *View { + viewData := realmViewParams{ + Article: ArticleData{ + ComponentContent: data.ComponentContent, + Classes: "realm-view lg:row-start-1", + }, + ComponentTOC: NewTemplateComponent("ui/toc_realm", data.TocItems), + } + + return NewTemplateView(RealmViewType, "renderRealm", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_redirect.go b/gno.land/pkg/gnoweb/components/view_redirect.go new file mode 100644 index 00000000000..57d2f59e20a --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_redirect.go @@ -0,0 +1,12 @@ +package components + +const RedirectViewType = "redirect-view" + +type RedirectData struct { + To string + WithAnalytics bool +} + +func RedirectView(data RedirectData) *View { + return NewTemplateView(RedirectViewType, "renderRedirect", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_source.go b/gno.land/pkg/gnoweb/components/view_source.go new file mode 100644 index 00000000000..7eb5227dea1 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_source.go @@ -0,0 +1,66 @@ +package components + +const SourceViewType ViewType = "source-view" + +type SourceData struct { + PkgPath string + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + FileSource Component +} + +type SourceTocData struct { + Icon string + Items []SourceTocItem +} + +type SourceTocItem struct { + Link string + Text string +} + +type sourceViewParams struct { + Article ArticleData + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + PkgPath string + ComponentTOC Component +} + +func SourceView(data SourceData) *View { + tocData := SourceTocData{ + Icon: "file", + Items: make([]SourceTocItem, len(data.Files)), + } + + for i, file := range data.Files { + tocData.Items[i] = SourceTocItem{ + Link: data.PkgPath + "$source&file=" + file, + Text: file, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/code_wrapper", data.FileSource) + viewData := sourceViewParams{ + Article: ArticleData{ + ComponentContent: content, + Classes: "source-view col-span-1 lg:col-span-7 lg:row-start-2 pb-24 text-gray-900", + }, + ComponentTOC: toc, + Files: data.Files, + FileName: data.FileName, + FileSize: data.FileSize, + FileLines: data.FileLines, + FileCounter: data.FileCounter, + PkgPath: data.PkgPath, + } + + return NewTemplateView(SourceViewType, "renderSource", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_status.go b/gno.land/pkg/gnoweb/components/view_status.go new file mode 100644 index 00000000000..46f998c45cb --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_status.go @@ -0,0 +1,11 @@ +package components + +const StatusViewType ViewType = "status-view" + +type StatusData struct { + Message string +} + +func StatusComponent(message string) *View { + return NewTemplateView(StatusViewType, "status", StatusData{message}) +} diff --git a/gno.land/pkg/gnoweb/components/directory.gohtml b/gno.land/pkg/gnoweb/components/views/directory.html similarity index 85% rename from gno.land/pkg/gnoweb/components/directory.gohtml rename to gno.land/pkg/gnoweb/components/views/directory.html index 2254886f7af..9aedd658def 100644 --- a/gno.land/pkg/gnoweb/components/directory.gohtml +++ b/gno.land/pkg/gnoweb/components/views/directory.html @@ -1,7 +1,4 @@ {{ define "renderDir" }} -
            -
            - {{ $pkgpath := .PkgPath }}
            @@ -31,8 +28,5 @@

            {{ $pkgpath }}

          - - - {{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/help.html b/gno.land/pkg/gnoweb/components/views/help.html new file mode 100644 index 00000000000..b4bd7f92a56 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/help.html @@ -0,0 +1,38 @@ +{{ define "renderHelp" }} +{{ $data := . }} + +
          +
          +

          {{ .RealmName }}

          +
          +
          +
          + + + + +
          +
          + + +
          +
          +
          + + +{{ with render .ComponentTOC }} +{{ template "layout/aside" .}} +{{ end }} + + +{{ template "layout/article" .Article}} +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/realm.html b/gno.land/pkg/gnoweb/components/views/realm.html new file mode 100644 index 00000000000..6e0a771e701 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/realm.html @@ -0,0 +1,8 @@ +{{ define "renderRealm" }} + +{{ with render .ComponentTOC }} +{{ template "layout/aside" .}} +{{ end }} + + +{{ template "layout/article" .Article}} {{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.gohtml b/gno.land/pkg/gnoweb/components/views/redirect.html similarity index 79% rename from gno.land/pkg/gnoweb/components/redirect.gohtml rename to gno.land/pkg/gnoweb/components/views/redirect.html index 45dac0981cd..cd192b850a2 100644 --- a/gno.land/pkg/gnoweb/components/redirect.gohtml +++ b/gno.land/pkg/gnoweb/components/views/redirect.html @@ -10,7 +10,7 @@ {{.To}} - {{- if .WithAnalytics -}} {{- template "analytics" }} {{- end -}} + {{- if .WithAnalytics -}} {{- template "layout/analytics" }} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/views/source.html b/gno.land/pkg/gnoweb/components/views/source.html new file mode 100644 index 00000000000..44d39ca94ca --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/source.html @@ -0,0 +1,26 @@ +{{ define "renderSource" }} + + +{{ with render .ComponentTOC }} +{{ template "layout/aside" . }} +{{ end }} + + +
          +
          +

          {{ .FileName }}

          +
          +
          + {{ .FileSize }} · {{ .FileLines }} lines + +
          +
          + + +{{ template "layout/article" .Article }} +{{ end }} + + diff --git a/gno.land/pkg/gnoweb/components/views/status.html b/gno.land/pkg/gnoweb/components/views/status.html new file mode 100644 index 00000000000..ab068cbf7e4 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/status.html @@ -0,0 +1,8 @@ +{{ define "status" }} +
          + gno land +

          Error: {{ .Message }}

          +

          Something went wrong. Let’s find our way back!

          + Go Back Home +
          +{{ end }} diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index fb6a3dcd099..63d83e0d9f1 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -39,199 +39,199 @@ @apply my-0; } - .realm-content { - @apply text-200 break-words pt-10; + .realm-view { + @apply text-200 break-words pt-6 lg:pt-10; } - .realm-content > *:first-child { + .realm-view > *:first-child { @apply !mt-0; } - .realm-content a { + .realm-view a { @apply text-green-600 font-medium hover:underline; } - .realm-content h1, - .realm-content h2, - .realm-content h3, - .realm-content h4 { + .realm-view h1, + .realm-view h2, + .realm-view h3, + .realm-view h4 { @apply text-gray-900 mt-12 leading-tight; } - .realm-content h2, - .realm-content h2 * { + .realm-view h2, + .realm-view h2 * { @apply font-bold; } - .realm-content h3, - .realm-content h3 *, - .realm-content h4, - .realm-content h4 * { + .realm-view h3, + .realm-view h3 *, + .realm-view h4, + .realm-view h4 * { @apply font-semibold; } - .realm-content h1 + h2, - .realm-content h2 + h3, - .realm-content h3 + h4 { + .realm-view h1 + h2, + .realm-view h2 + h3, + .realm-view h3 + h4 { @apply mt-4; } - .realm-content h1 { + .realm-view h1 { @apply text-800 font-bold; } - .realm-content h2 { + .realm-view h2 { @apply text-600; } - .realm-content h3 { + .realm-view h3 { @apply text-400 text-gray-600 mt-10; } - .realm-content h4 { + .realm-view h4 { @apply text-300 text-gray-600 font-medium my-6; } - .realm-content p { + .realm-view p { @apply my-5; } - .realm-content strong { + .realm-view strong { @apply font-bold text-gray-900; } - .realm-content strong * { + .realm-view strong * { @apply font-bold; } - .realm-content em { + .realm-view em { @apply italic-subtle; } - .realm-content blockquote { + .realm-view blockquote { @apply border-l-4 border-gray-300 pl-4 text-gray-600 italic-subtle my-4; } - .realm-content ul, - .realm-content ol { + .realm-view ul, + .realm-view ol { @apply pl-4 my-6; } - .realm-content ul li, - .realm-content ol li { + .realm-view ul li, + .realm-view ol li { @apply mb-2; } - .realm-content img { + .realm-view img { @apply max-w-full my-8; } - .realm-content figure { + .realm-view figure { @apply my-6 text-center; } - .realm-content figcaption { + .realm-view figcaption { @apply text-100 text-gray-600; } - .realm-content :not(pre) > code { + .realm-view :not(pre) > code { @apply bg-gray-100 px-1 py-0.5 rounded-sm text-[.96em] font-mono; } - .realm-content pre { + .realm-view pre { @apply bg-gray-50 p-4 rounded overflow-x-auto font-mono; } - .realm-content hr { + .realm-view hr { @apply border-t border-gray-100 my-10; } - .realm-content table { - @apply border-collapse my-8 block w-full max-w-full overflow-x-auto border-collapse; + .realm-view table { + @apply my-8 block w-full max-w-full overflow-x-auto border-collapse; } - .realm-content th, - .realm-content td { + .realm-view th, + .realm-view td { @apply border px-4 py-2 break-words whitespace-normal; } - .realm-content th { + .realm-view th { @apply bg-gray-100 font-bold; } - .realm-content caption { + .realm-view caption { @apply mt-2 text-100 text-gray-600 text-left; } - .realm-content q { + .realm-view q { @apply quotes; } - .realm-content q::before { + .realm-view q::before { content: open-quote; } - .realm-content q::after { + .realm-view q::after { content: close-quote; } - .realm-content ul ul, - .realm-content ul ol, - .realm-content ol ul, - .realm-content ol ol { + .realm-view ul ul, + .realm-view ul ol, + .realm-view ol ul, + .realm-view ol ol { @apply mt-3 mb-2 pl-4; } - .realm-content ul { + .realm-view ul { @apply list-disc; } - .realm-content ol { + .realm-view ol { @apply list-decimal; } - .realm-content abbr[title] { + .realm-view abbr[title] { @apply border-b border-dotted cursor-help; } - .realm-content details { + .realm-view details { @apply my-5; } - .realm-content summary { + .realm-view summary { @apply font-bold cursor-pointer; } - .realm-content a code { + .realm-view a code { @apply text-inherit; } - .realm-content video { + .realm-view video { @apply max-w-full my-8; } - .realm-content math { + .realm-view math { @apply font-mono; } - .realm-content small { + .realm-view small { @apply text-100; } - .realm-content del { + .realm-view del { @apply line-through; } - .realm-content sub { + .realm-view sub { @apply text-50 align-sub; } - .realm-content sup { + .realm-view sup { @apply text-50 align-super; } - .realm-content input, - .realm-content button { + .realm-view input, + .realm-view button { @apply px-4 py-2 border border-gray-300; } @@ -269,7 +269,7 @@ @apply block; } - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main .realm-content, + :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main .realm-view, :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) .main-navigation { @apply md:col-span-6; } @@ -285,22 +285,32 @@ main :is(.source-code) > pre { @apply !bg-light overflow-scroll rounded py-4 md:py-8 px-1 md:px-3 font-mono text-100 md:text-200; } - main .realm-content > pre a { + main .realm-view > pre a { @apply hover:no-underline; } - main :is(.realm-content, .source-code) > pre .chroma-ln:target { + main :is(.realm-view, .source-code) > pre .chroma-ln:target { @apply !bg-transparent; } - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target) .chroma-cl, - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl { + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-ln:target), + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover), + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-ln:target) .chroma-cl, + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl { @apply !bg-gray-100 rounded; } - main :is(.realm-content, .source-code) > pre .chroma-ln { + main :is(.realm-view, .source-code) > pre .chroma-ln { @apply scroll-mt-24; } + + .dev-mode .toc-expend-btn { + @apply bg-gray-100 hover:bg-gray-50 cursor-pointer border lg:border-none lg:bg-transparent; + } + .dev-mode #sidebar-summary { + @apply bg-light lg:bg-transparent; + } + .dev-mode .toc-nav { + @apply font-mono; + } } @layer utilities { diff --git a/gno.land/pkg/gnoweb/frontend/css/tx.config.js b/gno.land/pkg/gnoweb/frontend/css/tx.config.js index 21b6a101dd6..451688d7da6 100644 --- a/gno.land/pkg/gnoweb/frontend/css/tx.config.js +++ b/gno.land/pkg/gnoweb/frontend/css/tx.config.js @@ -1,7 +1,7 @@ const pxToRem = (px) => px / 16; export default { - content: ["./components/**/*.{gohtml,ts}"], + content: ["./components/**/*.{html,ts}"], theme: { screens: { xs: `${pxToRem(360)}rem`, @@ -68,5 +68,6 @@ export default { 900: `${pxToRem(42)}rem`, }, }, + safelist: ["realm-view", { pattern: /^realm-view/ }], plugins: [], }; diff --git a/gno.land/pkg/gnoweb/frontend/js/copy.ts b/gno.land/pkg/gnoweb/frontend/js/copy.ts index f3e5c725783..5f559e7eb46 100644 --- a/gno.land/pkg/gnoweb/frontend/js/copy.ts +++ b/gno.land/pkg/gnoweb/frontend/js/copy.ts @@ -9,7 +9,7 @@ class Copy { private isAnimationRunning: boolean = false; private static SELECTORS = { - button: "[data-copy-btn]", + button: ".js-copy-btn", icon: `[data-copy-icon] > use`, content: (id: string) => `[data-copy-content="${id}"]`, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/index.ts b/gno.land/pkg/gnoweb/frontend/js/index.ts index 3927f794b94..47b60f70360 100644 --- a/gno.land/pkg/gnoweb/frontend/js/index.ts +++ b/gno.land/pkg/gnoweb/frontend/js/index.ts @@ -6,15 +6,15 @@ const modules: Record = { copy: { - selector: "[data-copy-btn]", + selector: ".js-copy-btn", path: "/public/js/copy.js", }, help: { - selector: "#help", + selector: ".js-help-view", path: "/public/js/realmhelp.js", }, searchBar: { - selector: "#header-searchbar", + selector: ".js-header-searchbar", path: "/public/js/searchbar.js", }, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts index d72102e2a2e..3177e034257 100644 --- a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts +++ b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts @@ -11,7 +11,7 @@ class Help { private funcList: HelpFunc[]; private static SELECTORS = { - container: "#help", + container: ".js-help-view", func: "[data-func]", addressInput: "[data-role='help-input-addr']", cmdModeSelect: "[data-role='help-select-mode']", diff --git a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts index 6cca444aa0f..a6859245af6 100644 --- a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts +++ b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts @@ -8,7 +8,7 @@ class SearchBar { private baseUrl: string; private static SELECTORS = { - container: "#header-searchbar", + container: ".js-header-searchbar", inputSearch: "[data-role='header-input-search']", breadcrumb: "[data-role='header-breadcrumb-search']", }; diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 2dc51d64029..3fdfc33909c 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -4,8 +4,6 @@ import ( "bytes" "errors" "fmt" - "html/template" - "io" "log/slog" "net/http" "path/filepath" @@ -47,6 +45,13 @@ type WebHandler struct { Client WebClient } +// PageData groups layout, component, and dev mode information. +type PageData struct { + Layout string + Component string + IsDevmodView bool +} + // NewWebHandler creates a new WebHandler. func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) (*WebHandler, error) { if err := cfg.validate(); err != nil { @@ -74,8 +79,6 @@ func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Get processes a GET HTTP request. func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { - var body bytes.Buffer - start := time.Now() defer func() { h.Logger.Debug("request completed", @@ -94,29 +97,22 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { }, } - status, err := h.renderPage(&body, r, &indexData) - if err != nil { - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - w.WriteHeader(status) - - // NOTE: HTML escaping should have already been done by markdown rendering package - indexData.Body = template.HTML(body.String()) //nolint:gosec + var status int + status, indexData.BodyView = h.prepareIndexBodyView(r, &indexData) // Render the final page with the rendered body - if err = components.RenderIndexComponent(w, indexData); err != nil { - h.Logger.Error("failed to render index component", "err", err) + w.WriteHeader(status) + if err := components.IndexLayout(indexData).Render(w); err != nil { + h.Logger.Error("failed to render index component", "error", err) } } -// renderPage renders the page into the given buffer and prepares the index data. -func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData *components.IndexData) (int, error) { +// prepareIndexBodyView prepares the data and main view for the index. +func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components.IndexData) (int, *components.View) { gnourl, err := ParseGnoURL(r.URL) if err != nil { - h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "err", err) - return http.StatusNotFound, components.RenderStatusComponent(body, "invalid path") + h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err) + return http.StatusNotFound, components.StatusComponent("invalid path") } breadcrumb := generateBreadcrumbPaths(gnourl) @@ -129,69 +125,62 @@ func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData * switch { case gnourl.IsRealm(), gnourl.IsPure(): - return h.GetPackagePage(body, gnourl) + return h.GetPackageView(gnourl) default: h.Logger.Debug("invalid path: path is neither a pure package or a realm") - return http.StatusBadRequest, components.RenderStatusComponent(body, "invalid path") + return http.StatusBadRequest, components.StatusComponent("invalid path") } } -// GetPackagePage handles package pages. -func (h *WebHandler) GetPackagePage(w io.Writer, gnourl *GnoURL) (int, error) { - h.Logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - +// GetPackageView handles package pages. +func (h *WebHandler) GetPackageView(gnourl *GnoURL) (int, *components.View) { // Handle Help page if gnourl.WebQuery.Has("help") { - return h.GetHelpPage(w, gnourl) + return h.GetHelpView(gnourl) } // Handle Source page if gnourl.WebQuery.Has("source") || gnourl.IsFile() { - return h.GetSourcePage(w, gnourl) + return h.GetSourceView(gnourl) } // Handle Source page if gnourl.IsDir() || gnourl.IsPure() { - return h.GetDirectoryPage(w, gnourl) + return h.GetDirectoryView(gnourl) } - // Ultimately render realm content - return h.renderRealmContent(w, gnourl) + // Ultimately get realm view + return h.GetRealmView(gnourl) } -// renderRealmContent renders the content of a realm. -func (h *WebHandler) renderRealmContent(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) { var content bytes.Buffer + meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { - h.Logger.Error("unable to render realm", "err", err, "path", gnourl.EncodeArgs()) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) + return GetClientErrorStatusPage(gnourl, err) } - err = components.RenderRealmComponent(w, components.RealmData{ + return http.StatusOK, components.RealmView(components.RealmData{ TocItems: &components.RealmTOCData{ Items: meta.Toc.Items, }, + // NOTE: `RenderRealm` should ensure that HTML content is // sanitized before rendering - Content: template.HTML(content.String()), //nolint:gosec + ComponentContent: components.NewReaderComponent(&content), }) - if err != nil { - h.Logger.Error("unable to render template", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetHelpPage renders the help page. -func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetHelpView(gnourl *GnoURL) (int, *components.View) { fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { - h.Logger.Error("unable to fetch path functions", "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to fetch path functions", "error", err) + return GetClientErrorStatusPage(gnourl, err) } + // Get selected function selArgs := make(map[string]string) selFn := gnourl.WebQuery.Get("func") if selFn != "" { @@ -210,36 +199,29 @@ func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { } realmName := filepath.Base(gnourl.Path) - err = components.RenderHelpComponent(w, components.HelpData{ + return http.StatusOK, components.HelpView(components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, - ChainId: h.Static.ChainId, // TODO: get chain domain and use that. + ChainId: h.Static.ChainId, PkgPath: filepath.Join(h.Static.Domain, gnourl.Path), Remote: h.Static.RemoteHelp, Functions: fsigs, }) - if err != nil { - h.Logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetSource renders the source page. -func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) { pkgPath := gnourl.Path files, err := h.Client.Sources(pkgPath) if err != nil { - h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") + return http.StatusOK, components.StatusComponent("no files available") } var fileName string @@ -250,76 +232,62 @@ func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { } if fileName == "" { - fileName = files[0] // fallback on the first file if + fileName = files[0] // fallback on the first file } var source bytes.Buffer meta, err := h.Client.SourceFile(&source, pkgPath, fileName) if err != nil { - h.Logger.Error("unable to get source file", "file", fileName, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to get source file", "file", fileName, "error", err) + return GetClientErrorStatusPage(gnourl, err) } fileSizeStr := fmt.Sprintf("%.2f Kb", meta.SizeKb) - err = components.RenderSourceComponent(w, components.SourceData{ + return http.StatusOK, components.SourceView(components.SourceData{ PkgPath: gnourl.Path, Files: files, FileName: fileName, FileCounter: len(files), FileLines: meta.Lines, FileSize: fileSizeStr, - FileSource: template.HTML(source.String()), //nolint:gosec + FileSource: components.NewReaderComponent(&source), }) - if err != nil { - h.Logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -// GetDirectoryPage renders the directory page. -func (h *WebHandler) GetDirectoryPage(w io.Writer, gnourl *GnoURL) (int, error) { +func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) { pkgPath := strings.TrimSuffix(gnourl.Path, "/") - files, err := h.Client.Sources(pkgPath) if err != nil { - h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return renderClientErrorStatusPage(w, gnourl, err) + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { h.Logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") + return http.StatusOK, components.StatusComponent("no files available") } - err = components.RenderDirectoryComponent(w, components.DirData{ + return http.StatusOK, components.DirectoryView(components.DirData{ PkgPath: gnourl.Path, Files: files, FileCounter: len(files), }) - if err != nil { - h.Logger.Error("unable to render directory", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "not found") - } - - return http.StatusOK, nil } -func renderClientErrorStatusPage(w io.Writer, _ *GnoURL, err error) (int, error) { +func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) { if err == nil { return http.StatusOK, nil } switch { case errors.Is(err, ErrClientPathNotFound): - return http.StatusNotFound, components.RenderStatusComponent(w, err.Error()) + return http.StatusNotFound, components.StatusComponent(err.Error()) case errors.Is(err, ErrClientBadRequest): - return http.StatusInternalServerError, components.RenderStatusComponent(w, "bad request") + return http.StatusInternalServerError, components.StatusComponent("bad request") case errors.Is(err, ErrClientResponse): fallthrough // XXX: for now fallback as internal error default: - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + return http.StatusInternalServerError, components.StatusComponent("internal error") } } diff --git a/gno.land/pkg/gnoweb/public/js/copy.js b/gno.land/pkg/gnoweb/public/js/copy.js index 918a30b1ca3..2bcfea74f84 100644 --- a/gno.land/pkg/gnoweb/public/js/copy.js +++ b/gno.land/pkg/gnoweb/public/js/copy.js @@ -1 +1 @@ -var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:"[data-copy-btn]",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; +var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:".js-copy-btn",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; diff --git a/gno.land/pkg/gnoweb/public/js/index.js b/gno.land/pkg/gnoweb/public/js/index.js index e990dd91f5f..0230bbd64bc 100644 --- a/gno.land/pkg/gnoweb/public/js/index.js +++ b/gno.land/pkg/gnoweb/public/js/index.js @@ -1 +1 @@ -(()=>{let s={copy:{selector:"[data-copy-btn]",path:"/public/js/copy.js"},help:{selector:"#help",path:"/public/js/realmhelp.js"},searchBar:{selector:"#header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(t){console.error(`Error while loading script ${o}:`,t)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(s).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); +(()=>{let t={copy:{selector:".js-copy-btn",path:"/public/js/copy.js"},help:{selector:".js-help-view",path:"/public/js/realmhelp.js"},searchBar:{selector:".js-header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(s){console.error(`Error while loading script ${o}:`,s)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(t).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); diff --git a/gno.land/pkg/gnoweb/public/js/realmhelp.js b/gno.land/pkg/gnoweb/public/js/realmhelp.js index 5d4a3feeba6..68bcafbb75f 100644 --- a/gno.land/pkg/gnoweb/public/js/realmhelp.js +++ b/gno.land/pkg/gnoweb/public/js/realmhelp.js @@ -1 +1 @@ -function d(s,e=250){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{s.apply(this,a)},e)}}var l=class s{DOM;funcList;static SELECTORS={container:"#help",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(s.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(s.SELECTORS.func)),this.DOM.addressInput=e.querySelector(s.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(s.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(a=>a.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,a=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>a(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class s{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(s.SELECTORS.address)),args:Array.from(e.querySelectorAll(s.SELECTORS.args)),modes:Array.from(e.querySelectorAll(s.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(s.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",a=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:a}}bindEvents(){let e=d((t,a)=>{t&&this.updateArg(t,a)});this.DOM.el.addEventListener("input",t=>{let a=t.target;if(a.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=s.sanitizeArgsInput(a);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:a}=s.sanitizeArgsInput(e);t&&this.updateArg(t,a)})}updateArg(e,t){this.DOM.args.filter(a=>a.dataset.arg===e).forEach(a=>{a.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let a=t.dataset.codeMode===e;t.classList.toggle("inline",a),t.classList.toggle("hidden",!a),t.dataset.copyContent=a?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; +function d(a,e=250){let t;return function(...s){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{a.apply(this,s)},e)}}var l=class a{DOM;funcList;static SELECTORS={container:".js-help-view",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(a.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(a.SELECTORS.func)),this.DOM.addressInput=e.querySelector(a.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(a.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(s=>s.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,s=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>s(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class a{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(a.SELECTORS.address)),args:Array.from(e.querySelectorAll(a.SELECTORS.args)),modes:Array.from(e.querySelectorAll(a.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(a.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",s=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:s}}bindEvents(){let e=d((t,s)=>{t&&this.updateArg(t,s)});this.DOM.el.addEventListener("input",t=>{let s=t.target;if(s.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=a.sanitizeArgsInput(s);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:s}=a.sanitizeArgsInput(e);t&&this.updateArg(t,s)})}updateArg(e,t){this.DOM.args.filter(s=>s.dataset.arg===e).forEach(s=>{s.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let s=t.dataset.codeMode===e;t.classList.toggle("inline",s),t.classList.toggle("hidden",!s),t.dataset.copyContent=s?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; diff --git a/gno.land/pkg/gnoweb/public/js/searchbar.js b/gno.land/pkg/gnoweb/public/js/searchbar.js index e8012b9b6d9..424e8f75db1 100644 --- a/gno.land/pkg/gnoweb/public/js/searchbar.js +++ b/gno.land/pkg/gnoweb/public/js/searchbar.js @@ -1 +1 @@ -var n=class r{DOM;baseUrl;static SELECTORS={container:"#header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; +var n=class r{DOM;baseUrl;static SELECTORS={container:".js-header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index a4c02629111..ec575bb3735 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 14deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-view{overflow-wrap:break-word;padding-top:1.5rem;font-size:1rem}@media (min-width:51.25rem){.realm-view{padding-top:2.5rem}}.realm-view>:first-child{margin-top:0!important}.realm-view a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-view a:hover{text-decoration-line:underline}.realm-view h1,.realm-view h2,.realm-view h3,.realm-view h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view h2,.realm-view h2 *{font-weight:700}.realm-view h3,.realm-view h3 *,.realm-view h4,.realm-view h4 *{font-weight:600}.realm-view h1+h2,.realm-view h2+h3,.realm-view h3+h4{margin-top:1rem}.realm-view h1{font-size:2.375rem;font-weight:700}.realm-view h2{font-size:1.5rem}.realm-view h3{margin-top:2.5rem;font-size:1.25rem}.realm-view h3,.realm-view h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-view p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view strong *{font-weight:700}.realm-view em{font-style:oblique 14deg}.realm-view blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-view ol,.realm-view ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-view ol li,.realm-view ul li{margin-bottom:.5rem}.realm-view img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-view figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-view :not(pre)>code,.realm-view pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-view hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-view table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-view td,.realm-view th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-view th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-view caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-view q:after,.realm-view q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-view q:after{content:close-quote}.realm-view q:before{content:open-quote}.realm-view q:after{content:close-quote}.realm-view ol ol,.realm-view ol ul,.realm-view ul ol,.realm-view ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-view ul{list-style-type:disc}.realm-view ol{list-style-type:decimal}.realm-view abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-view details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view summary{cursor:pointer;font-weight:700}.realm-view a code{color:inherit}.realm-view video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view small{font-size:.875rem}.realm-view del{text-decoration-line:line-through}.realm-view sub{vertical-align:sub;font-size:.75rem}.realm-view sup{vertical-align:super;font-size:.75rem}.realm-view button,.realm-view input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-view{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-view>pre a:hover{text-decoration-line:none}main :is(.realm-view,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-view,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.dev-mode .toc-expend-btn{cursor:pointer;border-width:1px;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.dev-mode .toc-expend-btn:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode .toc-expend-btn{border-style:none;background-color:transparent}}.dev-mode #sidebar-summary{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode #sidebar-summary{background-color:transparent}}.dev-mode .toc-nav{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:mt-8:first-child{margin-top:2rem}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:first\:mt-0:first-child{margin-top:0}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From 8851ff41fd0837fe8a8899a5d6b428484b582f5d Mon Sep 17 00:00:00 2001 From: Mustapha <102119509+mous1985@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:47:18 +0100 Subject: [PATCH 67/75] docs: remove `require`, add example gno doc (#3576) This pull request is for : - remove `require` statement from official documentation [#3123](https://github.com/gnolang/gno/pull/3123) - add an example that was `//TODO` in the `effective-gno / Documentation is for users` section --- docs/concepts/effective-gno.md | 52 +++++++++++++++++++++++++++++++++- docs/concepts/gno-modules.md | 8 +----- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/docs/concepts/effective-gno.md b/docs/concepts/effective-gno.md index 45872dd1d63..d4e2af8422e 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/concepts/effective-gno.md @@ -297,7 +297,57 @@ main purpose in Gno is for discoverability. This shift towards user-centric documentation reflects the broader shift in Gno towards making code more accessible and understandable for all users, not just developers. -TODO: `func ExampleXXX`. +Here's an example from [grc20](https://gno.land/p/demo/grc/grc20$source&file=types.gno) +to illustrate the concept: + +```go +// Teller interface defines the methods that a GRC20 token must implement. It +// extends the TokenMetadata interface to include methods for managing token +// transfers, allowances, and querying balances. +// +// The Teller interface is designed to ensure that any token adhering to this +// standard provides a consistent API for interacting with fungible tokens. +type Teller interface { + exts.TokenMetadata + + // Returns the amount of tokens in existence. + TotalSupply() uint64 + + // Returns the amount of tokens owned by `account`. + BalanceOf(account std.Address) uint64 + + // Moves `amount` tokens from the caller's account to `to`. + // + // Returns an error if the operation failed. + Transfer(to std.Address, amount uint64) error + + // Returns the remaining number of tokens that `spender` will be + // allowed to spend on behalf of `owner` through {transferFrom}. This is + // zero by default. + // + // This value changes when {approve} or {transferFrom} are called. + Allowance(owner, spender std.Address) uint64 + + // Sets `amount` as the allowance of `spender` over the caller's tokens. + // + // Returns an error if the operation failed. + // + // IMPORTANT: Beware that changing an allowance with this method brings + // the risk that someone may use both the old and the new allowance by + // unfortunate transaction ordering. One possible solution to mitigate + // this race condition is to first reduce the spender's allowance to 0 + // and set the desired value afterwards: + // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + Approve(spender std.Address, amount uint64) error + + // Moves `amount` tokens from `from` to `to` using the + // allowance mechanism. `amount` is then deducted from the caller's + // allowance. + // + // Returns an error if the operation failed. + TransferFrom(from, to std.Address, amount uint64) error +} +``` ### Reflection is never clear diff --git a/docs/concepts/gno-modules.md b/docs/concepts/gno-modules.md index 8ebc56b312e..280fe365cb6 100644 --- a/docs/concepts/gno-modules.md +++ b/docs/concepts/gno-modules.md @@ -24,7 +24,6 @@ The gno command-line tool provides several commands to work with the gno.mod fil - **gno mod init**: small helper to initialize a new `gno.mod` file. - **gno mod download**: downloads the dependencies specified in the gno.mod file. This command fetches the required dependencies from chain and ensures they are available for local testing and development. -- **gno mod tidy**: removes any unused dependency and adds any required but not yet listed in the file -- most of the maintenance you'll usually need to do! - **gno mod why**: explains why the specified package or module is being kept by `gno mod tidy`. ## Sample `gno.mod` file @@ -32,12 +31,7 @@ The gno command-line tool provides several commands to work with the gno.mod fil ``` module gno.land/p/demo/sample -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest -) - ``` - **`module gno.land/p/demo/sample`**: specifies the package/realm import path. -- **`require` Block**: lists the required dependencies. Here using the latest available versions of "gno.land/p/demo/avl" and "gno.land/p/demo/testutils". These dependencies should be specified with the version "v0.0.0-latest" since on-chain packages currently do not support versioning. + From b9d93b7238a86f418f0ff1cd14d92bbde0a2dc66 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 23 Jan 2025 11:59:57 +0100 Subject: [PATCH 68/75] ci(goreleaser): use `concurrency` to only execute 1 job at a time (#3557) This will make it so that on master, there is only 1 job of releaser-master executing at a time. There may be another pending one (if, say, we merge two PRs back to back), and it will be put as `pending`. It's cancelled if we merge a third PR while the second one is still `pending`. For more information: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency --- .github/workflows/releaser-master.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 7c81789b060..ddf51ac6683 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -6,6 +6,9 @@ on: - master workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + permissions: contents: write # needed to write releases id-token: write # needed for keyless signing From ed1700a1fe9c0f5d963493a827ebf1686a4829a3 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:01:02 +0900 Subject: [PATCH 69/75] refactor(examples): use std.Address based behaviour (#3580) ## Description To make contracts more simpler(and low computation for execution), it is better for contract to manipulate with only `std.Address` rather than using `users.AddressOrName` and resolving it. ([Discussed with @moul](https://github.com/gnoswap-labs/gnoswap/pull/465#discussion_r1922735140)) Clients such as gnoeky, gnoweb or adena should perform look up(convert username to address) and pass address value to contract. --- examples/gno.land/r/demo/foo20/foo20.gno | 44 +++++++------------ examples/gno.land/r/demo/foo20/foo20_test.gno | 14 +++--- examples/gno.land/r/demo/wugnot/wugnot.gno | 30 +++++-------- .../gno.land/r/demo/wugnot/z0_filetest.gno | 4 +- 4 files changed, 34 insertions(+), 58 deletions(-) diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 6522fbdc90e..917a037d49c 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -9,9 +9,7 @@ import ( "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" "gno.land/r/demo/grc20reg" - "gno.land/r/demo/users" ) var ( @@ -29,31 +27,24 @@ func TotalSupply() uint64 { return UserTeller.TotalSupply() } -func BalanceOf(owner pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - return UserTeller.BalanceOf(ownerAddr) +func BalanceOf(owner std.Address) uint64 { + return UserTeller.BalanceOf(owner) } -func Allowance(owner, spender pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - spenderAddr := users.Resolve(spender) - return UserTeller.Allowance(ownerAddr, spenderAddr) +func Allowance(owner, spender std.Address) uint64 { + return UserTeller.Allowance(owner, spender) } -func Transfer(to pusers.AddressOrName, amount uint64) { - toAddr := users.Resolve(to) - checkErr(UserTeller.Transfer(toAddr, amount)) +func Transfer(to std.Address, amount uint64) { + checkErr(UserTeller.Transfer(to, amount)) } -func Approve(spender pusers.AddressOrName, amount uint64) { - spenderAddr := users.Resolve(spender) - checkErr(UserTeller.Approve(spenderAddr, amount)) +func Approve(spender std.Address, amount uint64) { + checkErr(UserTeller.Approve(spender, amount)) } -func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - fromAddr := users.Resolve(from) - toAddr := users.Resolve(to) - checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +func TransferFrom(from, to std.Address, amount uint64) { + checkErr(UserTeller.TransferFrom(from, to, amount)) } // Faucet is distributing foo20 tokens without restriction (unsafe). @@ -64,16 +55,14 @@ func Faucet() { checkErr(privateLedger.Mint(caller, amount)) } -func Mint(to pusers.AddressOrName, amount uint64) { +func Mint(to std.Address, amount uint64) { Ownable.AssertCallerIsOwner() - toAddr := users.Resolve(to) - checkErr(privateLedger.Mint(toAddr, amount)) + checkErr(privateLedger.Mint(to, amount)) } -func Burn(from pusers.AddressOrName, amount uint64) { +func Burn(from std.Address, amount uint64) { Ownable.AssertCallerIsOwner() - fromAddr := users.Resolve(from) - checkErr(privateLedger.Burn(fromAddr, amount)) + checkErr(privateLedger.Burn(from, amount)) } func Render(path string) string { @@ -84,9 +73,8 @@ func Render(path string) string { case path == "": return Token.RenderHome() case c == 2 && parts[0] == "balance": - owner := pusers.AddressOrName(parts[1]) - ownerAddr := users.Resolve(owner) - balance := UserTeller.BalanceOf(ownerAddr) + owner := std.Address(parts[1]) + balance := UserTeller.BalanceOf(owner) return ufmt.Sprintf("%d\n", balance) default: return "404\n" diff --git a/examples/gno.land/r/demo/foo20/foo20_test.gno b/examples/gno.land/r/demo/foo20/foo20_test.gno index dbafe2d6c1d..d24faef279c 100644 --- a/examples/gno.land/r/demo/foo20/foo20_test.gno +++ b/examples/gno.land/r/demo/foo20/foo20_test.gno @@ -6,15 +6,13 @@ import ( "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" - pusers "gno.land/p/demo/users" - "gno.land/r/demo/users" ) func TestReadOnlyPublicMethods(t *testing.T) { var ( - admin = pusers.AddressOrName("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - alice = pusers.AddressOrName(testutils.TestAddress("alice")) - bob = pusers.AddressOrName(testutils.TestAddress("bob")) + admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") ) type test struct { @@ -39,7 +37,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { } // bob uses the faucet. - std.TestSetOrigCaller(users.Resolve(bob)) + std.TestSetOrigCaller(bob) Faucet() // check balances #2. @@ -60,8 +58,8 @@ func TestReadOnlyPublicMethods(t *testing.T) { func TestErrConditions(t *testing.T) { var ( - admin = pusers.AddressOrName("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - empty = pusers.AddressOrName("") + admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + empty = std.Address("") ) type test struct { diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index b72f5161e7d..d0395fe7e8c 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -6,9 +6,8 @@ import ( "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" + "gno.land/r/demo/grc20reg" - "gno.land/r/demo/users" ) var Token, adm = grc20.NewToken("wrapped GNOT", "wugnot", 0) @@ -65,34 +64,27 @@ func Render(path string) string { func TotalSupply() uint64 { return Token.TotalSupply() } -func BalanceOf(owner pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - return Token.BalanceOf(ownerAddr) +func BalanceOf(owner std.Address) uint64 { + return Token.BalanceOf(owner) } -func Allowance(owner, spender pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - spenderAddr := users.Resolve(spender) - return Token.Allowance(ownerAddr, spenderAddr) +func Allowance(owner, spender std.Address) uint64 { + return Token.Allowance(owner, spender) } -func Transfer(to pusers.AddressOrName, amount uint64) { - toAddr := users.Resolve(to) +func Transfer(to std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.Transfer(toAddr, amount)) + checkErr(userTeller.Transfer(to, amount)) } -func Approve(spender pusers.AddressOrName, amount uint64) { - spenderAddr := users.Resolve(spender) +func Approve(spender std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.Approve(spenderAddr, amount)) + checkErr(userTeller.Approve(spender, amount)) } -func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - fromAddr := users.Resolve(from) - toAddr := users.Resolve(to) +func TransferFrom(from, to std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.TransferFrom(fromAddr, toAddr, amount)) + checkErr(userTeller.TransferFrom(from, to, amount)) } func require(condition bool, msg string) { diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index 264bc8f19aa..ff2ea86bc0d 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -7,8 +7,6 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/wugnot" - - pusers "gno.land/p/demo/users" ) var ( @@ -39,7 +37,7 @@ func main() { func printBalances() { printSingleBalance := func(name string, addr std.Address) { - wugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr)) + wugnotBal := wugnot.BalanceOf(addr) std.TestSetOrigCaller(addr) robanker := std.GetBanker(std.BankerTypeReadonly) coins := robanker.GetCoins(addr).AmountOf("ugnot") From 4b6779ac4ecb3c5c828463218e400d71518b11ff Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:29:12 +0100 Subject: [PATCH 70/75] fix(gno.land): update txtar, not modified in #3580 (#3590) ## Description Fixes txtar that needed to be in #3580 Should've been caught by CI, wasn't --------- Co-authored-by: Morgan Bazalgette --- .github/workflows/gnoland.yml | 3 +++ gno.land/pkg/integration/testdata/issue_1786.txtar | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 0d3a7a10516..b02e7b364e6 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -11,6 +11,9 @@ on: # since there are integration suites that cover the gnovm / tm2 - gnovm/** - tm2/** + # Changes to examples/ can create failures in gno.land, eg. txtars, + # see: https://github.com/gnolang/gno/pull/3590 + - examples/** workflow_dispatch: jobs: diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 1cbaf2c6643..71cd19e7ed7 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -46,7 +46,6 @@ import ( "std" "gno.land/r/demo/wugnot" - pusers "gno.land/p/demo/users" ) func ProxyWrap() { @@ -64,7 +63,7 @@ func ProxyWrap() { wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER - wugnot.Transfer(pusers.AddressOrName(std.GetOrigCaller()), ugnotSent) + wugnot.Transfer(std.GetOrigCaller(), ugnotSent) } func ProxyUnwrap(wugnotAmount uint64) { @@ -73,7 +72,7 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(pusers.AddressOrName(std.GetOrigCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) + wugnot.TransferFrom(std.GetOrigCaller(), std.CurrentRealm().Addr(), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) From b7f9f745e2d0b36bd29548a4b0169e9d10b3fe6e Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 23 Jan 2025 15:20:10 +0100 Subject: [PATCH 71/75] feat(github-bot): avoid triage-pending if there is any tech-staff review (#3574) After a first day of experimentation, we determined we don't want the triage-pending label on PRs that have already received a review from a tech-staff member. Some additional changes: - Split between `ApprovalBy` and `ReviewBy` in reviewer.go, to distinguish between the requirements that only work with an approval and those that are more generic. - Don't request a team review if a member of the team already reviewed or is requested for review. (The bot was nagging a bit on some PRs because of this) --- contribs/github-bot/internal/config/config.go | 13 +- .../internal/requirements/reviewer.go | 223 +++++++++++++----- .../internal/requirements/reviewer_test.go | 111 +++++---- .../github-bot/internal/utils/github_const.go | 20 ++ 4 files changed, 269 insertions(+), 98 deletions(-) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index 83219c04d1d..43a505ad15a 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -4,6 +4,7 @@ import ( "github.com/gnolang/gno/contribs/github-bot/internal/client" c "github.com/gnolang/gno/contribs/github-bot/internal/conditions" r "github.com/gnolang/gno/contribs/github-bot/internal/requirements" + "github.com/gnolang/gno/contribs/github-bot/internal/utils" ) type Teams []string @@ -41,11 +42,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { Then: r.And( r.Or( r.AuthorInTeam(gh, "tech-staff"), - r.ReviewByTeamMembers(gh, "tech-staff", 1), + r.ReviewByTeamMembers(gh, "tech-staff").WithDesiredState(utils.ReviewStateApproved), ), r.Or( r.AuthorInTeam(gh, "devrels"), - r.ReviewByTeamMembers(gh, "devrels", 1), + r.ReviewByTeamMembers(gh, "devrels").WithDesiredState(utils.ReviewStateApproved), ), ), }, @@ -55,10 +56,14 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { Then: r.Never(), }, { - Description: "Pending initial approval by a review team member (and label matches review triage state)", + Description: "Pending initial approval by a review team member, or review from tech-staff", If: c.Not(c.AuthorInTeam(gh, "tech-staff")), Then: r. - If(r.Or(r.ReviewByOrgMembers(gh, 1), r.Draft())). + If(r.Or( + r.ReviewByOrgMembers(gh).WithDesiredState(utils.ReviewStateApproved), + r.ReviewByTeamMembers(gh, "tech-staff"), + r.Draft(), + )). // Either there was a first approval from a member, and we // assert that the label for triage-pending is removed... Then(r.Not(r.Label(gh, "review/triage-pending", r.LabelRemove))). diff --git a/contribs/github-bot/internal/requirements/reviewer.go b/contribs/github-bot/internal/requirements/reviewer.go index 3c0d4f98510..3e91c394fb7 100644 --- a/contribs/github-bot/internal/requirements/reviewer.go +++ b/contribs/github-bot/internal/requirements/reviewer.go @@ -2,6 +2,7 @@ package requirements import ( "fmt" + "slices" "github.com/gnolang/gno/contribs/github-bot/internal/client" "github.com/gnolang/gno/contribs/github-bot/internal/utils" @@ -10,18 +11,22 @@ import ( "github.com/xlab/treeprint" ) -// Reviewer Requirement. -type reviewByUser struct { - gh *client.GitHub - user string +// ReviewByUserRequirement asserts that there is a review by the given user, +// and if given that the review matches the desiredState. +type ReviewByUserRequirement struct { + gh *client.GitHub + user string + desiredState string } -const approvedState = "APPROVED" +var _ Requirement = &ReviewByUserRequirement{} -var _ Requirement = &reviewByUser{} - -func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { - detail := fmt.Sprintf("This user approved pull request: %s", r.user) +// IsSatisfied implements [Requirement]. +func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("This user reviewed pull request: %s", r.user) + if r.desiredState != "" { + detail += fmt.Sprintf(" (with state %q)", r.desiredState) + } // If not a dry run, make the user a reviewer if he's not already. if !r.gh.DryRun { @@ -67,7 +72,8 @@ func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tre for _, review := range reviews { if review.GetUser().GetLogin() == r.user { r.gh.Logger.Debugf("User %s already reviewed PR %d with state %s", r.user, pr.GetNumber(), review.GetState()) - return utils.AddStatusNode(review.GetState() == approvedState, detail, details) + result := r.desiredState == "" || review.GetState() == r.desiredState + return utils.AddStatusNode(result, detail, details) } } r.gh.Logger.Debugf("User %s has not reviewed PR %d yet", r.user, pr.GetNumber()) @@ -75,25 +81,54 @@ func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tre return utils.AddStatusNode(false, detail, details) } -func ReviewByUser(gh *client.GitHub, user string) Requirement { - return &reviewByUser{gh, user} +// WithDesiredState asserts that the review by the given user should also be +// of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByUserRequirement) WithDesiredState(s utils.ReviewState) *ReviewByUserRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) + } + r.desiredState = string(s) + return r } -// Reviewer Requirement. -type reviewByTeamMembers struct { - gh *client.GitHub - team string - count uint +// ReviewByUser asserts that the PR has been reviewed by the given user. +func ReviewByUser(gh *client.GitHub, user string) *ReviewByUserRequirement { + return &ReviewByUserRequirement{gh: gh, user: user} } -var _ Requirement = &reviewByTeamMembers{} +// ReviewByTeamMembersRequirement asserts that count members of the given team +// have reviewed the PR. Additionally, using desiredState, it may be required +// that the PR reviews be of that state. +type ReviewByTeamMembersRequirement struct { + gh *client.GitHub + team string + count uint + desiredState string +} -func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { - detail := fmt.Sprintf("At least %d user(s) of the team %s approved pull request", r.count, r.team) +var _ Requirement = &ReviewByTeamMembersRequirement{} - // If not a dry run, make the user a reviewer if he's not already. +// IsSatisfied implements [Requirement]. +func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("At least %d user(s) of the team %s reviewed pull request", r.count, r.team) + if r.desiredState != "" { + detail += fmt.Sprintf("(with state %q)", r.desiredState) + } + + teamMembers, err := r.gh.ListTeamMembers(r.team) + if err != nil { + r.gh.Logger.Errorf(err.Error()) + return utils.AddStatusNode(false, detail, details) + } + + // If not a dry run, request a team review if no member has reviewed yet, + // and the team review has not been requested. if !r.gh.DryRun { - requested := false + var teamRequested bool + var usersRequested []string + reviewers, err := r.gh.ListPRReviewers(pr.GetNumber()) if err != nil { r.gh.Logger.Errorf("unable to check if team %s review is already requested: %v", r.team, err) @@ -102,14 +137,28 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr for _, team := range reviewers.Teams { if team.GetSlug() == r.team { - requested = true + teamRequested = true break } } - if requested { + if !teamRequested { + for _, user := range reviewers.Users { + if slices.ContainsFunc(teamMembers, func(memb *github.User) bool { + return memb.GetID() == user.GetID() + }) { + usersRequested = append(usersRequested, user.GetLogin()) + } + } + } + + switch { + case teamRequested: r.gh.Logger.Debugf("Review of team %s already requested on PR %d", r.team, pr.GetNumber()) - } else { + case len(usersRequested) > 0: + r.gh.Logger.Debugf("Members %v of team %s already requested on (or reviewed) PR %d", + usersRequested, r.team, pr.GetNumber()) + default: r.gh.Logger.Debugf("Requesting review from team %s on PR %d", r.team, pr.GetNumber()) if _, _, err := r.gh.Client.PullRequests.RequestReviewers( r.gh.Ctx, @@ -125,72 +174,138 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr } } - // Check how many members of this team already approved this PR. - approved := uint(0) + // Check how many members of this team already reviewed this PR. + reviewCount := uint(0) reviews, err := r.gh.ListPRReviews(pr.GetNumber()) if err != nil { - r.gh.Logger.Errorf("unable to check if a member of team %s already approved this PR: %v", r.team, err) + r.gh.Logger.Errorf("unable to check if a member of team %s already reviewed this PR: %v", r.team, err) return utils.AddStatusNode(false, detail, details) } - teamMembers, err := r.gh.ListTeamMembers(r.team) - if err != nil { - r.gh.Logger.Errorf(err.Error()) - return utils.AddStatusNode(false, detail, details) + stateStr := "" + if r.desiredState != "" { + stateStr = fmt.Sprintf("%q ", r.desiredState) } - for _, review := range reviews { for _, member := range teamMembers { if review.GetUser().GetLogin() == member.GetLogin() { - if review.GetState() == approvedState { - approved += 1 + if desired := r.desiredState; desired == "" || desired == review.GetState() { + reviewCount += 1 } - r.gh.Logger.Debugf("Member %s from team %s already reviewed PR %d with state %s (%d/%d required approval(s))", member.GetLogin(), r.team, pr.GetNumber(), review.GetState(), approved, r.count) + r.gh.Logger.Debugf( + "Member %s from team %s already reviewed PR %d with state %s (%d/%d required %sreview(s))", + member.GetLogin(), r.team, pr.GetNumber(), review.GetState(), reviewCount, r.count, stateStr, + ) } } } - return utils.AddStatusNode(approved >= r.count, detail, details) + return utils.AddStatusNode(reviewCount >= r.count, detail, details) } -func ReviewByTeamMembers(gh *client.GitHub, team string, count uint) Requirement { - return &reviewByTeamMembers{gh, team, count} +// WithCount specifies the number of required reviews. +// By default, this is 1. +func (r *ReviewByTeamMembersRequirement) WithCount(n uint) *ReviewByTeamMembersRequirement { + if n < 1 { + panic("number of required reviews should be at least 1") + } + r.count = n + return r } -type reviewByOrgMembers struct { - gh *client.GitHub - count uint +// WithDesiredState asserts that the reviews should also be of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByTeamMembersRequirement) WithDesiredState(s utils.ReviewState) *ReviewByTeamMembersRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) + } + r.desiredState = string(s) + return r } -var _ Requirement = &reviewByOrgMembers{} +// ReviewByTeamMembers specifies that the given pull request should receive at +// least one review from a member of the given team. +// +// The number of required reviews, or the state of the reviews (e.g., to filter +// only for approval reviews) can be modified using WithCount and WithDesiredState. +func ReviewByTeamMembers(gh *client.GitHub, team string) *ReviewByTeamMembersRequirement { + return &ReviewByTeamMembersRequirement{ + gh: gh, + team: team, + count: 1, + } +} -func (r *reviewByOrgMembers) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { - detail := fmt.Sprintf("At least %d user(s) of the organization approved the pull request", r.count) +// ReviewByOrgMembersRequirement asserts that the given PR has been reviewed by +// at least count members of the given organization, filtering for PR reviews +// with state desiredState. +type ReviewByOrgMembersRequirement struct { + gh *client.GitHub + count uint + desiredState string +} - // Check how many members of this team already approved this PR. - approved := uint(0) +var _ Requirement = &ReviewByOrgMembersRequirement{} + +// IsSatisfied implements [Requirement]. +func (r *ReviewByOrgMembersRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("At least %d user(s) of the organization reviewed the pull request", r.count) + if r.desiredState != "" { + detail += fmt.Sprintf(" (with state %q)", r.desiredState) + } + + // Check how many members of this team already reviewed this PR. + reviewed := uint(0) reviews, err := r.gh.ListPRReviews(pr.GetNumber()) if err != nil { r.gh.Logger.Errorf("unable to check number of reviews on this PR: %v", err) return utils.AddStatusNode(false, detail, details) } + stateStr := "" + if r.desiredState != "" { + stateStr = fmt.Sprintf("%q ", r.desiredState) + } for _, review := range reviews { if review.GetAuthorAssociation() == "MEMBER" { - if review.GetState() == approvedState { - approved++ + if r.desiredState == "" || review.GetState() == r.desiredState { + reviewed++ } r.gh.Logger.Debugf( - "Member %s already reviewed PR %d with state %s (%d/%d required approval(s))", + "Member %s already reviewed PR %d with state %s (%d/%d required %sreviews)", review.GetUser().GetLogin(), pr.GetNumber(), review.GetState(), - approved, r.count, + reviewed, r.count, stateStr, ) } } - return utils.AddStatusNode(approved >= r.count, detail, details) + return utils.AddStatusNode(reviewed >= r.count, detail, details) +} + +// WithCount specifies the number of required reviews. +// By default, this is 1. +func (r *ReviewByOrgMembersRequirement) WithCount(n uint) *ReviewByOrgMembersRequirement { + if n < 1 { + panic("number of required reviews should be at least 1") + } + r.count = n + return r +} + +// WithDesiredState asserts that the reviews should also be of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByOrgMembersRequirement) WithDesiredState(s utils.ReviewState) *ReviewByOrgMembersRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) + } + r.desiredState = string(s) + return r } -func ReviewByOrgMembers(gh *client.GitHub, count uint) Requirement { - return &reviewByOrgMembers{gh, count} +// ReviewByOrgMembers asserts that at least 1 member of the organization +// reviewed this PR. +func ReviewByOrgMembers(gh *client.GitHub) *ReviewByOrgMembersRequirement { + return &ReviewByOrgMembersRequirement{gh: gh, count: 1} } diff --git a/contribs/github-bot/internal/requirements/reviewer_test.go b/contribs/github-bot/internal/requirements/reviewer_test.go index 98f68384d8f..235dca14034 100644 --- a/contribs/github-bot/internal/requirements/reviewer_test.go +++ b/contribs/github-bot/internal/requirements/reviewer_test.go @@ -36,7 +36,7 @@ func TestReviewByUser(t *testing.T) { State: github.String("APPROVED"), }, { User: &github.User{Login: github.String("anotherOne")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), }, } @@ -87,7 +87,7 @@ func TestReviewByUser(t *testing.T) { pr := &github.PullRequest{} details := treeprint.New() - requirement := ReviewByUser(gh, testCase.user) + requirement := ReviewByUser(gh, testCase.user).WithDesiredState(utils.ReviewStateApproved) assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) @@ -99,13 +99,25 @@ func TestReviewByUser(t *testing.T) { func TestReviewByTeamMembers(t *testing.T) { t.Parallel() - reviewers := github.Reviewers{ - Teams: []*github.Team{ - {Slug: github.String("team1")}, - {Slug: github.String("team2")}, - {Slug: github.String("team3")}, - }, - } + var ( + reviewers = github.Reviewers{ + Teams: []*github.Team{ + {Slug: github.String("team1")}, + {Slug: github.String("team2")}, + {Slug: github.String("team3")}, + }, + } + noReviewers = github.Reviewers{} + userReviewers = github.Reviewers{ + Users: []*github.User{ + {Login: github.String("user1")}, + {Login: github.String("user2")}, + {Login: github.String("user3")}, + {Login: github.String("user4")}, + {Login: github.String("user5")}, + }, + } + ) members := map[string][]*github.User{ "team1": { @@ -136,26 +148,37 @@ func TestReviewByTeamMembers(t *testing.T) { State: github.String("APPROVED"), }, { User: &github.User{Login: github.String("user4")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), }, { User: &github.User{Login: github.String("user5")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), }, } + const ( + notSatisfied = 0 + satisfied = 1 + withRequest = 2 + ) + for _, testCase := range []struct { - name string - team string - count uint - isSatisfied bool - testRequest bool + name string + team string + count uint + state utils.ReviewState + reviewers github.Reviewers + expectedResult byte }{ - {"3/3 team members approved;", "team1", 3, true, false}, - {"1/1 team member approved", "team2", 1, true, false}, - {"1/2 team member approved", "team2", 2, false, false}, - {"0/1 team member approved", "team3", 1, false, false}, - {"0/1 team member approved with request", "team3", 1, false, true}, - {"team doesn't exist with request", "team4", 1, false, true}, + {"3/3 team members approved", "team1", 3, utils.ReviewStateApproved, reviewers, satisfied}, + {"3/3 team members approved (with user reviewers)", "team1", 3, utils.ReviewStateApproved, userReviewers, satisfied}, + {"1/1 team member approved", "team2", 1, utils.ReviewStateApproved, reviewers, satisfied}, + {"1/2 team member approved", "team2", 2, utils.ReviewStateApproved, reviewers, notSatisfied}, + {"0/1 team member approved", "team3", 1, utils.ReviewStateApproved, reviewers, notSatisfied}, + {"0/1 team member approved with request", "team3", 1, utils.ReviewStateApproved, noReviewers, notSatisfied | withRequest}, + {"team doesn't exist with request", "team4", 1, utils.ReviewStateApproved, noReviewers, notSatisfied | withRequest}, + {"3/3 team members reviewed", "team2", 3, "", reviewers, satisfied}, + {"2/2 team members rejected", "team2", 2, utils.ReviewStateChangesRequested, reviewers, satisfied}, + {"1/3 team members approved", "team2", 3, utils.ReviewStateApproved, reviewers, notSatisfied}, } { t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -170,11 +193,7 @@ func TestReviewByTeamMembers(t *testing.T) { }, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if firstRequest { - if testCase.testRequest { - w.Write(mock.MustMarshal(github.Reviewers{})) - } else { - w.Write(mock.MustMarshal(reviewers)) - } + w.Write(mock.MustMarshal(testCase.reviewers)) firstRequest = false } else { requested = true @@ -205,11 +224,18 @@ func TestReviewByTeamMembers(t *testing.T) { pr := &github.PullRequest{} details := treeprint.New() - requirement := ReviewByTeamMembers(gh, testCase.team, testCase.count) + requirement := ReviewByTeamMembers(gh, testCase.team). + WithCount(testCase.count). + WithDesiredState(testCase.state) - assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) - assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) - assert.Equal(t, testCase.testRequest, requested, fmt.Sprintf("requirement should have requested to create item: %t", testCase.testRequest)) + expSatisfied := testCase.expectedResult&satisfied != 0 + expRequested := testCase.expectedResult&withRequest > 0 + assert.Equal(t, expSatisfied, requirement.IsSatisfied(pr, details), + "requirement should have a satisfied status: %t", expSatisfied) + assert.True(t, utils.TestLastNodeStatus(t, expSatisfied, details), + "requirement details should have a status: %t", expSatisfied) + assert.Equal(t, expRequested, requested, + "requirement should have requested to create item: %t", expRequested) }) } } @@ -232,23 +258,26 @@ func TestReviewByOrgMembers(t *testing.T) { AuthorAssociation: github.String("MEMBER"), }, { User: &github.User{Login: github.String("user4")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), AuthorAssociation: github.String("MEMBER"), }, { User: &github.User{Login: github.String("user5")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), AuthorAssociation: github.String("NONE"), }, } for _, testCase := range []struct { - name string - count uint - isSatisfied bool + name string + count uint + desiredState utils.ReviewState + isSatisfied bool }{ - {"2/3 org members approved", 3, false}, - {"2/2 org members approved", 2, true}, - {"2/1 org members approved", 1, true}, + {"2/3 org members approved", 3, utils.ReviewStateApproved, false}, + {"2/2 org members approved", 2, utils.ReviewStateApproved, true}, + {"2/1 org members approved", 1, utils.ReviewStateApproved, true}, + {"3/3 org members reviewed", 3, "", true}, + {"3/4 org members reviewed", 4, "", false}, } { t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -271,7 +300,9 @@ func TestReviewByOrgMembers(t *testing.T) { pr := &github.PullRequest{} details := treeprint.New() - requirement := ReviewByOrgMembers(gh, testCase.count) + requirement := ReviewByOrgMembers(gh). + WithCount(testCase.count). + WithDesiredState(testCase.desiredState) assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) diff --git a/contribs/github-bot/internal/utils/github_const.go b/contribs/github-bot/internal/utils/github_const.go index f030d9365f7..423fcb2a4b0 100644 --- a/contribs/github-bot/internal/utils/github_const.go +++ b/contribs/github-bot/internal/utils/github_const.go @@ -13,3 +13,23 @@ const ( PRStateOpen = "open" PRStateClosed = "closed" ) + +// ReviewState is the state of a PR review. See: +// https://docs.github.com/en/graphql/reference/enums#pullrequestreviewstate +type ReviewState string + +// Possible values of ReviewState. +const ( + ReviewStateApproved ReviewState = "APPROVED" + ReviewStateChangesRequested ReviewState = "CHANGES_REQUESTED" + ReviewStateCommented ReviewState = "COMMENTED" +) + +// Valid determines whether the ReviewState is one of the known ReviewStates. +func (r ReviewState) Valid() bool { + switch r { + case ReviewStateApproved, ReviewStateChangesRequested, ReviewStateCommented: + return true + } + return false +} From fd244862eb9124977c207ba2352308e21f1d38fd Mon Sep 17 00:00:00 2001 From: n0izn0iz Date: Thu, 23 Jan 2025 17:51:52 +0100 Subject: [PATCH 72/75] fix: remove import cycles (#3304) Depends on #3323 - add test in `examples/no_cycles_test.go` to detect import cycles in stdlibs and examples - add `matchString` native injection in the `testing` stdlib to avoid a cycle in `regexp` tests - remove other import cycles Go never allows import cycles. Our stdlibs have a lot of import cycles, and some examples import self which is not allowed in golang either. Keeping support for import cycles in stdlib and importing self will require a lot of hacky and weird logic in generic package loading code so I try to tackle this first. TODO: - [x] fix tests - [x] ~~check cycles with the test stdlibs overlay applied~~ -> be explicit about the lack of support for modifying imports in testing stdlibs overlay --------- Signed-off-by: Norman Meier Signed-off-by: Norman Signed-off-by: Norman Co-authored-by: Morgan Co-authored-by: Norman --- examples/gno.land/p/moul/md/md_test.gno | 2 +- .../gno.land/r/gnoland/faucet/faucet_test.gno | 2 +- examples/no_cycles_test.go | 214 ++++++++++ gnovm/pkg/packages/imports_test.go | 17 +- gnovm/stdlibs/crypto/ed25519/ed25519_test.gno | 2 +- gnovm/stdlibs/crypto/sha256/sha256_test.gno | 2 +- gnovm/stdlibs/encoding/binary/binary_test.gno | 2 +- gnovm/stdlibs/generated.go | 38 ++ gnovm/stdlibs/hash/marshal_test.gno | 2 +- gnovm/stdlibs/io/export_test.gno | 10 + gnovm/stdlibs/io/io_test.gno | 169 ++++---- gnovm/stdlibs/io/multi_test.gno | 81 ++-- gnovm/stdlibs/math/bits/bits_test.gno | 381 +++++++++--------- gnovm/stdlibs/math/bits/export_test.gno | 5 + gnovm/stdlibs/strconv/atob_test.gno | 15 +- gnovm/stdlibs/strconv/atof_test.gno | 275 ++++++------- gnovm/stdlibs/strconv/atoi_test.gno | 315 +++++++-------- gnovm/stdlibs/strconv/decimal_test.gno | 13 +- gnovm/stdlibs/strconv/ftoa_test.gno | 41 +- gnovm/stdlibs/strconv/ftoaryu_test.gno | 7 +- gnovm/stdlibs/strconv/itoa_test.gno | 37 +- gnovm/stdlibs/strconv/quote_test.gno | 59 +-- gnovm/stdlibs/strconv/strconv_test.gno | 33 +- gnovm/stdlibs/strings/printtrie_impl_test.gno | 29 ++ gnovm/stdlibs/strings/printtrie_test.gno | 35 +- gnovm/stdlibs/testing/match.gno | 5 +- gnovm/stdlibs/testing/testing.gno | 3 + gnovm/stdlibs/testing/testing.go | 8 + gnovm/tests/stdlibs/README.md | 2 + gnovm/tests/stdlibs/generated.go | 38 ++ .../tests/stdlibs/testing/native_testing.gno | 2 + gnovm/tests/stdlibs/testing/native_testing.go | 13 +- 32 files changed, 1101 insertions(+), 756 deletions(-) create mode 100644 examples/no_cycles_test.go create mode 100644 gnovm/stdlibs/io/export_test.gno create mode 100644 gnovm/stdlibs/strings/printtrie_impl_test.gno diff --git a/examples/gno.land/p/moul/md/md_test.gno b/examples/gno.land/p/moul/md/md_test.gno index 144ae58d918..3e8e70efaf6 100644 --- a/examples/gno.land/p/moul/md/md_test.gno +++ b/examples/gno.land/p/moul/md/md_test.gno @@ -1,4 +1,4 @@ -package md +package md_test import ( "testing" diff --git a/examples/gno.land/r/gnoland/faucet/faucet_test.gno b/examples/gno.land/r/gnoland/faucet/faucet_test.gno index cecbb2ebcd6..5672f317469 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet_test.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet_test.gno @@ -1,4 +1,4 @@ -package faucet +package faucet_test import ( "std" diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go new file mode 100644 index 00000000000..7cc6fbfd183 --- /dev/null +++ b/examples/no_cycles_test.go @@ -0,0 +1,214 @@ +package examples_test + +import ( + "fmt" + "io/fs" + "os" + pathlib "path" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/stretchr/testify/require" +) + +// XXX: move this into `gno lint` + +var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "internal/os_test"} + +// TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples +func TestNoCycles(t *testing.T) { + // find stdlibs + gnoRoot := gnoenv.RootDir() + pkgs, err := listPkgs(gnomod.Pkg{ + Dir: filepath.Join(gnoRoot, "gnovm", "stdlibs"), + Name: "", + }) + require.NoError(t, err) + + // find examples + examples, err := gnomod.ListPkgs(filepath.Join(gnoRoot, "examples")) + require.NoError(t, err) + for _, example := range examples { + if example.Draft { + continue + } + examplePkgs, err := listPkgs(example) + require.NoError(t, err) + pkgs = append(pkgs, examplePkgs...) + } + + // detect cycles + visited := make(map[string]bool) + for _, p := range pkgs { + require.NoError(t, detectCycles(p, pkgs, visited)) + } +} + +// detectCycles detects import cycles +// +// We need to check +// 3 kinds of nodes +// +// - normal pkg: compiled source +// +// - xtest pkg: external test source (include xtests and filetests), can be treated as their own package +// +// - test pkg: embedded test sources, +// these should not have their corresponding normal package in their dependencies tree +// +// The tricky thing is that we need to split test sources and normal source +// while not considering them as distincitive packages. +// Otherwise we will have false positive for example if we have these edges: +// +// - foo_pkg/foo_test.go imports bar_pkg +// +// - bar_pkg/bar_test.go import foo_pkg +// +// In go, the above example is allowed +// but the following is not +// +// - foo_pkg/foo.go imports bar_pkg +// +// - bar_pkg/bar_test.go imports foo_pkg +func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { + // check cycles in package's sources + stack := []string{} + if err := visitPackage(root, pkgs, visited, stack); err != nil { + return fmt.Errorf("pkgsrc import: %w", err) + } + // check cycles in external tests' dependencies we might have missed + if err := visitImports([]packages.FileKind{packages.FileKindXTest, packages.FileKindFiletest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("xtest import: %w", err) + } + + // check cycles in tests' imports by marking the current package as visited while visiting the tests' imports + // we also consider PackageSource imports here because tests can call package code + visited = map[string]bool{root.PkgPath: true} + stack = []string{root.PkgPath} + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource, packages.FileKindTest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("test import: %w", err) + } + + return nil +} + +// visitImports resolves and visits imports by kinds +func visitImports(kinds []packages.FileKind, root testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + for _, imp := range root.Imports.Merge(kinds...) { + if slices.Contains(injectedTestingLibs, imp.PkgPath) { + continue + } + idx := slices.IndexFunc(pkgs, func(p testPkg) bool { return p.PkgPath == imp.PkgPath }) + if idx == -1 { + return fmt.Errorf("import %q not found for %q tests", imp.PkgPath, root.PkgPath) + } + if err := visitPackage(pkgs[idx], pkgs, visited, stack); err != nil { + return fmt.Errorf("test import error: %w", err) + } + } + + return nil +} + +// visitNode visits a package and its imports recursively. It only considers imports in PackageSource +func visitPackage(pkg testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + if slices.Contains(stack, pkg.PkgPath) { + return fmt.Errorf("cycle detected: %s -> %s", strings.Join(stack, " -> "), pkg.PkgPath) + } + if visited[pkg.PkgPath] { + return nil + } + + visited[pkg.PkgPath] = true + stack = append(stack, pkg.PkgPath) + + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource}, pkg, pkgs, visited, stack); err != nil { + return err + } + + return nil +} + +type testPkg struct { + Dir string + PkgPath string + Imports packages.ImportsMap +} + +// listPkgs lists all packages in rootMod +func listPkgs(rootMod gnomod.Pkg) ([]testPkg, error) { + res := []testPkg{} + rootDir := rootMod.Dir + visited := map[string]struct{}{} + if err := fs.WalkDir(os.DirFS(rootDir), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), ".gno") { + return nil + } + subPath := filepath.Dir(path) + dir := filepath.Join(rootDir, subPath) + if _, ok := visited[dir]; ok { + return nil + } + visited[dir] = struct{}{} + + subPkgPath := pathlib.Join(rootMod.Name, subPath) + + pkg := testPkg{ + Dir: dir, + PkgPath: subPkgPath, + } + + memPkg, err := readPkg(pkg.Dir, pkg.PkgPath) + if err != nil { + return fmt.Errorf("read pkg %q: %w", pkg.Dir, err) + } + pkg.Imports, err = packages.Imports(memPkg, nil) + if err != nil { + return fmt.Errorf("list imports of %q: %w", memPkg.Path, err) + } + + res = append(res, pkg) + return nil + }); err != nil { + return nil, fmt.Errorf("walk dirs at %q: %w", rootDir, err) + } + return res, nil +} + +// readPkg reads the sources of a package. It includes all .gno files but ignores the package name +func readPkg(dir string, pkgPath string) (*gnovm.MemPackage, error) { + list, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + memPkg := &gnovm.MemPackage{Path: pkgPath} + for _, entry := range list { + fpath := filepath.Join(dir, entry.Name()) + if !strings.HasSuffix(fpath, ".gno") { + continue + } + fname := filepath.Base(fpath) + bz, err := os.ReadFile(fpath) + if err != nil { + return nil, err + } + memPkg.Files = append(memPkg.Files, + &gnovm.MemFile{ + Name: fname, + Body: string(bz), + }) + } + return memPkg, nil +} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index f9f58b967c8..444964fbbf3 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,4 +1,4 @@ -package packages +package packages_test import ( "os" @@ -6,6 +6,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -112,22 +113,22 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := map[FileKind][]string{ - FileKindPackageSource: { + expected := map[packages.FileKind][]string{ + packages.FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", "std", }, - FileKindTest: { + packages.FileKindTest: { "gno.land/p/demo/testpkg", "testing", }, - FileKindXTest: { + packages.FileKindXTest: { "gno.land/p/demo/testpkg", "gno.land/p/demo/xtestdep", "testing", }, - FileKindFiletest: { + packages.FileKindFiletest: { "gno.land/p/demo/filetestdep", }, } @@ -145,11 +146,11 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - importsMap, err := Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) require.NoError(t, err) // ignore specs - got := map[FileKind][]string{} + got := map[packages.FileKind][]string{} for key, vals := range importsMap { stringVals := make([]string, len(vals)) for i, val := range vals { diff --git a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno index 615ea0eb6a7..db0cd1b9c1a 100644 --- a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno +++ b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno @@ -1,4 +1,4 @@ -package ed25519 +package ed25519_test import ( "crypto/ed25519" diff --git a/gnovm/stdlibs/crypto/sha256/sha256_test.gno b/gnovm/stdlibs/crypto/sha256/sha256_test.gno index 809f826f007..21e376736a4 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256_test.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256_test.gno @@ -1,4 +1,4 @@ -package sha256 +package sha256_test import ( "crypto/sha256" diff --git a/gnovm/stdlibs/encoding/binary/binary_test.gno b/gnovm/stdlibs/encoding/binary/binary_test.gno index 5407eb5061b..7905c381189 100644 --- a/gnovm/stdlibs/encoding/binary/binary_test.gno +++ b/gnovm/stdlibs/encoding/binary/binary_test.gno @@ -1,4 +1,4 @@ -package binary +package binary_test import ( "bytes" diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index ab35fc6b6bf..6bd45de3589 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -800,6 +800,44 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := libs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "time", "now", diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno index bf823d97cce..d15c02e87b5 100644 --- a/gnovm/stdlibs/hash/marshal_test.gno +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -6,7 +6,7 @@ // BinaryMarshaler, BinaryUnmarshaler, // and lock in the current representations. -package hash +package hash_test import ( "bytes" diff --git a/gnovm/stdlibs/io/export_test.gno b/gnovm/stdlibs/io/export_test.gno new file mode 100644 index 00000000000..06853f975f5 --- /dev/null +++ b/gnovm/stdlibs/io/export_test.gno @@ -0,0 +1,10 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package io + +// exported for test +var ErrInvalidWrite = errInvalidWrite +var ErrWhence = errWhence +var ErrOffset = errOffset diff --git a/gnovm/stdlibs/io/io_test.gno b/gnovm/stdlibs/io/io_test.gno index 4915982057b..38e535b3cfb 100644 --- a/gnovm/stdlibs/io/io_test.gno +++ b/gnovm/stdlibs/io/io_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "errors" "fmt" + "io" "strings" "testing" ) @@ -15,8 +16,8 @@ import ( // A version of bytes.Buffer without ReadFrom and WriteTo type Buffer struct { bytes.Buffer - ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. - WriterTo // conflicts with and hides bytes.Buffer's WriterTo. + io.ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. + io.WriterTo // conflicts with and hides bytes.Buffer's WriterTo. } // Simple tests, primarily to verify the ReadFrom and WriteTo callouts inside Copy, CopyBuffer and CopyN. @@ -25,7 +26,7 @@ func TestCopy(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -35,12 +36,12 @@ func TestCopyNegative(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello") - Copy(wb, &LimitedReader{R: rb, N: -1}) + io.Copy(wb, &io.LimitedReader{R: rb, N: -1}) if wb.String() != "" { t.Errorf("Copy on LimitedReader with N<0 copied data") } - CopyN(wb, rb, -1) + io.CopyN(wb, rb, -1) if wb.String() != "" { t.Errorf("CopyN with N<0 copied data") } @@ -50,7 +51,7 @@ func TestCopyBuffer(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. + io.CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -60,7 +61,7 @@ func TestCopyBufferNil(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, nil) // Should allocate a buffer. + io.CopyBuffer(wb, rb, nil) // Should allocate a buffer. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -70,7 +71,7 @@ func TestCopyReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -80,7 +81,7 @@ func TestCopyWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -92,7 +93,7 @@ type writeToChecker struct { writeToCalled bool } -func (wt *writeToChecker) WriteTo(w Writer) (int64, error) { +func (wt *writeToChecker) WriteTo(w io.Writer) (int64, error) { wt.writeToCalled = true return wt.Buffer.WriteTo(w) } @@ -104,7 +105,7 @@ func TestCopyPriority(t *testing.T) { rb := new(writeToChecker) wb := new(bytes.Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } else if !rb.writeToCalled { @@ -134,7 +135,7 @@ func (w errWriter) Write([]byte) (int, error) { func TestCopyReadErrWriteErr(t *testing.T) { er, ew := errors.New("readError"), errors.New("writeError") r, w := zeroErrReader{err: er}, errWriter{err: ew} - n, err := Copy(w, r) + n, err := io.Copy(w, r) if n != 0 || err != ew { t.Errorf("Copy(zeroErrReader, errWriter) = %d, %v; want 0, writeError", n, err) } @@ -144,7 +145,7 @@ func TestCopyN(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -154,7 +155,7 @@ func TestCopyNReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -164,7 +165,7 @@ func TestCopyNWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -177,7 +178,7 @@ func BenchmarkCopyNSmall(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 512) + io.CopyN(buf, rd, 512) rd.Reset(bs) } } @@ -189,13 +190,13 @@ func BenchmarkCopyNLarge(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 32*1024) + io.CopyN(buf, rd, 32*1024) rd.Reset(bs) } } type noReadFrom struct { - w Writer + w io.Writer } func (w *noReadFrom) Write(p []byte) (n int, err error) { @@ -214,32 +215,32 @@ func TestCopyNEOF(t *testing.T) { b := new(bytes.Buffer) - n, err := CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) + n, err := io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) if n != 3 || err != nil { t.Errorf("CopyN(noReadFrom, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) - if n != 3 || err != EOF { + n, err = io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) + if n != 3 || err != io.EOF { t.Errorf("CopyN(noReadFrom, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 3) // b has read from + n, err = io.CopyN(b, strings.NewReader("foo"), 3) // b has read from if n != 3 || err != nil { t.Errorf("CopyN(bytes.Buffer, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 4) // b has read from - if n != 3 || err != EOF { + n, err = io.CopyN(b, strings.NewReader("foo"), 4) // b has read from + if n != 3 || err != io.EOF { t.Errorf("CopyN(bytes.Buffer, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, wantedAndErrReader{}, 5) + n, err = io.CopyN(b, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(bytes.Buffer, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) + n, err = io.CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(noReadFrom, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } @@ -267,7 +268,7 @@ func (r *dataAndErrorBuffer) Read(p []byte) (n int, err error) { func TestReadAtLeastWithDataAndEOF(t *testing.T) { var rb dataAndErrorBuffer - rb.err = EOF + rb.err = io.EOF testReadAtLeast(t, &rb) } @@ -277,41 +278,41 @@ func TestReadAtLeastWithDataAndError(t *testing.T) { testReadAtLeast(t, &rb) } -func testReadAtLeast(t *testing.T, rb ReadWriter) { +func testReadAtLeast(t *testing.T, rb io.ReadWriter) { rb.Write([]byte("0123")) buf := make([]byte, 2) - n, err := ReadAtLeast(rb, buf, 2) + n, err := io.ReadAtLeast(rb, buf, 2) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 4) - if err != ErrShortBuffer { + n, err = io.ReadAtLeast(rb, buf, 4) + if err != io.ErrShortBuffer { t.Errorf("expected `ErrShortBuffer` got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 1) + n, err = io.ReadAtLeast(rb, buf, 1) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 2) - if err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + if err != io.EOF { t.Errorf("expected EOF, got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } rb.Write([]byte("4")) - n, err = ReadAtLeast(rb, buf, 2) - want := ErrUnexpectedEOF - if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + want := io.ErrUnexpectedEOF + if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != io.EOF { want = rb.err } if err != want { @@ -338,7 +339,7 @@ func TestTeeReader(t *testing.T) { if !bytes.Equal(wb.Bytes(), src) { t.Errorf("bytes written = %q want %q", wb.Bytes(), src) } - if n, err := r.Read(dst); n != 0 || err != EOF { + if n, err := r.Read(dst); n != 0 || err != io.EOF { t.Errorf("r.Read at EOF = %d, %v want 0, EOF", n, err) } rb = bytes.NewBuffer(src) @@ -362,22 +363,22 @@ func TestSectionReader_ReadAt(t *testing.T) { exp string err error }{ - {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: EOF}, + {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat), bufLen: 0, at: 0, exp: "", err: nil}, - {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: EOF}, + {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat) + 2, bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 0, exp: dat[:len(dat)/2], err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[2 : 2+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[5 : 5+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 - 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: nil}, - {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: EOF}, + {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: io.EOF}, } for i, tt := range tests { r := strings.NewReader(tt.data) - s := NewSectionReader(r, int64(tt.off), int64(tt.n)) + s := io.NewSectionReader(r, int64(tt.off), int64(tt.n)) buf := make([]byte, tt.bufLen) if n, err := s.ReadAt(buf, int64(tt.at)); n != len(tt.exp) || string(buf[:n]) != tt.exp || err != tt.err { t.Fatalf("%d: ReadAt(%d) = %q, %v; expected %q, %v", i, tt.at, buf[:n], err, tt.exp, tt.err) @@ -388,9 +389,9 @@ func TestSectionReader_ReadAt(t *testing.T) { func TestSectionReader_Seek(t *testing.T) { // Verifies that NewSectionReader's Seeker behaves like bytes.NewReader (which is like strings.NewReader) br := bytes.NewReader([]byte("foo")) - sr := NewSectionReader(br, 0, int64(len("foo"))) + sr := io.NewSectionReader(br, 0, int64(len("foo"))) - for _, whence := range []int{SeekStart, SeekCurrent, SeekEnd} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent, io.SeekEnd} { for offset := int64(-3); offset <= 4; offset++ { brOff, brErr := br.Seek(offset, whence) srOff, srErr := sr.Seek(offset, whence) @@ -402,13 +403,13 @@ func TestSectionReader_Seek(t *testing.T) { } // And verify we can just seek past the end and get an EOF - got, err := sr.Seek(100, SeekStart) + got, err := sr.Seek(100, io.SeekStart) if err != nil || got != 100 { t.Errorf("Seek = %v, %v; want 100, nil", got, err) } n, err := sr.Read(make([]byte, 10)) - if n != 0 || err != EOF { + if n != 0 || err != io.EOF { t.Errorf("Read = %v, %v; want 0, EOF", n, err) } } @@ -424,7 +425,7 @@ func TestSectionReader_Size(t *testing.T) { for _, tt := range tests { r := strings.NewReader(tt.data) - sr := NewSectionReader(r, 0, int64(len(tt.data))) + sr := io.NewSectionReader(r, 0, int64(len(tt.data))) if got := sr.Size(); got != tt.want { t.Errorf("Size = %v; want %v", got, tt.want) } @@ -442,11 +443,11 @@ func (w largeWriter) Write(p []byte) (int, error) { } func TestCopyLargeWriter(t *testing.T) { - want := errInvalidWrite + want := io.ErrInvalidWrite rb := new(Buffer) wb := largeWriter{} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } @@ -454,7 +455,7 @@ func TestCopyLargeWriter(t *testing.T) { rb = new(Buffer) wb = largeWriter{err: want} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } } @@ -462,18 +463,18 @@ func TestCopyLargeWriter(t *testing.T) { func TestNopCloserWriterToForwarding(t *testing.T) { for _, tc := range [...]struct { Name string - r Reader + r io.Reader }{ - {"not a WriterTo", Reader(nil)}, + {"not a WriterTo", io.Reader(nil)}, {"a WriterTo", struct { - Reader - WriterTo + io.Reader + io.WriterTo }{}}, } { - nc := NopCloser(tc.r) + nc := io.NopCloser(tc.r) - _, expected := tc.r.(WriterTo) - _, got := nc.(WriterTo) + _, expected := tc.r.(io.WriterTo) + _, got := nc.(io.WriterTo) if expected != got { t.Errorf("NopCloser incorrectly forwards WriterTo for %s, got %t want %t", tc.Name, got, expected) } @@ -488,28 +489,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err) // } // defer tmpfile.Close() -// w := NewOffsetWriter(tmpfile, 0) +// w := io.NewOffsetWriter(tmpfile, 0) // // Should throw error errWhence if whence is not valid // t.Run("errWhence", func(t *testing.T) { // for _, whence := range []int{-3, -2, -1, 3, 4, 5} { // var offset int64 = 0 // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errWhence { +// if gotOff != 0 || gotErr != io.ErrWhence { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, errWhence) +// whence, offset, gotOff, gotErr, 0, io.ErrWhence) // } // } // }) // // Should throw error errOffset if offset is negative // t.Run("errOffset", func(t *testing.T) { -// for _, whence := range []int{SeekStart, SeekCurrent} { +// for _, whence := range []int{io.SeekStart, io.SeekCurrent} { // for offset := int64(-3); offset < 0; offset++ { // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errOffset { +// if gotOff != 0 || gotErr != io.ErrOffset { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, ErrOffset) +// whence, offset, gotOff, gotErr, 0, io.ErrOffset) // } // } // } @@ -523,12 +524,12 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // returnOff int64 // }{ // // keep in order -// {whence: SeekStart, offset: 1, returnOff: 1}, -// {whence: SeekStart, offset: 2, returnOff: 2}, -// {whence: SeekStart, offset: 3, returnOff: 3}, -// {whence: SeekCurrent, offset: 1, returnOff: 4}, -// {whence: SeekCurrent, offset: 2, returnOff: 6}, -// {whence: SeekCurrent, offset: 3, returnOff: 9}, +// {whence: io.SeekStart, offset: 1, returnOff: 1}, +// {whence: io.SeekStart, offset: 2, returnOff: 2}, +// {whence: io.SeekStart, offset: 3, returnOff: 3}, +// {whence: io.SeekCurrent, offset: 1, returnOff: 4}, +// {whence: io.SeekCurrent, offset: 2, returnOff: 6}, +// {whence: io.SeekCurrent, offset: 3, returnOff: 9}, // } // for idx, tt := range tests { // gotOff, gotErr := w.Seek(tt.offset, tt.whence) @@ -546,28 +547,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // to use the original approach instead of this method. (just un-comment the test above) func TestOffsetWriter_Seek(t *testing.T) { buf := new(bytes.Buffer) - w := NewOffsetWriter(testWriterAt{buf}, 0) + w := io.NewOffsetWriter(testWriterAt{buf}, 0) // Should throw error errWhence if whence is not valid t.Run("errWhence", func(t *testing.T) { for _, whence := range []int{-3, -2, -1, 3, 4, 5} { var offset int64 = 0 gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errWhence { + if gotOff != 0 || gotErr != io.ErrWhence { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errWhence) + whence, offset, gotOff, gotErr, 0, io.ErrWhence) } } }) // Should throw error errOffset if offset is negative t.Run("errOffset", func(t *testing.T) { - for _, whence := range []int{SeekStart, SeekCurrent} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent} { for offset := int64(-3); offset < 0; offset++ { gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errOffset { + if gotOff != 0 || gotErr != io.ErrOffset { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errOffset) + whence, offset, gotOff, gotErr, 0, io.ErrOffset) } } } @@ -579,12 +580,12 @@ func TestOffsetWriter_Seek(t *testing.T) { whence int returnOff int64 }{ - {whence: SeekStart, offset: 1, returnOff: 1}, - {whence: SeekStart, offset: 2, returnOff: 2}, - {whence: SeekStart, offset: 3, returnOff: 3}, - {whence: SeekCurrent, offset: 1, returnOff: 4}, - {whence: SeekCurrent, offset: 2, returnOff: 6}, - {whence: SeekCurrent, offset: 3, returnOff: 9}, + {whence: io.SeekStart, offset: 1, returnOff: 1}, + {whence: io.SeekStart, offset: 2, returnOff: 2}, + {whence: io.SeekStart, offset: 3, returnOff: 3}, + {whence: io.SeekCurrent, offset: 1, returnOff: 4}, + {whence: io.SeekCurrent, offset: 2, returnOff: 6}, + {whence: io.SeekCurrent, offset: 3, returnOff: 9}, } for idx, tt := range tests { gotOff, gotErr := w.Seek(tt.offset, tt.whence) diff --git a/gnovm/stdlibs/io/multi_test.gno b/gnovm/stdlibs/io/multi_test.gno index f39895ea776..908dffd3cce 100644 --- a/gnovm/stdlibs/io/multi_test.gno +++ b/gnovm/stdlibs/io/multi_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "crypto/sha256" "fmt" + "io" "strings" "testing" ) @@ -17,14 +18,14 @@ type Stringer interface { } func TestMultiReader(t *testing.T) { - var mr Reader + var mr io.Reader var buf []byte nread := 0 withFooBar := func(tests func()) { r1 := strings.NewReader("foo ") r2 := strings.NewReader("") r3 := strings.NewReader("bar") - mr = MultiReader(r1, r2, r3) + mr = io.MultiReader(r1, r2, r3) buf = make([]byte, 20) tests() } @@ -50,13 +51,13 @@ func TestMultiReader(t *testing.T) { expectRead(2, "fo", nil) expectRead(5, "o ", nil) expectRead(5, "bar", nil) - expectRead(5, "", EOF) + expectRead(5, "", io.EOF) }) withFooBar(func() { expectRead(4, "foo ", nil) expectRead(1, "b", nil) expectRead(3, "ar", nil) - expectRead(1, "", EOF) + expectRead(1, "", io.EOF) }) withFooBar(func() { expectRead(5, "foo ", nil) @@ -67,7 +68,7 @@ func TestMultiWriter(t *testing.T) { sink := new(bytes.Buffer) // Hide bytes.Buffer's WriteString method: testMultiWriter(t, struct { - Writer + io.Writer Stringer }{sink, sink}) } @@ -82,11 +83,11 @@ func TestMultiWriter_String(t *testing.T) { func TestMultiWriter_WriteStringSingleAlloc(t *testing.T) { var sink1, sink2 bytes.Buffer type simpleWriter struct { // hide bytes.Buffer's WriteString - Writer + io.Writer } - mw := MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) + mw := io.MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) allocs := int(testing.AllocsPerRun2(1000, func() { - WriteString(mw, "foo") + io.WriteString(mw, "foo") })) if allocs != 1 { t.Errorf("num allocations = %d; want 1", allocs) @@ -107,24 +108,24 @@ func (c *writeStringChecker) Write(p []byte) (n int, err error) { func TestMultiWriter_StringCheckCall(t *testing.T) { var c writeStringChecker - mw := MultiWriter(&c) - WriteString(mw, "foo") + mw := io.MultiWriter(&c) + io.WriteString(mw, "foo") if !c.called { t.Error("did not see WriteString call to writeStringChecker") } } func testMultiWriter(t *testing.T, sink interface { - Writer + io.Writer Stringer }, ) { var buf bytes.Buffer - mw := MultiWriter(&buf, sink) + mw := io.MultiWriter(&buf, sink) sourceString := "My input text." source := strings.NewReader(sourceString) - written, err := Copy(mw, source) + written, err := io.Copy(mw, source) if written != int64(len(sourceString)) { t.Errorf("short write of %d, not %d", written, len(sourceString)) @@ -158,7 +159,7 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var writeDepth int // will contain the depth from which writerFunc.Writer was called - var w Writer = MultiWriter(writerFunc(func(p []byte) (int, error) { + var w io.Writer = io.MultiWriter(writerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) writeDepth += callDepth(pc[:n]) return 0, nil @@ -167,10 +168,10 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { mw := w // chain a bunch of multiWriters for i := 0; i < 100; i++ { - mw = MultiWriter(w) + mw = io.MultiWriter(w) } - mw = MultiWriter(w, mw, w, mw) + mw = io.MultiWriter(w, mw, w, mw) mw.Write(nil) // don't care about errors, just want to check the call-depth for Write if writeDepth != 4*(myDepth+2) { // 2 should be multiWriter.Write and writerFunc.Write @@ -182,25 +183,25 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { func TestMultiWriterError(t *testing.T) { f1 := writerFunc(func(p []byte) (int, error) { - return len(p) / 2, ErrShortWrite + return len(p) / 2, io.ErrShortWrite }) f2 := writerFunc(func(p []byte) (int, error) { t.Errorf("MultiWriter called f2.Write") return len(p), nil }) - w := MultiWriter(f1, f2) + w := io.MultiWriter(f1, f2) n, err := w.Write(make([]byte, 100)) - if n != 50 || err != ErrShortWrite { + if n != 50 || err != io.ErrShortWrite { t.Errorf("Write = %d, %v, want 50, ErrShortWrite", n, err) } } // Test that MultiReader copies the input slice and is insulated from future modification. func TestMultiReaderCopy(t *testing.T) { - slice := []Reader{strings.NewReader("hello world")} - r := MultiReader(slice...) + slice := []io.Reader{strings.NewReader("hello world")} + r := io.MultiReader(slice...) slice[0] = nil - data, err := ReadAll(r) + data, err := io.ReadAll(r) if err != nil || string(data) != "hello world" { t.Errorf("ReadAll() = %q, %v, want %q, nil", data, err, "hello world") } @@ -209,8 +210,8 @@ func TestMultiReaderCopy(t *testing.T) { // Test that MultiWriter copies the input slice and is insulated from future modification. func TestMultiWriterCopy(t *testing.T) { var buf bytes.Buffer - slice := []Writer{&buf} - w := MultiWriter(slice...) + slice := []io.Writer{&buf} + w := io.MultiWriter(slice...) slice[0] = nil n, err := w.Write([]byte("hello world")) if err != nil || n != 11 { @@ -246,7 +247,7 @@ func TestMultiReaderFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var readDepth int // will contain the depth from which fakeReader.Read was called - var r Reader = MultiReader(readerFunc(func(p []byte) (int, error) { + var r io.Reader = io.MultiReader(readerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) readDepth = callDepth(pc[:n]) return 0, errors.New("irrelevant") @@ -254,7 +255,7 @@ func TestMultiReaderFlatten(t *testing.T) { // chain a bunch of multiReaders for i := 0; i < 100; i++ { - r = MultiReader(r) + r = io.MultiReader(r) } r.Read(nil) // don't care about errors, just want to check the call-depth for Read @@ -277,12 +278,12 @@ func (b byteAndEOFReader) Read(p []byte) (n int, err error) { panic("unexpected call") } p[0] = byte(b) - return 1, EOF + return 1, io.EOF } // This used to yield bytes forever; issue 16795. func TestMultiReaderSingleByteWithEOF(t *testing.T) { - got, err := ReadAll(LimitReader(MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) + got, err := io.ReadAll(io.LimitReader(io.MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) if err != nil { t.Fatal(err) } @@ -296,17 +297,17 @@ func TestMultiReaderSingleByteWithEOF(t *testing.T) { // chain continues to return EOF on its final read, rather than // yielding a (0, EOF). func TestMultiReaderFinalEOF(t *testing.T) { - r := MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) + r := io.MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) buf := make([]byte, 2) n, err := r.Read(buf) - if n != 1 || err != EOF { + if n != 1 || err != io.EOF { t.Errorf("got %v, %v; want 1, EOF", n, err) } } /* func TestMultiReaderFreesExhaustedReaders(t *testing.T) { - var mr Reader + var mr io.Reader closed := make(chan struct{}) // The closure ensures that we don't have a live reference to buf1 // on our stack after MultiReader is inlined (Issue 18819). This @@ -314,14 +315,14 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { func() { buf1 := bytes.NewReader([]byte("foo")) buf2 := bytes.NewReader([]byte("bar")) - mr = MultiReader(buf1, buf2) - runtime.SetFinalizer(buf1, func(*bytes.Reader) { + mr = io.MultiReader(buf1, buf2) + runtime.SetFinalizer(buf1, func(*bytes.io.Reader) { close(closed) }) }() buf := make([]byte, 4) - if n, err := ReadFull(mr, buf); err != nil || string(buf) != "foob" { + if n, err := io.ReadFull(mr, buf); err != nil || string(buf) != "foob" { t.Fatalf(`ReadFull = %d (%q), %v; want 3, "foo", nil`, n, buf[:n], err) } @@ -332,7 +333,7 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { t.Fatal("timeout waiting for collection of buf1") } - if n, err := ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { + if n, err := io.ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { t.Fatalf(`ReadFull = %d (%q), %v; want 2, "ar", nil`, n, buf[:n], err) } } @@ -342,21 +343,21 @@ func TestInterleavedMultiReader(t *testing.T) { r1 := strings.NewReader("123") r2 := strings.NewReader("45678") - mr1 := MultiReader(r1, r2) - mr2 := MultiReader(mr1) + mr1 := io.MultiReader(r1, r2) + mr2 := io.MultiReader(mr1) buf := make([]byte, 4) // Have mr2 use mr1's []Readers. // Consume r1 (and clear it for GC to handle) and consume part of r2. - n, err := ReadFull(mr2, buf) + n, err := io.ReadFull(mr2, buf) if got := string(buf[:n]); got != "1234" || err != nil { t.Errorf(`ReadFull(mr2) = (%q, %v), want ("1234", nil)`, got, err) } // Consume the rest of r2 via mr1. // This should not panic even though mr2 cleared r1. - n, err = ReadFull(mr1, buf) + n, err = io.ReadFull(mr1, buf) if got := string(buf[:n]); got != "5678" || err != nil { t.Errorf(`ReadFull(mr1) = (%q, %v), want ("5678", nil)`, got, err) } diff --git a/gnovm/stdlibs/math/bits/bits_test.gno b/gnovm/stdlibs/math/bits/bits_test.gno index e2834cacf7c..b92de1b94d2 100644 --- a/gnovm/stdlibs/math/bits/bits_test.gno +++ b/gnovm/stdlibs/math/bits/bits_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package bits +package bits_test import ( + "math/bits" "testing" ) @@ -14,7 +15,7 @@ func TestLeadingZeros(t *testing.T) { for k := 0; k < 64-8; k++ { x := uint64(i) << uint(k) if x <= 1<<8-1 { - got := LeadingZeros8(uint8(x)) + got := bits.LeadingZeros8(uint8(x)) want := nlz - k + (8 - 8) if x == 0 { want = 8 @@ -25,7 +26,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := LeadingZeros16(uint16(x)) + got := bits.LeadingZeros16(uint16(x)) want := nlz - k + (16 - 8) if x == 0 { want = 16 @@ -36,7 +37,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := LeadingZeros32(uint32(x)) + got := bits.LeadingZeros32(uint32(x)) want := nlz - k + (32 - 8) if x == 0 { want = 32 @@ -44,8 +45,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#08x) == %d; want %d", x, got, want) } @@ -53,7 +54,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := LeadingZeros64(uint64(x)) + got := bits.LeadingZeros64(uint64(x)) want := nlz - k + (64 - 8) if x == 0 { want = 64 @@ -61,8 +62,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#016x) == %d; want %d", x, got, want) } @@ -75,7 +76,7 @@ func TestLeadingZeros(t *testing.T) { // Exported (global) variable serving as input for some // of the benchmarks to ensure side-effect free calls // are not optimized away. -var Input uint64 = DeBruijn64 +var Input uint64 = bits.DeBruijn64 // Exported (global) variable to store function results // during benchmarking to ensure side-effect free calls @@ -85,7 +86,7 @@ var Output int func BenchmarkLeadingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros(uint(Input) >> (uint(i) % UintSize)) + s += bits.LeadingZeros(uint(Input) >> (uint(i) % bits.UintSize)) } Output = s } @@ -93,7 +94,7 @@ func BenchmarkLeadingZeros(b *testing.B) { func BenchmarkLeadingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros8(uint8(Input) >> (uint(i) % 8)) + s += bits.LeadingZeros8(uint8(Input) >> (uint(i) % 8)) } Output = s } @@ -101,7 +102,7 @@ func BenchmarkLeadingZeros8(b *testing.B) { func BenchmarkLeadingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros16(uint16(Input) >> (uint(i) % 16)) + s += bits.LeadingZeros16(uint16(Input) >> (uint(i) % 16)) } Output = s } @@ -109,7 +110,7 @@ func BenchmarkLeadingZeros16(b *testing.B) { func BenchmarkLeadingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros32(uint32(Input) >> (uint(i) % 32)) + s += bits.LeadingZeros32(uint32(Input) >> (uint(i) % 32)) } Output = s } @@ -117,7 +118,7 @@ func BenchmarkLeadingZeros32(b *testing.B) { func BenchmarkLeadingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros64(uint64(Input) >> (uint(i) % 64)) + s += bits.LeadingZeros64(uint64(Input) >> (uint(i) % 64)) } Output = s } @@ -129,7 +130,7 @@ func TestTrailingZeros(t *testing.T) { x := uint64(i) << uint(k) want := ntz + k if x <= 1<<8-1 { - got := TrailingZeros8(uint8(x)) + got := bits.TrailingZeros8(uint8(x)) if x == 0 { want = 8 } @@ -139,7 +140,7 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := TrailingZeros16(uint16(x)) + got := bits.TrailingZeros16(uint16(x)) if x == 0 { want = 16 } @@ -149,15 +150,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := TrailingZeros32(uint32(x)) + got := bits.TrailingZeros32(uint32(x)) if x == 0 { want = 32 } if got != want { t.Fatalf("TrailingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#08x) == %d; want %d", x, got, want) } @@ -165,15 +166,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := TrailingZeros64(uint64(x)) + got := bits.TrailingZeros64(uint64(x)) if x == 0 { want = 64 } if got != want { t.Fatalf("TrailingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#016x) == %d; want %d", x, got, want) } @@ -186,7 +187,7 @@ func TestTrailingZeros(t *testing.T) { func BenchmarkTrailingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros(uint(Input) << (uint(i) % UintSize)) + s += bits.TrailingZeros(uint(Input) << (uint(i) % bits.UintSize)) } Output = s } @@ -194,7 +195,7 @@ func BenchmarkTrailingZeros(b *testing.B) { func BenchmarkTrailingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros8(uint8(Input) << (uint(i) % 8)) + s += bits.TrailingZeros8(uint8(Input) << (uint(i) % 8)) } Output = s } @@ -202,7 +203,7 @@ func BenchmarkTrailingZeros8(b *testing.B) { func BenchmarkTrailingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros16(uint16(Input) << (uint(i) % 16)) + s += bits.TrailingZeros16(uint16(Input) << (uint(i) % 16)) } Output = s } @@ -210,7 +211,7 @@ func BenchmarkTrailingZeros16(b *testing.B) { func BenchmarkTrailingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros32(uint32(Input) << (uint(i) % 32)) + s += bits.TrailingZeros32(uint32(Input) << (uint(i) % 32)) } Output = s } @@ -218,7 +219,7 @@ func BenchmarkTrailingZeros32(b *testing.B) { func BenchmarkTrailingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros64(uint64(Input) << (uint(i) % 64)) + s += bits.TrailingZeros64(uint64(Input) << (uint(i) % 64)) } Output = s } @@ -244,26 +245,26 @@ func TestOnesCount(t *testing.T) { func testOnesCount(t *testing.T, x uint64, want int) { if x <= 1<<8-1 { - got := OnesCount8(uint8(x)) + got := bits.OnesCount8(uint8(x)) if got != want { t.Fatalf("OnesCount8(%#02x) == %d; want %d", uint8(x), got, want) } } if x <= 1<<16-1 { - got := OnesCount16(uint16(x)) + got := bits.OnesCount16(uint16(x)) if got != want { t.Fatalf("OnesCount16(%#04x) == %d; want %d", uint16(x), got, want) } } if x <= 1<<32-1 { - got := OnesCount32(uint32(x)) + got := bits.OnesCount32(uint32(x)) if got != want { t.Fatalf("OnesCount32(%#08x) == %d; want %d", uint32(x), got, want) } - if UintSize == 32 { - got = OnesCount(uint(x)) + if bits.UintSize == 32 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#08x) == %d; want %d", uint32(x), got, want) } @@ -271,12 +272,12 @@ func testOnesCount(t *testing.T, x uint64, want int) { } if x <= 1<<64-1 { - got := OnesCount64(uint64(x)) + got := bits.OnesCount64(uint64(x)) if got != want { t.Fatalf("OnesCount64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = OnesCount(uint(x)) + if bits.UintSize == 64 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#016x) == %d; want %d", x, got, want) } @@ -287,7 +288,7 @@ func testOnesCount(t *testing.T, x uint64, want int) { func BenchmarkOnesCount(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount(uint(Input)) + s += bits.OnesCount(uint(Input)) } Output = s } @@ -295,7 +296,7 @@ func BenchmarkOnesCount(b *testing.B) { func BenchmarkOnesCount8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount8(uint8(Input)) + s += bits.OnesCount8(uint8(Input)) } Output = s } @@ -303,7 +304,7 @@ func BenchmarkOnesCount8(b *testing.B) { func BenchmarkOnesCount16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount16(uint16(Input)) + s += bits.OnesCount16(uint16(Input)) } Output = s } @@ -311,7 +312,7 @@ func BenchmarkOnesCount16(b *testing.B) { func BenchmarkOnesCount32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount32(uint32(Input)) + s += bits.OnesCount32(uint32(Input)) } Output = s } @@ -319,78 +320,78 @@ func BenchmarkOnesCount32(b *testing.B) { func BenchmarkOnesCount64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount64(uint64(Input)) + s += bits.OnesCount64(uint64(Input)) } Output = s } func TestRotateLeft(t *testing.T) { - var m uint64 = DeBruijn64 + var m uint64 = bits.DeBruijn64 for k := uint(0); k < 128; k++ { x8 := uint8(m) - got8 := RotateLeft8(x8, int(k)) + got8 := bits.RotateLeft8(x8, int(k)) want8 := x8<<(k&0x7) | x8>>(8-k&0x7) if got8 != want8 { t.Fatalf("RotateLeft8(%#02x, %d) == %#02x; want %#02x", x8, k, got8, want8) } - got8 = RotateLeft8(want8, -int(k)) + got8 = bits.RotateLeft8(want8, -int(k)) if got8 != x8 { t.Fatalf("RotateLeft8(%#02x, -%d) == %#02x; want %#02x", want8, k, got8, x8) } x16 := uint16(m) - got16 := RotateLeft16(x16, int(k)) + got16 := bits.RotateLeft16(x16, int(k)) want16 := x16<<(k&0xf) | x16>>(16-k&0xf) if got16 != want16 { t.Fatalf("RotateLeft16(%#04x, %d) == %#04x; want %#04x", x16, k, got16, want16) } - got16 = RotateLeft16(want16, -int(k)) + got16 = bits.RotateLeft16(want16, -int(k)) if got16 != x16 { t.Fatalf("RotateLeft16(%#04x, -%d) == %#04x; want %#04x", want16, k, got16, x16) } x32 := uint32(m) - got32 := RotateLeft32(x32, int(k)) + got32 := bits.RotateLeft32(x32, int(k)) want32 := x32<<(k&0x1f) | x32>>(32-k&0x1f) if got32 != want32 { t.Fatalf("RotateLeft32(%#08x, %d) == %#08x; want %#08x", x32, k, got32, want32) } - got32 = RotateLeft32(want32, -int(k)) + got32 = bits.RotateLeft32(want32, -int(k)) if got32 != x32 { t.Fatalf("RotateLeft32(%#08x, -%d) == %#08x; want %#08x", want32, k, got32, x32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x1f) | x>>(32-k&0x1f) if got != want { t.Fatalf("RotateLeft(%#08x, %d) == %#08x; want %#08x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } } x64 := uint64(m) - got64 := RotateLeft64(x64, int(k)) + got64 := bits.RotateLeft64(x64, int(k)) want64 := x64<<(k&0x3f) | x64>>(64-k&0x3f) if got64 != want64 { t.Fatalf("RotateLeft64(%#016x, %d) == %#016x; want %#016x", x64, k, got64, want64) } - got64 = RotateLeft64(want64, -int(k)) + got64 = bits.RotateLeft64(want64, -int(k)) if got64 != x64 { t.Fatalf("RotateLeft64(%#016x, -%d) == %#016x; want %#016x", want64, k, got64, x64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x3f) | x>>(64-k&0x3f) if got != want { t.Fatalf("RotateLeft(%#016x, %d) == %#016x; want %#016x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } @@ -401,7 +402,7 @@ func TestRotateLeft(t *testing.T) { func BenchmarkRotateLeft(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += RotateLeft(uint(Input), i) + s += bits.RotateLeft(uint(Input), i) } Output = int(s) } @@ -409,7 +410,7 @@ func BenchmarkRotateLeft(b *testing.B) { func BenchmarkRotateLeft8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += RotateLeft8(uint8(Input), i) + s += bits.RotateLeft8(uint8(Input), i) } Output = int(s) } @@ -417,7 +418,7 @@ func BenchmarkRotateLeft8(b *testing.B) { func BenchmarkRotateLeft16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += RotateLeft16(uint16(Input), i) + s += bits.RotateLeft16(uint16(Input), i) } Output = int(s) } @@ -425,7 +426,7 @@ func BenchmarkRotateLeft16(b *testing.B) { func BenchmarkRotateLeft32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += RotateLeft32(uint32(Input), i) + s += bits.RotateLeft32(uint32(Input), i) } Output = int(s) } @@ -433,7 +434,7 @@ func BenchmarkRotateLeft32(b *testing.B) { func BenchmarkRotateLeft64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += RotateLeft64(uint64(Input), i) + s += bits.RotateLeft64(uint64(Input), i) } Output = int(s) } @@ -474,41 +475,41 @@ func TestReverse(t *testing.T) { func testReverse(t *testing.T, x64, want64 uint64) { x8 := uint8(x64) - got8 := Reverse8(x8) + got8 := bits.Reverse8(x8) want8 := uint8(want64 >> (64 - 8)) if got8 != want8 { t.Fatalf("Reverse8(%#02x) == %#02x; want %#02x", x8, got8, want8) } x16 := uint16(x64) - got16 := Reverse16(x16) + got16 := bits.Reverse16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("Reverse16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := Reverse32(x32) + got32 := bits.Reverse32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("Reverse32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want32) if got != want { t.Fatalf("Reverse(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := Reverse64(x64) + got64 := bits.Reverse64(x64) if got64 != want64 { t.Fatalf("Reverse64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want64) if got != want { t.Fatalf("Reverse(%#08x) == %#016x; want %#016x", x, got, want) @@ -519,7 +520,7 @@ func testReverse(t *testing.T, x64, want64 uint64) { func BenchmarkReverse(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += Reverse(uint(i)) + s += bits.Reverse(uint(i)) } Output = int(s) } @@ -527,7 +528,7 @@ func BenchmarkReverse(b *testing.B) { func BenchmarkReverse8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += Reverse8(uint8(i)) + s += bits.Reverse8(uint8(i)) } Output = int(s) } @@ -535,7 +536,7 @@ func BenchmarkReverse8(b *testing.B) { func BenchmarkReverse16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += Reverse16(uint16(i)) + s += bits.Reverse16(uint16(i)) } Output = int(s) } @@ -543,7 +544,7 @@ func BenchmarkReverse16(b *testing.B) { func BenchmarkReverse32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += Reverse32(uint32(i)) + s += bits.Reverse32(uint32(i)) } Output = int(s) } @@ -551,7 +552,7 @@ func BenchmarkReverse32(b *testing.B) { func BenchmarkReverse64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += Reverse64(uint64(i)) + s += bits.Reverse64(uint64(i)) } Output = int(s) } @@ -577,34 +578,34 @@ func TestReverseBytes(t *testing.T) { func testReverseBytes(t *testing.T, x64, want64 uint64) { x16 := uint16(x64) - got16 := ReverseBytes16(x16) + got16 := bits.ReverseBytes16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("ReverseBytes16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := ReverseBytes32(x32) + got32 := bits.ReverseBytes32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("ReverseBytes32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want32) if got != want { t.Fatalf("ReverseBytes(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := ReverseBytes64(x64) + got64 := bits.ReverseBytes64(x64) if got64 != want64 { t.Fatalf("ReverseBytes64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want64) if got != want { t.Fatalf("ReverseBytes(%#016x) == %#016x; want %#016x", x, got, want) @@ -615,7 +616,7 @@ func testReverseBytes(t *testing.T, x64, want64 uint64) { func BenchmarkReverseBytes(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += ReverseBytes(uint(i)) + s += bits.ReverseBytes(uint(i)) } Output = int(s) } @@ -623,7 +624,7 @@ func BenchmarkReverseBytes(b *testing.B) { func BenchmarkReverseBytes16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += ReverseBytes16(uint16(i)) + s += bits.ReverseBytes16(uint16(i)) } Output = int(s) } @@ -631,7 +632,7 @@ func BenchmarkReverseBytes16(b *testing.B) { func BenchmarkReverseBytes32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += ReverseBytes32(uint32(i)) + s += bits.ReverseBytes32(uint32(i)) } Output = int(s) } @@ -639,7 +640,7 @@ func BenchmarkReverseBytes32(b *testing.B) { func BenchmarkReverseBytes64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += ReverseBytes64(uint64(i)) + s += bits.ReverseBytes64(uint64(i)) } Output = int(s) } @@ -654,26 +655,26 @@ func TestLen(t *testing.T) { want = length + k } if x <= 1<<8-1 { - got := Len8(uint8(x)) + got := bits.Len8(uint8(x)) if got != want { t.Fatalf("Len8(%#02x) == %d; want %d", x, got, want) } } if x <= 1<<16-1 { - got := Len16(uint16(x)) + got := bits.Len16(uint16(x)) if got != want { t.Fatalf("Len16(%#04x) == %d; want %d", x, got, want) } } if x <= 1<<32-1 { - got := Len32(uint32(x)) + got := bits.Len32(uint32(x)) if got != want { t.Fatalf("Len32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got := Len(uint(x)) + if bits.UintSize == 32 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#08x) == %d; want %d", x, got, want) } @@ -681,12 +682,12 @@ func TestLen(t *testing.T) { } if x <= 1<<64-1 { - got := Len64(uint64(x)) + got := bits.Len64(uint64(x)) if got != want { t.Fatalf("Len64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got := Len(uint(x)) + if bits.UintSize == 64 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#016x) == %d; want %d", x, got, want) } @@ -697,7 +698,7 @@ func TestLen(t *testing.T) { } const ( - _M = 1< 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 0 { return x } @@ -863,35 +864,35 @@ func TestSub64OverflowPanic(t *testing.T) { // These are designed to improve coverage of compiler intrinsics. tests := []func(uint64, uint64) uint64{ func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c > 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 0 { return x } @@ -937,19 +938,19 @@ func TestMulDiv(t *testing.T) { x, y uint hi, lo, r uint }{ - {1 << (UintSize - 1), 2, 1, 0, 1}, + {1 << (bits.UintSize - 1), 2, 1, 0, 1}, {_M, _M, _M - 1, 1, 42}, } { - testMul("Mul", Mul, a.x, a.y, a.hi, a.lo) - testMul("Mul symmetric", Mul, a.y, a.x, a.hi, a.lo) - testDiv("Div", Div, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div symmetric", Div, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul", bits.Mul, a.x, a.y, a.hi, a.lo) + testMul("Mul symmetric", bits.Mul, a.y, a.x, a.hi, a.lo) + testDiv("Div", bits.Div, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div symmetric", bits.Div, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -974,10 +975,10 @@ func TestMulDiv32(t *testing.T) { {0xc47dfa8c, 50911, 0x98a4, 0x998587f4, 13}, {_M32, _M32, _M32 - 1, 1, 42}, } { - testMul("Mul32", Mul32, a.x, a.y, a.hi, a.lo) - testMul("Mul32 symmetric", Mul32, a.y, a.x, a.hi, a.lo) - testDiv("Div32", Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div32 symmetric", Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul32", bits.Mul32, a.x, a.y, a.hi, a.lo) + testMul("Mul32 symmetric", bits.Mul32, a.y, a.x, a.hi, a.lo) + testDiv("Div32", bits.Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div32 symmetric", bits.Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1002,16 +1003,16 @@ func TestMulDiv64(t *testing.T) { {0x3626229738a3b9, 0xd8988a9f1cc4a61, 0x2dd0712657fe8, 0x9dd6a3364c358319, 13}, {_M64, _M64, _M64 - 1, 1, 42}, } { - testMul("Mul64", Mul64, a.x, a.y, a.hi, a.lo) - testMul("Mul64 symmetric", Mul64, a.y, a.x, a.hi, a.lo) - testDiv("Div64", Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 symmetric", Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64", bits.Mul64, a.x, a.y, a.hi, a.lo) + testMul("Mul64 symmetric", bits.Mul64, a.y, a.x, a.hi, a.lo) + testDiv("Div64", bits.Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 symmetric", bits.Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1020,11 +1021,11 @@ func TestDivPanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div(1, 0, 1) + q, r := bits.Div(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1033,11 +1034,11 @@ func TestDiv32PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div32 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div32 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div32(1, 0, 1) + q, r := bits.Div32(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1046,11 +1047,11 @@ func TestDiv64PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div64 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div64 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div64(1, 0, 1) + q, r := bits.Div64(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1059,11 +1060,11 @@ func TestDivPanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div(1, 1, 0) + q, r := bits.Div(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1072,11 +1073,11 @@ func TestDiv32PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div32 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div32 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div32(1, 1, 0) + q, r := bits.Div32(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1085,11 +1086,11 @@ func TestDiv64PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div64 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div64 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div64(1, 1, 0) + q, r := bits.Div64(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1098,8 +1099,8 @@ func TestRem32(t *testing.T) { // same as the rem returned by Div32 hi, lo, y := uint32(510510), uint32(9699690), uint32(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { - r := Rem32(hi, lo, y) - _, r2 := Div32(hi, lo, y) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div32(hi, lo, y) if r != r2 { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div32 returned rem %v", hi, lo, y, r, r2) } @@ -1111,8 +1112,8 @@ func TestRem32Overflow(t *testing.T) { // To trigger a quotient overflow, we need y <= hi hi, lo, y := uint32(510510), uint32(9699690), uint32(7) for i := 0; i < 1000; i++ { - r := Rem32(hi, lo, y) - _, r2 := Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) if r != uint32(r2) { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1125,8 +1126,8 @@ func TestRem64(t *testing.T) { // same as the rem returned by Div64 hi, lo, y := uint64(510510), uint64(9699690), uint64(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { - r := Rem64(hi, lo, y) - _, r2 := Div64(hi, lo, y) + r := bits.Rem64(hi, lo, y) + _, r2 := bits.Div64(hi, lo, y) if r != r2 { t.Errorf("Rem64(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1155,7 +1156,7 @@ func TestRem64Overflow(t *testing.T) { if rt.hi < rt.y { t.Fatalf("Rem64(%v, %v, %v) is not a test with quo overflow", rt.hi, rt.lo, rt.y) } - rem := Rem64(rt.hi, rt.lo, rt.y) + rem := bits.Rem64(rt.hi, rt.lo, rt.y) if rem != rt.rem { t.Errorf("Rem64(%v, %v, %v) returned %v, wanted %v", rt.hi, rt.lo, rt.y, rem, rt.rem) @@ -1166,7 +1167,7 @@ func TestRem64Overflow(t *testing.T) { func BenchmarkAdd(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Add(uint(Input), uint(i), c) + z, c = bits.Add(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1174,7 +1175,7 @@ func BenchmarkAdd(b *testing.B) { func BenchmarkAdd32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Add32(uint32(Input), uint32(i), c) + z, c = bits.Add32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1182,7 +1183,7 @@ func BenchmarkAdd32(b *testing.B) { func BenchmarkAdd64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Add64(uint64(Input), uint64(i), c) + z, c = bits.Add64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1194,10 +1195,10 @@ func BenchmarkAdd64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Add64(z0, uint64(i), c) - z1, c = Add64(z1, uint64(i), c) - z2, c = Add64(z2, uint64(i), c) - z3, _ = Add64(z3, uint64(i), c) + z0, c = bits.Add64(z0, uint64(i), c) + z1, c = bits.Add64(z1, uint64(i), c) + z2, c = bits.Add64(z2, uint64(i), c) + z3, _ = bits.Add64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1205,7 +1206,7 @@ func BenchmarkAdd64multiple(b *testing.B) { func BenchmarkSub(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Sub(uint(Input), uint(i), c) + z, c = bits.Sub(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1213,7 +1214,7 @@ func BenchmarkSub(b *testing.B) { func BenchmarkSub32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Sub32(uint32(Input), uint32(i), c) + z, c = bits.Sub32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1221,7 +1222,7 @@ func BenchmarkSub32(b *testing.B) { func BenchmarkSub64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Sub64(uint64(Input), uint64(i), c) + z, c = bits.Sub64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1233,10 +1234,10 @@ func BenchmarkSub64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Sub64(z0, uint64(i), c) - z1, c = Sub64(z1, uint64(i), c) - z2, c = Sub64(z2, uint64(i), c) - z3, _ = Sub64(z3, uint64(i), c) + z0, c = bits.Sub64(z0, uint64(i), c) + z1, c = bits.Sub64(z1, uint64(i), c) + z2, c = bits.Sub64(z2, uint64(i), c) + z3, _ = bits.Sub64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1244,7 +1245,7 @@ func BenchmarkSub64multiple(b *testing.B) { func BenchmarkMul(b *testing.B) { var hi, lo uint for i := 0; i < b.N; i++ { - hi, lo = Mul(uint(Input), uint(i)) + hi, lo = bits.Mul(uint(Input), uint(i)) } Output = int(hi + lo) } @@ -1252,7 +1253,7 @@ func BenchmarkMul(b *testing.B) { func BenchmarkMul32(b *testing.B) { var hi, lo uint32 for i := 0; i < b.N; i++ { - hi, lo = Mul32(uint32(Input), uint32(i)) + hi, lo = bits.Mul32(uint32(Input), uint32(i)) } Output = int(hi + lo) } @@ -1260,7 +1261,7 @@ func BenchmarkMul32(b *testing.B) { func BenchmarkMul64(b *testing.B) { var hi, lo uint64 for i := 0; i < b.N; i++ { - hi, lo = Mul64(uint64(Input), uint64(i)) + hi, lo = bits.Mul64(uint64(Input), uint64(i)) } Output = int(hi + lo) } @@ -1268,7 +1269,7 @@ func BenchmarkMul64(b *testing.B) { func BenchmarkDiv(b *testing.B) { var q, r uint for i := 0; i < b.N; i++ { - q, r = Div(1, uint(i), uint(Input)) + q, r = bits.Div(1, uint(i), uint(Input)) } Output = int(q + r) } @@ -1276,7 +1277,7 @@ func BenchmarkDiv(b *testing.B) { func BenchmarkDiv32(b *testing.B) { var q, r uint32 for i := 0; i < b.N; i++ { - q, r = Div32(1, uint32(i), uint32(Input)) + q, r = bits.Div32(1, uint32(i), uint32(Input)) } Output = int(q + r) } @@ -1284,7 +1285,7 @@ func BenchmarkDiv32(b *testing.B) { func BenchmarkDiv64(b *testing.B) { var q, r uint64 for i := 0; i < b.N; i++ { - q, r = Div64(1, uint64(i), uint64(Input)) + q, r = bits.Div64(1, uint64(i), uint64(Input)) } Output = int(q + r) } diff --git a/gnovm/stdlibs/math/bits/export_test.gno b/gnovm/stdlibs/math/bits/export_test.gno index 8c6f9332cca..a0165163d8f 100644 --- a/gnovm/stdlibs/math/bits/export_test.gno +++ b/gnovm/stdlibs/math/bits/export_test.gno @@ -4,4 +4,9 @@ package bits +// exported for tests + const DeBruijn64 = deBruijn64 + +var OverflowError = overflowError +var DivideError = divideError diff --git a/gnovm/stdlibs/strconv/atob_test.gno b/gnovm/stdlibs/strconv/atob_test.gno index 39746f8953d..6e6d34e8320 100644 --- a/gnovm/stdlibs/strconv/atob_test.gno +++ b/gnovm/stdlibs/strconv/atob_test.gno @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "bytes" + "strconv" "testing" ) @@ -16,8 +17,8 @@ type atobTest struct { } var atobtests = []atobTest{ - {"", false, ErrSyntax}, - {"asdf", false, ErrSyntax}, + {"", false, strconv.ErrSyntax}, + {"asdf", false, strconv.ErrSyntax}, {"0", false, nil}, {"f", false, nil}, {"F", false, nil}, @@ -34,14 +35,14 @@ var atobtests = []atobTest{ func TestParseBool(t *testing.T) { for _, test := range atobtests { - b, e := ParseBool(test.in) + b, e := strconv.ParseBool(test.in) if test.err != nil { // expect an error if e == nil { t.Errorf("ParseBool(%s) = nil; want %s", test.in, test.err) } else { // NumError assertion must succeed; it's the only thing we return. - if e.(*NumError).Err != test.err { + if e.(*strconv.NumError).Err != test.err { t.Errorf("ParseBool(%s) = %s; want %s", test.in, e, test.err) } } @@ -63,7 +64,7 @@ var boolString = map[bool]string{ func TestFormatBool(t *testing.T) { for b, s := range boolString { - if f := FormatBool(b); f != s { + if f := strconv.FormatBool(b); f != s { t.Errorf("FormatBool(%v) = %q; want %q", b, f, s) } } @@ -82,7 +83,7 @@ var appendBoolTests = []appendBoolTest{ func TestAppendBool(t *testing.T) { for _, test := range appendBoolTests { - b := AppendBool(test.in, test.b) + b := strconv.AppendBool(test.in, test.b) if !bytes.Equal(b, test.out) { t.Errorf("AppendBool(%q, %v) = %q; want %q", test.in, test.b, b, test.out) } diff --git a/gnovm/stdlibs/strconv/atof_test.gno b/gnovm/stdlibs/strconv/atof_test.gno index 29d9e4e2f4b..63cf659018b 100644 --- a/gnovm/stdlibs/strconv/atof_test.gno +++ b/gnovm/stdlibs/strconv/atof_test.gno @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test // XXX: changed not to use dot imports import ( "math" "math/rand" + "strconv" "strings" "testing" ) @@ -20,11 +21,11 @@ type atofTest struct { } var atoftests = []atofTest{ - {"", "0", ErrSyntax}, + {"", "0", strconv.ErrSyntax}, {"1", "1", nil}, {"+1", "1", nil}, - {"1x", "0", ErrSyntax}, - {"1.1.", "0", ErrSyntax}, + {"1x", "0", strconv.ErrSyntax}, + {"1.1.", "0", strconv.ErrSyntax}, {"1e23", "1e+23", nil}, {"1E23", "1e+23", nil}, {"100000000000000000000000", "1e+23", nil}, @@ -55,8 +56,8 @@ var atoftests = []atofTest{ {"-0x2p3", "-16", nil}, {"0x0.fp4", "15", nil}, {"0x0.fp0", "0.9375", nil}, - {"0x1e2", "0", ErrSyntax}, - {"1p2", "0", ErrSyntax}, + {"0x1e2", "0", strconv.ErrSyntax}, + {"1p2", "0", strconv.ErrSyntax}, // zeros {"0", "0", nil}, @@ -131,16 +132,16 @@ var atoftests = []atofTest{ {"-0x.1fffffffffffffp1027", "-1.7976931348623157e+308", nil}, // next float64 - too large - {"1.7976931348623159e308", "+Inf", ErrRange}, - {"-1.7976931348623159e308", "-Inf", ErrRange}, - {"0x1p1024", "+Inf", ErrRange}, - {"-0x1p1024", "-Inf", ErrRange}, - {"0x2p1023", "+Inf", ErrRange}, - {"-0x2p1023", "-Inf", ErrRange}, - {"0x.1p1028", "+Inf", ErrRange}, - {"-0x.1p1028", "-Inf", ErrRange}, - {"0x.2p1027", "+Inf", ErrRange}, - {"-0x.2p1027", "-Inf", ErrRange}, + {"1.7976931348623159e308", "+Inf", strconv.ErrRange}, + {"-1.7976931348623159e308", "-Inf", strconv.ErrRange}, + {"0x1p1024", "+Inf", strconv.ErrRange}, + {"-0x1p1024", "-Inf", strconv.ErrRange}, + {"0x2p1023", "+Inf", strconv.ErrRange}, + {"-0x2p1023", "-Inf", strconv.ErrRange}, + {"0x.1p1028", "+Inf", strconv.ErrRange}, + {"-0x.1p1028", "-Inf", strconv.ErrRange}, + {"0x.2p1027", "+Inf", strconv.ErrRange}, + {"-0x.2p1027", "-Inf", strconv.ErrRange}, // the border is ...158079 // borderline - okay @@ -149,34 +150,34 @@ var atoftests = []atofTest{ {"0x1.fffffffffffff7fffp1023", "1.7976931348623157e+308", nil}, {"-0x1.fffffffffffff7fffp1023", "-1.7976931348623157e+308", nil}, // borderline - too large - {"1.797693134862315808e308", "+Inf", ErrRange}, - {"-1.797693134862315808e308", "-Inf", ErrRange}, - {"0x1.fffffffffffff8p1023", "+Inf", ErrRange}, - {"-0x1.fffffffffffff8p1023", "-Inf", ErrRange}, - {"0x1fffffffffffff.8p+971", "+Inf", ErrRange}, - {"-0x1fffffffffffff8p+967", "-Inf", ErrRange}, - {"0x.1fffffffffffff8p1027", "+Inf", ErrRange}, - {"-0x.1fffffffffffff9p1027", "-Inf", ErrRange}, + {"1.797693134862315808e308", "+Inf", strconv.ErrRange}, + {"-1.797693134862315808e308", "-Inf", strconv.ErrRange}, + {"0x1.fffffffffffff8p1023", "+Inf", strconv.ErrRange}, + {"-0x1.fffffffffffff8p1023", "-Inf", strconv.ErrRange}, + {"0x1fffffffffffff.8p+971", "+Inf", strconv.ErrRange}, + {"-0x1fffffffffffff8p+967", "-Inf", strconv.ErrRange}, + {"0x.1fffffffffffff8p1027", "+Inf", strconv.ErrRange}, + {"-0x.1fffffffffffff9p1027", "-Inf", strconv.ErrRange}, // a little too large {"1e308", "1e+308", nil}, - {"2e308", "+Inf", ErrRange}, - {"1e309", "+Inf", ErrRange}, - {"0x1p1025", "+Inf", ErrRange}, + {"2e308", "+Inf", strconv.ErrRange}, + {"1e309", "+Inf", strconv.ErrRange}, + {"0x1p1025", "+Inf", strconv.ErrRange}, // way too large - {"1e310", "+Inf", ErrRange}, - {"-1e310", "-Inf", ErrRange}, - {"1e400", "+Inf", ErrRange}, - {"-1e400", "-Inf", ErrRange}, - {"1e400000", "+Inf", ErrRange}, - {"-1e400000", "-Inf", ErrRange}, - {"0x1p1030", "+Inf", ErrRange}, - {"0x1p2000", "+Inf", ErrRange}, - {"0x1p2000000000", "+Inf", ErrRange}, - {"-0x1p1030", "-Inf", ErrRange}, - {"-0x1p2000", "-Inf", ErrRange}, - {"-0x1p2000000000", "-Inf", ErrRange}, + {"1e310", "+Inf", strconv.ErrRange}, + {"-1e310", "-Inf", strconv.ErrRange}, + {"1e400", "+Inf", strconv.ErrRange}, + {"-1e400", "-Inf", strconv.ErrRange}, + {"1e400000", "+Inf", strconv.ErrRange}, + {"-1e400000", "-Inf", strconv.ErrRange}, + {"0x1p1030", "+Inf", strconv.ErrRange}, + {"0x1p2000", "+Inf", strconv.ErrRange}, + {"0x1p2000000000", "+Inf", strconv.ErrRange}, + {"-0x1p1030", "-Inf", strconv.ErrRange}, + {"-0x1p2000", "-Inf", strconv.ErrRange}, + {"-0x1p2000000000", "-Inf", strconv.ErrRange}, // denormalized {"1e-305", "1e-305", nil}, @@ -238,29 +239,29 @@ var atoftests = []atofTest{ // try to overflow exponent {"1e-4294967296", "0", nil}, - {"1e+4294967296", "+Inf", ErrRange}, + {"1e+4294967296", "+Inf", strconv.ErrRange}, {"1e-18446744073709551616", "0", nil}, - {"1e+18446744073709551616", "+Inf", ErrRange}, + {"1e+18446744073709551616", "+Inf", strconv.ErrRange}, {"0x1p-4294967296", "0", nil}, - {"0x1p+4294967296", "+Inf", ErrRange}, + {"0x1p+4294967296", "+Inf", strconv.ErrRange}, {"0x1p-18446744073709551616", "0", nil}, - {"0x1p+18446744073709551616", "+Inf", ErrRange}, + {"0x1p+18446744073709551616", "+Inf", strconv.ErrRange}, // Parse errors - {"1e", "0", ErrSyntax}, - {"1e-", "0", ErrSyntax}, - {".e-1", "0", ErrSyntax}, - {"1\x00.2", "0", ErrSyntax}, - {"0x", "0", ErrSyntax}, - {"0x.", "0", ErrSyntax}, - {"0x1", "0", ErrSyntax}, - {"0x.1", "0", ErrSyntax}, - {"0x1p", "0", ErrSyntax}, - {"0x.1p", "0", ErrSyntax}, - {"0x1p+", "0", ErrSyntax}, - {"0x.1p+", "0", ErrSyntax}, - {"0x1p-", "0", ErrSyntax}, - {"0x.1p-", "0", ErrSyntax}, + {"1e", "0", strconv.ErrSyntax}, + {"1e-", "0", strconv.ErrSyntax}, + {".e-1", "0", strconv.ErrSyntax}, + {"1\x00.2", "0", strconv.ErrSyntax}, + {"0x", "0", strconv.ErrSyntax}, + {"0x.", "0", strconv.ErrSyntax}, + {"0x1", "0", strconv.ErrSyntax}, + {"0x.1", "0", strconv.ErrSyntax}, + {"0x1p", "0", strconv.ErrSyntax}, + {"0x.1p", "0", strconv.ErrSyntax}, + {"0x1p+", "0", strconv.ErrSyntax}, + {"0x.1p+", "0", strconv.ErrSyntax}, + {"0x1p-", "0", strconv.ErrSyntax}, + {"0x.1p-", "0", strconv.ErrSyntax}, {"0x1p+2", "4", nil}, {"0x.1p+2", "0.25", nil}, {"0x1p-2", "0.25", nil}, @@ -309,40 +310,40 @@ var atoftests = []atofTest{ // Underscores. {"1_23.50_0_0e+1_2", "1.235e+14", nil}, - {"-_123.5e+12", "0", ErrSyntax}, - {"+_123.5e+12", "0", ErrSyntax}, - {"_123.5e+12", "0", ErrSyntax}, - {"1__23.5e+12", "0", ErrSyntax}, - {"123_.5e+12", "0", ErrSyntax}, - {"123._5e+12", "0", ErrSyntax}, - {"123.5_e+12", "0", ErrSyntax}, - {"123.5__0e+12", "0", ErrSyntax}, - {"123.5e_+12", "0", ErrSyntax}, - {"123.5e+_12", "0", ErrSyntax}, - {"123.5e_-12", "0", ErrSyntax}, - {"123.5e-_12", "0", ErrSyntax}, - {"123.5e+1__2", "0", ErrSyntax}, - {"123.5e+12_", "0", ErrSyntax}, + {"-_123.5e+12", "0", strconv.ErrSyntax}, + {"+_123.5e+12", "0", strconv.ErrSyntax}, + {"_123.5e+12", "0", strconv.ErrSyntax}, + {"1__23.5e+12", "0", strconv.ErrSyntax}, + {"123_.5e+12", "0", strconv.ErrSyntax}, + {"123._5e+12", "0", strconv.ErrSyntax}, + {"123.5_e+12", "0", strconv.ErrSyntax}, + {"123.5__0e+12", "0", strconv.ErrSyntax}, + {"123.5e_+12", "0", strconv.ErrSyntax}, + {"123.5e+_12", "0", strconv.ErrSyntax}, + {"123.5e_-12", "0", strconv.ErrSyntax}, + {"123.5e-_12", "0", strconv.ErrSyntax}, + {"123.5e+1__2", "0", strconv.ErrSyntax}, + {"123.5e+12_", "0", strconv.ErrSyntax}, {"0x_1_2.3_4_5p+1_2", "74565", nil}, - {"-_0x12.345p+12", "0", ErrSyntax}, - {"+_0x12.345p+12", "0", ErrSyntax}, - {"_0x12.345p+12", "0", ErrSyntax}, - {"0x__12.345p+12", "0", ErrSyntax}, - {"0x1__2.345p+12", "0", ErrSyntax}, - {"0x12_.345p+12", "0", ErrSyntax}, - {"0x12._345p+12", "0", ErrSyntax}, - {"0x12.3__45p+12", "0", ErrSyntax}, - {"0x12.345_p+12", "0", ErrSyntax}, - {"0x12.345p_+12", "0", ErrSyntax}, - {"0x12.345p+_12", "0", ErrSyntax}, - {"0x12.345p_-12", "0", ErrSyntax}, - {"0x12.345p-_12", "0", ErrSyntax}, - {"0x12.345p+1__2", "0", ErrSyntax}, - {"0x12.345p+12_", "0", ErrSyntax}, - - {"1e100x", "0", ErrSyntax}, - {"1e1000x", "0", ErrSyntax}, + {"-_0x12.345p+12", "0", strconv.ErrSyntax}, + {"+_0x12.345p+12", "0", strconv.ErrSyntax}, + {"_0x12.345p+12", "0", strconv.ErrSyntax}, + {"0x__12.345p+12", "0", strconv.ErrSyntax}, + {"0x1__2.345p+12", "0", strconv.ErrSyntax}, + {"0x12_.345p+12", "0", strconv.ErrSyntax}, + {"0x12._345p+12", "0", strconv.ErrSyntax}, + {"0x12.3__45p+12", "0", strconv.ErrSyntax}, + {"0x12.345_p+12", "0", strconv.ErrSyntax}, + {"0x12.345p_+12", "0", strconv.ErrSyntax}, + {"0x12.345p+_12", "0", strconv.ErrSyntax}, + {"0x12.345p_-12", "0", strconv.ErrSyntax}, + {"0x12.345p-_12", "0", strconv.ErrSyntax}, + {"0x12.345p+1__2", "0", strconv.ErrSyntax}, + {"0x12.345p+12_", "0", strconv.ErrSyntax}, + + {"1e100x", "0", strconv.ErrSyntax}, + {"1e1000x", "0", strconv.ErrSyntax}, } var atof32tests = []atofTest{ @@ -374,10 +375,10 @@ var atof32tests = []atofTest{ {"-340282346638528859811704183484516925440", "-3.4028235e+38", nil}, {"-0x.ffffffp128", "-3.4028235e+38", nil}, // next float32 - too large - {"3.4028236e38", "+Inf", ErrRange}, - {"-3.4028236e38", "-Inf", ErrRange}, - {"0x1.0p128", "+Inf", ErrRange}, - {"-0x1.0p128", "-Inf", ErrRange}, + {"3.4028236e38", "+Inf", strconv.ErrRange}, + {"-3.4028236e38", "-Inf", strconv.ErrRange}, + {"0x1.0p128", "+Inf", strconv.ErrRange}, + {"-0x1.0p128", "-Inf", strconv.ErrRange}, // the border is 3.40282356779...e+38 // borderline - okay {"3.402823567e38", "3.4028235e+38", nil}, @@ -385,10 +386,10 @@ var atof32tests = []atofTest{ {"0x.ffffff7fp128", "3.4028235e+38", nil}, {"-0x.ffffff7fp128", "-3.4028235e+38", nil}, // borderline - too large - {"3.4028235678e38", "+Inf", ErrRange}, - {"-3.4028235678e38", "-Inf", ErrRange}, - {"0x.ffffff8p128", "+Inf", ErrRange}, - {"-0x.ffffff8p128", "-Inf", ErrRange}, + {"3.4028235678e38", "+Inf", strconv.ErrRange}, + {"-3.4028235678e38", "-Inf", strconv.ErrRange}, + {"0x.ffffff8p128", "+Inf", strconv.ErrRange}, + {"-0x.ffffff8p128", "-Inf", strconv.ErrRange}, // Denormals: less than 2^-126 {"1e-38", "1e-38", nil}, @@ -454,13 +455,13 @@ func initAtofOnce() { for i := range atoftests { test := &atoftests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } for i := range atof32tests { test := &atof32tests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } @@ -473,19 +474,19 @@ func initAtofOnce() { for i := range atofRandomTests { n := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(n) - s := FormatFloat(x, 'g', -1, 64) + s := strconv.FormatFloat(x, 'g', -1, 64) atofRandomTests[i] = atofSimpleTest{x, s} } for i := range benchmarksRandomBits { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - benchmarksRandomBits[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomBits[i] = strconv.FormatFloat(x, 'g', -1, 64) } for i := range benchmarksRandomNormal { x := rand.NormFloat64() - benchmarksRandomNormal[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomNormal[i] = strconv.FormatFloat(x, 'g', -1, 64) } } @@ -500,7 +501,7 @@ func TestParseFloatPrefix(t *testing.T) { // correctly as "inf" with suffix. for _, suffix := range []string{" ", "q", "+", "-", "<", "=", ">", "(", ")", "i", "init"} { in := test.in + suffix - _, n, err := ParseFloatPrefix(in, 64) + _, n, err := strconv.ParseFloatPrefix(in, 64) if err != nil { t.Errorf("ParseFloatPrefix(%q, 64): err = %v; want no error", in, err) } @@ -530,24 +531,24 @@ func printError(err error) string { func testAtof(t *testing.T, opt bool) { initAtof() - oldopt := SetOptimize(opt) + oldopt := strconv.SetOptimize(opt) for i := 0; i < len(atoftests); i++ { test := &atoftests[i] - out, err := ParseFloat(test.in, 64) - outs := FormatFloat(out, 'g', -1, 64) + out, err := strconv.ParseFloat(test.in, 64) + outs := strconv.FormatFloat(out, 'g', -1, 64) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 64) = %v, %v want %v, %v", test.in, out, printError(err), test.out, printError(test.err)) } if float64(float32(out)) == out { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) @@ -555,19 +556,19 @@ func testAtof(t *testing.T, opt bool) { } } for _, test := range atof32tests { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) } } - SetOptimize(oldopt) + strconv.SetOptimize(oldopt) } func TestAtof(t *testing.T) { testAtof(t, true) } @@ -577,7 +578,7 @@ func TestAtofSlow(t *testing.T) { testAtof(t, false) } func TestAtofRandom(t *testing.T) { initAtof() for _, test := range atofRandomTests { - x, _ := ParseFloat(test.s, 64) + x, _ := strconv.ParseFloat(test.s, 64) switch { default: t.Errorf("number %s badly parsed as %b (expected %b)", test.s, x, test.x) @@ -604,25 +605,25 @@ var roundTripCases = []struct { func TestRoundTrip(t *testing.T) { for _, tt := range roundTripCases { - old := SetOptimize(false) - s := FormatFloat(tt.f, 'g', -1, 64) + old := strconv.SetOptimize(false) + s := strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("no-opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err := ParseFloat(tt.s, 64) + f, err := strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("no-opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(true) - s = FormatFloat(tt.f, 'g', -1, 64) + strconv.SetOptimize(true) + s = strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err = ParseFloat(tt.s, 64) + f, err = strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(old) + strconv.SetOptimize(old) } } @@ -638,9 +639,9 @@ func TestRoundTrip32(t *testing.T) { if i&1 == 1 { f = -f // negative } - s := FormatFloat(float64(f), 'g', -1, 32) + s := strconv.FormatFloat(float64(f), 'g', -1, 32) - parsed, err := ParseFloat(s, 32) + parsed, err := strconv.ParseFloat(s, 32) parsed32 := float32(parsed) switch { case err != nil: @@ -662,7 +663,7 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { const want = 1.5e308 for _, bitSize := range []int{0, 10, 100, 128} { - f, err := ParseFloat(s, bitSize) + f, err := strconv.ParseFloat(s, bitSize) if err != nil { t.Fatalf("ParseFloat(%q, %d) gave error %s", s, bitSize, err) } @@ -674,25 +675,25 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { func BenchmarkAtof64Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 64) + strconv.ParseFloat("33909", 64) } } func BenchmarkAtof64Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.7784", 64) + strconv.ParseFloat("339.7784", 64) } } func BenchmarkAtof64FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("-5.09e75", 64) + strconv.ParseFloat("-5.09e75", 64) } } func BenchmarkAtof64Big(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("123456789123456789123456789", 64) + strconv.ParseFloat("123456789123456789123456789", 64) } } @@ -700,7 +701,7 @@ func BenchmarkAtof64RandomBits(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomBits[i%1024], 64) + strconv.ParseFloat(benchmarksRandomBits[i%1024], 64) } } @@ -708,7 +709,7 @@ func BenchmarkAtof64RandomFloats(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomNormal[i%1024], 64) + strconv.ParseFloat(benchmarksRandomNormal[i%1024], 64) } } @@ -716,12 +717,12 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { initAtof() samples := make([]string, len(atofRandomTests)) for i, t := range atofRandomTests { - samples[i] = FormatFloat(t.x, 'g', 20, 64) + samples[i] = strconv.FormatFloat(t.x, 'g', 20, 64) } b.ResetTimer() idx := 0 for i := 0; i < b.N; i++ { - ParseFloat(samples[idx], 64) + strconv.ParseFloat(samples[idx], 64) idx++ if idx == len(samples) { idx = 0 @@ -731,19 +732,19 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { func BenchmarkAtof32Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 32) + strconv.ParseFloat("33909", 32) } } func BenchmarkAtof32Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.778", 32) + strconv.ParseFloat("339.778", 32) } } func BenchmarkAtof32FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("12.3456e32", 32) + strconv.ParseFloat("12.3456e32", 32) } } @@ -752,11 +753,11 @@ func BenchmarkAtof32Random(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } @@ -765,10 +766,10 @@ func BenchmarkAtof32RandomLong(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } diff --git a/gnovm/stdlibs/strconv/atoi_test.gno b/gnovm/stdlibs/strconv/atoi_test.gno index cb150628f5c..5dc0577924f 100644 --- a/gnovm/stdlibs/strconv/atoi_test.gno +++ b/gnovm/stdlibs/strconv/atoi_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "errors" "fmt" + "strconv" "testing" ) @@ -17,23 +18,23 @@ type parseUint64Test struct { } var parseUint64Tests = []parseUint64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"98765432100", 98765432100, nil}, {"18446744073709551615", 1<<64 - 1, nil}, - {"18446744073709551616", 1<<64 - 1, ErrRange}, - {"18446744073709551620", 1<<64 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"-0", 0, ErrSyntax}, - {"-1", 0, ErrSyntax}, - {"+1", 0, ErrSyntax}, + {"18446744073709551616", 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 1<<64 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"-0", 0, strconv.ErrSyntax}, + {"-1", 0, strconv.ErrSyntax}, + {"+1", 0, strconv.ErrSyntax}, } type parseUint64BaseTest struct { @@ -44,91 +45,91 @@ type parseUint64BaseTest struct { } var parseUint64BaseTests = []parseUint64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, - {"0x", 0, 0, ErrSyntax}, - {"0X", 0, 0, ErrSyntax}, + {"0x", 0, 0, strconv.ErrSyntax}, + {"0X", 0, 0, strconv.ErrSyntax}, {"1", 0, 1, nil}, {"12345", 0, 12345, nil}, {"012345", 0, 012345, nil}, {"0x12345", 0, 0x12345, nil}, {"0X12345", 0, 0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"0xabcdefg123", 0, 0, ErrSyntax}, - {"123456789abc", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"0xabcdefg123", 0, 0, strconv.ErrSyntax}, + {"123456789abc", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"18446744073709551615", 0, 1<<64 - 1, nil}, - {"18446744073709551616", 0, 1<<64 - 1, ErrRange}, - {"18446744073709551620", 0, 1<<64 - 1, ErrRange}, + {"18446744073709551616", 0, 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 0, 1<<64 - 1, strconv.ErrRange}, {"0xFFFFFFFFFFFFFFFF", 0, 1<<64 - 1, nil}, - {"0x10000000000000000", 0, 1<<64 - 1, ErrRange}, + {"0x10000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"01777777777777777777777", 0, 1<<64 - 1, nil}, - {"01777777777777777777778", 0, 0, ErrSyntax}, - {"02000000000000000000000", 0, 1<<64 - 1, ErrRange}, + {"01777777777777777777778", 0, 0, strconv.ErrSyntax}, + {"02000000000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"0200000000000000000000", 0, 1 << 61, nil}, - {"0b", 0, 0, ErrSyntax}, - {"0B", 0, 0, ErrSyntax}, + {"0b", 0, 0, strconv.ErrSyntax}, + {"0B", 0, 0, strconv.ErrSyntax}, {"0b101", 0, 5, nil}, {"0B101", 0, 5, nil}, - {"0o", 0, 0, ErrSyntax}, - {"0O", 0, 0, ErrSyntax}, + {"0o", 0, 0, strconv.ErrSyntax}, + {"0O", 0, 0, strconv.ErrSyntax}, {"0o377", 0, 255, nil}, {"0O377", 0, 255, nil}, // underscores allowed with base == 0 only {"1_2_3_4_5", 0, 12345, nil}, // base 0 => 10 - {"_12345", 0, 0, ErrSyntax}, - {"1__2345", 0, 0, ErrSyntax}, - {"12345_", 0, 0, ErrSyntax}, + {"_12345", 0, 0, strconv.ErrSyntax}, + {"1__2345", 0, 0, strconv.ErrSyntax}, + {"12345_", 0, 0, strconv.ErrSyntax}, - {"1_2_3_4_5", 10, 0, ErrSyntax}, // base 10 - {"_12345", 10, 0, ErrSyntax}, - {"1__2345", 10, 0, ErrSyntax}, - {"12345_", 10, 0, ErrSyntax}, + {"1_2_3_4_5", 10, 0, strconv.ErrSyntax}, // base 10 + {"_12345", 10, 0, strconv.ErrSyntax}, + {"1__2345", 10, 0, strconv.ErrSyntax}, + {"12345_", 10, 0, strconv.ErrSyntax}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, // base 0 => 16 - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, - - {"1_2_3_4_5", 16, 0, ErrSyntax}, // base 16 - {"_12345", 16, 0, ErrSyntax}, - {"1__2345", 16, 0, ErrSyntax}, - {"1234__5", 16, 0, ErrSyntax}, - {"12345_", 16, 0, ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, + + {"1_2_3_4_5", 16, 0, strconv.ErrSyntax}, // base 16 + {"_12345", 16, 0, strconv.ErrSyntax}, + {"1__2345", 16, 0, strconv.ErrSyntax}, + {"1234__5", 16, 0, strconv.ErrSyntax}, + {"12345_", 16, 0, strconv.ErrSyntax}, {"0_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0377) - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"0o_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0o377) - {"_0o12345", 0, 0, ErrSyntax}, - {"0o__12345", 0, 0, ErrSyntax}, - {"0o1234__5", 0, 0, ErrSyntax}, - {"0o12345_", 0, 0, ErrSyntax}, + {"_0o12345", 0, 0, strconv.ErrSyntax}, + {"0o__12345", 0, 0, strconv.ErrSyntax}, + {"0o1234__5", 0, 0, strconv.ErrSyntax}, + {"0o12345_", 0, 0, strconv.ErrSyntax}, - {"0_1_2_3_4_5", 8, 0, ErrSyntax}, // base 8 - {"_012345", 8, 0, ErrSyntax}, - {"0__12345", 8, 0, ErrSyntax}, - {"01234__5", 8, 0, ErrSyntax}, - {"012345_", 8, 0, ErrSyntax}, + {"0_1_2_3_4_5", 8, 0, strconv.ErrSyntax}, // base 8 + {"_012345", 8, 0, strconv.ErrSyntax}, + {"0__12345", 8, 0, strconv.ErrSyntax}, + {"01234__5", 8, 0, strconv.ErrSyntax}, + {"012345_", 8, 0, strconv.ErrSyntax}, {"0b_1_0_1", 0, 5, nil}, // base 0 => 2 (0b101) - {"_0b101", 0, 0, ErrSyntax}, - {"0b__101", 0, 0, ErrSyntax}, - {"0b1__01", 0, 0, ErrSyntax}, - {"0b10__1", 0, 0, ErrSyntax}, - {"0b101_", 0, 0, ErrSyntax}, + {"_0b101", 0, 0, strconv.ErrSyntax}, + {"0b__101", 0, 0, strconv.ErrSyntax}, + {"0b1__01", 0, 0, strconv.ErrSyntax}, + {"0b10__1", 0, 0, strconv.ErrSyntax}, + {"0b101_", 0, 0, strconv.ErrSyntax}, - {"1_0_1", 2, 0, ErrSyntax}, // base 2 - {"_101", 2, 0, ErrSyntax}, - {"1_01", 2, 0, ErrSyntax}, - {"10_1", 2, 0, ErrSyntax}, - {"101_", 2, 0, ErrSyntax}, + {"1_0_1", 2, 0, strconv.ErrSyntax}, // base 2 + {"_101", 2, 0, strconv.ErrSyntax}, + {"1_01", 2, 0, strconv.ErrSyntax}, + {"10_1", 2, 0, strconv.ErrSyntax}, + {"101_", 2, 0, strconv.ErrSyntax}, } type parseInt64Test struct { @@ -138,7 +139,7 @@ type parseInt64Test struct { } var parseInt64Tests = []parseInt64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"+0", 0, nil}, @@ -153,16 +154,16 @@ var parseInt64Tests = []parseInt64Test{ {"-98765432100", -98765432100, nil}, {"9223372036854775807", 1<<63 - 1, nil}, {"-9223372036854775807", -(1<<63 - 1), nil}, - {"9223372036854775808", 1<<63 - 1, ErrRange}, + {"9223372036854775808", 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", -1 << 63, nil}, - {"9223372036854775809", 1<<63 - 1, ErrRange}, - {"-9223372036854775809", -1 << 63, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"9223372036854775809", 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", -1 << 63, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type parseInt64BaseTest struct { @@ -173,7 +174,7 @@ type parseInt64BaseTest struct { } var parseInt64BaseTests = []parseInt64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, {"-0", 0, 0, nil}, {"1", 0, 1, nil}, @@ -184,16 +185,16 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"-012345", 0, -012345, nil}, {"0x12345", 0, 0x12345, nil}, {"-0X12345", 0, -0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"-12345x", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"-12345x", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"-98765432100", 0, -98765432100, nil}, {"9223372036854775807", 0, 1<<63 - 1, nil}, {"-9223372036854775807", 0, -(1<<63 - 1), nil}, - {"9223372036854775808", 0, 1<<63 - 1, ErrRange}, + {"9223372036854775808", 0, 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", 0, -1 << 63, nil}, - {"9223372036854775809", 0, 1<<63 - 1, ErrRange}, - {"-9223372036854775809", 0, -1 << 63, ErrRange}, + {"9223372036854775809", 0, 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", 0, -1 << 63, strconv.ErrRange}, // other bases {"g", 17, 16, nil}, @@ -207,9 +208,9 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"1010", 2, 10, nil}, {"1000000000000000", 2, 1 << 15, nil}, {"111111111111111111111111111111111111111111111111111111111111111", 2, 1<<63 - 1, nil}, - {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, ErrRange}, + {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, strconv.ErrRange}, {"-1000000000000000000000000000000000000000000000000000000000000000", 2, -1 << 63, nil}, - {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, ErrRange}, + {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, strconv.ErrRange}, // base 8 {"-10", 8, -8, nil}, @@ -224,27 +225,27 @@ var parseInt64BaseTests = []parseInt64BaseTest{ // underscores {"-0x_1_2_3_4_5", 0, -0x12345, nil}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, - {"-_0x12345", 0, 0, ErrSyntax}, - {"_-0x12345", 0, 0, ErrSyntax}, - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, + {"-_0x12345", 0, 0, strconv.ErrSyntax}, + {"_-0x12345", 0, 0, strconv.ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, {"-0_1_2_3_4_5", 0, -012345, nil}, // octal {"0_1_2_3_4_5", 0, 012345, nil}, // octal - {"-_012345", 0, 0, ErrSyntax}, - {"_-012345", 0, 0, ErrSyntax}, - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"-_012345", 0, 0, strconv.ErrSyntax}, + {"_-012345", 0, 0, strconv.ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"+0xf", 0, 0xf, nil}, {"-0xf", 0, -0xf, nil}, - {"0x+f", 0, 0, ErrSyntax}, - {"0x-f", 0, 0, ErrSyntax}, + {"0x+f", 0, 0, strconv.ErrSyntax}, + {"0x-f", 0, 0, strconv.ErrSyntax}, } type parseUint32Test struct { @@ -254,20 +255,20 @@ type parseUint32Test struct { } var parseUint32Tests = []parseUint32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"4294967295", 1<<32 - 1, nil}, - {"4294967296", 1<<32 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, + {"4294967296", 1<<32 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, } type parseInt32Test struct { @@ -277,7 +278,7 @@ type parseInt32Test struct { } var parseInt32Tests = []parseInt32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"1", 1, nil}, @@ -286,22 +287,22 @@ var parseInt32Tests = []parseInt32Test{ {"-12345", -12345, nil}, {"012345", 12345, nil}, {"-012345", -12345, nil}, - {"12345x", 0, ErrSyntax}, - {"-12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, + {"-12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"-987654321", -987654321, nil}, {"2147483647", 1<<31 - 1, nil}, {"-2147483647", -(1<<31 - 1), nil}, - {"2147483648", 1<<31 - 1, ErrRange}, + {"2147483648", 1<<31 - 1, strconv.ErrRange}, {"-2147483648", -1 << 31, nil}, - {"2147483649", 1<<31 - 1, ErrRange}, - {"-2147483649", -1 << 31, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"2147483649", 1<<31 - 1, strconv.ErrRange}, + {"-2147483649", -1 << 31, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type numErrorTest struct { @@ -320,37 +321,37 @@ func init() { for i := range parseUint64Tests { test := &parseUint64Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt64Tests { test := &parseInt64Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseUint32Tests { test := &parseUint32Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt32Tests { test := &parseInt32Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } } @@ -358,7 +359,7 @@ func init() { func TestParseUint32(t *testing.T) { for i := range parseUint32Tests { test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 32) + out, err := strconv.ParseUint(test.in, 10, 32) if uint64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -369,7 +370,7 @@ func TestParseUint32(t *testing.T) { func TestParseUint64(t *testing.T) { for i := range parseUint64Tests { test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 64) + out, err := strconv.ParseUint(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -380,7 +381,7 @@ func TestParseUint64(t *testing.T) { func TestParseUint64Base(t *testing.T) { for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] - out, err := ParseUint(test.in, test.base, 64) + out, err := strconv.ParseUint(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -391,7 +392,7 @@ func TestParseUint64Base(t *testing.T) { func TestParseInt32(t *testing.T) { for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 32) + out, err := strconv.ParseInt(test.in, 10, 32) if int64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10 ,32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -402,7 +403,7 @@ func TestParseInt32(t *testing.T) { func TestParseInt64(t *testing.T) { for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 64) + out, err := strconv.ParseInt(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -413,7 +414,7 @@ func TestParseInt64(t *testing.T) { func TestParseInt64Base(t *testing.T) { for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] - out, err := ParseInt(test.in, test.base, 64) + out, err := strconv.ParseInt(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -422,11 +423,11 @@ func TestParseInt64Base(t *testing.T) { } func TestParseUint(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseUint32Tests { test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 0) + out, err := strconv.ParseUint(test.in, 10, 0) if uint64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -435,7 +436,7 @@ func TestParseUint(t *testing.T) { case 64: for i := range parseUint64Tests { test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 0) + out, err := strconv.ParseUint(test.in, 10, 0) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -445,11 +446,11 @@ func TestParseUint(t *testing.T) { } func TestParseInt(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 0) + out, err := strconv.ParseInt(test.in, 10, 0) if int64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -458,7 +459,7 @@ func TestParseInt(t *testing.T) { case 64: for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 0) + out, err := strconv.ParseInt(test.in, 10, 0) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -468,14 +469,14 @@ func TestParseInt(t *testing.T) { } func TestAtoi(t *testing.T) { - switch IntSize { + switch strconv.IntSize { case 32: for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := Atoi(test.in) + out, err := strconv.Atoi(test.in) var testErr error if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } if int(test.out) != out || !errEqual(testErr, err) { t.Errorf("Atoi(%q) = %v, %v want %v, %v", @@ -485,10 +486,10 @@ func TestAtoi(t *testing.T) { case 64: for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := Atoi(test.in) + out, err := strconv.Atoi(test.in) var testErr error if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } if test.out != int64(out) || !errEqual(testErr, err) { t.Errorf("Atoi(%q) = %v, %v want %v, %v", @@ -499,11 +500,11 @@ func TestAtoi(t *testing.T) { } func bitSizeErrStub(name string, bitSize int) error { - return BitSizeError(name, "0", bitSize) + return strconv.BitSizeError(name, "0", bitSize) } func baseErrStub(name string, base int) error { - return BaseError(name, "0", base) + return strconv.BaseError(name, "0", base) } func noErrStub(name string, arg int) error { @@ -545,7 +546,7 @@ func TestParseIntBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", 0, test.arg) + _, err := strconv.ParseInt("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -557,7 +558,7 @@ func TestParseUintBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", 0, test.arg) + _, err := strconv.ParseUint("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -569,7 +570,7 @@ func TestParseIntBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", test.arg, 0) + _, err := strconv.ParseInt("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -581,7 +582,7 @@ func TestParseUintBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", test.arg, 0) + _, err := strconv.ParseUint("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -591,21 +592,21 @@ func TestParseUintBase(t *testing.T) { func TestNumError(t *testing.T) { for _, test := range numErrorTests { - err := &NumError{ + err := &strconv.NumError{ Func: "ParseFloat", Num: test.num, Err: errors.New("failed"), } if got := err.Error(); got != test.want { - t.Errorf(`(&NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) + t.Errorf(`(&strconv.NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) } } } /* XXX: add when we support reflection / error un/wrapping. func TestNumErrorUnwrap(t *testing.T) { - err := &NumError{Err: ErrSyntax} - if !errEqual(err, ErrSyntax) { + err := &strconv.NumError{Err: strconv.ErrSyntax} + if !errEqual(err, strconv.ErrSyntax) { t.Error("errors.Is failed, wanted success") } } @@ -637,7 +638,7 @@ func benchmarkParseInt(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := ParseInt(s, 10, 64) + out, _ := strconv.ParseInt(s, 10, 64) BenchSink += int(out) } }) @@ -659,7 +660,7 @@ func benchmarkAtoi(b *testing.B, neg int) { {"26bit", 1<<26 - 1}, {"31bit", 1<<31 - 1}, } - if IntSize == 64 { + if strconv.IntSize == 64 { cases = append(cases, []benchCase{ {"56bit", 1<<56 - 1}, {"63bit", 1<<63 - 1}, @@ -669,7 +670,7 @@ func benchmarkAtoi(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := Atoi(s) + out, _ := strconv.Atoi(s) BenchSink += out } }) diff --git a/gnovm/stdlibs/strconv/decimal_test.gno b/gnovm/stdlibs/strconv/decimal_test.gno index 9dc8c997b9c..58036fe69b0 100644 --- a/gnovm/stdlibs/strconv/decimal_test.gno +++ b/gnovm/stdlibs/strconv/decimal_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -31,7 +32,7 @@ var shifttests = []shiftTest{ func TestDecimalShift(t *testing.T) { for i := 0; i < len(shifttests); i++ { test := &shifttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) s := d.String() if s != test.out { @@ -69,21 +70,21 @@ var roundtests = []roundTest{ func TestDecimalRound(t *testing.T) { for i := 0; i < len(roundtests); i++ { test := &roundtests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.RoundDown(test.nd) s := d.String() if s != test.down { t.Errorf("Decimal %v RoundDown %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.Round(test.nd) s = d.String() if s != test.round { t.Errorf("Decimal %v Round %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.RoundUp(test.nd) s = d.String() if s != test.up { @@ -115,7 +116,7 @@ var roundinttests = []roundIntTest{ func TestDecimalRoundedInteger(t *testing.T) { for i := 0; i < len(roundinttests); i++ { test := roundinttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) num := d.RoundedInteger() if num != test.int { diff --git a/gnovm/stdlibs/strconv/ftoa_test.gno b/gnovm/stdlibs/strconv/ftoa_test.gno index df1cc733827..bb860d62278 100644 --- a/gnovm/stdlibs/strconv/ftoa_test.gno +++ b/gnovm/stdlibs/strconv/ftoa_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" "math/rand" + "strconv" "testing" ) @@ -176,20 +177,20 @@ var ftoatests = []ftoaTest{ func TestFtoa(t *testing.T) { for i := 0; i < len(ftoatests); i++ { test := &ftoatests[i] - s := FormatFloat(test.f, test.fmt, test.prec, 64) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 64) if s != test.s { t.Error("testN=64", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=64", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } if float64(float32(test.f)) == test.f && test.fmt != 'b' { - s := FormatFloat(test.f, test.fmt, test.prec, 32) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 32) if s != test.s { t.Error("testN=32", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=32", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } @@ -201,15 +202,15 @@ func TestFtoaPowersOfTwo(t *testing.T) { for exp := -2048; exp <= 2048; exp++ { f := math.Ldexp(1, exp) if !math.IsInf(f, 0) { - s := FormatFloat(f, 'e', -1, 64) - if x, _ := ParseFloat(s, 64); x != f { + s := strconv.FormatFloat(f, 'e', -1, 64) + if x, _ := strconv.ParseFloat(s, 64); x != f { t.Errorf("failed roundtrip %v => %s => %v", f, s, x) } } f32 := float32(f) if !math.IsInf(float64(f32), 0) { - s := FormatFloat(float64(f32), 'e', -1, 32) - if x, _ := ParseFloat(s, 32); float32(x) != f32 { + s := strconv.FormatFloat(float64(f32), 'e', -1, 32) + if x, _ := strconv.ParseFloat(s, 32); float32(x) != f32 { t.Errorf("failed roundtrip %v => %s => %v", f32, s, float32(x)) } } @@ -226,19 +227,19 @@ func TestFtoaRandom(t *testing.T) { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - shortFast := FormatFloat(x, 'g', -1, 64) - SetOptimize(false) - shortSlow := FormatFloat(x, 'g', -1, 64) - SetOptimize(true) + shortFast := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(false) + shortSlow := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } prec := rand.IntN(12) + 5 - shortFast = FormatFloat(x, 'e', prec, 64) - SetOptimize(false) - shortSlow = FormatFloat(x, 'e', prec, 64) - SetOptimize(true) + shortFast = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(false) + shortSlow = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } @@ -251,7 +252,7 @@ func TestFormatFloatInvalidBitSize(t *testing.T) { t.Fatalf("expected panic due to invalid bitSize") } }() - _ = FormatFloat(3.14, 'g', -1, 100) + _ = strconv.FormatFloat(3.14, 'g', -1, 100) } var ftoaBenches = []struct { @@ -305,7 +306,7 @@ func BenchmarkFormatFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - FormatFloat(c.float, c.fmt, c.prec, c.bitSize) + strconv.FormatFloat(c.float, c.fmt, c.prec, c.bitSize) } }) } @@ -316,7 +317,7 @@ func BenchmarkAppendFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) + strconv.AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) } }) } diff --git a/gnovm/stdlibs/strconv/ftoaryu_test.gno b/gnovm/stdlibs/strconv/ftoaryu_test.gno index bd969e8e997..8a7cb1fb97d 100644 --- a/gnovm/stdlibs/strconv/ftoaryu_test.gno +++ b/gnovm/stdlibs/strconv/ftoaryu_test.gno @@ -2,16 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" + "strconv" "testing" ) func TestMulByLog2Log10(t *testing.T) { for x := -1600; x <= +1600; x++ { - iMath := MulByLog2Log10(x) + iMath := strconv.MulByLog2Log10(x) fMath := int(math.Floor(float64(x) * math.Ln2 / math.Ln10)) if iMath != fMath { t.Errorf("mulByLog2Log10(%d) failed: %d vs %d\n", x, iMath, fMath) @@ -21,7 +22,7 @@ func TestMulByLog2Log10(t *testing.T) { func TestMulByLog10Log2(t *testing.T) { for x := -500; x <= +500; x++ { - iMath := MulByLog10Log2(x) + iMath := strconv.MulByLog10Log2(x) fMath := int(math.Floor(float64(x) * math.Ln10 / math.Ln2)) if iMath != fMath { t.Errorf("mulByLog10Log2(%d) failed: %d vs %d\n", x, iMath, fMath) diff --git a/gnovm/stdlibs/strconv/itoa_test.gno b/gnovm/stdlibs/strconv/itoa_test.gno index b76acc78183..e463edeb5eb 100644 --- a/gnovm/stdlibs/strconv/itoa_test.gno +++ b/gnovm/stdlibs/strconv/itoa_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -60,24 +61,24 @@ var itob64tests = []itob64Test{ func TestItoa(t *testing.T) { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) if s != test.out { t.Errorf("FormatInt(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendInt([]byte("abc"), test.in, test.base) + x := strconv.AppendInt([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendInt(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) } if test.in >= 0 { - s := FormatUint(uint64(test.in), test.base) + s := strconv.FormatUint(uint64(test.in), test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint(nil, uint64(test.in), test.base) + x := strconv.AppendUint(nil, uint64(test.in), test.base) if string(x) != test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", uint64(test.in), test.base, x, test.out) @@ -85,7 +86,7 @@ func TestItoa(t *testing.T) { } if test.base == 10 && int64(int(test.in)) == test.in { - s := Itoa(int(test.in)) + s := strconv.Itoa(int(test.in)) if s != test.out { t.Errorf("Itoa(%v) = %v want %v", test.in, s, test.out) @@ -99,7 +100,7 @@ func TestItoa(t *testing.T) { t.Fatalf("expected panic due to illegal base") } }() - FormatUint(12345678, 1) + strconv.FormatUint(12345678, 1) } type uitob64Test struct { @@ -119,12 +120,12 @@ var uitob64tests = []uitob64Test{ func TestUitoa(t *testing.T) { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint([]byte("abc"), test.in, test.base) + x := strconv.AppendUint([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) @@ -161,7 +162,7 @@ var varlenUints = []struct { func TestFormatUintVarlen(t *testing.T) { for _, test := range varlenUints { - s := FormatUint(test.in, 10) + s := strconv.FormatUint(test.in, 10) if s != test.out { t.Errorf("FormatUint(%v, 10) = %v want %v", test.in, s, test.out) } @@ -171,7 +172,7 @@ func TestFormatUintVarlen(t *testing.T) { func BenchmarkFormatInt(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) BenchSink += len(s) } } @@ -181,7 +182,7 @@ func BenchmarkAppendInt(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range itob64tests { - dst = AppendInt(dst[:0], test.in, test.base) + dst = strconv.AppendInt(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -190,7 +191,7 @@ func BenchmarkAppendInt(b *testing.B) { func BenchmarkFormatUint(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) BenchSink += len(s) } } @@ -200,7 +201,7 @@ func BenchmarkAppendUint(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - dst = AppendUint(dst[:0], test.in, test.base) + dst = strconv.AppendUint(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -209,9 +210,9 @@ func BenchmarkAppendUint(b *testing.B) { func BenchmarkFormatIntSmall(b *testing.B) { smallInts := []int64{7, 42} for _, smallInt := range smallInts { - b.Run(Itoa(int(smallInt)), func(b *testing.B) { + b.Run(strconv.Itoa(int(smallInt)), func(b *testing.B) { for i := 0; i < b.N; i++ { - s := FormatInt(smallInt, 10) + s := strconv.FormatInt(smallInt, 10) BenchSink += len(s) } }) @@ -222,7 +223,7 @@ func BenchmarkAppendIntSmall(b *testing.B) { dst := make([]byte, 0, 30) const smallInt = 42 for i := 0; i < b.N; i++ { - dst = AppendInt(dst[:0], smallInt, 10) + dst = strconv.AppendInt(dst[:0], smallInt, 10) BenchSink += len(dst) } } @@ -232,7 +233,7 @@ func BenchmarkAppendUintVarlen(b *testing.B) { b.Run(test.out, func(b *testing.B) { dst := make([]byte, 0, 30) for j := 0; j < b.N; j++ { - dst = AppendUint(dst[:0], test.in, 10) + dst = strconv.AppendUint(dst[:0], test.in, 10) BenchSink += len(dst) } }) diff --git a/gnovm/stdlibs/strconv/quote_test.gno b/gnovm/stdlibs/strconv/quote_test.gno index b11e95461b0..40601365a30 100644 --- a/gnovm/stdlibs/strconv/quote_test.gno +++ b/gnovm/stdlibs/strconv/quote_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" "unicode" @@ -14,8 +15,8 @@ import ( func TestIsPrint(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsPrint(r) != unicode.IsPrint(r) { - t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r)) + if strconv.IsPrint(r) != unicode.IsPrint(r) { + t.Errorf("IsPrint(%U)=%t incorrect", r, strconv.IsPrint(r)) n++ if n > 10 { return @@ -28,8 +29,8 @@ func TestIsPrint(t *testing.T) { func TestIsGraphic(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsGraphic(r) != unicode.IsGraphic(r) { - t.Errorf("IsGraphic(%U)=%t incorrect", r, IsGraphic(r)) + if strconv.IsGraphic(r) != unicode.IsGraphic(r) { + t.Errorf("IsGraphic(%U)=%t incorrect", r, strconv.IsGraphic(r)) n++ if n > 10 { return @@ -59,10 +60,10 @@ var quotetests = []quoteTest{ func TestQuote(t *testing.T) { for _, tt := range quotetests { - if out := Quote(tt.in); out != tt.out { + if out := strconv.Quote(tt.in); out != tt.out { t.Errorf("Quote(%s) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuote(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -70,10 +71,10 @@ func TestQuote(t *testing.T) { func TestQuoteToASCII(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteToASCII(%s) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteToASCII(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -81,10 +82,10 @@ func TestQuoteToASCII(t *testing.T) { func TestQuoteToGraphic(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteToGraphic(%s) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteToGraphic(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -92,13 +93,13 @@ func TestQuoteToGraphic(t *testing.T) { func BenchmarkQuote(b *testing.B) { for i := 0; i < b.N; i++ { - Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + strconv.Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } func BenchmarkQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - QuoteRune('\a') + strconv.QuoteRune('\a') } } @@ -106,7 +107,7 @@ var benchQuoteBuf []byte func BenchmarkAppendQuote(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteBuf = AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + benchQuoteBuf = strconv.AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } @@ -114,7 +115,7 @@ var benchQuoteRuneBuf []byte func BenchmarkAppendQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteRuneBuf = AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') + benchQuoteRuneBuf = strconv.AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') } } @@ -144,10 +145,10 @@ var quoterunetests = []quoteRuneTest{ func TestQuoteRune(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRune(tt.in); out != tt.out { + if out := strconv.QuoteRune(tt.in); out != tt.out { t.Errorf("QuoteRune(%U) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuoteRune(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -155,10 +156,10 @@ func TestQuoteRune(t *testing.T) { func TestQuoteRuneToASCII(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteRuneToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteRuneToASCII(%U) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteRuneToASCII(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -166,10 +167,10 @@ func TestQuoteRuneToASCII(t *testing.T) { func TestQuoteRuneToGraphic(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteRuneToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteRuneToGraphic(%U) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteRuneToGraphic(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -228,7 +229,7 @@ var canbackquotetests = []canBackquoteTest{ func TestCanBackquote(t *testing.T) { for _, tt := range canbackquotetests { - if out := CanBackquote(tt.in); out != tt.out { + if out := strconv.CanBackquote(tt.in); out != tt.out { t.Errorf("CanBackquote(%q) = %v, want %v", tt.in, out, tt.out) } } @@ -318,7 +319,7 @@ func TestUnquote(t *testing.T) { testUnquote(t, tt.out, tt.in, nil) } for _, s := range misquoted { - testUnquote(t, s, "", ErrSyntax) + testUnquote(t, s, "", strconv.ErrSyntax) } } @@ -332,7 +333,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { wantErr error }{ {in: `"foo"`, want: "foo"}, - {in: `"foo`, wantErr: ErrSyntax}, + {in: `"foo`, wantErr: strconv.ErrSyntax}, {in: `"` + "\xc0" + `"`, want: "\xef\xbf\xbd"}, {in: `"a` + "\xc0" + `"`, want: "a\xef\xbf\xbd"}, {in: `"\t` + "\xc0" + `"`, want: "\t\xef\xbf\xbd"}, @@ -344,7 +345,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { func testUnquote(t *testing.T, in, want string, wantErr error) { // Test Unquote. - got, gotErr := Unquote(in) + got, gotErr := strconv.Unquote(in) if got != want || gotErr != wantErr { t.Errorf("Unquote(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr) } @@ -360,9 +361,9 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { suffix = strings.ReplaceAll(suffix, in[:1], "") } in += suffix - got, gotErr = QuotedPrefix(in) + got, gotErr = strconv.QuotedPrefix(in) if gotErr == nil && wantErr != nil { - _, wantErr = Unquote(got) // original input had trailing junk, reparse with only valid prefix + _, wantErr = strconv.Unquote(got) // original input had trailing junk, reparse with only valid prefix want = got } if got != want || gotErr != wantErr { @@ -372,12 +373,12 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { func BenchmarkUnquoteEasy(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"Give me a rock, paper and scissors and I will move the world."`) + strconv.Unquote(`"Give me a rock, paper and scissors and I will move the world."`) } } func BenchmarkUnquoteHard(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) + strconv.Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) } } diff --git a/gnovm/stdlibs/strconv/strconv_test.gno b/gnovm/stdlibs/strconv/strconv_test.gno index 5421ae84a93..e80eca207f7 100644 --- a/gnovm/stdlibs/strconv/strconv_test.gno +++ b/gnovm/stdlibs/strconv/strconv_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" ) @@ -21,24 +22,24 @@ var ( }{ {0, `AppendInt(localBuf[:0], 123, 10)`, func() { var localBuf [64]byte - AppendInt(localBuf[:0], 123, 10) + strconv.AppendInt(localBuf[:0], 123, 10) }}, - {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { AppendInt(globalBuf[:0], 123, 10) }}, + {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { strconv.AppendInt(globalBuf[:0], 123, 10) }}, {0, `AppendFloat(localBuf[:0], 1.23, 'g', 5, 64)`, func() { var localBuf [64]byte - AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) + strconv.AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) }}, - {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, + {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { strconv.AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, // In practice we see 7 for the next one, but allow some slop. // Before pre-allocation in appendQuotedWith, we saw 39. - {10, `AppendQuoteToASCII(nil, oneMB)`, func() { AppendQuoteToASCII(nil, string(oneMB)) }}, - {0, `ParseFloat("123.45", 64)`, func() { ParseFloat("123.45", 64) }}, - {0, `ParseFloat("123.456789123456789", 64)`, func() { ParseFloat("123.456789123456789", 64) }}, + {10, `AppendQuoteToASCII(nil, oneMB)`, func() { strconv.AppendQuoteToASCII(nil, string(oneMB)) }}, + {0, `ParseFloat("123.45", 64)`, func() { strconv.ParseFloat("123.45", 64) }}, + {0, `ParseFloat("123.456789123456789", 64)`, func() { strconv.ParseFloat("123.456789123456789", 64) }}, {0, `ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64)`, func() { - ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) + strconv.ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) }}, {0, `ParseFloat("1.0000000000000001110223024625156540423631668090820312500...001", 64)`, func() { - ParseFloat(nextToOne, 64) + strconv.ParseFloat(nextToOne, 64) }}, } ) @@ -125,11 +126,11 @@ func TestAllocationsFromBytes(t *testing.T) { */ func TestErrorPrefixes(t *testing.T) { - _, errInt := Atoi("INVALID") - _, errBool := ParseBool("INVALID") - _, errFloat := ParseFloat("INVALID", 64) - _, errInt64 := ParseInt("INVALID", 10, 64) - _, errUint64 := ParseUint("INVALID", 10, 64) + _, errInt := strconv.Atoi("INVALID") + _, errBool := strconv.ParseBool("INVALID") + _, errFloat := strconv.ParseFloat("INVALID", 64) + _, errInt64 := strconv.ParseInt("INVALID", 10, 64) + _, errUint64 := strconv.ParseUint("INVALID", 10, 64) vectors := []struct { err error // Input error @@ -143,7 +144,7 @@ func TestErrorPrefixes(t *testing.T) { } for _, v := range vectors { - nerr, ok := v.err.(*NumError) + nerr, ok := v.err.(*strconv.NumError) if !ok { t.Errorf("test %s, error was not a *NumError", v.want) continue diff --git a/gnovm/stdlibs/strings/printtrie_impl_test.gno b/gnovm/stdlibs/strings/printtrie_impl_test.gno new file mode 100644 index 00000000000..2a16c012eeb --- /dev/null +++ b/gnovm/stdlibs/strings/printtrie_impl_test.gno @@ -0,0 +1,29 @@ +package strings + +func (r *Replacer) PrintTrie() string { + r.buildOnce() + gen := r.r.(*genericReplacer) + return gen.printNode(&gen.root, 0) +} + +func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { + if t.priority > 0 { + s += "+" + } else { + s += "-" + } + s += "\n" + + if t.prefix != "" { + s += Repeat(".", depth) + t.prefix + s += r.printNode(t.next, depth+len(t.prefix)) + } else if t.table != nil { + for b, m := range r.mapping { + if int(m) != r.tableSize && t.table[m] != nil { + s += Repeat(".", depth) + string([]byte{byte(b)}) + s += r.printNode(t.table[m], depth+1) + } + } + } + return +} diff --git a/gnovm/stdlibs/strings/printtrie_test.gno b/gnovm/stdlibs/strings/printtrie_test.gno index b5b387b9bca..a51208f4756 100644 --- a/gnovm/stdlibs/strings/printtrie_test.gno +++ b/gnovm/stdlibs/strings/printtrie_test.gno @@ -1,37 +1,10 @@ -package strings +package strings_test import ( + "strings" "testing" ) -func (r *Replacer) PrintTrie() string { - r.buildOnce() - gen := r.r.(*genericReplacer) - return gen.printNode(&gen.root, 0) -} - -func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { - if t.priority > 0 { - s += "+" - } else { - s += "-" - } - s += "\n" - - if t.prefix != "" { - s += Repeat(".", depth) + t.prefix - s += r.printNode(t.next, depth+len(t.prefix)) - } else if t.table != nil { - for b, m := range r.mapping { - if int(m) != r.tableSize && t.table[m] != nil { - s += Repeat(".", depth) + string([]byte{byte(b)}) - s += r.printNode(t.table[m], depth+1) - } - } - } - return -} - func TestGenericTrieBuilding(t *testing.T) { testCases := []struct{ in, out string }{ {"abc;abdef;abdefgh;xx;xy;z", `- @@ -79,13 +52,13 @@ func TestGenericTrieBuilding(t *testing.T) { } for _, tc := range testCases { - keys := Split(tc.in, ";") + keys := strings.Split(tc.in, ";") args := make([]string, len(keys)*2) for i, key := range keys { args[i*2] = key } - got := NewReplacer(args...).PrintTrie() + got := strings.NewReplacer(args...).PrintTrie() // Remove tabs from tc.out wantbuf := make([]byte, 0, len(tc.out)) for i := 0; i < len(tc.out); i++ { diff --git a/gnovm/stdlibs/testing/match.gno b/gnovm/stdlibs/testing/match.gno index 8b099f37624..fae9391dc79 100644 --- a/gnovm/stdlibs/testing/match.gno +++ b/gnovm/stdlibs/testing/match.gno @@ -8,7 +8,6 @@ package testing import ( "fmt" - "regexp" "strings" "unicode" ) @@ -35,7 +34,7 @@ func (m simpleMatch) matches(name []string) (ok, partial bool) { if i >= len(m) { break } - if ok, _ := regexp.MatchString(m[i], s); !ok { + if ok, _ := matchString(m[i], s); !ok { return false, false } } @@ -48,7 +47,7 @@ func (m simpleMatch) verify(name string) error { } // Verify filters before doing any processing. for i, s := range m { - if _, err := regexp.MatchString(s, "non-empty"); err != nil { + if _, err := matchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index fdafd9652ba..08082894178 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -322,6 +322,9 @@ func formatDur(dur int64) string { // used to calculate execution times; only present in testing stdlibs func unixNano() int64 +// used to filter tests, we can't directly use regexp here due to a cyclic import; only present in testing stdlibs +func matchString(pat, str string) (result bool, err error) + func tRunner(t *T, fn testingFunc, verbose bool) { if !t.shouldRun(t.name) { return diff --git a/gnovm/stdlibs/testing/testing.go b/gnovm/stdlibs/testing/testing.go index 2c2e1d69904..b7408b7cceb 100644 --- a/gnovm/stdlibs/testing/testing.go +++ b/gnovm/stdlibs/testing/testing.go @@ -1,6 +1,14 @@ package testing +import ( + "errors" +) + func X_unixNano() int64 { // only implemented in testing stdlibs return 0 } + +func X_matchString(pat, str string) (result bool, err error) { + return false, errors.New("only implemented in testing stdlibs") +} diff --git a/gnovm/tests/stdlibs/README.md b/gnovm/tests/stdlibs/README.md index 16d5d171342..8742447e59a 100644 --- a/gnovm/tests/stdlibs/README.md +++ b/gnovm/tests/stdlibs/README.md @@ -4,3 +4,5 @@ This directory contains test-specific standard libraries. These are only available when testing gno code in `_test.gno` and `_filetest.gno` files. Re-declarations of functions already existing override the definitions of the normal stdlibs directory. + +Adding imports that don't exist in the corresponding normal stdlib is undefined behavior \ No newline at end of file diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index db5ecdec05d..4445d2467e8 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -333,6 +333,44 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := testlibs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "unicode", "IsPrint", diff --git a/gnovm/tests/stdlibs/testing/native_testing.gno b/gnovm/tests/stdlibs/testing/native_testing.gno index 3076a679e77..64ed5edd61d 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.gno +++ b/gnovm/tests/stdlibs/testing/native_testing.gno @@ -1,3 +1,5 @@ package testing func unixNano() int64 + +func matchString(pat, str string) (result bool, err error) diff --git a/gnovm/tests/stdlibs/testing/native_testing.go b/gnovm/tests/stdlibs/testing/native_testing.go index db681ad8a62..bde9bbca7a4 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.go +++ b/gnovm/tests/stdlibs/testing/native_testing.go @@ -1,7 +1,18 @@ package testing -import "time" +import ( + "regexp" + "time" +) func X_unixNano() int64 { return time.Now().UnixNano() } + +func X_matchString(pat, str string) (result bool, err error) { + var matchRe *regexp.Regexp + if matchRe, err = regexp.Compile(pat); err != nil { + return + } + return matchRe.MatchString(str), nil +} From 8e1c532801fac9e282cbe0995d612cf460739921 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:47:43 +0100 Subject: [PATCH 73/75] fix(p2p): infinity loop if no peer in queue (#3593) --- tm2/pkg/p2p/switch.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 5c1c37f7729..0dd087026dd 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -72,8 +72,9 @@ type MultiplexSwitch struct { privatePeers sync.Map // ID -> nothing; lookup table of peers who are not shared transport Transport - dialQueue *dial.Queue - events *events.Events + dialQueue *dial.Queue + dialNotify chan struct{} + events *events.Events } // NewMultiplexSwitch creates a new MultiplexSwitch with the given config. @@ -88,6 +89,7 @@ func NewMultiplexSwitch( peers: newSet(), transport: transport, dialQueue: dial.NewQueue(), + dialNotify: make(chan struct{}, 1), events: events.New(), maxInboundPeers: defaultCfg.MaxNumInboundPeers, maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, @@ -262,13 +264,15 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { select { case <-ctx.Done(): sw.Logger.Debug("dial context canceled") - return + default: // Grab a dial item item := sw.dialQueue.Peek() if item == nil { - // Nothing to dial + // Nothing to dial, wait until something is + // added to the queue + sw.waitForPeersToDial(ctx) continue } @@ -565,6 +569,7 @@ func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { } sw.dialQueue.Push(item) + sw.notifyAddPeerToDial() } } @@ -588,6 +593,7 @@ func (sw *MultiplexSwitch) dialItems(dialItems ...dial.Item) { } sw.dialQueue.Push(dialItem) + sw.notifyAddPeerToDial() } } @@ -698,6 +704,20 @@ func (sw *MultiplexSwitch) addPeer(p PeerConn) error { return nil } +func (sw *MultiplexSwitch) notifyAddPeerToDial() { + select { + case sw.dialNotify <- struct{}{}: + default: + } +} + +func (sw *MultiplexSwitch) waitForPeersToDial(ctx context.Context) { + select { + case <-ctx.Done(): + case <-sw.dialNotify: + } +} + // logTelemetry logs the switch telemetry data // to global metrics funnels func (sw *MultiplexSwitch) logTelemetry() { From 98c43534de031a25335f7c233ea603eabbc16c2b Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Fri, 24 Jan 2025 06:25:42 +0200 Subject: [PATCH 74/75] test(fuzz): appease goimports by renaming added .go corpus files to *go_fuzz (#3602) goimports doesn't provide an ignore pattern except by reading the $GOPATH/.goimportsignore which would be too cumbersome and non-standard to muck around with so instead this change modifies testdata/corpra/*.go to testdata/corpora/*.go_fuzz so that goimports won't throw a fit against CI/CD for this project. Fixes #3601 --- gnovm/pkg/gnolang/fuzz_test.go | 4 ++-- .../{corpra/parsefile/a.go => corpora/parsefile/a.go_fuzz} | 0 .../{corpra/parsefile/b.go => corpora/parsefile/b.go_fuzz} | 0 .../bug_3013.go => corpora/parsefile/bug_3013.go_fuzz} | 0 .../parsefile/bug_3014_redefine.go_fuzz} | 0 .../parsefile/bug_3014_redefine2.go_fuzz} | 0 .../parsefile/bug_3014_redefine3.go_fuzz} | 0 .../parsefile/bug_3014_redefine4.go_fuzz} | 0 .../parsefile/bug_3014_redefine5.go_fuzz} | 0 .../parsefile/bug_3014_redefine6.go_fuzz} | 0 10 files changed, 2 insertions(+), 2 deletions(-) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/a.go => corpora/parsefile/a.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/b.go => corpora/parsefile/b.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3013.go => corpora/parsefile/bug_3013.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine.go => corpora/parsefile/bug_3014_redefine.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine2.go => corpora/parsefile/bug_3014_redefine2.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine3.go => corpora/parsefile/bug_3014_redefine3.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine4.go => corpora/parsefile/bug_3014_redefine4.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine5.go => corpora/parsefile/bug_3014_redefine5.go_fuzz} (100%) rename gnovm/pkg/gnolang/testdata/{corpra/parsefile/bug_3014_redefine6.go => corpora/parsefile/bug_3014_redefine6.go_fuzz} (100%) diff --git a/gnovm/pkg/gnolang/fuzz_test.go b/gnovm/pkg/gnolang/fuzz_test.go index 977c7453b90..f19c35a3da1 100644 --- a/gnovm/pkg/gnolang/fuzz_test.go +++ b/gnovm/pkg/gnolang/fuzz_test.go @@ -50,8 +50,8 @@ func FuzzConvertUntypedBigdecToFloat(f *testing.F) { func FuzzParseFile(f *testing.F) { // 1. Add the corpra. - parseFileDir := filepath.Join("testdata", "corpra", "parsefile") - paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go")) + parseFileDir := filepath.Join("testdata", "corpora", "parsefile") + paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go_fuzz")) if err != nil { f.Fatal(err) } diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz From f67f186930f96b4c43e1d37769dc2875d16917a0 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:58:23 +0100 Subject: [PATCH 75/75] chore: reorganize gnovm/cmd/gno (#3587) - Increase Go-Gno compatibility. - Sort alphabetically commands. - Move Gno-specific commands under `gno tool `. - Improve `ShortHelp`. - Use `embedmd` in `gnovm/cmd/gno`'s README as an example of how we should use it more frequently. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/examples.yml | 2 +- .gitignore | 3 + CONTRIBUTING.md | 2 +- docs/reference/go-gno-compatibility.md | 56 +++++++++---------- examples/Makefile | 6 +- gnovm/Makefile | 10 +++- gnovm/cmd/gno/README.md | 24 ++++++-- gnovm/cmd/gno/bug.go | 2 +- gnovm/cmd/gno/clean.go | 2 +- gnovm/cmd/gno/doc.go | 3 +- gnovm/cmd/gno/env.go | 2 +- gnovm/cmd/gno/fmt.go | 2 +- gnovm/cmd/gno/main.go | 29 +++++----- gnovm/cmd/gno/mod.go | 6 +- gnovm/cmd/gno/run.go | 2 +- gnovm/cmd/gno/test.go | 2 +- gnovm/cmd/gno/testdata/lint/bad_import.txtar | 4 +- gnovm/cmd/gno/testdata/lint/file_error.txtar | 4 +- gnovm/cmd/gno/testdata/lint/no_error.txtar | 4 +- gnovm/cmd/gno/testdata/lint/no_gnomod.txtar | 4 +- .../cmd/gno/testdata/lint/not_declared.txtar | 4 +- .../transpile/gobuild_flag_build_error.txtar | 4 +- .../transpile/gobuild_flag_parse_error.txtar | 4 +- .../testdata/transpile/invalid_import.txtar | 4 +- .../cmd/gno/testdata/transpile/no_args.txtar | 4 +- .../gno/testdata/transpile/parse_error.txtar | 4 +- .../testdata/transpile/valid_empty_dir.txtar | 4 +- .../transpile/valid_gobuild_file.txtar | 4 +- .../transpile/valid_gobuild_flag.txtar | 6 +- .../transpile/valid_output_flag.txtar | 8 +-- .../transpile/valid_output_gobuild.txtar | 8 +-- .../transpile/valid_transpile_file.txtar | 6 +- .../transpile/valid_transpile_package.txtar | 4 +- .../transpile/valid_transpile_tree.txtar | 4 +- gnovm/cmd/gno/tool.go | 39 +++++++++++++ gnovm/cmd/gno/{lint.go => tool_lint.go} | 0 .../gno/{lint_test.go => tool_lint_test.go} | 20 +++---- gnovm/cmd/gno/{repl.go => tool_repl.go} | 0 .../gno/{repl_test.go => tool_repl_test.go} | 0 .../gno/{transpile.go => tool_transpile.go} | 0 ...anspile_test.go => tool_transpile_test.go} | 0 gnovm/tests/challenges/unused0.gno | 2 +- 42 files changed, 181 insertions(+), 117 deletions(-) create mode 100644 gnovm/cmd/gno/tool.go rename gnovm/cmd/gno/{lint.go => tool_lint.go} (100%) rename gnovm/cmd/gno/{lint_test.go => tool_lint_test.go} (70%) rename gnovm/cmd/gno/{repl.go => tool_repl.go} (100%) rename gnovm/cmd/gno/{repl_test.go => tool_repl_test.go} (100%) rename gnovm/cmd/gno/{transpile.go => tool_transpile.go} (100%) rename gnovm/cmd/gno/{transpile_test.go => tool_transpile_test.go} (100%) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5d606a2a663..ffe40c2db7e 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -29,7 +29,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples + - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples test: strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index 7d3f3f92b41..82cc109887e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ coverage.out *.swp *.swo *.bak + +.tmp/ +wal/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b58d63c6c75..9562a531227 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,7 +198,7 @@ Additionally, it's not possible to use `gofumpt` for code formatting with (flycheck-define-checker gno-lint "A GNO syntax checker using the gno lint tool." - :command ("gno" "lint" source-original) + :command ("gno" "tool" "lint" source-original) :error-patterns (;; ./file.gno:32: error message (code=1) (error line-start (file-name) ":" line ": " (message) " (code=" (id (one-or-more digit)) ")." line-end)) ;; Ensure the file is saved, to work around diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 9f9d611e4fd..87c734d935f 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -297,31 +297,31 @@ Legend: ## Tooling (`gno` binary) -| go command | gno command | comment | -|-------------------|---------------------------|-----------------------------------------------------------------------| -| go bug | gno bug | same behavior | -| go build | gno transpile -gobuild | same intention, limited compatibility | -| go clean | gno clean | same intention, limited compatibility | -| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | -| go env | gno env | | -| go fix | | | -| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | -| go generate | | | -| go get | | see `gno mod download`. | -| go help | gno $cmd --help | ie. `gno doc --help` | -| go install | | | -| go list | | | -| go mod | gno mod | | -| + go mod init | gno mod init | same behavior | -| + go mod download | gno mod download | same behavior | -| + go mod tidy | gno mod tidy | same behavior | -| + go mod why | gno mod why | same intention | -| | gno transpile | | -| go work | | | -| | gno repl | | -| go run | gno run | | -| go test | gno test | limited compatibility | -| go tool | | | -| go version | | | -| go vet | | | -| golint | gno lint | same intention | +| go command | gno command | comment | +|-------------------|------------------------------|-----------------------------------------------------------------------| +| go bug | gno bug | same behavior | +| go build | gno tool transpile -gobuild | same intention, limited compatibility | +| go clean | gno clean | same intention, limited compatibility | +| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | +| go env | gno env | | +| go fix | | | +| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | +| go generate | | | +| go get | | see `gno mod download`. | +| go help | gno $cmd --help | ie. `gno doc --help` | +| go install | | | +| go list | | | +| go mod | gno mod | | +| + go mod init | gno mod init | same behavior | +| + go mod download | gno mod download | same behavior | +| + go mod tidy | gno mod tidy | same behavior | +| + go mod why | gno mod why | same intention | +| | gno tool transpile | | +| go work | | | +| | gno tool repl | | +| go run | gno run | | +| go test | gno test | limited compatibility | +| go tool | | | +| go version | | | +| go vet | | | +| golint | gno tool lint | same intention | diff --git a/examples/Makefile b/examples/Makefile index cdc73ee6b3a..63a20f78eb9 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -33,11 +33,11 @@ OFFICIAL_PACKAGES += ./gno.land/r/gov # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v . + go run ../gnovm/cmd/gno tool transpile -v . .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild . + go run ../gnovm/cmd/gno tool transpile -v --gobuild . .PHONY: test test: @@ -45,7 +45,7 @@ test: .PHONY: lint lint: - go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES) + go run ../gnovm/cmd/gno tool lint -v $(OFFICIAL_PACKAGES) .PHONY: test.sync test.sync: diff --git a/gnovm/Makefile b/gnovm/Makefile index babb7ad74ca..ce745e44aae 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -54,8 +54,8 @@ lint: .PHONY: fmt fmt: - go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... .PHONY: imports imports: @@ -125,7 +125,13 @@ _test.filetest:; # TODO: move _dev.stringer to go:generate instructions, simplify generate # to just go generate. .PHONY: generate -generate: _dev.stringer _dev.generate +generate: _dev.stringer _dev.generate _dev.docs + +.PHONY: _dev.docs +_dev.docs: + mkdir -p .tmp + (go run ./cmd/gno -h 2>&1 || true) | grep -v "exit status 1" > .tmp/gno-help.txt + $(rundep) github.com/campoy/embedmd -w `find . -name "*.md"` stringer_cmd=$(rundep) golang.org/x/tools/cmd/stringer .PHONY: _dev.stringer diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md index f900b164783..81b45622c05 100644 --- a/gnovm/cmd/gno/README.md +++ b/gnovm/cmd/gno/README.md @@ -6,13 +6,25 @@ `gno [arguments]` -## Commands +## Usage -* `gno run` - run a Gno file -* `gno transpile` - transpile .gno to .go -* `gno test` - test a gno package -* `gno mod` - manages dependencies -* `gno repl` start a GnoVM REPL +[embedmd]:#(../../.tmp/gno-help.txt) +```txt +USAGE + gno [arguments] + +SUBCOMMANDS + bug start a bug report + clean remove generated and cached data + doc show documentation for package or symbol + env print gno environment information + fmt gnofmt (reformat) package sources + mod module maintenance + run run gno packages + test test packages + tool run specified gno tool + +``` ## Install diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index a1273d9ad59..7a4345fb1ed 100644 --- a/gnovm/cmd/gno/bug.go +++ b/gnovm/cmd/gno/bug.go @@ -61,7 +61,7 @@ func newBugCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "bug", ShortUsage: "bug", - ShortHelp: "Start a bug report", + ShortHelp: "start a bug report", LongHelp: `opens https://github.com/gnolang/gno/issues in a browser. The new issue body is prefilled for you with the following information: diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go index 0ca2e940d58..7c318c3957e 100644 --- a/gnovm/cmd/gno/clean.go +++ b/gnovm/cmd/gno/clean.go @@ -26,7 +26,7 @@ func newCleanCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "clean", ShortUsage: "clean [flags]", - ShortHelp: "removes generated files and cached data", + ShortHelp: "remove generated and cached data", }, cfg, func(ctx context.Context, args []string) error { diff --git a/gnovm/cmd/gno/doc.go b/gnovm/cmd/gno/doc.go index 794dd1ba7bb..c34ed984d9c 100644 --- a/gnovm/cmd/gno/doc.go +++ b/gnovm/cmd/gno/doc.go @@ -29,7 +29,8 @@ func newDocCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "doc", ShortUsage: "doc [flags] ", - ShortHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", + ShortHelp: "show documentation for package or symbol", + LongHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/env.go b/gnovm/cmd/gno/env.go index 9c601a270c7..20d32c85ec0 100644 --- a/gnovm/cmd/gno/env.go +++ b/gnovm/cmd/gno/env.go @@ -18,7 +18,7 @@ func newEnvCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "env", ShortUsage: "env [flags] ", - ShortHelp: "`env` prints Gno environment information", + ShortHelp: "print gno environment information", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/fmt.go b/gnovm/cmd/gno/fmt.go index de6c28c4df0..e37c50a079f 100644 --- a/gnovm/cmd/gno/fmt.go +++ b/gnovm/cmd/gno/fmt.go @@ -35,7 +35,7 @@ func newFmtCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "fmt", ShortUsage: "gno fmt [flags] [path ...]", - ShortHelp: "Run gno file formatter.", + ShortHelp: "gnofmt (reformat) package sources", LongHelp: "The `gno fmt` tool processes, formats, and cleans up `gno` source files.", }, cfg, diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 7a5799f2835..5f8bb7b522e 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -16,33 +16,32 @@ func main() { func newGnocliCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ - ShortUsage: " [flags] [...]", - LongHelp: "Runs the gno development toolkit", + ShortUsage: "gno [arguments]", }, commands.NewEmptyConfig(), commands.HelpExec, ) cmd.AddSubCommands( - newModCmd(io), - newTestCmd(io), - newLintCmd(io), - newRunCmd(io), - newTranspileCmd(io), + newBugCmd(io), + // build newCleanCmd(io), - newReplCmd(), newDocCmd(io), newEnvCmd(io), - newBugCmd(io), + // fix newFmtCmd(io), - // graph - // vendor -- download deps from the chain in vendor/ - // list -- list packages - // render -- call render()? - // publish/release // generate - // "vm" -- starts an in-memory chain that can be interacted with? + // get + // install + // list -- list packages + newModCmd(io), + // work + newRunCmd(io), + // telemetry + newTestCmd(io), + newToolCmd(io), // version -- show cmd/gno, golang versions + // vet ) return cmd diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 5479d934ce6..f303908d8ee 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -25,7 +25,7 @@ func newModCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "mod", ShortUsage: "mod ", - ShortHelp: "manage gno.mod", + ShortHelp: "module maintenance", }, commands.NewEmptyConfig(), commands.HelpExec, @@ -33,8 +33,12 @@ func newModCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newModDownloadCmd(io), + // edit + // graph newModInitCmd(), newModTidy(io), + // vendor + // verify newModWhy(io), ) diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 9a9beac5cd1..489016aa3d4 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -32,7 +32,7 @@ func newRunCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "run", ShortUsage: "run [flags] [...]", - ShortHelp: "runs the specified gno files", + ShortHelp: "run gno packages", }, cfg, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index ea06b25d8e2..ae67d69bc90 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -35,7 +35,7 @@ func newTestCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "test", ShortUsage: "test [flags] [...]", - ShortHelp: "runs the tests for the specified packages", + ShortHelp: "test packages", LongHelp: `Runs the tests for the specified packages. 'gno test' recompiles each package along with any files with names matching the diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index e2c0431443c..117a699fa6c 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: bad import error +# testing gno tool lint command: bad import error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar index 4fa50c6da81..f3301b46757 100644 --- a/gnovm/cmd/gno/testdata/lint/file_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar @@ -1,6 +1,6 @@ -# gno lint: test file error +# gno tool lint: test file error -! gno lint ./i_have_error_test.gno +! gno tool lint ./i_have_error_test.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar index 5dd3b164952..5033e054ac6 100644 --- a/gnovm/cmd/gno/testdata/lint/no_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar @@ -1,6 +1,6 @@ -# testing simple gno lint command with any error +# testing simple gno tool lint command with any error -gno lint ./good_file.gno +gno tool lint ./good_file.gno cmp stdout stdout.golden cmp stdout stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar index b5a046a7095..4564f2d9fff 100644 --- a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar @@ -1,6 +1,6 @@ -# gno lint: no gnomod +# gno tool lint: no gnomod -! gno lint . +! gno tool lint . cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar index ac56b27e0df..3c3c304797e 100644 --- a/gnovm/cmd/gno/testdata/lint/not_declared.txtar +++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: not declared error +# testing gno tool lint command: not declared error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar index 145fe796c09..0b41319a190 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag # The error messages changed sometime in go1.23, so this avoids errors -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:4:6: .*declared and not used' diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar index 629de2b7f3d..1008d45adb2 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on file with parse error +# Run gno tool transpile with -gobuild flag on file with parse error -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar index 0c51012feb7..23e9e805fe5 100644 --- a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar +++ b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with an invalid import path +# Run gno tool transpile with gno files with an invalid import path -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" does not exist$' diff --git a/gnovm/cmd/gno/testdata/transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar index 6d8592d4e3b..30ea24fbf73 100644 --- a/gnovm/cmd/gno/testdata/transpile/no_args.txtar +++ b/gnovm/cmd/gno/testdata/transpile/no_args.txtar @@ -1,6 +1,6 @@ -# Run gno transpile without args +# Run gno tool transpile without args -! gno transpile +! gno tool transpile ! stdout .+ stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar index b94b86992af..c19276ef273 100644 --- a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with parse errors +# Run gno tool transpile with gno files with parse errors -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar index 2bd1841d2b4..f9ccd8d8b53 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar @@ -1,6 +1,6 @@ -# Run gno transpile on an empty dir +# Run gno tool transpile on an empty dir -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar index 40bb1ecb98a..8e95c5994d0 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on an individual file +# Run gno tool transpile with -gobuild flag on an individual file -gno transpile -gobuild -v main.gno +gno tool transpile -gobuild -v main.gno ! stdout .+ cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar index 2eacfb9de60..72ca7a4f2f4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag -gno transpile -gobuild -v . +gno tool transpile -gobuild -v . ! stdout .+ cmp stderr stderr.golden @@ -12,7 +12,7 @@ cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden rm mai.gno.gen.gosub/sub.gno.gen.go # Re-try, but use an absolute path. -gno transpile -gobuild -v $WORK +gno tool transpile -gobuild -v $WORK ! stdout .+ cmpenv stderr stderr2.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar index b1a63890f46..62953e96fb7 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output flag. +# Run gno tool transpile with valid gno files, using the -output flag. -gno transpile -v -output directory/hello/ . +gno tool transpile -v -output directory/hello/ . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello $WORK +gno tool transpile -v -output directory/hello $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello .. +gno tool transpile -v -output hello .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar index 3540e865f3e..c948e53ebb3 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output and -gobuild flags together. +# Run gno tool transpile with valid gno files, using the -output and -gobuild flags together. -gno transpile -v -output directory/hello/ -gobuild . +gno tool transpile -v -output directory/hello/ -gobuild . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello -gobuild $WORK +gno tool transpile -v -output directory/hello -gobuild $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello -gobuild .. +gno tool transpile -v -output hello -gobuild .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 86cc6f12f7a..906c43cc41b 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -1,8 +1,8 @@ -# Run gno transpile with an individual file. +# Run gno tool transpile with an individual file. # Running transpile on the current directory should only precompile # main.gno. -gno transpile -v . +gno tool transpile -v . ! stdout .+ stderr ^\.$ @@ -12,7 +12,7 @@ exists main.gno.gen.go rm main.gno.gen.go # Running it using individual filenames should precompile hello_test.gno, as well. -gno transpile -v main.gno hello_test.gno +gno tool transpile -v main.gno hello_test.gno ! stdout .+ cmp stderr transpile-files-stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar index 2a24423598a..74923b9846a 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files +# Run gno tool transpile with valid gno files -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar index a765ab5093b..e0c42d986e4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with dependencies +# Run gno tool transpile with dependencies env GNOROOT=$WORK -gno transpile -v ./examples +gno tool transpile -v ./examples ! stdout .+ cmpenv stderr stderr.golden diff --git a/gnovm/cmd/gno/tool.go b/gnovm/cmd/gno/tool.go new file mode 100644 index 00000000000..0e4f7ff51d7 --- /dev/null +++ b/gnovm/cmd/gno/tool.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func newToolCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + Name: "tool", + ShortUsage: "gno tool command [args...]", + ShortHelp: "run specified gno tool", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + // go equivalent commands: + // + // compile + // transpile + // pprof + // trace + // vet + + // gno specific commands: + // + // ast + newLintCmd(io), + // publish/release + // render -- call render()? + newReplCmd(), + newTranspileCmd(io), + // "vm" -- starts an in-memory chain that can be interacted with? + ) + + return cmd +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/tool_lint.go similarity index 100% rename from gnovm/cmd/gno/lint.go rename to gnovm/cmd/gno/tool_lint.go diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/tool_lint_test.go similarity index 70% rename from gnovm/cmd/gno/lint_test.go rename to gnovm/cmd/gno/tool_lint_test.go index 4589fc55f92..85b625fa367 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -8,26 +8,26 @@ import ( func TestLintApp(t *testing.T) { tc := []testMainCase{ { - args: []string{"lint"}, + args: []string{"tool", "lint"}, errShouldBe: "flag: help requested", }, { - args: []string{"lint", "../../tests/integ/run_main/"}, + args: []string{"tool", "lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, + args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"}, stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, + args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, stderrShouldContain: func() string { lines := []string{ "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", @@ -39,17 +39,17 @@ func TestLintApp(t *testing.T) { }(), errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, + args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "../../tests/integ/invalid_module_name/"}, + args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid }, { - args: []string{"lint", "../../tests/integ/invalid_gno_file/"}, + args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/typecheck_missing_return/"}, + args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", errShouldBe: "exit code: 1", }, diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/tool_repl.go similarity index 100% rename from gnovm/cmd/gno/repl.go rename to gnovm/cmd/gno/tool_repl.go diff --git a/gnovm/cmd/gno/repl_test.go b/gnovm/cmd/gno/tool_repl_test.go similarity index 100% rename from gnovm/cmd/gno/repl_test.go rename to gnovm/cmd/gno/tool_repl_test.go diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/tool_transpile.go similarity index 100% rename from gnovm/cmd/gno/transpile.go rename to gnovm/cmd/gno/tool_transpile.go diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/tool_transpile_test.go similarity index 100% rename from gnovm/cmd/gno/transpile_test.go rename to gnovm/cmd/gno/tool_transpile_test.go diff --git a/gnovm/tests/challenges/unused0.gno b/gnovm/tests/challenges/unused0.gno index d5b0ff75c17..dc9aa950dec 100644 --- a/gnovm/tests/challenges/unused0.gno +++ b/gnovm/tests/challenges/unused0.gno @@ -1,7 +1,7 @@ package main // NOTE: instead of patching the vm code, this should be handled by an -// integration of `gno transpile --gobuild`, which uses `go build` under +// integration of `gno tool transpile --gobuild`, which uses `go build` under // the hood and already detects unused variables. func main() { var x int