diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index b8dd1a98352..7c2e17012a4 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -9,7 +9,7 @@ require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 github.com/gorilla/websocket v1.5.1 go.uber.org/zap v1.26.0 - golang.org/x/term v0.17.0 + golang.org/x/term v0.18.0 ) require ( @@ -46,7 +46,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.18.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index a0e8d584bec..e02d4d6232f 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -14,9 +14,8 @@ github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUB github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -175,10 +174,10 @@ 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/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= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index dc4267fa914..79f7747a5cc 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -37,7 +37,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 063c2af7945..e2458bb59c6 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -16,9 +16,8 @@ github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUB github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -159,8 +158,8 @@ 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gno.land/cmd/gnofaucet/README.md b/gno.land/cmd/gnofaucet/README.md deleted file mode 100644 index a9ce0abaecf..00000000000 --- a/gno.land/cmd/gnofaucet/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Start a local faucet - -## Step1: Import test1 key - -If you have imported the test1 key skip to Step2 - - ./build/gnokey add --recover test1 - -At prompt, input and confirm your password to protect the imported private key. - -Copy and paste the following mnemonic. - - source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast - -## Step2: - -Make sure you have started gnoland - - ./build/gnoland start - -## Step3: - - ./build/gnofaucet serve --chain-id dev test1 - -By default, the faucet sends out 1,000,000ugnot (1gnot) per request. If this is your local faucet, you can be a bit -generous to yourself with --send flag. With the following, the faucet will give you 500gnot per request. - - ./build/gnofaucet serve --chain-id dev --send 5000000000ugnot test1 - -## Step4: - -Make sure you have started website - - ./build/gnoweb - -Request testing tokens from following URL, Have fun! - - http://localhost:8888/faucet diff --git a/gno.land/cmd/gnofaucet/middleware.go b/gno.land/cmd/gnofaucet/middleware.go new file mode 100644 index 00000000000..03792b06bcd --- /dev/null +++ b/gno.land/cmd/gnofaucet/middleware.go @@ -0,0 +1,177 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "strings" + "time" +) + +// getIPMiddleware returns the IP verification middleware, using the given subnet throttler +func getIPMiddleware(behindProxy bool, st *ipThrottler) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Determine the remote address + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + http.Error( + w, + fmt.Sprintf("invalid request IP and port, %s", err.Error()), + http.StatusUnauthorized, + ) + + return + } + + // Check if the request is behind a proxy + if xff := r.Header.Get("X-Forwarded-For"); xff != "" && behindProxy { + host = xff + } + + // If the host is empty or IPv6 loopback, set it to IPv4 loopback + switch host { + case "", ipv6Loopback, ipv6ZeroAddr: + host = ipv4Loopback + } + + // Make sure the host IP is valid + hostAddr, err := netip.ParseAddr(host) + if err != nil { + http.Error( + w, + fmt.Sprintf("invalid request IP, %s", err.Error()), + http.StatusUnauthorized, + ) + + return + } + + // Register the request with the throttler + if err := st.registerNewRequest(hostAddr); err != nil { + http.Error( + w, + fmt.Sprintf("unable to verify IP request, %s", err.Error()), + http.StatusUnauthorized, + ) + + return + } + + // Continue with serving the faucet request + next.ServeHTTP(w, r) + }, + ) + } +} + +// getCaptchaMiddleware returns the captcha middleware, if any +func getCaptchaMiddleware(secret string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // Check if the captcha is enabled + if secret == "" { + // Continue with serving the faucet request + next.ServeHTTP(w, r) + + return + } + + // Parse the request to extract the captcha secret + var request struct { + Captcha string `json:"captcha"` + } + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "unable to read request body", http.StatusInternalServerError) + + return + } + + // Close the original body + if err := r.Body.Close(); err != nil { + http.Error(w, "unable to close request body", http.StatusInternalServerError) + + return + } + + // Create a new ReadCloser from the read bytes + // so that future middleware will be able to read + r.Body = io.NopCloser(bytes.NewReader(body)) + + // Decode the original request + if err := json.NewDecoder(bytes.NewBuffer(body)).Decode(&request); err != nil { + http.Error(w, "invalid captcha request", http.StatusBadRequest) + + return + } + + // Verify the captcha response + if err := checkRecaptcha(secret, strings.TrimSpace(request.Captcha)); err != nil { + http.Error(w, "invalid captcha", http.StatusUnauthorized) + + return + } + + // Continue with serving the faucet request + next.ServeHTTP(w, r) + }, + ) + } +} + +// checkRecaptcha checks the captcha challenge +func checkRecaptcha(secret, response string) error { + // Create an HTTP client with a timeout + client := &http.Client{ + Timeout: time.Second * 10, + } + + // Create the request + req, err := http.NewRequest( + http.MethodPost, + siteVerifyURL, + nil, + ) + if err != nil { + return fmt.Errorf("unable to create request, %w", err) + } + + // Craft the request query string + q := req.URL.Query() + q.Add("secret", secret) + q.Add("response", response) + req.URL.RawQuery = q.Encode() + + // Execute the verify request + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("unable to execute request, %w", err) + } + defer resp.Body.Close() + + // Verify the captcha-verify response + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code, %d", resp.StatusCode) + } + + // Decode the response body + var body SiteVerifyResponse + if err = json.NewDecoder(resp.Body).Decode(&body); err != nil { + return fmt.Errorf("failed to decode response, %w", err) + } + + // Check if the recaptcha verification was successful + if !body.Success { + return errInvalidCaptcha + } + + return nil +} diff --git a/gno.land/cmd/gnofaucet/serve.go b/gno.land/cmd/gnofaucet/serve.go index b810b8e96a0..f4d59e9ce08 100644 --- a/gno.land/cmd/gnofaucet/serve.go +++ b/gno.land/cmd/gnofaucet/serve.go @@ -2,29 +2,43 @@ package main import ( "context" - "encoding/json" "flag" "fmt" - "net" - "net/http" - "strings" + "regexp" + "strconv" "time" - "github.com/gnolang/gno/gno.land/pkg/gnoland" - "github.com/gnolang/gno/tm2/pkg/amino" - rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/faucet" + tm2Client "github.com/gnolang/faucet/client/http" + "github.com/gnolang/faucet/config" + "github.com/gnolang/faucet/estimate/static" + "github.com/gnolang/gno/gno.land/pkg/log" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/keys" - "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" + "go.uber.org/zap/zapcore" +) + +const ( + defaultGasFee = "1000000ugnot" + defaultGasWanted = "100000" + defaultRemote = "http://127.0.0.1:26657" + defaultListenAddress = "127.0.0.1:5050" ) // url & struct for verify captcha const siteVerifyURL = "https://www.google.com/recaptcha/api/siteverify" +const ( + ipv6Loopback = "::1" + ipv6ZeroAddr = "0:0:0:0:0:0:0:1" + ipv4Loopback = "127.0.0.1" +) + +var remoteRegex = regexp.MustCompile(`^https?://[a-z\d.-]+(:\d+)?(?:/[a-z\d]+)*$`) + +var errInvalidCaptcha = errors.New("unable to verify captcha") + type SiteVerifyResponse struct { Success bool `json:"success"` Score float64 `json:"score"` @@ -34,437 +48,164 @@ type SiteVerifyResponse struct { ErrorCodes []string `json:"error-codes"` } -type config struct { - client.BaseOptions // home, ... - - ChainID string - GasWanted int64 - GasFee string - Memo string - TestTo string - Send string - CaptchaSecret string - IsBehindProxy bool - InsecurePasswordStdin bool +type serveCfg struct { + listenAddress string + chainID string + mnemonic string + maxSendAmount string + numAccounts uint64 + + remote string + + captchaSecret string + isBehindProxy bool } func newServeCmd() *commands.Command { - cfg := &config{} + cfg := &serveCfg{} return commands.NewCommand( commands.Metadata{ Name: "serve", - ShortUsage: "serve [flags] ", + ShortUsage: "serve [flags]", LongHelp: "Serves the gno.land faucet to users", }, cfg, - func(_ context.Context, args []string) error { - return execServe(cfg, args, commands.NewDefaultIO()) + func(ctx context.Context, args []string) error { + return execServe(ctx, cfg, commands.NewDefaultIO()) }, ) } -func (c *config) RegisterFlags(fs *flag.FlagSet) { - // Base config options +func (c *serveCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( - &c.BaseOptions.Home, - "home", - client.DefaultBaseOptions.Home, - "home directory", + &c.listenAddress, + "listen-address", + defaultListenAddress, + "the faucet server listen address", ) fs.StringVar( - &c.BaseOptions.Remote, + &c.remote, "remote", - client.DefaultBaseOptions.Remote, + defaultRemote, "remote node URL", ) - fs.BoolVar( - &c.BaseOptions.Quiet, - "quiet", - client.DefaultBaseOptions.Quiet, - "for parsing output", - ) - - // Command options fs.StringVar( - &c.ChainID, - "chain-id", + &c.mnemonic, + "mnemonic", "", - "the ID of the chain", - ) - - fs.Int64Var( - &c.GasWanted, - "gas-wanted", - 50000, - "gas requested for the tx", - ) - - fs.StringVar( - &c.GasFee, - "gas-fee", - "1000000ugnot", - "gas payment fee", + "the mnemonic for faucet keys", ) - fs.StringVar( - &c.Memo, - "memo", - "", - "any descriptive text", + fs.Uint64Var( + &c.numAccounts, + "num-accounts", + 1, + "the number of faucet accounts, based on the mnemonic", ) fs.StringVar( - &c.TestTo, - "test-to", + &c.chainID, + "chain-id", "", - "test address (optional)", + "the chain ID associated with the remote Gno chain", ) fs.StringVar( - &c.Send, - "send", + &c.maxSendAmount, + "max-send-amount", "1000000ugnot", - "send coins", + "the static max send amount (native currency)", ) fs.StringVar( - &c.CaptchaSecret, + &c.captchaSecret, "captcha-secret", "", "recaptcha secret key (if empty, captcha are disabled)", ) fs.BoolVar( - &c.IsBehindProxy, + &c.isBehindProxy, "is-behind-proxy", false, "use X-Forwarded-For IP for throttling", ) - - fs.BoolVar( - &c.InsecurePasswordStdin, - "insecure-password-stdin", - false, - "WARNING! take password from stdin", - ) } -func execServe(cfg *config, args []string, io commands.IO) error { - if len(args) != 1 { - return flag.ErrHelp - } - - if cfg.ChainID == "" { - return errors.New("chain-id not specified") - } - - if cfg.GasWanted == 0 { - return errors.New("gas-wanted not specified") - } - - if cfg.GasFee == "" { - return errors.New("gas-fee not specified") - } - - remote := cfg.Remote - if remote == "" || remote == "y" { - return errors.New("missing remote url") - } - cli := rpcclient.NewHTTP(remote, "/websocket") - - // XXX XXX - // Read supply account pubkey. - name := args[0] - kb, err := keys.NewKeyBaseFromDir(cfg.Home) - if err != nil { - return err - } - - // Get password for supply account. - // Test by signing a dummy message; - const dummy = "test" - var pass string - if cfg.Quiet { - pass, err = io.GetPassword("", cfg.InsecurePasswordStdin) - } else { - pass, err = io.GetPassword("Enter password", cfg.InsecurePasswordStdin) - } +// generateFaucetConfig generates the Faucet configuration +// based on the flag data +func (c *serveCfg) generateFaucetConfig() *config.Config { + // Create the default configuration + cfg := config.DefaultConfig() - if err != nil { - return err - } + cfg.ListenAddress = c.listenAddress + cfg.ChainID = c.chainID + cfg.Mnemonic = c.mnemonic + cfg.MaxSendAmount = c.maxSendAmount + cfg.NumAccounts = c.numAccounts - _, _, err = kb.Sign(name, pass, []byte(dummy)) - if err != nil { - return err - } - - // Parse send amount. - send, err := std.ParseCoins(cfg.Send) - if err != nil { - return errors.Wrap(err, "parsing send coins") - } - - // Parse test-to address. If present, send and quit. - if cfg.TestTo != "" { - testToAddr, err := crypto.AddressFromBech32(cfg.TestTo) - if err != nil { - return err - } - err = sendAmountTo(cfg, cli, io, name, pass, testToAddr, send) - return err - } - - // Start throttled faucet. - st := NewSubnetThrottler() - st.Start() - - // handle route using handler function - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - host := "" - if !cfg.IsBehindProxy { - addr := r.RemoteAddr - host_, _, err := net.SplitHostPort(addr) - if err != nil { - return - } - host = host_ - } else if xff, found := r.Header["X-Forwarded-For"]; found && len(xff) > 0 { - host = xff[0] - } - - // if can't identify the IP, everyone is in the same pool. - // if host using ipv6 loopback addr, make it ipv4 - if host == "" || host == "::1" || host == "0:0:0:0:0:0:0:1" { - host = "127.0.0.1" - } - ip := net.ParseIP(host) - if ip == nil { - fmt.Println("no ip found") - w.Write([]byte("no ip found")) - return - } - - allowed, reason := st.Request(ip) - if !allowed { - msg := fmt.Sprintf("abuse protection system (%s)", reason) - fmt.Println(ip, msg) - w.Write([]byte(msg)) - return - } - - r.ParseForm() - - // only when command line argument 'captcha-secret' has entered > captcha are enabled. - // verify captcha - if cfg.CaptchaSecret != "" { - passedMsg := r.Form["g-recaptcha-response"] - if passedMsg == nil { - fmt.Println(ip, "no 'captcha' request") - w.Write([]byte("check captcha request")) - return - } - - capMsg := strings.TrimSpace(passedMsg[0]) - - if err := checkRecaptcha(cfg.CaptchaSecret, capMsg); err != nil { - fmt.Printf("%s recaptcha failed; %v\n", ip, err) - w.Write([]byte("Unauthorized")) - return - } - } - - passedAddr := r.Form["toaddr"] - if passedAddr == nil { - fmt.Println(ip, "no address found") - w.Write([]byte("no address found")) - return - } - - toAddrStr := strings.TrimSpace(passedAddr[0]) - - // OK. - toAddr, err := crypto.AddressFromBech32(toAddrStr) - if err != nil { - fmt.Println(ip, "invalid address format", err) - w.Write([]byte("invalid address format")) - return - } - err = sendAmountTo(cfg, cli, io, name, pass, toAddr, send) - if err != nil { - fmt.Println(ip, "faucet failed", err) - w.Write([]byte("faucet failed")) - return - } else { - fmt.Println(ip, "faucet success") - w.Write([]byte("faucet success")) - } - }) - - // listen to port - fmt.Println("Starting server at port 5050") - - server := &http.Server{ - Addr: ":5050", - ReadHeaderTimeout: 60 * time.Second, - } - if err := server.ListenAndServe(); err != nil { - return fmt.Errorf("http server stopped. %w", err) - } - - return nil + return cfg } -func sendAmountTo( - cfg *config, - cli rpcclient.Client, - io commands.IO, - name, - pass string, - toAddr crypto.Address, - send std.Coins, -) error { - // Read supply account pubkey. - kb, err := keys.NewKeyBaseFromDir(cfg.Home) - if err != nil { - return err - } - info, err := kb.GetByName(name) - if err != nil { - return err - } - fromAddr := info.GetAddress() - pub := info.GetPubKey() +func execServe(ctx context.Context, cfg *serveCfg, io commands.IO) error { + // Parse static gas values. + // It is worth noting that this is temporary, + // and will be removed once gas estimation is enabled + // on Gno.land + gasFee := std.MustParseCoin(defaultGasFee) - // parse gas wanted & fee. - gaswanted := cfg.GasWanted - gasfee, err := std.ParseCoin(cfg.GasFee) + gasWanted, err := strconv.ParseInt(defaultGasWanted, 10, 64) if err != nil { - return errors.Wrap(err, "parsing gas fee coin") + return fmt.Errorf("invalid gas wanted, %w", err) } - // construct msg & tx and marshal. - msg := bank.MsgSend{ - FromAddress: fromAddr, - ToAddress: toAddr, - Amount: send, - } - tx := std.Tx{ - Msgs: []std.Msg{msg}, - Fee: std.NewFee(gaswanted, gasfee), - Signatures: nil, - Memo: cfg.Memo, - } - // fill tx signatures. - signers := tx.GetSigners() - if tx.Signatures == nil { - for range signers { - tx.Signatures = append(tx.Signatures, std.Signature{ - PubKey: nil, // zero signature - Signature: nil, // zero signature - }) - } - } - err = tx.ValidateBasic() - if err != nil { - return err - } - // fmt.Println("will sign:", string(amino.MustMarshalJSON(tx))) - - // query for the account number and sequence each time in case the node is reset - path := fmt.Sprintf("auth/accounts/%s", fromAddr.String()) - data := []byte(nil) - opts2 := rpcclient.ABCIQueryOptions{} - qres, err := cli.ABCIQueryWithOptions( - path, data, opts2) + // Parse the send amount + _, err = std.ParseCoins(cfg.maxSendAmount) if err != nil { - return errors.Wrap(err, "querying") - } - if qres.Response.Error != nil { - fmt.Printf("Log: %s\n", - qres.Response.Log) - return qres.Response.Error - } - resdata := qres.Response.Data - var acc gnoland.GnoAccount - amino.MustUnmarshalJSON(resdata, &acc) - accountNumber := acc.BaseAccount.AccountNumber - sequence := acc.BaseAccount.Sequence - - // get sign-bytes and make signature. - chainID := cfg.ChainID - signbz := tx.GetSignBytes(chainID, accountNumber, sequence) - sig, _, err := kb.Sign(name, pass, signbz) - if err != nil { - return err - } - - found := false - for i := range tx.Signatures { - // override signature for matching slot. - if signers[i] == fromAddr { - found = true - tx.Signatures[i] = std.Signature{ - PubKey: pub, - Signature: sig, - } - } - } - if !found { - return errors.New("addr %v (%s) not in signer set", - fromAddr, name) + return fmt.Errorf("invalid send amount, %w", err) } - fmt.Println("will deliver:", string(amino.MustMarshalJSON(tx))) - - // construct tx serialized bytes. - txbz := amino.MustMarshal(tx) - // broadcast tx bytes. - bres, err := cli.BroadcastTxCommit(txbz) - if err != nil { - return errors.Wrap(err, "broadcasting bytes") - } - if bres.CheckTx.IsErr() { - return errors.New("transaction failed %#v\nlog %s", bres, bres.CheckTx.Log) - } else if bres.DeliverTx.IsErr() { - return errors.New("transaction failed %#v\nlog %s", bres, bres.DeliverTx.Log) - } else { - io.Println(string(bres.DeliverTx.Data)) - io.Println("OK!") - io.Println("GAS WANTED:", bres.DeliverTx.GasWanted) - io.Println("GAS USED: ", bres.DeliverTx.GasUsed) + // Validate the remote address + if !remoteRegex.MatchString(cfg.remote) { + return errors.New("invalid remote address") } - return nil -} -func checkRecaptcha(secret, response string) error { - req, err := http.NewRequest(http.MethodPost, siteVerifyURL, nil) - if err != nil { - return err - } + // Create the client (HTTP) + cli := tm2Client.NewClient(cfg.remote) - q := req.URL.Query() - q.Add("secret", secret) - q.Add("response", response) - req.URL.RawQuery = q.Encode() + // Set up the logger + logger := log.ZapLoggerToSlog( + log.NewZapJSONLogger( + io.Out(), + zapcore.DebugLevel, + ), + ) - resp, err := http.DefaultClient.Do(req) // 200 OK + // Start throttled faucet. + st := newIPThrottler(defaultRateLimitInterval, defaultCleanTimeout) + st.start(ctx) + + // Prepare the middlewares + middlewares := []faucet.Middleware{ + getIPMiddleware(cfg.isBehindProxy, st), + getCaptchaMiddleware(cfg.captchaSecret), + } + + // Create a new faucet with + // static gas estimation + f, err := faucet.NewFaucet( + static.New(gasFee, gasWanted), + cli, + faucet.WithLogger(logger), + faucet.WithConfig(cfg.generateFaucetConfig()), + faucet.WithMiddlewares(middlewares), + ) if err != nil { - return err - } - defer resp.Body.Close() - - var body SiteVerifyResponse - if err = json.NewDecoder(resp.Body).Decode(&body); err != nil { - return errors.New("fail, decode response") - } - - if !body.Success { - return errors.New("unsuccessful recaptcha verify request") + return fmt.Errorf("unable to create faucet, %w", err) } - return nil + return f.Serve(ctx) } diff --git a/gno.land/cmd/gnofaucet/throttle.go b/gno.land/cmd/gnofaucet/throttle.go index 36cfbdb9330..fd03d9dc710 100644 --- a/gno.land/cmd/gnofaucet/throttle.go +++ b/gno.land/cmd/gnofaucet/throttle.go @@ -1,60 +1,110 @@ package main import ( - "net" + "context" + "errors" + "net/netip" + "sync" "time" - "github.com/gnolang/gno/tm2/pkg/service" + "golang.org/x/time/rate" ) -type SubnetThrottler struct { - service.BaseService - ticker *time.Ticker - subnets3 [2 << (8 * 3)]uint8 - // subnets2 [2 << (8 * 2)]uint8 - // subnets1 [2 << (8 * 1)]uint8 +const ( + maxRequestsPerMinute = 5 + + defaultCleanTimeout = time.Minute * 3 + defaultRateLimitInterval = time.Minute / maxRequestsPerMinute +) + +var errInvalidNumberOfRequests = errors.New("invalid number of requests") + +type client struct { + limiter *rate.Limiter + seen time.Time } -func NewSubnetThrottler() *SubnetThrottler { - st := &SubnetThrottler{} - // st.ticker = time.NewTicker(time.Second) - st.ticker = time.NewTicker(time.Minute) - st.BaseService = *service.NewBaseService(nil, "SubnetThrottler", st) - return st +type requestMap map[netip.Addr]*client + +// iterate ranges over the request map (NOT thread safe) +func (r requestMap) iterate(cb func(key netip.Addr, value *client)) { + for ip, requests := range r { + cb(ip, requests) + } } -func (st *SubnetThrottler) OnStart() error { - st.BaseService.OnStart() - go st.routineTimer() - return nil +type ipThrottler struct { + cleanupInterval time.Duration + rateLimitInterval time.Duration + + requestMap requestMap + + sync.Mutex +} + +// newIPThrottler creates a new ip throttler +func newIPThrottler(rateLimitInterval, cleanupInterval time.Duration) *ipThrottler { + return &ipThrottler{ + cleanupInterval: cleanupInterval, + rateLimitInterval: rateLimitInterval, + requestMap: make(requestMap), + } } -func (st *SubnetThrottler) routineTimer() { +// start starts the throttle cleanup service +func (st *ipThrottler) start(ctx context.Context) { + go st.runCleanup(ctx) +} + +// runCleanup runs the main ip throttle cleanup loop +func (st *ipThrottler) runCleanup(ctx context.Context) { + ticker := time.NewTicker(st.cleanupInterval) + for { select { - case <-st.Quit(): + case <-ctx.Done(): return - case <-st.ticker.C: - // run something every time interval. - for i := range st.subnets3 { - st.subnets3[i] /= 2 - } + case <-ticker.C: + st.Lock() + + // Clean up stale requests + st.requestMap.iterate(func(ip netip.Addr, client *client) { + // Check if the request was last seen a while ago + if time.Since(client.seen) < st.cleanupInterval { + return + } + + delete(st.requestMap, ip) + }) + + st.Unlock() } } } -func (st *SubnetThrottler) Request(ip net.IP) (allowed bool, reason string) { - ip = ip.To4() - if len(ip) != 4 { - return false, "invalid ip format" +// registerNewRequest registers a new IP request with the throttler +func (st *ipThrottler) registerNewRequest(ip netip.Addr) error { + st.Lock() + defer st.Unlock() + + // Get the client associated with the address, if any + c := st.requestMap[ip] + if c == nil { + c = &client{ + limiter: rate.NewLimiter(rate.Every(st.rateLimitInterval), 5), + seen: time.Now(), + } + + st.requestMap[ip] = c } - bucket3 := int(ip[0])*256*256 + - int(ip[1])*256 + - int(ip[2]) - v := st.subnets3[bucket3] - if v > 5 { - return false, ">5" + + // Check if the IP exceeded the request count + if !c.limiter.Allow() { + return errInvalidNumberOfRequests } - st.subnets3[bucket3] += 1 - return true, "" + + // Update the last seen time + c.seen = time.Now() + + return nil } diff --git a/gno.land/cmd/gnofaucet/throttle_test.go b/gno.land/cmd/gnofaucet/throttle_test.go new file mode 100644 index 00000000000..f1d6f0db7f5 --- /dev/null +++ b/gno.land/cmd/gnofaucet/throttle_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "net/netip" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIPThrottler_RegisterNewRequest(t *testing.T) { + t.Parallel() + + t.Run("valid number of requests", func(t *testing.T) { + t.Parallel() + + addr, err := netip.ParseAddr("127.0.0.1") + require.NoError(t, err) + + // Create the IP throttler + th := newIPThrottler(defaultRateLimitInterval, defaultCleanTimeout) + + // Register < max requests + for i := uint64(0); i < maxRequestsPerMinute; i++ { + assert.NoError(t, th.registerNewRequest(addr)) + } + }) + + t.Run("exceeded number of requests", func(t *testing.T) { + t.Parallel() + + addr, err := netip.ParseAddr("127.0.0.1") + require.NoError(t, err) + + // Create the IP throttler + th := newIPThrottler(defaultRateLimitInterval, defaultCleanTimeout) + + // Register max requests + for i := uint64(0); i < maxRequestsPerMinute; i++ { + assert.NoError(t, th.registerNewRequest(addr)) + } + + // Attempt to register an additional request + assert.ErrorIs(t, th.registerNewRequest(addr), errInvalidNumberOfRequests) + }) +} + +func TestIPThrottler_RequestsThrottled(t *testing.T) { + t.Parallel() + + var ( + cleanupInterval = time.Millisecond * 100 + + requestInterval = 3 * cleanupInterval // requests triggered after ~5 cleans + numRequests = maxRequestsPerMinute * 2 // number of request loops + ) + + addr, err := netip.ParseAddr("127.0.0.1") + require.NoError(t, err) + + // Create the IP throttler + th := newIPThrottler(defaultRateLimitInterval, cleanupInterval) + + ctx, cancelFn := context.WithCancel(context.Background()) + defer cancelFn() + + // Start the throttler (async) + th.start(ctx) + + var wg sync.WaitGroup + + wg.Add(1) + + go func() { + defer wg.Done() + + var ( + requestsSent = 0 + ticker = time.NewTicker(requestInterval) + ) + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + // Fill out the request count for the address + for i := uint64(0); i < maxRequestsPerMinute; i++ { + require.NoError(t, th.registerNewRequest(addr)) + } + + requestsSent += maxRequestsPerMinute + + if requestsSent == numRequests { + // Loops done + return + } + } + } + }() + + wg.Wait() +} diff --git a/gno.land/pkg/gnoweb/views/faucet.html b/gno.land/pkg/gnoweb/views/faucet.html index 36c5987b9c9..066596f7145 100644 --- a/gno.land/pkg/gnoweb/views/faucet.html +++ b/gno.land/pkg/gnoweb/views/faucet.html @@ -9,25 +9,85 @@
- This is the Gno.land (test) faucet. {{ if .Data.captchaSite }} + This is the Gno.land (test) {{ if .Data.Config.CaptchaSite }} {{ end }} -
-
-
- {{ if .Data.captchaSite }} -
+ + + +
+
+ {{ if .Data.Config.CaptchaSite }} +
{{ end }} -
+
+
+
{{ template "footer" }}
diff --git a/go.mod b/go.mod index e1407807bb7..08acbc12a74 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/fortytw2/leaktest v1.3.0 github.com/gdamore/tcell/v2 v2.6.0 + github.com/gnolang/faucet v0.1.3 github.com/gnolang/goleveldb v0.0.9 github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 github.com/golang/protobuf v1.5.3 @@ -43,10 +44,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require golang.org/x/time v0.5.0 + require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/gdamore/encoding v1.0.0 // indirect + github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect @@ -56,7 +59,8 @@ require ( github.com/rivo/uniseg v0.4.3 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/go.sum b/go.sum index 344720c6325..854e4531bdd 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= @@ -13,9 +14,8 @@ github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUB github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -56,10 +56,14 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= +github.com/gnolang/faucet v0.1.3 h1:eg3S4rGkW6LYWo7nhc4AhWrPmHsaEFy6R8fyef/KgK4= +github.com/gnolang/faucet v0.1.3/go.mod h1:+91pqgE+pyX8FO9eoe2MGiwgTpYY0bITYsHO/4Zg+CY= github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -192,6 +196,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.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= @@ -207,8 +213,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -221,6 +227,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/misc/loop/go.mod b/misc/loop/go.mod index de33b61ffd5..da95e7f5f28 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -33,7 +33,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/tools v0.18.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index a557c6255e3..00cabcfd2d1 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -14,9 +14,8 @@ github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUB github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -162,8 +161,8 @@ 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=