From 42b1c1a8ad02eeed0a35a05d5bb3d9ce602abb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 13:01:07 +0200 Subject: [PATCH 1/7] Switch to using JSON-RPC 2.0 by default --- .github/golangci.yaml | 232 ++++++++++++++++++++++-------------------- go.mod | 54 +++++----- go.sum | 114 +++++++++++---------- handler.go | 181 ++++++++++++++++++-------------- handler_test.go | 81 +++++++-------- spec/errors.go | 8 ++ spec/spec.go | 115 +++++++++++++++++++++ types.go | 20 +--- 8 files changed, 475 insertions(+), 330 deletions(-) create mode 100644 spec/errors.go create mode 100644 spec/spec.go diff --git a/.github/golangci.yaml b/.github/golangci.yaml index 9fb9829..3198e13 100644 --- a/.github/golangci.yaml +++ b/.github/golangci.yaml @@ -1,124 +1,132 @@ +version: "2" run: concurrency: 8 - timeout: 10m - issue-exit-code: 1 - tests: true - skip-dirs-use-default: true + go: "" modules-download-mode: readonly + tests: true allow-parallel-runners: false - go: "" - output: - uniq-by-line: false path-prefix: "" - sort-results: true - +linters: + default: none + enable: + - asasalint + - asciicheck + - bidichk + - decorder + - dogsled + - dupl + - durationcheck + - errcheck + - errname + - errorlint + - gocheckcompilerdirectives + - gochecknoinits + - goconst + - gocritic + - godot + - gomoddirectives + - gosec + - govet + - importas + - ineffassign + - lll + - loggercheck + - makezero + - misspell + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - nolintlint + - nonamedreturns + - prealloc + - predeclared + - promlinter + - reassign + - revive + - staticcheck + - tagliatelle + - testableexamples + - thelper + - tparallel + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + - wsl + settings: + errcheck: + check-type-assertions: false + check-blank: true + exclude-functions: + - io/ioutil.ReadFile + - io.Copy(*bytes.Buffer) + - io.Copy(os.Stdout) + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + disabled-checks: + - hugeParam + - rangeExprCopy + - rangeValCopy + - importShadow + - unnamedResult + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + godot: + scope: all + period: false + govet: + enable-all: true + nakedret: + max-func-lines: 1 + tagliatelle: + case: + rules: + json: goCamel + yaml: goCamel + use-field-name: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - gosec + - nilnil + path: (.+)_test.go + paths: + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 new: false fix: false - exclude-rules: - - path: (.+)_test.go - linters: - - nilnil - - gosec - -linters: - fast: false - disable-all: true +formatters: enable: - - asasalint # Check for pass []any as any in variadic func(...any) - - asciicheck # Detects funky ASCII characters - - bidichk # Checks for dangerous unicode character sequences - - durationcheck # Check for two durations multiplied together - - errcheck # Forces to not skip error check - - exportloopref # Checks for pointers to enclosing loop variables - - gocritic # Bundles different linting checks - - godot # Checks for periods at the end of comments - - gomoddirectives # Allow or ban replace directives in go.mod - - gosimple # Code simplification - - govet # Official Go tool - - ineffassign # Detects when assignments to existing variables are not used - - nakedret # Finds naked/bare returns and requires change them - - nilerr # Requires explicit returns - - nilnil # Requires explicit returns - - promlinter # Lints Prometheus metrics names - - reassign # Checks that package variables are not reassigned - - revive # Drop-in replacement for golint - - tagliatelle # Checks struct tags - - tenv # Detects using os.Setenv instead of t.Setenv - - testableexamples # Checks if examples are testable (have expected output) - - unparam # Finds unused params - - usestdlibvars # Detects the possibility to use variables/constants from stdlib - - wastedassign # Finds wasted assignment statements - - loggercheck # Checks the odd number of key and value pairs for common logger libraries - - nestif # Finds deeply nested if statements - - nonamedreturns # Reports all named returns - - decorder # Check declaration order of types, consts, vars and funcs - - gocheckcompilerdirectives # Checks that compiler directive comments (//go:) are valid - - gochecknoinits # Checks for init methods - - whitespace # Tool for detection of leading and trailing whitespace - - wsl # Forces you to use empty lines - - unconvert # Unnecessary type conversions - - tparallel # Detects inappropriate usage of t.Parallel() method in your Go test codes - - thelper # Detects golang test helpers without t.Helper() call and checks the consistency of test helpers - - stylecheck # Stylecheck is a replacement for golint - - prealloc # Finds slice declarations that could potentially be pre-allocated - - predeclared # Finds code that shadows one of Go's predeclared identifiers - - nolintlint # Ill-formed or insufficient nolint directives - - nlreturn # Checks for a new line before return and branch statements to increase code clarity - - misspell # Misspelled English words in comments - - makezero # Finds slice declarations with non-zero initial length - - lll # Long lines - - importas # Enforces consistent import aliases - - gosec # Security problems - - gofmt # Whether the code was gofmt-ed - - gofumpt # Stricter gofmt - - goimports # Unused imports - - goconst # Repeated strings that could be replaced by a constant - - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) - - dupl # Code clone detection - - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error - - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 - - unused # Checks Go code for unused constants, variables, functions and types - -linters-settings: - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - disabled-checks: - - hugeParam - - rangeExprCopy - - rangeValCopy - - importShadow - - unnamedResult - errcheck: - check-type-assertions: false - check-blank: true - exclude-functions: - - io/ioutil.ReadFile - - io.Copy(*bytes.Buffer) - - io.Copy(os.Stdout) - nakedret: - max-func-lines: 1 - govet: - enable-all: true - gofmt: - simplify: true - goconst: - min-len: 3 - min-occurrences: 3 - godot: - scope: all - period: false - tagliatelle: - case: - use-field-name: true - rules: - json: goCamel - yaml: goCamel + - gofmt + - gofumpt + - goimports + settings: + gofmt: + simplify: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/go.mod b/go.mod index dff609b..0f21997 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,17 @@ module github.com/gnolang/faucet -go 1.22 +go 1.23.0 + +toolchain go1.24.2 require ( - github.com/gnolang/gno v0.0.0-20241127025539-d8589b06b14c - github.com/go-chi/chi/v5 v5.1.0 + github.com/gnolang/gno v0.0.0-20250505160026-9a21cfeaef7b + github.com/go-chi/chi/v5 v5.2.1 github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/rs/cors v1.11.1 github.com/stretchr/testify v1.10.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.14.0 ) require ( @@ -18,34 +20,36 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/go-logr/logr v1.4.2 // indirect 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.26.3 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.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 + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.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 - 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/crypto v0.38.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/grpc v1.72.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f76e044..1f568cb 100644 --- a/go.sum +++ b/go.sum @@ -37,22 +37,20 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= 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= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/gno v0.0.0-20241127025539-d8589b06b14c h1:OL5eBRF+eess5LTOvQ2+pXy176i7CPK17MMTfY1tP0s= -github.com/gnolang/gno v0.0.0-20241127025539-d8589b06b14c/go.mod h1:5qHWYtZl97bJ7ETcax1LF/19jlB/30TaPHeQx9cCEKQ= -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.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/gnolang/gno v0.0.0-20250505160026-9a21cfeaef7b h1:w5U/4iASmIyLvMcwF9SzfkNznjtlkPIU6NB5/Zh9GT8= +github.com/gnolang/gno v0.0.0-20250505160026-9a21cfeaef7b/go.mod h1:Fom74Q4ypl4DT/xJU2J3YAOBwaot6xFpOFzzvrBNEvc= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -65,13 +63,15 @@ 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= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -79,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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 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= @@ -109,12 +109,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/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= 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= @@ -126,45 +128,49 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= 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/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/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -174,30 +180,30 @@ 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/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 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-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= +google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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/handler.go b/handler.go index 29d1ea0..de982d5 100644 --- a/handler.go +++ b/handler.go @@ -1,13 +1,13 @@ package faucet import ( - "encoding/json" "errors" "fmt" "io" "net/http" "regexp" + "github.com/gnolang/faucet/spec" "github.com/gnolang/faucet/writer" httpWriter "github.com/gnolang/faucet/writer/http" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -19,11 +19,20 @@ const ( faucetSuccess = "successfully executed faucet transfer" ) +const DefaultDripMethod = "drip" // the default JSON-RPC method for a faucet drip + var ( errInvalidBeneficiary = errors.New("invalid beneficiary address") errInvalidSendAmount = errors.New("invalid send amount") + errInvalidMethod = errors.New("unknown RPC method call") ) +// drip is a single Faucet transfer request +type drip struct { + amount std.Coins + to crypto.Address +} + var amountRegex = regexp.MustCompile(`^\d+ugnot$`) // defaultHTTPHandler is the default faucet transfer handler @@ -31,13 +40,17 @@ func (f *Faucet) defaultHTTPHandler(w http.ResponseWriter, r *http.Request) { // Load the requests requestBody, readErr := io.ReadAll(r.Body) if readErr != nil { - http.Error(w, "unable to read request", http.StatusBadRequest) + http.Error( + w, + "unable to read request", + http.StatusBadRequest, + ) return } // Extract the requests - requests, err := extractRequests(requestBody) + requests, err := spec.ExtractBaseRequests(requestBody) if err != nil { http.Error( w, @@ -57,9 +70,9 @@ func (f *Faucet) defaultHTTPHandler(w http.ResponseWriter, r *http.Request) { } // handleRequest is the common default faucet handler -func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests Requests) { +func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests spec.BaseJSONRequests) { // Parse all JSON-RPC requests - responses := make(Responses, len(requests)) + responses := make(spec.BaseJSONResponses, len(requests)) for i, baseRequest := range requests { // Log the request @@ -69,51 +82,77 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests Requests) baseRequest, ) - // Extract the beneficiary - beneficiary, err := extractBeneficiary(baseRequest) - if err != nil { - // Save the error response - responses[i] = Response{ - Result: unableToHandleRequest, - Error: err.Error(), - } + // Make sure it's a valid base request + if !spec.IsValidBaseRequest(baseRequest) { + // Marshal the JSON-RPC error + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + nil, + spec.NewJSONError( + "invalid JSON-RPC 2.0 request", + spec.InvalidRequestErrorCode, + ), + ) + + continue + } + + // Make sure the method called on / is the drip method + if baseRequest.Method != DefaultDripMethod { + // Marshal the JSON-RPC error + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + nil, + spec.NewJSONError( + errInvalidMethod.Error(), + spec.MethodNotFoundErrorCode, + ), + ) continue } - // Extract the send amount - amount, err := extractSendAmount(baseRequest) + // Extract the drip request + dripRequest, err := extractDripRequest(baseRequest.Params) if err != nil { - // Save the error response - responses[i] = Response{ - Result: unableToHandleRequest, - Error: err.Error(), - } + // Marshal the JSON-RPC error + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + nil, + spec.NewJSONError( + err.Error(), + spec.InvalidParamsErrorCode, + ), + ) continue } // Check if the amount is set - if amount.IsZero() { - // Drip amount is not set, use + if dripRequest.amount.IsZero() { + // drip amount is not set, use // the max faucet drip amount - amount = f.maxSendAmount + dripRequest.amount = f.maxSendAmount } // Check if the amount exceeds the max // drip amount for the faucet - if amount.IsAllGT(f.maxSendAmount) { - // Save the error response - responses[i] = Response{ - Result: unableToHandleRequest, - Error: errInvalidSendAmount.Error(), - } + if dripRequest.amount.IsAllGT(f.maxSendAmount) { + // Marshal the JSON-RPC error + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + nil, + spec.NewJSONError( + errInvalidSendAmount.Error(), + spec.InvalidRequestErrorCode, + ), + ) continue } // Run the method handler - if err := f.transferFunds(beneficiary, amount); err != nil { + if err := f.transferFunds(dripRequest.to, dripRequest.amount); err != nil { f.logger.Debug( unableToHandleRequest, "request", @@ -122,17 +161,20 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests Requests) err, ) - responses[i] = Response{ - Result: unableToHandleRequest, - Error: err.Error(), - } + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + nil, + spec.GenerateResponseError(err), + ) continue } - responses[i] = Response{ - Result: faucetSuccess, - } + responses[i] = spec.NewJSONResponse( + baseRequest.ID, + faucetSuccess, + nil, + ) } if len(responses) == 1 { @@ -146,61 +188,42 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests Requests) writer.WriteResponse(responses) } -// extractRequests extracts the base JSON requests from the request body -func extractRequests(requestBody []byte) (Requests, error) { - // Extract the request - var requests Requests - - // Check if the request is a batch request - if err := json.Unmarshal(requestBody, &requests); err != nil { - // Try to get a single JSON request, since this is not a batch - var baseRequest Request - if err := json.Unmarshal(requestBody, &baseRequest); err != nil { - return nil, err - } - - requests = Requests{ - baseRequest, - } - } - - return requests, nil -} - -// extractBeneficiary extracts the beneficiary from the base faucet request -func extractBeneficiary(request Request) (crypto.Address, error) { - // Validate the beneficiary address is set - if request.To == "" { - return crypto.Address{}, errInvalidBeneficiary +// extractDripRequest extracts the base drip params from the request +func extractDripRequest(params []any) (*drip, error) { + // Extract the drip params + if len(params) < 1 { + return nil, errInvalidBeneficiary } // Validate the beneficiary address is valid - beneficiary, err := crypto.AddressFromBech32(request.To) + beneficiary, err := crypto.AddressFromBech32(params[0].(string)) if err != nil { - return crypto.Address{}, fmt.Errorf("%w, %w", errInvalidBeneficiary, err) + return nil, fmt.Errorf("%w, %w", errInvalidBeneficiary, err) } - return beneficiary, nil -} - -// extractSendAmount extracts the drip amount from the base faucet request, if any -func extractSendAmount(request Request) (std.Coins, error) { - // Check if the amount is set - if request.Amount == "" { - return std.Coins{}, nil + // Validate the send amount is valid + if len(params) == 1 { + // No amount specified + return &drip{ + to: beneficiary, + amount: std.Coins{}, + }, nil } - // Validate the send amount is valid - if !amountRegex.MatchString(request.Amount) { - return std.Coins{}, errInvalidSendAmount + amountStr := params[1].(string) + if !amountRegex.MatchString(amountStr) { + return nil, errInvalidSendAmount } - amount, err := std.ParseCoins(request.Amount) + amount, err := std.ParseCoins(amountStr) if err != nil { - return std.Coins{}, fmt.Errorf("%w, %w", errInvalidSendAmount, err) + return nil, fmt.Errorf("%w, %w", errInvalidSendAmount, err) } - return amount, nil + return &drip{ + to: beneficiary, + amount: amount, + }, nil } // healthcheckHandler is the default health check handler for the faucet diff --git a/handler_test.go b/handler_test.go index 391f6c2..6c5ecae 100644 --- a/handler_test.go +++ b/handler_test.go @@ -14,6 +14,7 @@ import ( "github.com/gnolang/faucet/config" "github.com/gnolang/faucet/estimate/static" + "github.com/gnolang/faucet/spec" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" coreTypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -25,7 +26,7 @@ import ( ) // decodeResponse decodes the JSON response -func decodeResponse[T Response | Responses](t *testing.T, responseBody []byte) *T { +func decodeResponse[T spec.BaseJSONResponse | spec.BaseJSONResponses](t *testing.T, responseBody []byte) *T { t.Helper() var response *T @@ -88,13 +89,11 @@ func TestFaucet_Serve_ValidRequests(t *testing.T) { var ( validAddress = crypto.MustAddressFromString("g155n659f89cfak0zgy575yqma64sm4tv6exqk99") - singleValidRequest = Request{ - To: validAddress.String(), - } + singleValidRequest = spec.NewJSONRequest(0, DefaultDripMethod, []any{validAddress.String()}) - bulkValidRequests = Requests{ - singleValidRequest, - singleValidRequest, + bulkValidRequests = spec.BaseJSONRequests{ + spec.NewJSONRequest(0, DefaultDripMethod, []any{validAddress.String()}), + spec.NewJSONRequest(1, DefaultDripMethod, []any{validAddress.String()}), } ) @@ -120,7 +119,7 @@ func TestFaucet_Serve_ValidRequests(t *testing.T) { }{ { func(resp []byte) { - response := decodeResponse[Response](t, resp) + response := decodeResponse[spec.BaseJSONResponse](t, resp) assert.Empty(t, response.Error) assert.Equal(t, faucetSuccess, response.Result) @@ -131,7 +130,7 @@ func TestFaucet_Serve_ValidRequests(t *testing.T) { }, { func(resp []byte) { - responses := decodeResponse[Responses](t, resp) + responses := decodeResponse[spec.BaseJSONResponses](t, resp) require.Len(t, *responses, len(bulkValidRequests)) for _, response := range *responses { @@ -146,11 +145,10 @@ func TestFaucet_Serve_ValidRequests(t *testing.T) { } for _, testCase := range testTable { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { t.Parallel() + //nolint:dupl // It's fine to leave this setup the same var ( requiredFunds = sendAmount.Add(std.NewCoins(gasFee)) fundAccount = crypto.Address{1} @@ -301,15 +299,11 @@ func TestFaucet_Serve_InvalidRequests(t *testing.T) { var ( invalidAddress = "invalid-address" - singleInvalidRequest = Request{ - To: invalidAddress, - } + singleInvalidRequest = spec.NewJSONRequest(0, DefaultDripMethod, []any{invalidAddress}) - bulkInvalidRequests = Requests{ + bulkInvalidRequests = spec.BaseJSONRequests{ singleInvalidRequest, - Request{ - To: "", // empty address - }, + spec.NewJSONRequest(1, DefaultDripMethod, []any{"", sendAmount.String()}), // empty address } ) @@ -335,10 +329,12 @@ func TestFaucet_Serve_InvalidRequests(t *testing.T) { }{ { func(resp []byte) { - response := decodeResponse[Response](t, resp) + response := decodeResponse[spec.BaseJSONResponse](t, resp) + + require.NotNil(t, response.Error) - assert.Contains(t, response.Error, errInvalidBeneficiary.Error()) - assert.Equal(t, response.Result, unableToHandleRequest) + assert.Contains(t, response.Error.Message, errInvalidBeneficiary.Error()) + assert.Nil(t, response.Result) }, "single request", encodedSingleInvalidRequest, @@ -346,12 +342,14 @@ func TestFaucet_Serve_InvalidRequests(t *testing.T) { }, { func(resp []byte) { - responses := decodeResponse[Responses](t, resp) + responses := decodeResponse[spec.BaseJSONResponses](t, resp) require.Len(t, *responses, len(bulkInvalidRequests)) for _, response := range *responses { - assert.Contains(t, response.Error, errInvalidBeneficiary.Error()) - assert.Equal(t, response.Result, unableToHandleRequest) + require.NotNil(t, response.Error) + + assert.Contains(t, response.Error.Message, errInvalidBeneficiary.Error()) + assert.Nil(t, response.Result) } }, "bulk request", @@ -361,11 +359,10 @@ func TestFaucet_Serve_InvalidRequests(t *testing.T) { } for _, testCase := range testTable { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { t.Parallel() + //nolint:dupl // It's fine to leave this setup the same var ( requiredFunds = sendAmount.Add(std.NewCoins(gasFee)) fundAccount = crypto.Address{1} @@ -513,8 +510,6 @@ func TestFaucet_Serve_MalformedRequests(t *testing.T) { } for _, testCase := range testTable { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -575,9 +570,7 @@ func TestFaucet_Serve_NoFundedAccounts(t *testing.T) { var ( validAddress = crypto.MustAddressFromString("g155n659f89cfak0zgy575yqma64sm4tv6exqk99") - singleValidRequest = Request{ - To: validAddress.String(), - } + singleValidRequest = spec.NewJSONRequest(0, DefaultDripMethod, []any{validAddress.String()}) ) encodedSingleValidRequest, err := json.Marshal( @@ -701,10 +694,11 @@ func TestFaucet_Serve_NoFundedAccounts(t *testing.T) { respBytes, err := io.ReadAll(respRaw.Body) require.NoError(t, err) - response := decodeResponse[Response](t, respBytes) + response := decodeResponse[spec.BaseJSONResponse](t, respBytes) - assert.Contains(t, response.Error, errNoFundedAccount.Error()) - assert.Equal(t, response.Result, unableToHandleRequest) + require.NotNil(t, response.Error) + assert.Contains(t, response.Error.Message, errNoFundedAccount.Error()) + assert.Nil(t, response.Result) // Stop the faucet and wait for it to finish cancelFn() @@ -735,8 +729,6 @@ func TestFaucet_Serve_InvalidSendAmount(t *testing.T) { } for _, testCase := range testTable { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -744,10 +736,11 @@ func TestFaucet_Serve_InvalidSendAmount(t *testing.T) { validAddress = crypto.MustAddressFromString("g155n659f89cfak0zgy575yqma64sm4tv6exqk99") gasFee = std.MustParseCoin("1ugnot") - singleInvalidRequest = Request{ - To: validAddress.String(), - Amount: testCase.sendAmount.String(), - } + singleInvalidRequest = spec.NewJSONRequest( + 0, + DefaultDripMethod, + []any{validAddress.String(), testCase.sendAmount.String()}, + ) ) encodedSingleInvalidRequest, err := json.Marshal( @@ -799,10 +792,12 @@ func TestFaucet_Serve_InvalidSendAmount(t *testing.T) { respBytes, err := io.ReadAll(respRaw.Body) require.NoError(t, err) - response := decodeResponse[Response](t, respBytes) + response := decodeResponse[spec.BaseJSONResponse](t, respBytes) + + require.NotNil(t, response.Error) - assert.Contains(t, response.Error, errInvalidSendAmount.Error()) - assert.Equal(t, unableToHandleRequest, response.Result) + assert.Contains(t, response.Error.Message, errInvalidSendAmount.Error()) + assert.Nil(t, response.Result) // Stop the faucet and wait for it to finish cancelFn() diff --git a/spec/errors.go b/spec/errors.go new file mode 100644 index 0000000..649db6f --- /dev/null +++ b/spec/errors.go @@ -0,0 +1,8 @@ +package spec + +const ( + InvalidParamsErrorCode int = -32602 + MethodNotFoundErrorCode int = -32601 + InvalidRequestErrorCode int = -32600 + ServerErrorCode int = -32000 +) diff --git a/spec/spec.go b/spec/spec.go new file mode 100644 index 0000000..56cac30 --- /dev/null +++ b/spec/spec.go @@ -0,0 +1,115 @@ +package spec + +import "encoding/json" + +const JSONRPCVersion = "2.0" + +// BaseJSON defines the base JSON fields +// all JSON-RPC requests and responses need to have +type BaseJSON struct { + JSONRPC string `json:"jsonrpc"` + ID uint `json:"id,omitempty"` +} + +// BaseJSONRequest defines the base JSON request format +type BaseJSONRequest struct { + BaseJSON + + Method string `json:"method"` + Params []any `json:"params"` +} + +// BaseJSONRequests represents a batch of JSON-RPC requests +type BaseJSONRequests []*BaseJSONRequest + +// BaseJSONResponses represents a batch of JSON-RPC responses +type BaseJSONResponses []*BaseJSONResponse + +// BaseJSONResponse defines the base JSON response format +type BaseJSONResponse struct { + Result any `json:"result"` + Error *BaseJSONError `json:"error,omitempty"` + BaseJSON +} + +// BaseJSONError defines the base JSON response error format +type BaseJSONError struct { + Data any `json:"data,omitempty"` + Message string `json:"message"` + Code int `json:"code"` +} + +// NewJSONResponse creates a new JSON-RPC response +func NewJSONResponse( + id uint, + result any, + err *BaseJSONError, +) *BaseJSONResponse { + return &BaseJSONResponse{ + BaseJSON: BaseJSON{ + ID: id, + JSONRPC: JSONRPCVersion, + }, + Result: result, + Error: err, + } +} + +// NewJSONError creates a new JSON-RPC error +func NewJSONError(message string, code int) *BaseJSONError { + return &BaseJSONError{ + Code: code, + Message: message, + } +} + +// NewJSONRequest creates a new JSON-RPC request +func NewJSONRequest( + id uint, + method string, + params []any, +) *BaseJSONRequest { + return &BaseJSONRequest{ + BaseJSON: BaseJSON{ + ID: id, + JSONRPC: JSONRPCVersion, + }, + Method: method, + Params: params, + } +} + +// GenerateResponseError generates the JSON-RPC server error response +func GenerateResponseError(err error) *BaseJSONError { + return NewJSONError(err.Error(), ServerErrorCode) +} + +// ExtractBaseRequests extracts the base JSON-RPC request from the request body +func ExtractBaseRequests(requestBody []byte) (BaseJSONRequests, error) { + // Extract the request + var requests BaseJSONRequests + + // Check if the request is a batch request + if err := json.Unmarshal(requestBody, &requests); err != nil { + // Try to get a single JSON-RPC request, since this is not a batch + var baseRequest *BaseJSONRequest + if err := json.Unmarshal(requestBody, &baseRequest); err != nil { + return nil, err + } + + requests = BaseJSONRequests{ + baseRequest, + } + } + + return requests, nil +} + +// IsValidBaseRequest validates that the base JSON request is valid +func IsValidBaseRequest(baseRequest *BaseJSONRequest) bool { + if baseRequest.Method == "" { + return false + } + + return baseRequest.JSONRPC == JSONRPCVersion +} diff --git a/types.go b/types.go index ec948b0..31fee32 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,8 @@ package faucet -import "net/http" +import ( + "net/http" +) const ( jsonMimeType = "application/json" @@ -14,19 +16,3 @@ type Handler struct { HandlerFunc http.HandlerFunc Pattern string } - -type Requests []Request - -// Request is a single Faucet transfer request -type Request struct { - To string `json:"to"` - Amount string `json:"amount"` -} - -type Responses []Response - -// Response is a single Faucet transfer response -type Response struct { - Result string `json:"result"` - Error string `json:"error,omitempty"` -} From 4852661555c42a4a4d6e795f84714b18b9c665ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 13:01:52 +0200 Subject: [PATCH 2/7] Bump go version --- .github/workflows/lint.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/test.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index db3c5d4..f75c0f3 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -9,7 +9,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1e3bb4b..df0ed75 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version: 1.23 cache: true - uses: sigstore/cosign-installer@v3.7.0 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cd28558..363c1a3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,7 +9,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x - name: Checkout code uses: actions/checkout@v4 @@ -23,7 +23,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x - name: Checkout code uses: actions/checkout@v4 From 389c6b54e343017b623e13f711424b407cf2a07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 13:07:19 +0200 Subject: [PATCH 3/7] Add omit --- spec/spec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec.go b/spec/spec.go index 56cac30..cd168a6 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -27,7 +27,7 @@ type BaseJSONResponses []*BaseJSONResponse // BaseJSONResponse defines the base JSON response format type BaseJSONResponse struct { - Result any `json:"result"` + Result any `json:"result,omitempty"` Error *BaseJSONError `json:"error,omitempty"` BaseJSON } From 031f7685fd25d8a82652a2ccabfc31cb0ff9e86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 14:41:51 +0200 Subject: [PATCH 4/7] Simplify default handler --- handler.go | 175 +++++++++++++++++++++-------------------------------- 1 file changed, 70 insertions(+), 105 deletions(-) diff --git a/handler.go b/handler.go index de982d5..ecee64e 100644 --- a/handler.go +++ b/handler.go @@ -74,107 +74,10 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests spec.BaseJ // Parse all JSON-RPC requests responses := make(spec.BaseJSONResponses, len(requests)) - for i, baseRequest := range requests { - // Log the request - f.logger.Debug( - "incoming request", - "request", - baseRequest, - ) + for i, req := range requests { + f.logger.Debug("incoming request", "request", req) - // Make sure it's a valid base request - if !spec.IsValidBaseRequest(baseRequest) { - // Marshal the JSON-RPC error - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - nil, - spec.NewJSONError( - "invalid JSON-RPC 2.0 request", - spec.InvalidRequestErrorCode, - ), - ) - - continue - } - - // Make sure the method called on / is the drip method - if baseRequest.Method != DefaultDripMethod { - // Marshal the JSON-RPC error - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - nil, - spec.NewJSONError( - errInvalidMethod.Error(), - spec.MethodNotFoundErrorCode, - ), - ) - - continue - } - - // Extract the drip request - dripRequest, err := extractDripRequest(baseRequest.Params) - if err != nil { - // Marshal the JSON-RPC error - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - nil, - spec.NewJSONError( - err.Error(), - spec.InvalidParamsErrorCode, - ), - ) - - continue - } - - // Check if the amount is set - if dripRequest.amount.IsZero() { - // drip amount is not set, use - // the max faucet drip amount - dripRequest.amount = f.maxSendAmount - } - - // Check if the amount exceeds the max - // drip amount for the faucet - if dripRequest.amount.IsAllGT(f.maxSendAmount) { - // Marshal the JSON-RPC error - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - nil, - spec.NewJSONError( - errInvalidSendAmount.Error(), - spec.InvalidRequestErrorCode, - ), - ) - - continue - } - - // Run the method handler - if err := f.transferFunds(dripRequest.to, dripRequest.amount); err != nil { - f.logger.Debug( - unableToHandleRequest, - "request", - baseRequest, - "error", - err, - ) - - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - nil, - spec.GenerateResponseError(err), - ) - - continue - } - - responses[i] = spec.NewJSONResponse( - baseRequest.ID, - faucetSuccess, - nil, - ) + responses[i] = f.handleSingleRequest(req) } if len(responses) == 1 { @@ -188,6 +91,63 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests spec.BaseJ writer.WriteResponse(responses) } +// handleSingleRequest validates and executes one drip request +func (f *Faucet) handleSingleRequest(req *spec.BaseJSONRequest) *spec.BaseJSONResponse { + // Make sure it's a valid base request + if !spec.IsValidBaseRequest(req) { + return spec.NewJSONResponse( + req.ID, + nil, + spec.NewJSONError("invalid JSON-RPC 2.0 request", spec.InvalidRequestErrorCode), + ) + } + + // Make sure the method call on "/" is "drip" + if req.Method != DefaultDripMethod { + return spec.NewJSONResponse( + req.ID, + nil, + spec.NewJSONError(errInvalidMethod.Error(), spec.MethodNotFoundErrorCode), + ) + } + + // Parse params into a drip request + dripRequest, err := extractDripRequest(req.Params) + if err != nil { + return spec.NewJSONResponse( + req.ID, + nil, + spec.NewJSONError(err.Error(), spec.InvalidParamsErrorCode), + ) + } + + // Check if the amount is set + if dripRequest.amount.IsZero() { + // drip amount is not set, use + // the max faucet drip amount + dripRequest.amount = f.maxSendAmount + } + + // Check if the amount exceeds the max + // drip amount for the faucet + if dripRequest.amount.IsAllGT(f.maxSendAmount) { + return spec.NewJSONResponse( + req.ID, + nil, + spec.NewJSONError(errInvalidSendAmount.Error(), spec.InvalidRequestErrorCode), + ) + } + + // Attempt fund transfer + if err := f.transferFunds(dripRequest.to, dripRequest.amount); err != nil { + f.logger.Debug("unable to handle drip", "req", req, "err", err) + + return spec.NewJSONResponse(req.ID, nil, spec.GenerateResponseError(err)) + } + + return spec.NewJSONResponse(req.ID, faucetSuccess, nil) +} + // extractDripRequest extracts the base drip params from the request func extractDripRequest(params []any) (*drip, error) { // Extract the drip params @@ -195,10 +155,15 @@ func extractDripRequest(params []any) (*drip, error) { return nil, errInvalidBeneficiary } + addrStr, ok := params[0].(string) + if !ok { + return nil, fmt.Errorf("%w: beneficiary must be a string", errInvalidBeneficiary) + } + // Validate the beneficiary address is valid - beneficiary, err := crypto.AddressFromBech32(params[0].(string)) + beneficiary, err := crypto.AddressFromBech32(addrStr) if err != nil { - return nil, fmt.Errorf("%w, %w", errInvalidBeneficiary, err) + return nil, fmt.Errorf("%w: %w", errInvalidBeneficiary, err) } // Validate the send amount is valid @@ -210,14 +175,14 @@ func extractDripRequest(params []any) (*drip, error) { }, nil } - amountStr := params[1].(string) - if !amountRegex.MatchString(amountStr) { + amountStr, ok := params[1].(string) + if !ok || !amountRegex.MatchString(amountStr) { return nil, errInvalidSendAmount } amount, err := std.ParseCoins(amountStr) if err != nil { - return nil, fmt.Errorf("%w, %w", errInvalidSendAmount, err) + return nil, fmt.Errorf("%w: %w", errInvalidSendAmount, err) } return &drip{ From e4212a1a4ee4940476db2333ca982eadcd3aadfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 14:42:58 +0200 Subject: [PATCH 5/7] Bump linter workflow version --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f75c0f3..92d0660 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: args: --config=./.github/golangci.yaml From cdcd63a2ea3ef2157a1d53bd2d340130c2d710a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 6 May 2025 14:49:45 +0200 Subject: [PATCH 6/7] Update README --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2aaa277..7a4c5ab 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,27 @@ `faucet` is a versatile command-line interface (CLI) tool and library designed to effortlessly deploy a faucet server for Gno Tendermint 2 networks. +### Default endpoint (root) + +The `faucet` adopts the [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification) for requests / responses. + +By default, the `/` endpoint is the home of the `drip` method, to handle faucet drips. The first parameter is the +beneficiary address, and the second one is the string representation of the drip amount (`std.Coins`). + +This can of course be overwritten with custom handling logic by the faucet creator (see below). + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "method": "drip", + "params": [ + "g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2", + "1000ugnot" + ] +} +``` + ## Key Features ### Customizability @@ -46,6 +67,7 @@ make build 3. Run the faucet To run the faucet, start the built binary: + ```bash ./build/faucet --mnemonic "" ``` @@ -54,16 +76,19 @@ The provided mnemonic will be used to derive accounts which will be used to serv funds to users. Make sure that the accounts derived from it are well funded. It should print something like the following. (Note the port number at the end.) + ``` 2024-01-11T12:47:27.826+0100 INFO cmd/logger.go:17 faucet started at [::]:8545 ``` 4. To send coins to a single account, in a new terminal enter the following (change to the correct recipient address): + ```bash -curl --location --request POST 'http://localhost:8545' --header 'Content-Type: application/json' --data '{"To": "g1juz2yxmdsa6audkp6ep9vfv80c8p5u76e03vvh"}' +curl --location --request POST 'http://localhost:8545' --header 'Content-Type: application/json' --data '{ "jsonrpc": "2.0", "id": 0, "method": "drip", "params": [ "g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2", "1000ugnot" ] }' ``` -5. To ensure the faucet is listening to requests, you can ping the health endpoint: +5. To ensure the faucet is listening to requests, you can ping the health endpoint (returns a simple status 200): + ```bash curl --location --request GET 'http://localhost:8545/health' ``` @@ -94,7 +119,7 @@ func main() { f, err := NewFaucet( static.New(...), // gas estimator http.NewClient(...), // remote address - ) +) // The faucet is controlled through a top-level context ctx, cancelFn := context.WithCancel(context.Background()) From 7438753f5f4b757f5bf546b7ea76db9d92ec167f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Mon, 19 May 2025 14:19:20 +0200 Subject: [PATCH 7/7] Update README.md Co-authored-by: Antoine Eddi <5222525+aeddi@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a4c5ab..0b1ab2f 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ func main() { f, err := NewFaucet( static.New(...), // gas estimator http.NewClient(...), // remote address -) + ) // The faucet is controlled through a top-level context ctx, cancelFn := context.WithCancel(context.Background())