Skip to content

Commit 96b556b

Browse files
authored
Merge branch 'master' into fix/gnovm/storage-proportional-refund
2 parents f0ad4ab + 80115c1 commit 96b556b

6 files changed

Lines changed: 197 additions & 64 deletions

File tree

contribs/github-bot/internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
8686
// c) be a draft
8787
If(r.Or(
8888
r.ReviewByAnyUser(gh,
89-
"jefft0", "notJoon", "omarsy", "MikaelVallenet",
89+
"davd-gzl", "jefft0", "notJoon", "omarsy", "MikaelVallenet",
9090
).WithDesiredState(utils.ReviewStateApproved),
9191
r.ReviewByTeamMembers(gh, "tech-staff", r.RequestIgnore),
9292
r.Draft(),

contribs/gnofaucet/serve.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ func serveFaucet(
194194
return errors.New("ratelimit-cleanup-timeout must be greater than zero")
195195
}
196196

197+
if cfg.rateLimitCleanTimeout < cfg.rateLimitInterval {
198+
return errors.New("ratelimit-cleanup-timeout must be >= ratelimit-interval, otherwise cleanup defeats the rate limit")
199+
}
200+
197201
// Parse static gas values.
198202
// It is worth noting that this is temporary,
199203
// and will be removed once gas estimation is enabled

contribs/gnofaucet/serve_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestServeFaucet_CleanupShorterThanRateLimit(t *testing.T) {
12+
t.Parallel()
13+
14+
cfg := &serveCfg{
15+
rateLimitInterval: 24 * time.Hour,
16+
rateLimitCleanTimeout: time.Hour,
17+
}
18+
19+
err := serveFaucet(context.Background(), cfg, nil)
20+
21+
assert.ErrorContains(t, err, "ratelimit-cleanup-timeout must be >= ratelimit-interval")
22+
}

contribs/gnofaucet/throttle.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ import (
1111
)
1212

1313
const (
14-
maxRequestsPerMinute = 5
15-
1614
defaultCleanTimeout = time.Minute * 3
17-
defaultRateLimitInterval = time.Minute / maxRequestsPerMinute
15+
defaultRateLimitInterval = time.Minute
1816
)
1917

2018
var errInvalidNumberOfRequests = errors.New("invalid number of requests")
@@ -91,7 +89,7 @@ func (st *ipThrottler) registerNewRequest(ip netip.Addr) error {
9189
c := st.requestMap[ip]
9290
if c == nil {
9391
c = &client{
94-
limiter: rate.NewLimiter(rate.Every(st.rateLimitInterval), 5),
92+
limiter: rate.NewLimiter(rate.Every(st.rateLimitInterval), 1),
9593
seen: time.Now(),
9694
}
9795

contribs/gnofaucet/throttle_test.go

Lines changed: 32 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"context"
55
"net/netip"
6-
"sync"
76
"testing"
87
"time"
98

@@ -14,93 +13,67 @@ import (
1413
func TestIPThrottler_RegisterNewRequest(t *testing.T) {
1514
t.Parallel()
1615

17-
t.Run("valid number of requests", func(t *testing.T) {
16+
t.Run("first request allowed", func(t *testing.T) {
1817
t.Parallel()
1918

20-
addr, err := netip.ParseAddr("127.0.0.1")
21-
require.NoError(t, err)
19+
addr := netip.MustParseAddr("127.0.0.1")
2220

23-
// Create the IP throttler
2421
th := newIPThrottler(defaultRateLimitInterval, defaultCleanTimeout)
2522

26-
// Register < max requests
27-
for i := uint64(0); i < maxRequestsPerMinute; i++ {
28-
assert.NoError(t, th.registerNewRequest(addr))
29-
}
23+
assert.NoError(t, th.registerNewRequest(addr))
3024
})
3125

32-
t.Run("exceeded number of requests", func(t *testing.T) {
26+
t.Run("second request rejected", func(t *testing.T) {
3327
t.Parallel()
3428

35-
addr, err := netip.ParseAddr("127.0.0.1")
36-
require.NoError(t, err)
29+
addr := netip.MustParseAddr("127.0.0.1")
3730

38-
// Create the IP throttler
3931
th := newIPThrottler(defaultRateLimitInterval, defaultCleanTimeout)
4032

41-
// Register max requests
42-
for i := uint64(0); i < maxRequestsPerMinute; i++ {
43-
assert.NoError(t, th.registerNewRequest(addr))
44-
}
33+
require.NoError(t, th.registerNewRequest(addr))
4534

46-
// Attempt to register an additional request
4735
assert.ErrorIs(t, th.registerNewRequest(addr), errInvalidNumberOfRequests)
4836
})
4937
}
5038

51-
func TestIPThrottler_RequestsThrottled(t *testing.T) {
39+
func TestIPThrottler_SecondRequestRejected(t *testing.T) {
5240
t.Parallel()
5341

54-
var (
55-
cleanupInterval = time.Millisecond * 100
42+
addr := netip.MustParseAddr("192.168.1.1")
5643

57-
requestInterval = 3 * cleanupInterval // requests triggered after ~5 cleans
58-
numRequests = maxRequestsPerMinute * 2 // number of request loops
59-
)
44+
// Use a long interval so no tokens regenerate during the test
45+
th := newIPThrottler(time.Hour, defaultCleanTimeout)
6046

61-
addr, err := netip.ParseAddr("127.0.0.1")
62-
require.NoError(t, err)
47+
// First request must succeed
48+
require.NoError(t, th.registerNewRequest(addr))
6349

64-
// Create the IP throttler
65-
th := newIPThrottler(defaultRateLimitInterval, cleanupInterval)
66-
67-
ctx, cancelFn := context.WithCancel(context.Background())
68-
defer cancelFn()
50+
// Second request from the same IP must be rejected
51+
assert.ErrorIs(t, th.registerNewRequest(addr), errInvalidNumberOfRequests)
52+
}
6953

70-
// Start the throttler (async)
71-
th.start(ctx)
54+
func TestIPThrottler_CleanupAllowsNewRequest(t *testing.T) {
55+
t.Parallel()
7256

73-
var wg sync.WaitGroup
57+
cleanupInterval := time.Millisecond * 100
7458

75-
wg.Add(1)
59+
addr := netip.MustParseAddr("127.0.0.1")
7660

77-
go func() {
78-
defer wg.Done()
61+
// Rate interval is long so tokens won't regenerate on their own;
62+
// only cleanup (removing the stale entry) should allow a new request.
63+
th := newIPThrottler(time.Hour, cleanupInterval)
7964

80-
var (
81-
requestsSent = 0
82-
ticker = time.NewTicker(requestInterval)
83-
)
65+
ctx, cancelFn := context.WithCancel(context.Background())
66+
defer cancelFn()
8467

85-
for {
86-
select {
87-
case <-ctx.Done():
88-
return
89-
case <-ticker.C:
90-
// Fill out the request count for the address
91-
for i := uint64(0); i < maxRequestsPerMinute; i++ {
92-
require.NoError(t, th.registerNewRequest(addr))
93-
}
68+
th.start(ctx)
9469

95-
requestsSent += maxRequestsPerMinute
70+
// First request succeeds, second is rejected
71+
require.NoError(t, th.registerNewRequest(addr))
72+
require.ErrorIs(t, th.registerNewRequest(addr), errInvalidNumberOfRequests)
9673

97-
if requestsSent == numRequests {
98-
// Loops done
99-
return
100-
}
101-
}
102-
}
103-
}()
74+
// Wait for the cleanup cycle to evict the stale entry
75+
time.Sleep(cleanupInterval * 3)
10476

105-
wg.Wait()
77+
// After cleanup the IP entry is gone, so a new request succeeds
78+
assert.NoError(t, th.registerNewRequest(addr))
10679
}

misc/build-wasm.sh

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env bash
2+
# misc/build-wasm.sh — Build gno.wasm and root.zip from gnovm/cmd/gno
3+
#
4+
# Requirements: go (GOARCH=wasm GOOS=js), zip
5+
# For --push: gh (GitHub CLI) with write access to gnolang/gno
6+
set -euo pipefail
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10+
11+
usage() {
12+
cat <<USAGE
13+
Usage: $(basename "$0") <tag> [--push] [output-dir]
14+
15+
tag Git tag or ref to build (use HEAD for current checkout)
16+
--push Publish assets to the GitHub release for the given tag
17+
output-dir Where to write gno.wasm and root.zip (default: gnovm/build/)
18+
19+
Examples:
20+
$(basename "$0") chain/gnoland1.0
21+
$(basename "$0") chain/gnoland1.0 --push
22+
$(basename "$0") HEAD --push
23+
$(basename "$0") chain/gnoland1.0 --push ./out/
24+
USAGE
25+
}
26+
27+
if [ $# -eq 0 ]; then
28+
usage
29+
exit 0
30+
fi
31+
32+
TAG=""
33+
PUSH=false
34+
OUTPUT_DIR=""
35+
36+
for arg in "$@"; do
37+
case "$arg" in
38+
--push) PUSH=true ;;
39+
--help|-h) usage; exit 0 ;;
40+
*)
41+
if [ -z "$TAG" ]; then
42+
TAG="$arg"
43+
else
44+
OUTPUT_DIR="$arg"
45+
fi
46+
;;
47+
esac
48+
done
49+
50+
if [ -z "$TAG" ]; then
51+
echo "ERROR: <tag> is required."
52+
echo ""
53+
usage
54+
exit 1
55+
fi
56+
57+
OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/gnovm/build}"
58+
mkdir -p "$OUTPUT_DIR"
59+
60+
# Resolve tag: if not HEAD, checkout the tag (detached) then restore
61+
ORIG_HEAD="$(git -C "$REPO_ROOT" symbolic-ref --short HEAD 2>/dev/null || git -C "$REPO_ROOT" rev-parse HEAD)"
62+
NEEDS_RESTORE=false
63+
64+
if [ "$TAG" != "HEAD" ]; then
65+
echo "==> Checking out $TAG..."
66+
git -C "$REPO_ROOT" checkout --quiet "$TAG"
67+
NEEDS_RESTORE=true
68+
fi
69+
70+
restore() {
71+
if $NEEDS_RESTORE; then
72+
echo "==> Restoring $ORIG_HEAD..."
73+
git -C "$REPO_ROOT" checkout --quiet "$ORIG_HEAD"
74+
fi
75+
}
76+
trap restore EXIT
77+
78+
echo "==> Building gno.wasm (tag: $TAG)..."
79+
cd "$REPO_ROOT/gnovm"
80+
GOARCH=wasm GOOS=js go build \
81+
-ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$REPO_ROOT/" \
82+
-o "$OUTPUT_DIR/gno.wasm" \
83+
./cmd/gno
84+
echo " $(du -sh "$OUTPUT_DIR/gno.wasm" | cut -f1) $OUTPUT_DIR/gno.wasm"
85+
86+
echo "==> Creating root.zip..."
87+
cd "$REPO_ROOT"
88+
zip -qq -i "*.toml" -i "*.gno" \
89+
-x "*_test.gno" -x "*_filetest.gno" \
90+
-r "$OUTPUT_DIR/root.zip" \
91+
gnovm/stdlibs gnovm/tests/stdlibs examples
92+
echo " $(du -sh "$OUTPUT_DIR/root.zip" | cut -f1) $OUTPUT_DIR/root.zip"
93+
94+
if ! $PUSH; then
95+
echo "==> Done. (pass --push to publish to GitHub release)"
96+
exit 0
97+
fi
98+
99+
RELEASE_TAG="$TAG"
100+
if [ "$TAG" = "HEAD" ]; then
101+
RELEASE_TAG="$(git -C "$REPO_ROOT" describe --exact-match HEAD 2>/dev/null || true)"
102+
if [ -z "$RELEASE_TAG" ]; then
103+
echo "ERROR: HEAD is not on a tag. Use an explicit tag name with --push."
104+
exit 1
105+
fi
106+
fi
107+
108+
ENCODED_TAG="$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$RELEASE_TAG")"
109+
110+
echo "==> Publishing to GitHub release: $RELEASE_TAG"
111+
if gh release view "$RELEASE_TAG" --repo gnolang/gno &>/dev/null; then
112+
echo " Release exists, uploading assets..."
113+
else
114+
echo " Creating release..."
115+
gh release create "$RELEASE_TAG" \
116+
--repo gnolang/gno \
117+
--title "GnoVM $RELEASE_TAG" \
118+
--notes "GnoVM WebAssembly build for tag \`$RELEASE_TAG\`.
119+
120+
Built from [gnovm/cmd/gno](https://github.com/gnolang/gno/tree/$RELEASE_TAG/gnovm/cmd/gno).
121+
122+
## Assets
123+
- \`gno.wasm\` — GnoVM WebAssembly binary (GOOS=js GOARCH=wasm)
124+
- \`root.zip\` — Standard libraries and examples (stdlibs + tests/stdlibs + examples)"
125+
fi
126+
127+
gh release upload "$RELEASE_TAG" \
128+
--repo gnolang/gno \
129+
--clobber \
130+
"$OUTPUT_DIR/gno.wasm" \
131+
"$OUTPUT_DIR/root.zip"
132+
133+
echo ""
134+
echo "==> Done!"
135+
echo " gno.wasm: https://github.com/gnolang/gno/releases/download/${ENCODED_TAG}/gno.wasm"
136+
echo " root.zip: https://github.com/gnolang/gno/releases/download/${ENCODED_TAG}/root.zip"

0 commit comments

Comments
 (0)