ratelimitdemo.gno
3.11 Kb · 88 lines
1// Package ratelimitdemo is a small on-chain demo of the token-bucket rate
2// limiter provided by the [p/moul/x/daily/ratelimit](/p/moul/x/daily/ratelimit/v0)
3// library.
4//
5// It holds a single package-level [ratelimit.Limiter] and wires it to the
6// chain: the tick is runtime.ChainHeight() and each caller is keyed by its
7// address (unsafe.PreviousRealm().Address()). It contains no limiter logic of
8// its own — refill/consume math all lives in the library.
9package ratelimitdemo
10
11import (
12 "strconv"
13
14 "chain/runtime"
15 "chain/runtime/unsafe"
16
17 "gno.land/p/moul/x/daily/ratelimit/v0"
18)
19
20// limiter starts at 0.5 tokens/block (one every two blocks), capacity 5.
21var limiter = ratelimit.New(0.5, 5.0)
22
23// Allow consumes one token for the calling realm/user at the current block
24// height and reports whether the request is permitted. Returns false (and
25// consumes nothing) when the caller's bucket is empty.
26func Allow(cur realm) bool {
27 addr := unsafe.PreviousRealm().Address()
28 return limiter.Allow(string(addr), runtime.ChainHeight())
29}
30
31// SetConfig updates the shared rate (tokens per block) and burst (capacity).
32// rate is clamped to >= 0, burst to >= 1.
33func SetConfig(cur realm, rate, burst float64) {
34 limiter.SetConfig(rate, burst)
35}
36
37// Tokens reports how many whole tokens `addr` has available at the current
38// block height, without mutating any state.
39func Tokens(addr address) int {
40 return limiter.Tokens(string(addr), runtime.ChainHeight())
41}
42
43// --- rendering --------------------------------------------------------------
44
45func ftoa(f float64) string { return strconv.FormatFloat(f, 'g', -1, 64) }
46
47// Render shows the limiter config and a table of known callers with their
48// current tokens and last-seen height. Path "/addr/<address>" focuses one
49// caller.
50func Render(path string) string {
51 now := runtime.ChainHeight()
52 rate, burst := limiter.Config()
53
54 if len(path) > 6 && path[:6] == "/addr/" {
55 key := path[6:]
56 out := "# Rate Limiter — caller\n\n"
57 out += "Address: `" + key + "`\n\n"
58 out += "- Current tokens: **" + strconv.Itoa(limiter.Tokens(key, now)) + "** / " + ftoa(burst) + "\n"
59 out += "- Now (height): " + strconv.FormatInt(now, 10) + "\n"
60 return out
61 }
62
63 out := "# Rate Limiter\n\n"
64 out += "Demo of the [`p/moul/x/daily/ratelimit`](/p/moul/x/daily/ratelimit/v0) library — "
65 out += "an on-chain token-bucket limiter (port of `golang.org/x/time/rate`).\n\n"
66 out += "## Config\n\n"
67 out += "| Parameter | Value |\n|---|---|\n"
68 out += "| Rate (tokens/block) | " + ftoa(rate) + " |\n"
69 out += "| Burst (capacity) | " + ftoa(burst) + " |\n"
70 out += "| Clock height | " + strconv.FormatInt(now, 10) + " |\n\n"
71
72 out += "## Callers\n\n"
73 if limiter.Len() == 0 {
74 out += "_No caller has hit the limiter yet._\n\n"
75 } else {
76 out += "| Address | Tokens | Last height |\n|---|---|---|\n"
77 limiter.Iterate(now, func(key string, tokens int, last int64) bool {
78 out += "| `" + key + "` | " + strconv.Itoa(tokens) +
79 " | " + strconv.FormatInt(last, 10) + " |\n"
80 return false
81 })
82 out += "\n"
83 }
84
85 out += "---\n"
86 out += "Call `Allow(cur)` to consume a token; read `Tokens(addr)` for a caller's balance.\n"
87 return out
88}