diff --git a/api/api/codes.go b/api/api/codes.go deleted file mode 100644 index 1d5b6696..00000000 --- a/api/api/codes.go +++ /dev/null @@ -1,9 +0,0 @@ -package api - -// API codes used in guardian node. -// The same codes are used to facilitate portability to the new api. -const ( - InvalidArgument = 3 - NotFound = 5 - Internal = 13 -) diff --git a/api/errors/isof.go b/api/errors/isof.go deleted file mode 100644 index db70015a..00000000 --- a/api/errors/isof.go +++ /dev/null @@ -1,12 +0,0 @@ -package errors - -import "errors" - -func IsOf(received error, targets ...error) bool { - for _, t := range targets { - if errors.Is(received, t) { - return true - } - } - return false -} diff --git a/api/go.mod b/api/go.mod index d1279e01..7f15e012 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,27 +3,26 @@ module github.com/wormhole-foundation/wormhole-explorer/api go 1.19 require ( + github.com/ansrivas/fiberprometheus/v2 v2.4.1 github.com/certusone/wormhole/node v0.0.0-20220907133901-8e231501b6cd github.com/gofiber/fiber/v2 v2.39.0 + github.com/ipfs/go-log/v2 v2.5.1 github.com/spf13/viper v1.13.0 go.mongodb.org/mongo-driver v1.10.3 + go.uber.org/zap v1.22.0 ) require ( github.com/andybalholm/brotli v1.0.4 // indirect - github.com/ansrivas/fiberprometheus/v2 v2.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/ethereum/go-ethereum v1.10.6 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gofiber/adaptor/v2 v2.1.29 // indirect - github.com/gogo/protobuf v1.3.3 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/ipfs/go-log v1.0.5 // indirect - github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/klauspost/compress v1.15.12 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -32,7 +31,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -55,7 +53,6 @@ require ( github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.22.0 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.1.0 // indirect diff --git a/api/go.sum b/api/go.sum index 15d6b24c..64b6ad6c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -83,6 +83,7 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbE github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -105,7 +106,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/certusone/wormhole/node v0.0.0-20220907133901-8e231501b6cd h1:j/D2a0Hvs+JBKteCTFGhi9f7MA0SaDE+BPFoGNqSENo= github.com/certusone/wormhole/node v0.0.0-20220907133901-8e231501b6cd/go.mod h1:hbEdpBxPoBplIJ+PMVitVGX5194g+Opd7dvONcxhykQ= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -173,8 +173,6 @@ github.com/gofiber/adaptor/v2 v2.1.25/go.mod h1:gOxtwMVqUStB5goAYtKd+hSvGupdd+aR github.com/gofiber/adaptor/v2 v2.1.29 h1:JnYd6fbqVM9D4zPchk+kg89PfxyuKqZKhBWGQDHfKH4= github.com/gofiber/adaptor/v2 v2.1.29/go.mod h1:z4mAV9mMsUgIEVGGS5Ii6ZMTJq4VdV1KWL1JAbsZdUA= github.com/gofiber/fiber/v2 v2.36.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ= -github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= -github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= github.com/gofiber/fiber/v2 v2.39.0 h1:uhWpYQ6EHN8J7FOPYbI2hrdBD/KNZBC5CjbuOd4QUt4= github.com/gofiber/fiber/v2 v2.39.0/go.mod h1:Cmuu+elPYGqlvQvdKyjtYsjGMi69PDp8a1AY2I5B2gM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -271,9 +269,6 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= -github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= -github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -302,8 +297,6 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= @@ -371,8 +364,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -420,7 +411,6 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -482,7 +472,6 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= @@ -512,19 +501,16 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0= go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= @@ -709,7 +695,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -746,8 +731,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -891,7 +874,6 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/governor/controller.go b/api/handlers/governor/controller.go similarity index 60% rename from api/governor/controller.go rename to api/handlers/governor/controller.go index eef9b1a0..3362078c 100644 --- a/api/governor/controller.go +++ b/api/handlers/governor/controller.go @@ -1,23 +1,26 @@ +// Package governor handle the request of governor data from governor endpoint defined in the api. package governor import ( "github.com/gofiber/fiber/v2" "github.com/wormhole-foundation/wormhole-explorer/api/middleware" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" "go.uber.org/zap" ) +// Controller definition. type Controller struct { srv *Service logger *zap.Logger } +// NewController create a new controler. func NewController(serv *Service, logger *zap.Logger) *Controller { return &Controller{srv: serv, logger: logger.With(zap.String("module", "GovernorController"))} } +// FindGovernorConfigurations handler for the endpoint /governor/config/ func (c *Controller) FindGovernorConfigurations(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) governorConfigs, err := c.srv.FindGovernorConfig(ctx.Context(), p) if err != nil { return err @@ -25,9 +28,10 @@ func (c *Controller) FindGovernorConfigurations(ctx *fiber.Ctx) error { return ctx.JSON(governorConfigs) } +// FindGovernorConfigurationByGuardianAddress handler for the endpoint /governor/config/:guardian_address. func (c *Controller) FindGovernorConfigurationByGuardianAddress(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - guardianAddress, err := middleware.ExtractGuardianAddress(ctx) + p := middleware.GetPaginationFromContext(ctx) + guardianAddress, err := middleware.ExtractGuardianAddress(ctx, c.logger) if err != nil { return err } @@ -38,8 +42,9 @@ func (c *Controller) FindGovernorConfigurationByGuardianAddress(ctx *fiber.Ctx) return ctx.JSON(govConfig) } +// FindGovernorStatus handler for the endpoint /governor/status/. func (c *Controller) FindGovernorStatus(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) governorStatus, err := c.srv.FindGovernorStatus(ctx.Context(), p) if err != nil { return err @@ -47,9 +52,10 @@ func (c *Controller) FindGovernorStatus(ctx *fiber.Ctx) error { return ctx.JSON(governorStatus) } +// FindGovernorStatusByGuardianAddress handler for the endpoint /governor/status/:guardian_address. func (c *Controller) FindGovernorStatusByGuardianAddress(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - guardianAddress, err := middleware.ExtractGuardianAddress(ctx) + p := middleware.GetPaginationFromContext(ctx) + guardianAddress, err := middleware.ExtractGuardianAddress(ctx, c.logger) if err != nil { return err } @@ -60,8 +66,9 @@ func (c *Controller) FindGovernorStatusByGuardianAddress(ctx *fiber.Ctx) error { return ctx.JSON(govStatus) } +// GetGovernorLimit handler for the endpoint /governor/limit/ func (c *Controller) GetGovernorLimit(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) governorLimit, err := c.srv.GetGovernorLimit(ctx.Context(), p) if err != nil { return err @@ -69,8 +76,9 @@ func (c *Controller) GetGovernorLimit(ctx *fiber.Ctx) error { return ctx.JSON(governorLimit) } +// FindNotionalLimit handler for the endpoint governor/notional/limit/ func (c *Controller) FindNotionalLimit(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) notionalLimit, err := c.srv.FindNotionalLimit(ctx.Context(), p) if err != nil { return err @@ -78,9 +86,10 @@ func (c *Controller) FindNotionalLimit(ctx *fiber.Ctx) error { return ctx.JSON(notionalLimit) } +// GetNotionalLimitByChainID handler for the endpoint governor/notional/limit/:chain. func (c *Controller) GetNotionalLimitByChainID(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) if err != nil { return err } @@ -91,8 +100,9 @@ func (c *Controller) GetNotionalLimitByChainID(ctx *fiber.Ctx) error { return ctx.JSON(notionalLimit) } +// GetAvailableNotional handler for the endpoint governor/notional/available/ func (c *Controller) GetAvailableNotional(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) notionalAvaialabilies, err := c.srv.GetAvailableNotional(ctx.Context(), p) if err != nil { return err @@ -100,9 +110,10 @@ func (c *Controller) GetAvailableNotional(ctx *fiber.Ctx) error { return ctx.JSON(notionalAvaialabilies) } +// GetAvailableNotionalByChainID handler for the endpoint governor/notional/available/:chain func (c *Controller) GetAvailableNotionalByChainID(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) if err != nil { return err } @@ -113,9 +124,10 @@ func (c *Controller) GetAvailableNotionalByChainID(ctx *fiber.Ctx) error { return ctx.JSON(response) } +// GetMaxNotionalAvailableByChainID handler for the endpoint governor/max_available/:chain. func (c *Controller) GetMaxNotionalAvailableByChainID(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) if err != nil { return err } @@ -126,8 +138,9 @@ func (c *Controller) GetMaxNotionalAvailableByChainID(ctx *fiber.Ctx) error { return ctx.JSON(response) } +// GetEnqueueVass handler for the endpoint governor/enqueued_vaas/ func (c *Controller) GetEnqueueVass(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) enqueuedVaas, err := c.srv.GetEnqueueVass(ctx.Context(), p) if err != nil { return err @@ -135,9 +148,10 @@ func (c *Controller) GetEnqueueVass(ctx *fiber.Ctx) error { return ctx.JSON(enqueuedVaas) } +// GetEnqueueVassByChainID handler for the endpoint governor/enqueued_vaas/:chain. func (c *Controller) GetEnqueueVassByChainID(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) if err != nil { return err } diff --git a/api/governor/model.go b/api/handlers/governor/model.go similarity index 90% rename from api/governor/model.go rename to api/handlers/governor/model.go index 845f4bd4..306e573a 100644 --- a/api/governor/model.go +++ b/api/handlers/governor/model.go @@ -1,3 +1,4 @@ +// Package governor handle the request of governor data from governor endpoint defined in the api. package governor import ( @@ -6,7 +7,7 @@ import ( "github.com/certusone/wormhole/node/pkg/vaa" ) -// GovConfigPage definition. +// GovConfigPage represent a governor configuration. type GovConfig struct { ID string `bson:"_id" json:"id"` CreatedAt *time.Time `bson:"createdAt" json:"createdAt"` @@ -29,7 +30,7 @@ type GovConfigfTokens struct { Price float64 `bson:"price" json:"price"` } -// GovStatusPage definition. +// GovStatusPage represent a governor status. type GovStatus struct { ID string `bson:"_id" json:"id"` CreatedAt *time.Time `bson:"createdAt" json:"createdAt"` @@ -50,17 +51,17 @@ type GovStatusChainEmitter struct { EnqueuedVass interface{} `bson:"enqueuedvaas" json:"enqueuedvaas"` } -// NotionalLimit definition. +// NotionalLimit represent the notional limit value and maximun tranasction size for a chainID. type NotionalLimit struct { ChainID vaa.ChainID `bson:"chainid" json:"chainid"` NotionalLimit *int64 `bson:"notionalLimit" json:"notionalLimit"` MaxTrasactionSize *int64 `bson:"maxTransactionSize" json:"maxTransactionSize"` } -// NotionalLimitDetail definition. +// NotionalLimitDetail represent a notional limit value type NotionalLimitDetail struct { ID string `bson:"_id" json:"id"` - ChainID vaa.ChainID `bson:"chainid" json:"chainid"` + ChainID vaa.ChainID `bson:"chainId" json:"chainId"` NodeName string `bson:"nodename" json:"nodename"` NotionalLimit *int64 `bson:"notionalLimit" json:"notionalLimit"` MaxTrasactionSize *int64 `bson:"maxTransactionSize" json:"maxTransactionSize"` @@ -68,11 +69,13 @@ type NotionalLimitDetail struct { UpdatedAt *time.Time `bson:"updatedAt" json:"updatedAt"` } +// NotionalAvailable represent the available notional for chainID. type NotionalAvailable struct { ChainID vaa.ChainID `bson:"chainid" json:"chainId"` AvailableNotional *int64 `bson:"availableNotional" json:"availableNotional"` } +// NotionalAvailableDetail represent a notional available value. type NotionalAvailableDetail struct { ID string `bson:"_id" json:"id"` ChainID vaa.ChainID `bson:"chainId" json:"chainId"` @@ -108,6 +111,7 @@ type EnqueuedVaa struct { TxHash string `bson:"txHash" json:"txHash"` } +// EnqueuedVaas definition. type EnqueuedVaas struct { ChainID vaa.ChainID `bson:"chainid" json:"chainId"` EnqueuedVaa []*EnqueuedVaa `bson:"enqueuedVaas" json:"enqueuedVaas"` diff --git a/api/governor/repository.go b/api/handlers/governor/repository.go similarity index 76% rename from api/governor/repository.go rename to api/handlers/governor/repository.go index ae2525aa..f763ef4a 100644 --- a/api/governor/repository.go +++ b/api/handlers/governor/repository.go @@ -1,14 +1,16 @@ +// Package governor handle the request of governor data from governor endpoint defined in the api. package governor import ( "context" - "errors" "fmt" "sort" "time" "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" + "github.com/pkg/errors" + errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -17,10 +19,7 @@ import ( const minGuardianNum = 13 -var ( - ErrWrongQuery = errors.New("MALFORMED_QUERY") -) - +// Repository definition. type Repository struct { db *mongo.Database logger *zap.Logger @@ -30,6 +29,7 @@ type Repository struct { } } +// NewRepository create a new Repository. func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { return &Repository{db: db, logger: logger.With(zap.String("module", "GovernorRepository")), @@ -43,21 +43,25 @@ func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { } } +// GovernorQuery respresent a query for the governors mongodb documents. type GovernorQuery struct { pagination.Pagination id string } +// QueryGovernor create a new GovernorQuery with default pagination values. func QueryGovernor() *GovernorQuery { page := pagination.FirstPage() return &GovernorQuery{Pagination: *page} } +// SetID set the id field of the GovernorQuery struct. func (q *GovernorQuery) SetID(id string) *GovernorQuery { q.id = id return q } +// SetPagination set the pagination field of the GovernorQuery struct. func (q *GovernorQuery) SetPagination(p *pagination.Pagination) *GovernorQuery { q.Pagination = *p return q @@ -71,6 +75,7 @@ func (q *GovernorQuery) toBSON() *bson.D { return &r } +// FindGovConfigurations get a list of *GovConfig. func (r *Repository) FindGovConfigurations(ctx context.Context, q *GovernorQuery) ([]*GovConfig, error) { if q == nil { q = QueryGovernor() @@ -87,19 +92,26 @@ func (r *Repository) FindGovConfigurations(ctx context.Context, q *GovernorQuery options := options.Find().SetProjection(projection).SetLimit(q.PageSize).SetSkip(q.Offset).SetSort(sort) cur, err := r.collections.governorConfig.Find(ctx, q.toBSON(), options) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Find command to get governor configurations", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } var govConfigs []*GovConfig err = cur.All(ctx, &govConfigs) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*GovConfig", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return govConfigs, err } +// FindGovConfiguration get a *GovConfig. The q parameter define the filter to apply to the query. func (r *Repository) FindGovConfiguration(ctx context.Context, q *GovernorQuery) (*GovConfig, error) { if q == nil { - return nil, ErrWrongQuery + return nil, errs.ErrMalformedQuery } var govConfig GovConfig projection := bson.D{ @@ -113,11 +125,18 @@ func (r *Repository) FindGovConfiguration(ctx context.Context, q *GovernorQuery) options := options.FindOne().SetProjection(projection) err := r.collections.governorConfig.FindOne(ctx, q.toBSON(), options).Decode(&govConfig) if err != nil { - return nil, err + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errs.ErrNotFound + } + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute FindOne command to get governor configuration", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return &govConfig, err } +// FindGovernorStatus get a list of *GovStatus. func (r *Repository) FindGovernorStatus(ctx context.Context, q *GovernorQuery) ([]*GovStatus, error) { if q == nil { q = QueryGovernor() @@ -132,19 +151,26 @@ func (r *Repository) FindGovernorStatus(ctx context.Context, q *GovernorQuery) ( options := options.Find().SetProjection(projection).SetLimit(q.PageSize).SetSkip(q.Offset).SetSort(sort) cur, err := r.collections.governorStatus.Find(ctx, q.toBSON(), options) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Find command to get all governor status", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } var govStatus []*GovStatus err = cur.All(ctx, &govStatus) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*GovStatus", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return govStatus, err } +// FindOneGovernorStatus get a *GovStatus. The q parameter define the filter to apply to the query. func (r *Repository) FindOneGovernorStatus(ctx context.Context, q *GovernorQuery) (*GovStatus, error) { if q == nil { - return nil, ErrWrongQuery + return nil, errs.ErrMalformedQuery } var govConfig GovStatus projection := bson.D{ @@ -156,37 +182,49 @@ func (r *Repository) FindOneGovernorStatus(ctx context.Context, q *GovernorQuery options := options.FindOne().SetProjection(projection) err := r.collections.governorStatus.FindOne(ctx, q.toBSON(), options).Decode(&govConfig) if err != nil { - return nil, err + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errs.ErrNotFound + } + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute FindOne command to get governor status", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return &govConfig, err } +// NotionalLimitQuery type NotionalLimitQuery struct { pagination.Pagination id string chainID vaa.ChainID } +// QueryNotionalLimit create a new NotionalLimitQuery with default pagination values. func QueryNotionalLimit() *NotionalLimitQuery { page := pagination.FirstPage() return &NotionalLimitQuery{Pagination: *page} } +// SetID set the id field of the NotionalLimitQuery struct. func (q *NotionalLimitQuery) SetID(id string) *NotionalLimitQuery { q.id = id return q } +// SetChain set the chainID field of the NotionalLimitQuery struct. func (q *NotionalLimitQuery) SetChain(chainID vaa.ChainID) *NotionalLimitQuery { q.chainID = chainID return q } +// SetPagination set the Pagination field of the NotionalLimitQuery struct. func (q *NotionalLimitQuery) SetPagination(p *pagination.Pagination) *NotionalLimitQuery { q.Pagination = *p return q } +// FindNotionalLimit get a list *NotionalLimit. func (r *Repository) FindNotionalLimit(ctx context.Context, q *NotionalLimitQuery) ([]*NotionalLimit, error) { // agreggation stages to get notionalLimit for each chainID. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -259,24 +297,31 @@ func (r *Repository) FindNotionalLimit(ctx context.Context, q *NotionalLimitQuer // execute aggregate operations. cur, err := r.collections.governorConfig.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get notional limit", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decodes to NotionalLimit. var notionalLimits []*NotionalLimit err = cur.All(ctx, ¬ionalLimits) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*NotionalLimit", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // check records exists. if len(notionalLimits) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } return notionalLimits, nil } +// GetNotionalLimitByChainID get a list *NotionalLimitDetail. func (r *Repository) GetNotionalLimitByChainID(ctx context.Context, q *NotionalLimitQuery) ([]*NotionalLimitDetail, error) { // agreggation stages to get notionalLimit by chainID. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -333,19 +378,26 @@ func (r *Repository) GetNotionalLimitByChainID(ctx context.Context, q *NotionalL // execute aggregate operations. cur, err := r.collections.governorConfig.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get notional limit by chainID", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decode to []NotionalLimitRecord. var notionalLimits []*NotionalLimitDetail err = cur.All(ctx, ¬ionalLimits) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*NotionalLimitDetail", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return notionalLimits, nil } +// GetAvailableNotional get a list of *NotionalAvailable. func (r *Repository) GetAvailableNotional(ctx context.Context, q *NotionalLimitQuery) ([]*NotionalAvailable, error) { // stage. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -421,24 +473,31 @@ func (r *Repository) GetAvailableNotional(ctx context.Context, q *NotionalLimitQ // execute aggregate operations. cur, err := r.collections.governorStatus.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get available notional", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decode to []NotionalLimitRecord. var notionalAvailables []*NotionalAvailable err = cur.All(ctx, ¬ionalAvailables) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*NotionalAvailable", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // check exists records if len(notionalAvailables) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } return notionalAvailables, nil } +// GetAvailableNotionalByChainID get a list of *NotionalAvailableDetail. func (r *Repository) GetAvailableNotionalByChainID(ctx context.Context, q *NotionalLimitQuery) ([]*NotionalAvailableDetail, error) { // stage definitions. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -497,19 +556,26 @@ func (r *Repository) GetAvailableNotionalByChainID(ctx context.Context, q *Notio // execute aggregate operations. cur, err := r.collections.governorStatus.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get available notional by chainID", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decode to []NotionalLimitRecord. var notionalAvailability []*NotionalAvailableDetail err = cur.All(ctx, ¬ionalAvailability) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*NotionalAvailableDetail", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return notionalAvailability, nil } +// GetMaxNotionalAvailableByChainID get a *MaxNotionalAvailableRecord. func (r *Repository) GetMaxNotionalAvailableByChainID(ctx context.Context, q *NotionalLimitQuery) (*MaxNotionalAvailableRecord, error) { // stage definitions. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -577,55 +643,67 @@ func (r *Repository) GetMaxNotionalAvailableByChainID(ctx context.Context, q *No // execute aggregate operations. cur, err := r.collections.governorStatus.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get maximun available notional by chainID", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decode to []NotionalLimitRecord. var rows []*MaxNotionalAvailableRecord err = cur.All(ctx, &rows) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*MaxNotionalAvailableRecord", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // check exists records if len(rows) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } if len(rows) < minGuardianNum { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } maxNotionalLimit := rows[minGuardianNum-1] return maxNotionalLimit, nil } +// EnqueuedVaaQuery respresent a query for enqueuedVaa queries. type EnqueuedVaaQuery struct { pagination.Pagination id string chainID vaa.ChainID } +// QueryEnqueuedVaa create a new EnqueuedVaaQuery with default pagination values. func QueryEnqueuedVaa() *EnqueuedVaaQuery { page := pagination.FirstPage() return &EnqueuedVaaQuery{Pagination: *page} } +// SetID set the id field of the EnqueuedVaaQuery struct. func (q *EnqueuedVaaQuery) SetID(id string) *EnqueuedVaaQuery { q.id = id return q } +// SetChain set the chainID field of the EnqueuedVaaQuery struct. func (q *EnqueuedVaaQuery) SetChain(chainID vaa.ChainID) *EnqueuedVaaQuery { q.chainID = chainID return q } +// SetPagination set the pagination field of the EnqueuedVaaQuery struct. func (q *EnqueuedVaaQuery) SetPagination(p *pagination.Pagination) *EnqueuedVaaQuery { q.Pagination = *p return q } +// GetEnqueueVass get a list of *EnqueuedVaas. func (r *Repository) GetEnqueueVass(ctx context.Context, q *EnqueuedVaaQuery) ([]*EnqueuedVaas, error) { // match stage. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -678,7 +756,10 @@ func (r *Repository) GetEnqueueVass(ctx context.Context, q *EnqueuedVaaQuery) ([ cur, err := r.collections.governorStatus.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get enqueued vaas", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } var rows []struct { @@ -697,11 +778,14 @@ func (r *Repository) GetEnqueueVass(ctx context.Context, q *EnqueuedVaaQuery) ([ // decode query response. err = cur.All(ctx, &rows) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to rows", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } if len(rows) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } // TODO: Change this logic to mongo query code. @@ -732,7 +816,7 @@ func (r *Repository) GetEnqueueVass(ctx context.Context, q *EnqueuedVaaQuery) ([ } if len(enqueuedVaas) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } // group by chainID. @@ -760,6 +844,7 @@ func (r *Repository) GetEnqueueVass(ctx context.Context, q *EnqueuedVaaQuery) ([ return response, nil } +// GetEnqueueVassByChainID get a list of *EnqueuedVaaDetail by chainID. func (r *Repository) GetEnqueueVassByChainID(ctx context.Context, q *EnqueuedVaaQuery) ([]*EnqueuedVaaDetail, error) { // stage definitions. matchStage1 := bson.D{{Key: "$match", Value: bson.D{}}} @@ -823,7 +908,10 @@ func (r *Repository) GetEnqueueVassByChainID(ctx context.Context, q *EnqueuedVaa cur, err := r.collections.governorStatus.Aggregate(ctx, pipeline) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get enqueued vaas by chainID", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decode query response. @@ -840,7 +928,10 @@ func (r *Repository) GetEnqueueVassByChainID(ctx context.Context, q *EnqueuedVaa } err = cur.All(ctx, &rows) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to rows", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // TODO: Change this logic to mongo query code. @@ -871,7 +962,7 @@ func (r *Repository) GetEnqueueVassByChainID(ctx context.Context, q *EnqueuedVaa } if len(response) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } // sort response by sequence. @@ -881,6 +972,7 @@ func (r *Repository) GetEnqueueVassByChainID(ctx context.Context, q *EnqueuedVaa return response, nil } +// GetGovernorLimit get a list of *GovernorLimit. func (r *Repository) GetGovernorLimit(ctx context.Context, q *GovernorQuery) ([]*GovernorLimit, error) { // lookup. lookupStage1 := bson.D{ @@ -1017,19 +1109,25 @@ func (r *Repository) GetGovernorLimit(ctx context.Context, q *GovernorQuery) ([] // execute aggregate operations. cur, err := r.collections.governorConfig.Aggregate(ctx, pipeLine) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get governor limit", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // decodes to RawDocRecord. var governorLimits []*GovernorLimit err = cur.All(ctx, &governorLimits) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*GovernorLimit", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } // check exists records if len(governorLimits) == 0 { - return nil, errors.New("not found") + return nil, errs.ErrNotFound } return governorLimits, nil diff --git a/api/governor/service.go b/api/handlers/governor/service.go similarity index 65% rename from api/governor/service.go rename to api/handlers/governor/service.go index 4179fa65..d55af465 100644 --- a/api/governor/service.go +++ b/api/handlers/governor/service.go @@ -1,11 +1,12 @@ +// Package governor handle the request of governor data from governor endpoint defined in the api. package governor import ( "context" "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" - "github.com/wormhole-foundation/wormhole-explorer/api/services" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" + "github.com/wormhole-foundation/wormhole-explorer/api/response" "go.uber.org/zap" ) @@ -20,118 +21,118 @@ func NewService(dao *Repository, logger *zap.Logger) *Service { } // FindGovernorConfig get a list of governor configurations. -func (s *Service) FindGovernorConfig(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*GovConfig], error) { +func (s *Service) FindGovernorConfig(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*GovConfig], error) { if p == nil { p = pagination.FirstPage() } query := QueryGovernor().SetPagination(p) govConfigs, err := s.repo.FindGovConfigurations(ctx, query) - res := services.Response[[]*GovConfig]{Data: govConfigs, Error: err} + res := response.Response[[]*GovConfig]{Data: govConfigs} return &res, err } // FindGovernorConfigByGuardianAddress get a governor configuration by guardianAddress. -func (s *Service) FindGovernorConfigByGuardianAddress(ctx context.Context, guardianAddress string, p *pagination.Pagination) (*services.Response[*GovConfig], error) { +func (s *Service) FindGovernorConfigByGuardianAddress(ctx context.Context, guardianAddress string, p *pagination.Pagination) (*response.Response[*GovConfig], error) { query := QueryGovernor().SetID(guardianAddress).SetPagination(p) govConfig, err := s.repo.FindGovConfiguration(ctx, query) - res := services.Response[*GovConfig]{Data: govConfig, Error: err} + res := response.Response[*GovConfig]{Data: govConfig} return &res, err } // FindGovernorStatus get a list of governor status. -func (s *Service) FindGovernorStatus(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*GovStatus], error) { +func (s *Service) FindGovernorStatus(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*GovStatus], error) { if p == nil { p = pagination.FirstPage() } query := QueryGovernor().SetPagination(p) govStatus, err := s.repo.FindGovernorStatus(ctx, query) - res := services.Response[[]*GovStatus]{Data: govStatus, Error: err} + res := response.Response[[]*GovStatus]{Data: govStatus} return &res, err } // FindGovernorStatusByGuardianAddress get a governor status by guardianAddress. -func (s *Service) FindGovernorStatusByGuardianAddress(ctx context.Context, guardianAddress string, p *pagination.Pagination) (*services.Response[*GovStatus], error) { +func (s *Service) FindGovernorStatusByGuardianAddress(ctx context.Context, guardianAddress string, p *pagination.Pagination) (*response.Response[*GovStatus], error) { query := QueryGovernor().SetID(guardianAddress).SetPagination(p) govStatus, err := s.repo.FindOneGovernorStatus(ctx, query) - res := services.Response[*GovStatus]{Data: govStatus, Error: err} + res := response.Response[*GovStatus]{Data: govStatus} return &res, err } // FindNotionalLimit get a notional limit for each chainID. -func (s *Service) FindNotionalLimit(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*NotionalLimit], error) { +func (s *Service) FindNotionalLimit(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*NotionalLimit], error) { if p == nil { p = pagination.FirstPage() } query := QueryNotionalLimit().SetPagination(p) notionalLimit, err := s.repo.FindNotionalLimit(ctx, query) - res := services.Response[[]*NotionalLimit]{Data: notionalLimit, Error: err} + res := response.Response[[]*NotionalLimit]{Data: notionalLimit} return &res, err } // GetNotionalLimitByChainID get a notional limit by chainID. -func (s *Service) GetNotionalLimitByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*services.Response[[]*NotionalLimitDetail], error) { +func (s *Service) GetNotionalLimitByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*response.Response[[]*NotionalLimitDetail], error) { query := QueryNotionalLimit().SetPagination(p).SetChain(chainID) notionalLimit, err := s.repo.GetNotionalLimitByChainID(ctx, query) - res := services.Response[[]*NotionalLimitDetail]{Data: notionalLimit, Error: err} + res := response.Response[[]*NotionalLimitDetail]{Data: notionalLimit} return &res, err } // GetAvailableNotional get a available notional for each chainID. -func (s *Service) GetAvailableNotional(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*NotionalAvailable], error) { +func (s *Service) GetAvailableNotional(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*NotionalAvailable], error) { if p == nil { p = pagination.FirstPage() } query := QueryNotionalLimit().SetPagination(p) notionalAvailability, err := s.repo.GetAvailableNotional(ctx, query) - res := services.Response[[]*NotionalAvailable]{Data: notionalAvailability, Error: err} + res := response.Response[[]*NotionalAvailable]{Data: notionalAvailability} return &res, err } // GetAvailableNotionalByChainID get a available notional by chainID. -func (s *Service) GetAvailableNotionalByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*services.Response[[]*NotionalAvailableDetail], error) { +func (s *Service) GetAvailableNotionalByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*response.Response[[]*NotionalAvailableDetail], error) { query := QueryNotionalLimit().SetPagination(p).SetChain(chainID) notionaLAvailability, err := s.repo.GetAvailableNotionalByChainID(ctx, query) - res := services.Response[[]*NotionalAvailableDetail]{Data: notionaLAvailability, Error: err} + res := response.Response[[]*NotionalAvailableDetail]{Data: notionaLAvailability} return &res, err } // GetMaxNotionalAvailableByChainID get a maximun notional value by chainID. -func (s *Service) GetMaxNotionalAvailableByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*services.Response[*MaxNotionalAvailableRecord], error) { +func (s *Service) GetMaxNotionalAvailableByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*response.Response[*MaxNotionalAvailableRecord], error) { query := QueryNotionalLimit().SetPagination(p).SetChain(chainID) maxNotionaLAvailable, err := s.repo.GetMaxNotionalAvailableByChainID(ctx, query) - res := services.Response[*MaxNotionalAvailableRecord]{Data: maxNotionaLAvailable, Error: err} + res := response.Response[*MaxNotionalAvailableRecord]{Data: maxNotionaLAvailable} return &res, err } -// GetEnqueueVass. -func (s *Service) GetEnqueueVass(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*EnqueuedVaas], error) { +// GetEnqueueVaas get all the enqueued vaa. +func (s *Service) GetEnqueueVass(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*EnqueuedVaas], error) { if p == nil { p = pagination.FirstPage() } query := QueryEnqueuedVaa().SetPagination(p) enqueuedVaaResponse, err := s.repo.GetEnqueueVass(ctx, query) - res := services.Response[[]*EnqueuedVaas]{Data: enqueuedVaaResponse, Error: err} + res := response.Response[[]*EnqueuedVaas]{Data: enqueuedVaaResponse} return &res, err } -// GetEnqueueVassByChainID by chainID. -func (s *Service) GetEnqueueVassByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*services.Response[[]*EnqueuedVaaDetail], error) { +// GetEnqueueVassByChainID get enequeued vaa by chainID. +func (s *Service) GetEnqueueVassByChainID(ctx context.Context, p *pagination.Pagination, chainID vaa.ChainID) (*response.Response[[]*EnqueuedVaaDetail], error) { if p == nil { p = pagination.FirstPage() } query := QueryEnqueuedVaa().SetPagination(p).SetChain(chainID) enqueuedVaaRecord, err := s.repo.GetEnqueueVassByChainID(ctx, query) - res := services.Response[[]*EnqueuedVaaDetail]{Data: enqueuedVaaRecord, Error: err} + res := response.Response[[]*EnqueuedVaaDetail]{Data: enqueuedVaaRecord} return &res, err } // GetGovernorLimit get governor limit. -func (s *Service) GetGovernorLimit(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*GovernorLimit], error) { +func (s *Service) GetGovernorLimit(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*GovernorLimit], error) { if p == nil { p = pagination.FirstPage() } query := QueryGovernor().SetPagination(p) governorLimit, err := s.repo.GetGovernorLimit(ctx, query) - res := services.Response[[]*GovernorLimit]{Data: governorLimit, Error: err} + res := response.Response[[]*GovernorLimit]{Data: governorLimit} return &res, err } diff --git a/api/observations/controller.go b/api/handlers/observations/controller.go similarity index 50% rename from api/observations/controller.go rename to api/handlers/observations/controller.go index c9f49afe..990b05fd 100644 --- a/api/observations/controller.go +++ b/api/handlers/observations/controller.go @@ -1,19 +1,19 @@ +// Package observations handle the request of observations data from governor endpoint defined in the api. package observations import ( - "github.com/certusone/wormhole/node/pkg/vaa" "github.com/gofiber/fiber/v2" - "github.com/wormhole-foundation/wormhole-explorer/api/errors" "github.com/wormhole-foundation/wormhole-explorer/api/middleware" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" "go.uber.org/zap" ) +// Controller definition. type Controller struct { srv *Service logger *zap.Logger } +// NewController create a new controler. func NewController(srv *Service, logger *zap.Logger) *Controller { return &Controller{ srv: srv, @@ -21,8 +21,9 @@ func NewController(srv *Service, logger *zap.Logger) *Controller { } } +// FindAll handler for the endpoint /observations/. func (c *Controller) FindAll(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) obs, err := c.srv.FindAll(ctx.Context(), p) if err != nil { return err @@ -30,9 +31,13 @@ func (c *Controller) FindAll(ctx *fiber.Ctx) error { return ctx.JSON(obs) } +// FindAllByChain handler for the endpoint /observations/:chain. func (c *Controller) FindAllByChain(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) + if err != nil { + return err + } obs, err := c.srv.FindByChain(ctx.Context(), chainID, p) if err != nil { return err @@ -40,12 +45,14 @@ func (c *Controller) FindAllByChain(ctx *fiber.Ctx) error { return ctx.JSON(obs) } +// FindAllByEmitter handler for the endpoint /observations/:chain/:emitter. func (c *Controller) FindAllByEmitter(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, addr, _, err := middleware.ExtractVAAParams(ctx) - if errors.IsOf(err, middleware.ErrMalformedChain, middleware.ErrMalformedAddr) { + p := middleware.GetPaginationFromContext(ctx) + chainID, addr, err := middleware.ExtractVAAChainIDEmitter(ctx, c.logger) + if err != nil { return err } + obs, err := c.srv.FindByEmitter(ctx.Context(), chainID, addr, p) if err != nil { return err @@ -53,9 +60,10 @@ func (c *Controller) FindAllByEmitter(ctx *fiber.Ctx) error { return ctx.JSON(obs) } +// FindAllByVAA handler for the endpoint /observations/:chain/:emitter/:sequence func (c *Controller) FindAllByVAA(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, addr, seq, err := middleware.ExtractVAAParams(ctx) + p := middleware.GetPaginationFromContext(ctx) + chainID, addr, seq, err := middleware.ExtractVAAParams(ctx, c.logger) if err != nil { return err } @@ -66,18 +74,21 @@ func (c *Controller) FindAllByVAA(ctx *fiber.Ctx) error { return ctx.JSON(obs) } +// FindOne handler for the endpoint /observations/:chain/:emitter/:sequence/:signer/:hash func (c *Controller) FindOne(ctx *fiber.Ctx) error { - chainID, addr, seq, err := middleware.ExtractVAAParams(ctx) + chainID, addr, seq, err := middleware.ExtractVAAParams(ctx, c.logger) if err != nil { return err } - signer := ctx.Params("signer") - signerAddr, err := vaa.StringToAddress(signer) + signerAddr, err := middleware.ExtractObservationSigner(ctx, c.logger) if err != nil { return err } - hash := ctx.Params("hash") - obs, err := c.srv.FindOne(ctx.Context(), chainID, addr, seq, &signerAddr, []byte(hash)) + hash, err := middleware.ExtractObservationHash(ctx, c.logger) + if err != nil { + return err + } + obs, err := c.srv.FindOne(ctx.Context(), chainID, addr, seq, signerAddr, []byte(hash)) if err != nil { return err } diff --git a/api/observations/model.go b/api/handlers/observations/model.go similarity index 70% rename from api/observations/model.go rename to api/handlers/observations/model.go index af645129..6b857888 100644 --- a/api/observations/model.go +++ b/api/handlers/observations/model.go @@ -1,10 +1,13 @@ +// Package observations handle the request of observations data from governor endpoint defined in the api. package observations import ( - "github.com/certusone/wormhole/node/pkg/vaa" "time" + + "github.com/certusone/wormhole/node/pkg/vaa" ) +// ObservationDoc represent an observation document. type ObservationDoc struct { ID string `bson:"_id" json:"id"` Version uint8 `bson:"version" json:"version"` @@ -15,7 +18,6 @@ type ObservationDoc struct { TxHash []byte `bson:"txHash" json:"txHash"` GuardianAddr string `bson:"guardianAddr" json:"guardianAddr"` Signature []byte `bson:"signature" json:"signature"` - - UpdatedAt *time.Time `bson:"updatedAt" json:"updatedAt"` - IndexedAt *time.Time `bson:"indexedAt" json:"indexedAt"` + UpdatedAt *time.Time `bson:"updatedAt" json:"updatedAt"` + IndexedAt *time.Time `bson:"indexedAt" json:"indexedAt"` } diff --git a/api/observations/repository.go b/api/handlers/observations/repository.go similarity index 57% rename from api/observations/repository.go rename to api/handlers/observations/repository.go index bbad1125..9dab1475 100644 --- a/api/observations/repository.go +++ b/api/handlers/observations/repository.go @@ -1,16 +1,21 @@ +// Package observations handle the request of observations data from governor endpoint defined in the api. package observations import ( "context" - "errors" + "fmt" + "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" + "github.com/pkg/errors" + errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.uber.org/zap" ) +// Repository definition. type Repository struct { db *mongo.Database logger *zap.Logger @@ -19,6 +24,7 @@ type Repository struct { } } +// NewRepository create a new Repository. func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { return &Repository{db: db, logger: logger.With(zap.String("module", "ObservationsRepository")), @@ -26,6 +32,8 @@ func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { } } +// Find get a list of ObservationDoc pointers. +// The input parameter [q *ObservationQuery] define the filters to apply in the query. func (r *Repository) Find(ctx context.Context, q *ObservationQuery) ([]*ObservationDoc, error) { if q == nil { q = Query() @@ -33,32 +41,43 @@ func (r *Repository) Find(ctx context.Context, q *ObservationQuery) ([]*Observat sort := bson.D{{q.SortBy, q.GetSortInt()}} cur, err := r.collections.observations.Find(ctx, q.toBSON(), options.Find().SetLimit(q.PageSize).SetSkip(q.Offset).SetSort(sort)) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Find command to get observations", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } var obs []*ObservationDoc err = cur.All(ctx, &obs) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*ObservationDoc", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return obs, err } -var ( - ErrWrongQuery = errors.New("MALFORMED_QUERY") -) - +// Find get ObservationDoc pointer. +// The input parameter [q *ObservationQuery] define the filters to apply in the query. func (r *Repository) FindOne(ctx context.Context, q *ObservationQuery) (*ObservationDoc, error) { if q == nil { - return nil, ErrWrongQuery + return nil, errs.ErrMalformedQuery } var obs ObservationDoc err := r.collections.observations.FindOne(ctx, q.toBSON()).Decode(&obs) if err != nil { - return nil, err + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errs.ErrNotFound + } + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute FindOne command to get observations", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return &obs, err } +// ObservationQuery respresent a query for the observation mongodb document. type ObservationQuery struct { pagination.Pagination chainId vaa.ChainID @@ -69,36 +88,43 @@ type ObservationQuery struct { uint64 } +// Query create a new ObservationQuery with default pagination vaues. func Query() *ObservationQuery { page := pagination.FirstPage() return &ObservationQuery{Pagination: *page} } +// SetEmitter set the chainId field of the ObservationQuery struct. func (q *ObservationQuery) SetChain(chainID vaa.ChainID) *ObservationQuery { q.chainId = chainID return q } +// SetEmitter set the emitter field of the ObservationQuery struct. func (q *ObservationQuery) SetEmitter(emitter string) *ObservationQuery { q.emitter = emitter return q } +// SetSequence set the sequence field of the ObservationQuery struct. func (q *ObservationQuery) SetSequence(seq uint64) *ObservationQuery { q.sequence = seq return q } +// SetGuardianAddr set the guardianAddr field of the ObservationQuery struct. func (q *ObservationQuery) SetGuardianAddr(guardianAddr string) *ObservationQuery { q.guardianAddr = guardianAddr return q } +// SetHash set the hash field of the ObservationQuery struct. func (q *ObservationQuery) SetHash(hash []byte) *ObservationQuery { q.hash = hash return q } +// SetPagination set the pagination field of the ObservationQuery struct. func (q *ObservationQuery) SetPagination(p *pagination.Pagination) *ObservationQuery { q.Pagination = *p return q diff --git a/api/observations/service.go b/api/handlers/observations/service.go similarity index 72% rename from api/observations/service.go rename to api/handlers/observations/service.go index d4e6b911..facc5b9e 100644 --- a/api/observations/service.go +++ b/api/handlers/observations/service.go @@ -1,40 +1,49 @@ +// Package observations handle the request of observations data from governor endpoint defined in the api. package observations import ( "context" + "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" "go.uber.org/zap" ) +// Service definition. type Service struct { repo *Repository logger *zap.Logger } +// NewService create a new Service. func NewService(dao *Repository, logger *zap.Logger) *Service { return &Service{repo: dao, logger: logger.With(zap.String("module", "ObservationsService"))} } +// FindAll get all the observations. func (s *Service) FindAll(ctx context.Context, p *pagination.Pagination) ([]*ObservationDoc, error) { return s.repo.Find(ctx, Query().SetPagination(p)) } +// FindByChain get all the observations by chainID. func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *pagination.Pagination) ([]*ObservationDoc, error) { query := Query().SetChain(chain).SetPagination(p) return s.repo.Find(ctx, query) } +// FindByEmitter get all the observations by chainID and emitter address. func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter *vaa.Address, p *pagination.Pagination) ([]*ObservationDoc, error) { query := Query().SetChain(chain).SetEmitter(emitter.String()).SetPagination(p) return s.repo.Find(ctx, query) } +// FindByVAA get all the observations for a VAA (chainID, emitter addrress and sequence number). func (s *Service) FindByVAA(ctx context.Context, chain vaa.ChainID, emitter *vaa.Address, seq uint64, p *pagination.Pagination) ([]*ObservationDoc, error) { query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq).SetPagination(p) return s.repo.Find(ctx, query) } +// FindOne get a observation by chainID, emitter address, sequence, signer address and hash. func (s *Service) FindOne(ctx context.Context, chainID vaa.ChainID, emitterAddr *vaa.Address, seq uint64, signerAddr *vaa.Address, hash []byte) (*ObservationDoc, error) { query := Query().SetChain(chainID).SetEmitter(emitterAddr.String()).SetSequence(seq).SetGuardianAddr(signerAddr.String()).SetHash(hash) return s.repo.FindOne(ctx, query) diff --git a/api/vaa/controller.go b/api/handlers/vaa/controller.go similarity index 64% rename from api/vaa/controller.go rename to api/handlers/vaa/controller.go index c8745fa0..0377cfb8 100644 --- a/api/vaa/controller.go +++ b/api/handlers/vaa/controller.go @@ -1,83 +1,86 @@ +// Package observations handle the request of VAA data from governor endpoint defined in the api. package vaa import ( "fmt" "github.com/gofiber/fiber/v2" - "github.com/wormhole-foundation/wormhole-explorer/api/errors" "github.com/wormhole-foundation/wormhole-explorer/api/middleware" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" "go.uber.org/zap" ) +// Controller definition. type Controller struct { srv *Service logger *zap.Logger } +// NewController create a new controler. func NewController(serv *Service, logger *zap.Logger) *Controller { return &Controller{srv: serv, logger: logger.With(zap.String("module", "VaaController"))} } +// FindAll handler for the endpoint /vaas/. func (c *Controller) FindAll(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) + p := middleware.GetPaginationFromContext(ctx) vaas, err := c.srv.FindAll(ctx.Context(), p) if err != nil { - fmt.Printf("error finding vaas: %v", err) + return err } return ctx.JSON(vaas) } +// FindByChain handler for the endpoint /vaas/:chain. func (c *Controller) FindByChain(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, err := middleware.ExtractChainID(ctx) + + fmt.Println(ctx.Locals("requestid")) + p := middleware.GetPaginationFromContext(ctx) + chainID, err := middleware.ExtractChainID(ctx, c.logger) if err != nil { return err } vaas, err := c.srv.FindByChain(ctx.Context(), chainID, p) if err != nil { - fmt.Printf("error finding vaas: %v", err) + return err } return ctx.JSON(vaas) } +// FindByEmitter handler for the endpoint /vaas/:chain/:emitter. func (c *Controller) FindByEmitter(ctx *fiber.Ctx) error { - p := pagination.GetFromContext(ctx) - chainID, emitter, _, err := middleware.ExtractVAAParams(ctx) - if errors.IsOf(err, middleware.ErrMalformedChain, middleware.ErrMalformedAddr) { + p := middleware.GetPaginationFromContext(ctx) + chainID, emitter, err := middleware.ExtractVAAChainIDEmitter(ctx, c.logger) + if err != nil { return err } vaas, err := c.srv.FindByEmitter(ctx.Context(), chainID, *emitter, p) if err != nil { - //TODO logging - fmt.Printf("error finding vaas: %v", err) + return err } return ctx.JSON(vaas) } +// FindById handler for the endpoint /vaas/:chain/:emitter/:sequence/:signer/:hash. func (c *Controller) FindById(ctx *fiber.Ctx) error { - chainID, emitter, seq, err := middleware.ExtractVAAParams(ctx) + chainID, emitter, seq, err := middleware.ExtractVAAParams(ctx, c.logger) if err != nil { return err } vaa, err := c.srv.FindById(ctx.Context(), chainID, *emitter, seq) if err != nil { - //TODO logging - fmt.Printf("error finding vaa: %v", err) + return err } return ctx.JSON(vaa) } // FindSignedVAAByID get a signedVAA []byte from a chainID, emitter address and sequence. func (c *Controller) FindSignedVAAByID(ctx *fiber.Ctx) error { - chainID, emitter, seq, err := middleware.ExtractVAAParams(ctx) + chainID, emitter, seq, err := middleware.ExtractVAAParams(ctx, c.logger) if err != nil { - // TODO: Handle InvalidArgument code (3) in the response. return err } vaa, err := c.srv.FindById(ctx.Context(), chainID, *emitter, seq) if err != nil { - // TODO: handle not found(5) and internal(13) response code. return err } response := struct { diff --git a/api/vaa/model.go b/api/handlers/vaa/model.go similarity index 95% rename from api/vaa/model.go rename to api/handlers/vaa/model.go index 8eecc6c6..4095435b 100644 --- a/api/vaa/model.go +++ b/api/handlers/vaa/model.go @@ -1,10 +1,12 @@ package vaa import ( - "github.com/certusone/wormhole/node/pkg/vaa" "time" + + "github.com/certusone/wormhole/node/pkg/vaa" ) +// VaaDoc represent an vaa document. type VaaDoc struct { ID string `bson:"_id" json:"id"` Version uint8 `bson:"version" json:"version"` diff --git a/api/vaa/repository.go b/api/handlers/vaa/repository.go similarity index 59% rename from api/vaa/repository.go rename to api/handlers/vaa/repository.go index 786842ac..531dd856 100644 --- a/api/vaa/repository.go +++ b/api/handlers/vaa/repository.go @@ -2,15 +2,19 @@ package vaa import ( "context" + "fmt" "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" + "github.com/pkg/errors" + errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.uber.org/zap" ) +// Repository definition type Repository struct { db *mongo.Database logger *zap.Logger @@ -20,6 +24,7 @@ type Repository struct { } } +// NewRepository create a new Repository. func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { return &Repository{db: db, logger: logger.With(zap.String("module", "VaaRepository")), @@ -29,6 +34,8 @@ func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository { }{vaas: db.Collection("vaas"), invalidVaas: db.Collection("invalid_vaas")}} } +// Find get a list of *VaaDoc. +// The input parameter [q *VaaQuery] define the filters to apply in the query. func (r *Repository) Find(ctx context.Context, q *VaaQuery) ([]*VaaDoc, error) { if q == nil { q = Query() @@ -36,22 +43,37 @@ func (r *Repository) Find(ctx context.Context, q *VaaQuery) ([]*VaaDoc, error) { sort := bson.D{{q.SortBy, q.GetSortInt()}} cur, err := r.collections.vaas.Find(ctx, q.toBSON(), options.Find().SetLimit(q.PageSize).SetSkip(q.Offset).SetSort(sort)) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Find command to get vaas", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } var vaas []*VaaDoc err = cur.All(ctx, &vaas) if err != nil { - return nil, err + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed decoding cursor to []*VaaDoc", zap.Error(err), zap.Any("q", q), + zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } return vaas, err } +// FindOne get *VaaDoc. +// The input parameter [q *VaaQuery] define the filters to apply in the query. func (r *Repository) FindOne(ctx context.Context, q *VaaQuery) (*VaaDoc, error) { var vaaDoc VaaDoc err := r.collections.vaas.FindOne(ctx, q.toBSON()).Decode(&vaaDoc) if err != nil { - return nil, err + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, errs.ErrNotFound + } + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute FindOne command to get vaas", + zap.Error(err), zap.Any("q", q), zap.String("requestID", requestID)) + return nil, errors.WithStack(err) } + return &vaaDoc, err } @@ -64,6 +86,9 @@ func (r *Repository) FindStats(ctx context.Context) ([]*VaaStats, error) { } c, err := r.collections.vaas.Aggregate(ctx, mongo.Pipeline{group}) if err != nil { + requestID := fmt.Sprintf("%v", ctx.Value("requestid")) + r.logger.Error("failed execute Aggregate command to get vaa stats", + zap.Error(err), zap.String("requestID", requestID)) return nil, err } var stats []*VaaStats @@ -71,6 +96,7 @@ func (r *Repository) FindStats(ctx context.Context) ([]*VaaStats, error) { return stats, err } +// VaaQuery respresent a query for the vaa mongodb document. type VaaQuery struct { pagination.Pagination chainId vaa.ChainID @@ -78,26 +104,31 @@ type VaaQuery struct { sequence uint64 } +// Query create a new VaaQuery with default pagination vaues. func Query() *VaaQuery { page := pagination.FirstPage() return &VaaQuery{Pagination: *page} } +// SetChain set the chainId field of the VaaQuery struct. func (q *VaaQuery) SetChain(chainID vaa.ChainID) *VaaQuery { q.chainId = chainID return q } +// SetEmitter set the emitter field of the VaaQuery struct. func (q *VaaQuery) SetEmitter(emitter string) *VaaQuery { q.emitter = emitter return q } +// SetSequence set the sequence field of the VaaQuery struct. func (q *VaaQuery) SetSequence(seq uint64) *VaaQuery { q.sequence = seq return q } +// SetPagination set the pagination field of the VaaQuery struct. func (q *VaaQuery) SetPagination(p *pagination.Pagination) *VaaQuery { q.Pagination = *p return q diff --git a/api/vaa/service.go b/api/handlers/vaa/service.go similarity index 54% rename from api/vaa/service.go rename to api/handlers/vaa/service.go index fefd29ae..52142b2f 100644 --- a/api/vaa/service.go +++ b/api/handlers/vaa/service.go @@ -4,54 +4,60 @@ import ( "context" "github.com/certusone/wormhole/node/pkg/vaa" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" - "github.com/wormhole-foundation/wormhole-explorer/api/services" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" + "github.com/wormhole-foundation/wormhole-explorer/api/response" "go.uber.org/zap" ) +// Service definition. type Service struct { repo *Repository logger *zap.Logger } +// NewService create a new Service. func NewService(r *Repository, logger *zap.Logger) *Service { return &Service{repo: r, logger: logger.With(zap.String("module", "VaaService"))} } -func (s *Service) FindAll(ctx context.Context, p *pagination.Pagination) (*services.Response[[]*VaaDoc], error) { +// FindAll get all the the vaa. +func (s *Service) FindAll(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) { if p == nil { p = pagination.FirstPage() } query := Query().SetPagination(p) vaas, err := s.repo.Find(ctx, query) - res := services.Response[[]*VaaDoc]{Data: vaas, Error: err} + res := response.Response[[]*VaaDoc]{Data: vaas} return &res, err } -func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *pagination.Pagination) (*services.Response[[]*VaaDoc], error) { +// FindByChain get all the vaa by chainID. +func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) { query := Query().SetChain(chain).SetPagination(p) vaas, err := s.repo.Find(ctx, query) - res := services.Response[[]*VaaDoc]{Data: vaas, Error: err} + res := response.Response[[]*VaaDoc]{Data: vaas} return &res, err } -func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, p *pagination.Pagination) (*services.Response[[]*VaaDoc], error) { +// FindByEmitter get all the vaa by chainID and emitter address. +func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) { query := Query().SetChain(chain).SetEmitter(emitter.String()).SetPagination(p) vaas, err := s.repo.Find(ctx, query) - res := services.Response[[]*VaaDoc]{Data: vaas, Error: err} + res := response.Response[[]*VaaDoc]{Data: vaas} return &res, err } -func (s *Service) FindById(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq uint64) (*services.Response[*VaaDoc], error) { +// FindById get a vaa by chainID, emitter address and sequence number. +func (s *Service) FindById(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq uint64) (*response.Response[*VaaDoc], error) { query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq) vaas, err := s.repo.FindOne(ctx, query) - res := services.Response[*VaaDoc]{Data: vaas, Error: err} + res := response.Response[*VaaDoc]{Data: vaas} return &res, err } -func (s *Service) GetVAAStats(ctx context.Context) (*services.Response[[]*VaaStats], error) { +func (s *Service) GetVAAStats(ctx context.Context) (*response.Response[[]*VaaStats], error) { stats, err := s.repo.FindStats(ctx) - res := services.Response[[]*VaaStats]{Data: stats, Error: err} + res := response.Response[[]*VaaStats]{Data: stats} return &res, err } diff --git a/api/config/config.go b/api/internal/config/config.go similarity index 70% rename from api/config/config.go rename to api/internal/config/config.go index 729262d5..0fa7cb9d 100644 --- a/api/config/config.go +++ b/api/internal/config/config.go @@ -1,13 +1,23 @@ +// Package config implement a simple configuration package. +// It define a type [AppConfig] that represent the aplication configuration and +// use viper [https://github.com/spf13/viper] to load the configuration. package config import ( "bytes" "encoding/json" + "strings" + ipfslog "github.com/ipfs/go-log/v2" "github.com/spf13/viper" - "strings" ) +const ( + RunModeProduction = "PRODUCTION" + RunModeDevelopmernt = "DEVELOPMENT" +) + +// AppConfig defines the configuration for the app. type AppConfig struct { DB struct { URL string @@ -15,11 +25,12 @@ type AppConfig struct { Name string } - PORT int - + PORT int LogLevel string + RunMode string } +// GetLogLevel get zapcore.Level define in the configuraion. func (cfg *AppConfig) GetLogLevel() (ipfslog.LogLevel, error) { return ipfslog.LevelFromString(cfg.LogLevel) } @@ -27,6 +38,8 @@ func (cfg *AppConfig) GetLogLevel() (ipfslog.LogLevel, error) { func init() { viper.SetDefault("port", 8000) viper.SetDefault("loglevel", "INFO") + viper.SetDefault("runmode", "PRODUCTION") + // Consider environment variables in unmarshall doesn't work unless doing this: https://github.com/spf13/viper/issues/188#issuecomment-1168898503 b, err := json.Marshal(AppConfig{}) if err != nil { @@ -50,6 +63,7 @@ func init() { viper.AutomaticEnv() } +// Get returns the app configuration. func Get() (*AppConfig, error) { var cfg AppConfig err := viper.Unmarshal(&cfg) diff --git a/api/db/db.go b/api/internal/db/db.go similarity index 71% rename from api/db/db.go rename to api/internal/db/db.go index 7426a3b2..ebd53167 100644 --- a/api/db/db.go +++ b/api/internal/db/db.go @@ -1,11 +1,14 @@ +// Package db handle mongodb connections. package db import ( "context" + "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) +// Connect create a new mongo db client for the options defined in the input param url. func Connect(ctx context.Context, url string) (*mongo.Client, error) { cli, err := mongo.NewClient(options.Client().ApplyURI(url)) if err != nil { diff --git a/api/internal/errors/error.go b/api/internal/errors/error.go new file mode 100644 index 00000000..823ca282 --- /dev/null +++ b/api/internal/errors/error.go @@ -0,0 +1,10 @@ +package errors + +import "errors" + +// Error definitions to use in service and repository layers. +var ( + ErrMalformedQuery = errors.New("MALFORMED_QUERY") + ErrNotFound = errors.New("NOT FOUND") + ErrInternalError = errors.New("INTERNAL ERROR") +) diff --git a/api/pagination/pagination.go b/api/internal/pagination/pagination.go similarity index 65% rename from api/pagination/pagination.go rename to api/internal/pagination/pagination.go index 19345355..02f57db4 100644 --- a/api/pagination/pagination.go +++ b/api/internal/pagination/pagination.go @@ -1,5 +1,6 @@ package pagination +// Pagination definition. type Pagination struct { Offset int64 PageSize int64 @@ -7,41 +8,49 @@ type Pagination struct { SortBy string } +// FirstPage return a *Pagination with default values offset and page size. func FirstPage() *Pagination { return &Pagination{Offset: 0, PageSize: 50} } +// BuildPagination create a new *Pagination. func BuildPagination(page, pageSize int64, sortOrder, sortBy string) *Pagination { p := Pagination{} p.SetPage(page).SetPageSize(pageSize).SetSortOrder(sortOrder).SetSortBy(sortBy) return &p } +// SetPageSize set the PageSize field of the Pagination struct. func (p *Pagination) SetPageSize(limit int64) *Pagination { p.PageSize = limit return p } +// SetOffset set the Offset field of the Pagination struct. func (p *Pagination) SetOffset(offset int64) *Pagination { p.Offset = offset return p } +// SetPage set the Page field of the Pagination struct. func (p *Pagination) SetPage(page int64) *Pagination { p.Offset = page * p.PageSize return p } +// SetSortOrder set the SortOrder field of the Pagination struct. func (p *Pagination) SetSortOrder(order string) *Pagination { p.SortOrder = order return p } +// SetSortBy set the SortBy field of the Pagination struct. func (p *Pagination) SetSortBy(by string) *Pagination { p.SortBy = by return p } +// GetSortInt mapping to mongodb sort values. func (p *Pagination) GetSortInt() int { if p.SortOrder == "ASC" { return 1 diff --git a/api/main.go b/api/main.go index 4238a303..35698c92 100644 --- a/api/main.go +++ b/api/main.go @@ -14,12 +14,13 @@ import ( "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/requestid" ipfslog "github.com/ipfs/go-log/v2" - "github.com/wormhole-foundation/wormhole-explorer/api/config" - "github.com/wormhole-foundation/wormhole-explorer/api/db" - "github.com/wormhole-foundation/wormhole-explorer/api/governor" + "github.com/wormhole-foundation/wormhole-explorer/api/handlers/governor" + "github.com/wormhole-foundation/wormhole-explorer/api/handlers/observations" + "github.com/wormhole-foundation/wormhole-explorer/api/handlers/vaa" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/config" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/db" "github.com/wormhole-foundation/wormhole-explorer/api/middleware" - "github.com/wormhole-foundation/wormhole-explorer/api/observations" - "github.com/wormhole-foundation/wormhole-explorer/api/vaa" + "github.com/wormhole-foundation/wormhole-explorer/api/response" ) var cacheConfig = cache.Config{ @@ -54,7 +55,7 @@ func main() { panic(err) } - rootLogger := ipfslog.Logger("wormhole-spy").Desugar() + rootLogger := ipfslog.Logger("wormhole-api").Desugar() ipfslog.SetAllLoggers(lvl) // Setup DB @@ -79,8 +80,9 @@ func main() { observationsCtrl := observations.NewController(obsService, rootLogger) governorCtrl := governor.NewController(governorService, rootLogger) - // Setup API - app := fiber.New() + // Setup app with custom error handling. + response.SetEnableStackTrace(*cfg) + app := fiber.New(fiber.Config{ErrorHandler: middleware.ErrorHandler}) // Middleware prometheus := fiberprometheus.New("wormscan") diff --git a/api/middleware/error_handler.go b/api/middleware/error_handler.go new file mode 100644 index 00000000..3e9128b3 --- /dev/null +++ b/api/middleware/error_handler.go @@ -0,0 +1,32 @@ +// package middleare contains all the middleware function to use in the API. +package middleware + +import ( + "github.com/pkg/errors" + errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors" + "github.com/wormhole-foundation/wormhole-explorer/api/response" + + "github.com/gofiber/fiber/v2" +) + +// ErrorHandler define a fiber custom error handler. This function process all errors +// returned from any handlers in the stack. +// +// To setup this function we must set the ErrorHandler field of the fiber.Config struct +// with this function and create a new fiber with this config. +// +// example: fiber.New(fiber.Config{ErrorHandler: errs.APIErrorHandler} +func ErrorHandler(ctx *fiber.Ctx, err error) error { + var apiError response.APIError + switch { + case errors.As(err, &apiError): + ctx.Status(apiError.StatusCode).JSON(apiError) + case errors.Is(err, errs.ErrNotFound): + apiError = response.NewNotFoundError(ctx) + ctx.Status(fiber.StatusNotFound).JSON(apiError) + default: + apiError = response.NewInternalError(ctx, err) + ctx.Status(fiber.StatusInternalServerError).JSON(apiError) + } + return nil +} diff --git a/api/middleware/extract_pagination.go b/api/middleware/extract_pagination.go index d8761534..fdcd5f39 100644 --- a/api/middleware/extract_pagination.go +++ b/api/middleware/extract_pagination.go @@ -1,15 +1,50 @@ +// package middleare contains all the middleware function to use in the API. package middleware import ( - "github.com/gofiber/fiber/v2" - "github.com/wormhole-foundation/wormhole-explorer/api/pagination" "net/http" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination" ) +// ExtractPagination middleware invoke pagination.ExtractPagination. func ExtractPagination(c *fiber.Ctx) error { if c.Method() != http.MethodGet { return c.Next() } - pagination.ExtractPagination(c) + extractPagination(c) return c.Next() } + +// extractPagination get pagination query params and build a *Pagination. +func extractPagination(ctx *fiber.Ctx) (*pagination.Pagination, error) { + pageNumberStr := ctx.Query("page", "0") + pageNumber, err := strconv.ParseInt(pageNumberStr, 10, 64) + if err != nil { + return nil, err + } + + pageSizeStr := ctx.Query("pageSize", "50") + pageSize, err := strconv.ParseInt(pageSizeStr, 10, 64) + if err != nil { + return nil, err + } + + sortOrder := ctx.Query("sortOrder", "DESC") + sortBy := ctx.Query("sortBy", "indexedAt") + + p := pagination.BuildPagination(pageNumber, pageSize, sortOrder, sortBy) + ctx.Locals("pagination", p) + return p, nil +} + +// GetPaginationFromContext get pagination from context. +func GetPaginationFromContext(ctx *fiber.Ctx) *pagination.Pagination { + p := ctx.Locals("pagination") + if p == nil { + return nil + } + return p.(*pagination.Pagination) +} diff --git a/api/middleware/extract_parameters.go b/api/middleware/extract_parameters.go index f10ac835..a0e21c77 100644 --- a/api/middleware/extract_parameters.go +++ b/api/middleware/extract_parameters.go @@ -1,67 +1,114 @@ +// package middleare contains all the middleware function to use in the API. package middleware import ( - "errors" + "fmt" "strconv" "github.com/certusone/wormhole/node/pkg/vaa" "github.com/gofiber/fiber/v2" + "github.com/pkg/errors" + "github.com/wormhole-foundation/wormhole-explorer/api/response" + "go.uber.org/zap" ) -var ( - ErrMalformedChain = errors.New("WRONG_CHAIN_ID") - ErrMalformedAddr = errors.New("MALFORMED_EMITTER_ADDR") - ErrMalformedSequence = errors.New("MALFORMED_SEQUENCE_NUMBER") - ErrMalFormedGuardianAddress = errors.New("MALFORMED_GUARDIAN_ADDR") -) - -func ExtractChainID(c *fiber.Ctx) (vaa.ChainID, error) { +// ExtractChainID get chain parameter from route path. +func ExtractChainID(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, error) { chain, err := c.ParamsInt("chain") if err != nil { - return vaa.ChainIDUnset, ErrMalformedChain + requestID := fmt.Sprintf("%v", c.Locals("requestid")) + l.Error("failed to get chain parameter", zap.Error(err), zap.Int("chain", chain), + zap.String("requestID", requestID)) + + return vaa.ChainIDUnset, response.NewInvalidParamError(c, "WRONG CHAIN ID", errors.WithStack(err)) } return vaa.ChainID(chain), nil } -func ExtractEmitterAddr(c *fiber.Ctx) (*vaa.Address, error) { +// ExtractEmitterAddr get emitter parameter from route path. +func ExtractEmitterAddr(c *fiber.Ctx, l *zap.Logger) (*vaa.Address, error) { emitterStr := c.Params("emitter") emitter, err := vaa.StringToAddress(emitterStr) if err != nil { - return nil, ErrMalformedAddr + requestID := fmt.Sprintf("%v", c.Locals("requestid")) + l.Error("failed to covert emitter to address", zap.Error(err), zap.String("emitterStr", emitterStr), + zap.String("requestID", requestID)) + return nil, response.NewInvalidParamError(c, "MALFORMED EMITTER_ADDR", errors.WithStack(err)) } return &emitter, nil } -func ExtractSequence(c *fiber.Ctx) (uint64, error) { +// ExtractSequence get sequence parameter from route path. +func ExtractSequence(c *fiber.Ctx, l *zap.Logger) (uint64, error) { sequence := c.Params("sequence") seq, err := strconv.ParseUint(sequence, 10, 64) if err != nil { - return 0, err + requestID := fmt.Sprintf("%v", c.Locals("requestid")) + l.Error("failed to get sequence parameter", zap.Error(err), zap.String("sequence", sequence), + zap.String("requestID", requestID)) + return 0, response.NewInvalidParamError(c, "MALFORMED SEQUENCE NUMBER", errors.WithStack(err)) } return seq, nil } -func ExtractGuardianAddress(c *fiber.Ctx) (string, error) { +// ExtractGuardianAddress get guardian address from route path. +func ExtractGuardianAddress(c *fiber.Ctx, l *zap.Logger) (string, error) { //TODO: check guardianAddress [vaa.StringToAddress(emitterStr)] guardianAddress := c.Params("guardian_address") if guardianAddress == "" { - return "", ErrMalFormedGuardianAddress + return "", response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", nil) } return guardianAddress, nil } -func ExtractVAAParams(c *fiber.Ctx) (vaa.ChainID, *vaa.Address, uint64, error) { - chainID, err := ExtractChainID(c) +// ExtractVAAParams get VAA chain, address from route path. +func ExtractVAAChainIDEmitter(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *vaa.Address, error) { + chainID, err := ExtractChainID(c, l) + if err != nil { + return vaa.ChainIDUnset, nil, err + } + address, err := ExtractEmitterAddr(c, l) + if err != nil { + return chainID, nil, err + } + return chainID, address, nil +} + +// ExtractVAAParams get VAAA chain, address and sequence from route path. +func ExtractVAAParams(c *fiber.Ctx, l *zap.Logger) (vaa.ChainID, *vaa.Address, uint64, error) { + chainID, err := ExtractChainID(c, l) if err != nil { return vaa.ChainIDUnset, nil, 0, err } - address, err := ExtractEmitterAddr(c) + address, err := ExtractEmitterAddr(c, l) if err != nil { return chainID, nil, 0, err } - seq, err := ExtractSequence(c) + seq, err := ExtractSequence(c, l) if err != nil { return chainID, address, 0, err } return chainID, address, seq, nil } + +// ExtractObservationSigner get signer from route path. +func ExtractObservationSigner(c *fiber.Ctx, l *zap.Logger) (*vaa.Address, error) { + signer := c.Params("signer") + signerAddr, err := vaa.StringToAddress(signer) + if err != nil { + requestID := fmt.Sprintf("%v", c.Locals("requestid")) + l.Error("failed to covert signer to address", zap.Error(err), zap.String("signer", signer), + zap.String("requestID", requestID)) + return nil, response.NewInvalidParamError(c, "MALFORMED SIGNER", errors.WithStack(err)) + } + return &signerAddr, nil +} + +// ExtractObservationHash get a hash from route path. +func ExtractObservationHash(c *fiber.Ctx, l *zap.Logger) (string, error) { + hash := c.Params("hash") + if hash == "" { + return "", response.NewInvalidParamError(c, "MALFORMED HASH", nil) + } + return hash, nil +} diff --git a/api/pagination/rest_pagination.go b/api/pagination/rest_pagination.go deleted file mode 100644 index 9d947eda..00000000 --- a/api/pagination/rest_pagination.go +++ /dev/null @@ -1,35 +0,0 @@ -package pagination - -import ( - "github.com/gofiber/fiber/v2" - "strconv" -) - -func ExtractPagination(ctx *fiber.Ctx) (*Pagination, error) { - pageNumberStr := ctx.Query("page", "0") - pageNumber, err := strconv.ParseInt(pageNumberStr, 10, 64) - if err != nil { - return nil, err - } - - pageSizeStr := ctx.Query("pageSize", "50") - pageSize, err := strconv.ParseInt(pageSizeStr, 10, 64) - if err != nil { - return nil, err - } - - sortOrder := ctx.Query("sortOrder", "DESC") - sortBy := ctx.Query("sortBy", "indexedAt") - - p := BuildPagination(pageNumber, pageSize, sortOrder, sortBy) - ctx.Locals("pagination", p) - return p, nil -} - -func GetFromContext(ctx *fiber.Ctx) *Pagination { - p := ctx.Locals("pagination") - if p == nil { - return nil - } - return p.(*Pagination) -} diff --git a/api/response/error.go b/api/response/error.go new file mode 100644 index 00000000..cc83e71d --- /dev/null +++ b/api/response/error.go @@ -0,0 +1,113 @@ +// The response package defines the success and error response type. +// It define a type [AppError] that represent the api error response. +// Its define a custom error handling for the api. +package response + +import ( + "fmt" + + "github.com/gofiber/fiber/v2" + "github.com/wormhole-foundation/wormhole-explorer/api/internal/config" +) + +// API error codes. These error code are the same used in guardian API. +const ( + InvalidParam = 3 + NotFound = 5 + Internal = 13 +) + +var enableStackTrace bool + +// SetEnableStackTrace enable/disable send the stacktrace field in the response. +func SetEnableStackTrace(cfg config.AppConfig) { + if cfg.RunMode == config.RunModeDevelopmernt { + enableStackTrace = true + return + } + enableStackTrace = false +} + +// APIError api error response. +// This structure is defined to be aligned with the way the guardian API handles the error response. +type APIError struct { + StatusCode int `json:"-"` + Code int `json:"code"` // support to guardian-api code. + Message string `json:"message"` + Details []ErrorDetail `json:"details"` +} + +// ErrorDetail definition. +// This structure contains the requestID and the stacktrace of the error. +type ErrorDetail struct { + RequestID string `json:"request_id"` + StackTrace string `json:"stack_trace,omitempty"` +} + +// Error interface implementation. +func (a APIError) Error() string { + return fmt.Sprintf("code: %d, message: %s, details: %v", a.Code, a.Message, a.Details) +} + +// NewApiError create a new api response. +func NewApiError(ctx *fiber.Ctx, statusCode, code int, message string, err error) APIError { + detail := ErrorDetail{ + RequestID: fmt.Sprintf("%v", ctx.Locals("requestid")), + } + if enableStackTrace && err != nil { + detail.StackTrace = fmt.Sprintf("%+v\n", err) + } + return APIError{ + StatusCode: fiber.StatusBadRequest, + Code: InvalidParam, + Message: message, + Details: []ErrorDetail{detail}, + } +} + +// NewInvalidParamError create a invalid param Error. +func NewInvalidParamError(ctx *fiber.Ctx, message string, err error) APIError { + if message == "" { + message = "INVALID PARAM" + } + detail := ErrorDetail{ + RequestID: fmt.Sprintf("%v", ctx.Locals("requestid")), + } + if enableStackTrace && err != nil { + detail.StackTrace = fmt.Sprintf("%+v\n", err) + } + return APIError{ + StatusCode: fiber.StatusBadRequest, + Code: InvalidParam, + Message: message, + Details: []ErrorDetail{detail}, + } +} + +// NewInternalError create a new APIError for Internal Errors. +func NewInternalError(ctx *fiber.Ctx, err error) APIError { + detail := ErrorDetail{ + RequestID: fmt.Sprintf("%v", ctx.Locals("requestid")), + } + if enableStackTrace && err != nil { + detail.StackTrace = fmt.Sprintf("%+v\n", err) + } + return APIError{ + StatusCode: fiber.StatusInternalServerError, + Code: Internal, + Message: "INTERNAL ERROR", + Details: []ErrorDetail{detail}, + } +} + +// NewNotFoundError create a new APIError for Not Found errors. +func NewNotFoundError(ctx *fiber.Ctx) APIError { + return APIError{ + StatusCode: fiber.StatusNotFound, + Code: NotFound, + Message: "NOT FOUND", + Details: []ErrorDetail{{ + RequestID: fmt.Sprintf("%v", ctx.Locals("requestid")), + }}, + } +} diff --git a/api/services/response.go b/api/response/success.go similarity index 53% rename from api/services/response.go rename to api/response/success.go index cc65864f..7c8aaed8 100644 --- a/api/services/response.go +++ b/api/response/success.go @@ -1,11 +1,13 @@ -package services +// The response package defines the success and error response type. +package response +// ResponsePagination definition. type ResponsePagination struct { Next string `json:"next"` } +// Response represent a success API response. type Response[T any] struct { Data T `json:"data"` - Error error `json:"error"` Pagination ResponsePagination `json:"pagination"` }