Compare commits

...

38 Commits

Author SHA1 Message Date
dependabot[bot] 8b116f8a0f
build(deps): bump github.com/redis/go-redis/v9 from 9.0.4 to 9.0.5 (#95)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.4 to 9.0.5.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.4...v9.0.5)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:35:49 +08:00
dependabot[bot] 888fb4ce11
build(deps): bump github.com/go-playground/validator/v10 (#96)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.13.0 to 10.14.1.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.13.0...v10.14.1)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:34:43 +08:00
dependabot[bot] 27b300db93
build(deps): bump github.com/nats-io/nats.go from 1.25.0 to 1.27.1 (#99)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.25.0 to 1.27.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.25.0...v1.27.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:52 +08:00
dependabot[bot] 788cf66fc7
build(deps): bump github.com/VictoriaMetrics/metrics (#93)
Bumps [github.com/VictoriaMetrics/metrics](https://github.com/VictoriaMetrics/metrics) from 1.23.1 to 1.24.0.
- [Commits](https://github.com/VictoriaMetrics/metrics/compare/v1.23.1...v1.24.0)

---
updated-dependencies:
- dependency-name: github.com/VictoriaMetrics/metrics
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:19 +08:00
dependabot[bot] d2cb0796c8
build(deps): bump github.com/jackc/tern/v2 from 2.1.0 to 2.1.1 (#98)
Bumps [github.com/jackc/tern/v2](https://github.com/jackc/tern) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/jackc/tern/releases)
- [Changelog](https://github.com/jackc/tern/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/jackc/tern/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: github.com/jackc/tern/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 14:33:09 +08:00
Mohamed Sohail 9e1d62a014
feat: add transfer authorization (#100)
* wip: add transfer auithorization

* feat: update transferAuthPayload

* switch to MAX_INT for value and use revoke flag
* tranfer handler: fix nonce rollback on EOA account

* feat: switch to session based session transfer auth

* Demurrage contracts require setting the approval value to 0 before updating the value after the initial limit is set
* This implementation auto-revokes every 15 min after arequest is created. Subsequent requests will fail untill the value is set back to 0

* feat: settable approve session timeout
2023-07-10 14:32:05 +08:00
Mohamed Sohail 98ff897049
fix: tune gas params (celoutils v1.4.0) 2023-06-08 12:08:58 +08:00
Mohamed Sohail 8dc0dcf12d
fix: use celoutils.SafeGasLimit in place
* Gas limit is now 1M for all custodial related txs
* Precise value to be determined after testing phase is over
2023-05-28 20:43:43 +08:00
dependabot[bot] f84b90f411
build(deps): bump github.com/go-playground/validator/v10 (#86)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.12.0 to 10.13.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.12.0...v10.13.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:40 +03:00
dependabot[bot] 83113c8411
build(deps): bump github.com/swaggo/swag from 1.8.12 to 1.16.1 (#83)
Bumps [github.com/swaggo/swag](https://github.com/swaggo/swag) from 1.8.12 to 1.16.1.
- [Release notes](https://github.com/swaggo/swag/releases)
- [Changelog](https://github.com/swaggo/swag/blob/master/.goreleaser.yml)
- [Commits](https://github.com/swaggo/swag/compare/v1.8.12...v1.16.1)

---
updated-dependencies:
- dependency-name: github.com/swaggo/swag
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:25 +03:00
dependabot[bot] 6ccc27685c
build(deps): bump github.com/jackc/tern/v2 from 2.0.1 to 2.1.0 (#82)
Bumps [github.com/jackc/tern/v2](https://github.com/jackc/tern) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/jackc/tern/releases)
- [Changelog](https://github.com/jackc/tern/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/jackc/tern/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: github.com/jackc/tern/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:23:10 +03:00
dependabot[bot] e821da13d4
build(deps): bump github.com/bsm/redislock from 0.9.2 to 0.9.3 (#87)
Bumps [github.com/bsm/redislock](https://github.com/bsm/redislock) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/bsm/redislock/releases)
- [Changelog](https://github.com/bsm/redislock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bsm/redislock/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: github.com/bsm/redislock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:22:56 +03:00
dependabot[bot] 2b42da206b
build(deps): bump github.com/redis/go-redis/v9 from 9.0.3 to 9.0.4 (#88)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.3 to 9.0.4.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.3...v9.0.4)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 15:22:38 +03:00
Mohamed Sohail b9d3c219c8
fix (braking change): gas refill params (#89)
NOTE: This needs the db to be nuked if you are running a test cluster

* updated gas refilling logic to reflect EthFaucet contract
* fully dependant on on-chain contract to refill and unlock gas
* minor fixes to nonce bootstrapper

Ideal Values:

INITIAL GIFT = 0.015
THRESHOLD    = 0.01
TIME = 12 * 60 * 60

# Sample for 30 txs
EXTREME LOW = 0.00675
LOW GAS USAGE = 0.00694605
RISING = 0.0135
HIGH = 0.027
2023-05-16 15:20:01 +03:00
Mohamed Sohail 8ef2311d8e
fix: export TxStatus struct 2023-04-14 08:05:05 +00:00
Mohamed Sohail e91f82c08a
docs: add godocs comments to all routes
* disable serving docs by default
2023-04-14 07:57:43 +00:00
dependabot[bot] 223656b2bc
build(deps): bump github.com/bsm/redislock from 0.9.1 to 0.9.2 (#80)
Bumps [github.com/bsm/redislock](https://github.com/bsm/redislock) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/bsm/redislock/releases)
- [Changelog](https://github.com/bsm/redislock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bsm/redislock/compare/v0.9.1...v0.9.2)

---
updated-dependencies:
- dependency-name: github.com/bsm/redislock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:19:56 +03:00
dependabot[bot] 0218831bd9
build(deps): bump github.com/knadh/koanf/v2 from 2.0.0 to 2.0.1 (#81)
Bumps [github.com/knadh/koanf/v2](https://github.com/knadh/koanf) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/knadh/koanf/releases)
- [Commits](https://github.com/knadh/koanf/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: github.com/knadh/koanf/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:48 +03:00
dependabot[bot] b2df62f0df
build(deps): bump golang.org/x/crypto from 0.7.0 to 0.8.0 (#79)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:30 +03:00
dependabot[bot] d9acaa9583
build(deps): bump github.com/redis/go-redis/v9 from 9.0.2 to 9.0.3 (#77)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.0.2 to 9.0.3.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.0.2...v9.0.3)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 15:18:19 +03:00
Mohamed Sohail d51e74883d
docs: add swagger auto generator
* to generate docs, run `make docs`
* endpoint /swagger/
2023-04-13 11:55:07 +00:00
Mohamed Sohail 0e5db7f06f
feat: add nonce bootstrapper
Useful for rebuilding the nonce cache automatically.

* uses otx_sign as the first source
* can fallback to chain nonce value of the 1st source is corrupted
2023-04-13 10:38:23 +00:00
Mohamed Sohail b137088d38
sub: update handlers for latest CHAIN.* events
* Minor fixes and improvements
2023-04-11 10:18:06 +00:00
Mohamed Sohail 21a17d2735
feat: add SLA package which defines a global ctx timeout 2023-04-11 10:15:33 +00:00
Mohamed Sohail eba329eefa
refactor (store): consolidate all pg store related actions
* All postgres related functions now live in internal/store.
* Updated queries.sql file to match struct order (readibility)
* Moved keystore -> store
* Moved queries -> store
* Removed pkg/postgres
2023-04-11 10:14:49 +00:00
Mohamed Sohail 82294b96f8
refactor: pgx supports big.Int type conversion internally 2023-04-06 06:31:07 +00:00
Mohamed Sohail e67a42ede3
fix: config.toml and dev docker-compose 2023-04-06 06:12:37 +00:00
Mohamed Sohail a98fe958a3
refactor: remove unecessary publisher component
* clients should now rely on the CHAIn.* subjects
2023-04-06 06:08:38 +00:00
Mohamed Sohail e203c49049
major refactor: use proxy contract and gas faucet (see notes)
* remove uncessary tasks and task handlers
* reafctor custodial container
* refactor gas refiller. Gas refiller can queue at a later time to match cooldown
* refactor sub handler to process chain events
2023-03-29 16:10:58 +00:00
Mohamed Sohail 448b142f7c
refactor: nonce store interface
* A nonce value can  now be bootstrapped externally from any source
* Removed unecessary SetNewAccountNonce method
2023-03-29 07:37:59 +00:00
Mohamed Sohail 750fdd9c1f
fix: global lock middleware func signature 2023-03-28 06:55:51 +00:00
Mohamed Sohail 5f5678e25b
refactor: use echo ctx only for cancellation/propagation 2023-03-28 06:39:44 +00:00
Mohamed Sohail ef5bd1860f
deps(go-playground-validator): use inbuilt eth_addr_checksum validator on routes 2023-03-28 06:30:29 +00:00
Mohamed Sohail ac5567bc56
deps: update 2023-03-28 06:27:15 +00:00
Mohamed Sohail 2d5e41eb81
chore: cleanup unecessary checksum fn 2023-03-28 06:27:15 +00:00
dependabot[bot] fc49f2387d
build(deps): bump github.com/go-playground/validator/v10 (#72)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.2 to 10.12.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.11.2...v10.12.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 09:26:35 +03:00
dependabot[bot] 81a3bb7e3c
build(deps): bump github.com/nats-io/nats.go from 1.24.0 to 1.25.0 (#73)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 09:14:10 +03:00
Mohamed Sohail 0235f25f81
ci: fix outputs 2023-03-16 12:11:03 +00:00
57 changed files with 2282 additions and 1612 deletions

View File

@ -39,7 +39,7 @@ jobs:
- name: Set outputs
run: |
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build and push image
uses: docker/build-push-action@v2

View File

@ -2,7 +2,7 @@ BIN := cic-custodial
BUILD_CONF := CGO_ENABLED=1 GOOS=linux GOARCH=amd64
BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null)
.PHONY: build
.PHONY: build run run-debug docs
clean:
rm ${BIN}
@ -10,6 +10,10 @@ clean:
build:
${BUILD_CONF} go build -ldflags="-X main.build=${BUILD_COMMIT} -s -w" -o ${BIN} cmd/service/*
docs:
swag fmt --dir internal/api/
swag init --dir internal/api/ -g swagger.go
run:
${BUILD_CONF} go run cmd/service/*

View File

@ -2,25 +2,25 @@ package main
import (
"net/http"
"time"
"github.com/VictoriaMetrics/metrics"
"github.com/go-playground/validator/v10"
_ "github.com/grassrootseconomics/cic-custodial/docs"
"github.com/grassrootseconomics/cic-custodial/internal/api"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
echoSwagger "github.com/swaggo/echo-swagger"
)
const (
contextTimeout = 5 * time.Second
systemGlobalLockKey = "system:global_lock"
)
// Bootstrap API server.
func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
customValidator := validator.New()
customValidator.RegisterValidation("eth_checksum", api.EthChecksumValidator)
server := echo.New()
server.HideBanner = true
@ -30,15 +30,9 @@ func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
}
server.HTTPErrorHandler = customHTTPErrorHandler
server.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set("cu", custodialContainer)
return next(c)
}
})
server.Use(middleware.Recover())
server.Use(middleware.BodyLimit("1M"))
server.Use(middleware.ContextTimeout(contextTimeout))
server.Use(middleware.ContextTimeout(util.SLATimeout))
if ko.Bool("service.metrics") {
server.GET("/metrics", func(c echo.Context) error {
@ -47,12 +41,17 @@ func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
})
}
apiRoute := server.Group("/api", systemGlobalLock)
if ko.Bool("service.docs") {
server.GET("/docs/*", echoSwagger.WrapHandler)
}
apiRoute.POST("/account/create", api.HandleAccountCreate)
apiRoute.GET("/account/status/:address", api.HandleNetworkAccountStatus)
apiRoute.POST("/sign/transfer", api.HandleSignTransfer)
apiRoute.GET("/track/:trackingId", api.HandleTrackTx)
apiRoute := server.Group("/api", systemGlobalLock(custodialContainer))
apiRoute.POST("/account/create", api.HandleAccountCreate(custodialContainer))
apiRoute.GET("/account/status/:address", api.HandleNetworkAccountStatus(custodialContainer))
apiRoute.POST("/sign/transfer", api.HandleSignTransfer(custodialContainer))
apiRoute.POST("/sign/transferAuth", api.HandleSignTranserAuthorization(custodialContainer))
apiRoute.GET("/track/:trackingId", api.HandleTrackTx(custodialContainer))
return server
}
@ -85,24 +84,22 @@ func customHTTPErrorHandler(err error, c echo.Context) {
})
}
func systemGlobalLock(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
)
func systemGlobalLock(cu *custodial.Custodial) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
locked, err := cu.RedisClient.Get(c.Request().Context(), systemGlobalLockKey).Bool()
if err != nil {
return err
}
locked, err := cu.RedisClient.Get(c.Request().Context(), systemGlobalLockKey).Bool()
if err != nil {
return err
if locked {
return c.JSON(http.StatusServiceUnavailable, api.ErrResp{
Ok: false,
Message: "System manually locked.",
})
}
return next(c)
}
if locked {
return c.JSON(http.StatusServiceUnavailable, api.ErrResp{
Ok: false,
Message: "System manually locked.",
})
}
return next(c)
}
}

View File

@ -1,73 +0,0 @@
package main
import (
"context"
"math/big"
"time"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/redis/go-redis/v9"
)
// Define common smart contrcat ABI's that can be injected into the system container.
// Any relevant function signature that will be used by the custodial system can be defined here.
func initAbis() map[string]*w3.Func {
return map[string]*w3.Func{
// Keccak hash -> 0x449a52f8
"mintTo": w3.MustNewFunc("mintTo(address, uint256)", "bool"),
// Keccak hash -> 0xa9059cbb
"transfer": w3.MustNewFunc("transfer(address,uint256)", "bool"),
// Keccak hash -> 0x23b872dd
"transferFrom": w3.MustNewFunc("transferFrom(address, address, uint256)", "bool"),
// Add to account index
"add": w3.MustNewFunc("add(address)", "bool"),
// giveTo gas refill
"giveTo": w3.MustNewFunc("giveTo(address)", "uint256"),
}
}
// Bootstrap the internal custodial system configs and system signer key.
// This container is passed down to individual tasker and API handlers.
func initSystemContainer(ctx context.Context, noncestore nonce.Noncestore) *custodial.SystemContainer {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Some custodial system defaults loaded from the config file.
systemContainer := &custodial.SystemContainer{
Abis: initAbis(),
AccountIndexContract: w3.A(ko.MustString("system.account_index_address")),
GasFaucetContract: w3.A(ko.MustString("system.gas_faucet_address")),
GasRefillThreshold: big.NewInt(ko.MustInt64("system.gas_refill_threshold")),
GasRefillValue: big.NewInt(ko.MustInt64("system.gas_refill_value")),
GiftableGasValue: big.NewInt(ko.MustInt64("system.giftable_gas_value")),
GiftableToken: w3.A(ko.MustString("system.giftable_token_address")),
GiftableTokenValue: big.NewInt(ko.MustInt64("system.giftable_token_value")),
LockTimeout: 1 * time.Second,
PublicKey: ko.MustString("system.public_key"),
TokenDecimals: ko.MustInt("system.token_decimals"),
TokenTransferGasLimit: uint64(ko.MustInt64("system.token_transfer_gas_limit")),
}
// Check if system signer account nonce is present.
// If not (first boot), we bootstrap it from the network.
currentSystemNonce, err := noncestore.Peek(ctx, ko.MustString("system.public_key"))
lo.Info("custodial: loaded system nonce from noncestore", "nonce", currentSystemNonce)
if err == redis.Nil {
nonce, err := noncestore.SyncNetworkNonce(ctx, ko.MustString("system.public_key"))
lo.Info("custodial: syncing system nonce from network", "nonce", nonce)
if err != nil {
lo.Fatal("custodial: critical error bootstrapping system container", "error", err)
}
}
loadedPrivateKey, err := eth_crypto.HexToECDSA(ko.MustString("system.private_key"))
if err != nil {
lo.Fatal("custodial: critical error bootstrapping system container", "error", err)
}
systemContainer.PrivateKey = loadedPrivateKey
return systemContainer
}

View File

@ -3,23 +3,16 @@ package main
import (
"context"
"strings"
"time"
"github.com/bsm/redislock"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/sub"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/logg"
"github.com/grassrootseconomics/cic-custodial/pkg/postgres"
"github.com/grassrootseconomics/cic-custodial/pkg/redis"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/knadh/goyesql/v2"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
@ -86,21 +79,6 @@ func initCeloProvider() *celoutils.Provider {
return provider
}
// Load postgres pool.
func initPostgresPool() *pgxpool.Pool {
poolOpts := postgres.PostgresPoolOpts{
DSN: ko.MustString("postgres.dsn"),
MigrationsFolderPath: migrationsFolderFlag,
}
pool, err := postgres.NewPostgresPool(context.Background(), poolOpts)
if err != nil {
lo.Fatal("init: critical error connecting to postgres", "error", err)
}
return pool
}
// Load separate redis connection for the tasker on a reserved db namespace.
func initAsynqRedisPool() *redis.RedisPool {
poolOpts := redis.RedisPoolOpts{
@ -131,36 +109,12 @@ func initCommonRedisPool() *redis.RedisPool {
return pool
}
// Load SQL statements into struct.
func initQueries() *queries.Queries {
parsedQueries, err := goyesql.ParseFile(queriesFlag)
if err != nil {
lo.Fatal("init: critical error loading SQL queries", "error", err)
}
loadedQueries, err := queries.LoadQueries(parsedQueries)
if err != nil {
lo.Fatal("init: critical error loading SQL queries", "error", err)
}
return loadedQueries
}
// Load postgres based keystore.
func initPostgresKeystore(postgresPool *pgxpool.Pool, queries *queries.Queries) keystore.Keystore {
keystore := keystore.NewPostgresKeytore(keystore.Opts{
PostgresPool: postgresPool,
Queries: queries,
})
return keystore
}
// Load redis backed noncestore.
func initRedisNoncestore(redisPool *redis.RedisPool, celoProvider *celoutils.Provider) nonce.Noncestore {
func initRedisNoncestore(redisPool *redis.RedisPool, chainProvider *celoutils.Provider, store store.Store) nonce.Noncestore {
return nonce.NewRedisNoncestore(nonce.Opts{
RedisPool: redisPool,
CeloProvider: celoProvider,
ChainProvider: chainProvider,
RedisPool: redisPool,
Store: store,
})
}
@ -177,11 +131,17 @@ func initTaskerClient(redisPool *redis.RedisPool) *tasker.TaskerClient {
}
// Load Postgres store.
func initPostgresStore(postgresPool *pgxpool.Pool, queries *queries.Queries) store.Store {
return store.NewPostgresStore(store.Opts{
PostgresPool: postgresPool,
Queries: queries,
func initPgStore() store.Store {
store, err := store.NewPgStore(store.Opts{
DSN: ko.MustString("postgres.dsn"),
MigrationsFolderPath: migrationsFolderFlag,
QueriesFolderPath: queriesFlag,
})
if err != nil {
lo.Fatal("init: critical error loading Postgres store", "error", err)
}
return store
}
// Init JetStream context for both pub/sub.
@ -200,19 +160,6 @@ func initJetStream() (*nats.Conn, nats.JetStreamContext) {
return natsConn, js
}
func initPub(jsCtx nats.JetStreamContext) *pub.Pub {
pub, err := pub.NewPub(pub.PubOpts{
DedupDuration: time.Duration(ko.MustInt("jetstream.dedup_duration_hrs")) * time.Hour,
JsCtx: jsCtx,
PersistDuration: time.Duration(ko.MustInt("jetstream.persist_duration_hrs")) * time.Hour,
})
if err != nil {
lo.Fatal("init: critical error bootstrapping pub", "error", err)
}
return pub
}
func initSub(natsConn *nats.Conn, jsCtx nats.JetStreamContext, cu *custodial.Custodial) *sub.Sub {
sub, err := sub.NewSub(sub.SubOpts{
CustodialContainer: cu,

View File

@ -14,13 +14,11 @@ import (
"github.com/zerodha/logf"
)
type (
internalServiceContainer struct {
apiService *echo.Echo
jetstreamSub *sub.Sub
taskerService *tasker.TaskerServer
}
)
type internalServicesContainer struct {
apiService *echo.Echo
jetstreamSub *sub.Sub
taskerService *tasker.TaskerServer
}
var (
build string
@ -48,35 +46,35 @@ func init() {
func main() {
lo.Info("main: starting cic-custodial", "build", build)
parsedQueries := initQueries()
celoProvider := initCeloProvider()
postgresPool := initPostgresPool()
asynqRedisPool := initAsynqRedisPool()
redisPool := initCommonRedisPool()
postgresKeystore := initPostgresKeystore(postgresPool, parsedQueries)
pgStore := initPostgresStore(postgresPool, parsedQueries)
redisNoncestore := initRedisNoncestore(redisPool, celoProvider)
store := initPgStore()
redisNoncestore := initRedisNoncestore(redisPool, celoProvider, store)
lockProvider := initLockProvider(redisPool.Client)
taskerClient := initTaskerClient(asynqRedisPool)
systemContainer := initSystemContainer(context.Background(), redisNoncestore)
natsConn, jsCtx := initJetStream()
jsPub := initPub(jsCtx)
custodial := &custodial.Custodial{
CeloProvider: celoProvider,
Keystore: postgresKeystore,
LockProvider: lockProvider,
Noncestore: redisNoncestore,
PgStore: pgStore,
Pub: jsPub,
RedisClient: redisPool.Client,
SystemContainer: systemContainer,
TaskerClient: taskerClient,
custodial, err := custodial.NewCustodial(custodial.Opts{
ApprovalTimeout: ko.MustDuration("system.approve_timeout"),
CeloProvider: celoProvider,
LockProvider: lockProvider,
Logg: lo,
Noncestore: redisNoncestore,
Store: store,
RedisClient: redisPool.Client,
RegistryAddress: ko.MustString("chain.registry_address"),
SystemPrivateKey: ko.MustString("system.private_key"),
SystemPublicKey: ko.MustString("system.public_key"),
TaskerClient: taskerClient,
})
if err != nil {
lo.Fatal("main: crtical error loading custodial container", "error", err)
}
internalServices := &internalServiceContainer{}
internalServices := &internalServicesContainer{}
wg := &sync.WaitGroup{}
signalCh, closeCh := createSigChannel()

View File

@ -34,13 +34,10 @@ func initTasker(custodialContainer *custodial.Custodial, redisPool *redis.RedisP
observibilityMiddleware(),
})
taskerServer.RegisterHandlers(tasker.AccountPrepareTask, task.AccountPrepare(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountRegisterTask, task.AccountRegisterOnChainProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountGiftGasTask, task.AccountGiftGasProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountGiftVoucherTask, task.GiftVoucherProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountActivateTask, task.AccountActivateProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.AccountRefillGasTask, task.AccountRefillGasProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.SignTransferTask, task.SignTransfer(custodialContainer))
taskerServer.RegisterHandlers(tasker.SignTransferTaskAuth, task.SignTransferAuthorizationProcessor(custodialContainer))
taskerServer.RegisterHandlers(tasker.DispatchTxTask, task.DispatchTx(custodialContainer))
return taskerServer

View File

@ -17,7 +17,7 @@ func createSigChannel() (chan os.Signal, func()) {
}
}
func startGracefulShutdown(ctx context.Context, internalServices *internalServiceContainer) {
func startGracefulShutdown(ctx context.Context, internalServices *internalServicesContainer) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

View File

@ -3,32 +3,17 @@ address = ":5000"
# Exposes Go process Prometheus metrics
# /metrics endpoint
metrics = true
docs = false
[chain]
rpc_endpoint = ""
testnet = true
devnet = false
rpc_endpoint = ""
testnet = true
registry_address = ""
[system]
# System default values
# Gas values are in wei with 18 d.p. precision unless otherwise stated
# Token values are in wei with 6 d.p. precision unless otherwise stated
# All addresses MUST be checksumed
account_index_address = ""
gas_faucet_address = ""
gas_refill_threshold = 2500000000000000
gas_refill_value = 15000000000000000
giftable_gas_value = 15000000000000000
giftable_token_address = ""
giftable_token_value = 5000000
# System private key
# Should always be toped up
private_key = ""
public_key = ""
token_decimals = 6
token_transfer_gas_limit = 200000
public_key = ""
approve_timeout = "30m"
[postgres]
dsn = ""
@ -36,17 +21,15 @@ dsn = ""
[redis]
# Used for locks and the Noncestore
# Ideally use DB 1
dsn = ""
dsn = ""
min_idle_conn = 5
[asynq]
# Exclusively used by the asynq tasker
# Ideally use DB 0
dsn = ""
dsn = ""
task_retention_hrs = 24
worker_count = 15
worker_count = 15
[jetstream]
endpoint = ""
persist_duration_hrs = 48
dedup_duration_hrs = 6
endpoint = ""

312
docs/docs.go Normal file
View File

@ -0,0 +1,312 @@
// Code generated by swaggo/swag. DO NOT EDIT.
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "https://grassecon.org/pages/terms-and-conditions.html",
"contact": {
"name": "API Support",
"url": "https://grassecon.org/pages/contact-us",
"email": "devops@grassecon.org"
},
"license": {
"name": "AGPL-3.0",
"url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/account/create": {
"post": {
"description": "Create a new custodial account.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Create a new custodial account.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/account/status/{address}": {
"get": {
"description": "Return network balance and nonce.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Get an address's network balance and nonce.",
"parameters": [
{
"type": "string",
"description": "Account Public Key",
"name": "address",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transfer": {
"post": {
"description": "Sign and dispatch a transfer request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch transfer request.",
"parameters": [
{
"description": "Sign Transfer Request",
"name": "signTransferRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transferAuth": {
"post": {
"description": "Sign and dispatch a transfer authorization (approve) request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch a transfer authorization (approve) request.",
"parameters": [
{
"description": "Sign Transfer Authorization (approve) Request",
"name": "signTransferAuthorzationRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"authorizedAddress": {
"type": "string"
},
"authorizer": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/track/{trackingId}": {
"get": {
"description": "Track an OTX (Origin transaction) status.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"track"
],
"summary": "Track an OTX (Origin transaction) status.",
"parameters": [
{
"type": "string",
"description": "Tracking Id",
"name": "trackingId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
}
},
"definitions": {
"api.ErrResp": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"ok": {
"type": "boolean"
}
}
},
"api.H": {
"type": "object",
"additionalProperties": {}
},
"api.OkResp": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"result": {
"$ref": "#/definitions/api.H"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/api",
Schemes: []string{},
Title: "CIC Custodial API",
Description: "Interact with CIC Custodial API",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

286
docs/swagger.json Normal file
View File

@ -0,0 +1,286 @@
{
"swagger": "2.0",
"info": {
"description": "Interact with CIC Custodial API",
"title": "CIC Custodial API",
"termsOfService": "https://grassecon.org/pages/terms-and-conditions.html",
"contact": {
"name": "API Support",
"url": "https://grassecon.org/pages/contact-us",
"email": "devops@grassecon.org"
},
"license": {
"name": "AGPL-3.0",
"url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
},
"version": "1.0"
},
"basePath": "/api",
"paths": {
"/account/create": {
"post": {
"description": "Create a new custodial account.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Create a new custodial account.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/account/status/{address}": {
"get": {
"description": "Return network balance and nonce.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Get an address's network balance and nonce.",
"parameters": [
{
"type": "string",
"description": "Account Public Key",
"name": "address",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transfer": {
"post": {
"description": "Sign and dispatch a transfer request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch transfer request.",
"parameters": [
{
"description": "Sign Transfer Request",
"name": "signTransferRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/sign/transferAuth": {
"post": {
"description": "Sign and dispatch a transfer authorization (approve) request.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"network"
],
"summary": "Sign and dispatch a transfer authorization (approve) request.",
"parameters": [
{
"description": "Sign Transfer Authorization (approve) Request",
"name": "signTransferAuthorzationRequest",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
},
"authorizedAddress": {
"type": "string"
},
"authorizer": {
"type": "string"
},
"voucherAddress": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
},
"/track/{trackingId}": {
"get": {
"description": "Track an OTX (Origin transaction) status.",
"consumes": [
"*/*"
],
"produces": [
"application/json"
],
"tags": [
"track"
],
"summary": "Track an OTX (Origin transaction) status.",
"parameters": [
{
"type": "string",
"description": "Tracking Id",
"name": "trackingId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.OkResp"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/api.ErrResp"
}
}
}
}
}
},
"definitions": {
"api.ErrResp": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"ok": {
"type": "boolean"
}
}
},
"api.H": {
"type": "object",
"additionalProperties": {}
},
"api.OkResp": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"result": {
"$ref": "#/definitions/api.H"
}
}
}
}
}

188
docs/swagger.yaml Normal file
View File

@ -0,0 +1,188 @@
basePath: /api
definitions:
api.ErrResp:
properties:
message:
type: string
ok:
type: boolean
type: object
api.H:
additionalProperties: {}
type: object
api.OkResp:
properties:
ok:
type: boolean
result:
$ref: '#/definitions/api.H'
type: object
info:
contact:
email: devops@grassecon.org
name: API Support
url: https://grassecon.org/pages/contact-us
description: Interact with CIC Custodial API
license:
name: AGPL-3.0
url: https://www.gnu.org/licenses/agpl-3.0.en.html
termsOfService: https://grassecon.org/pages/terms-and-conditions.html
title: CIC Custodial API
version: "1.0"
paths:
/account/create:
post:
consumes:
- '*/*'
description: Create a new custodial account.
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Create a new custodial account.
tags:
- account
/account/status/{address}:
get:
consumes:
- '*/*'
description: Return network balance and nonce.
parameters:
- description: Account Public Key
in: path
name: address
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Get an address's network balance and nonce.
tags:
- network
/sign/transfer:
post:
consumes:
- application/json
description: Sign and dispatch a transfer request.
parameters:
- description: Sign Transfer Request
in: body
name: signTransferRequest
required: true
schema:
properties:
amount:
type: integer
from:
type: string
to:
type: string
voucherAddress:
type: string
type: object
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Sign and dispatch transfer request.
tags:
- network
/sign/transferAuth:
post:
consumes:
- application/json
description: Sign and dispatch a transfer authorization (approve) request.
parameters:
- description: Sign Transfer Authorization (approve) Request
in: body
name: signTransferAuthorzationRequest
required: true
schema:
properties:
amount:
type: integer
authorizedAddress:
type: string
authorizer:
type: string
voucherAddress:
type: string
type: object
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Sign and dispatch a transfer authorization (approve) request.
tags:
- network
/track/{trackingId}:
get:
consumes:
- '*/*'
description: Track an OTX (Origin transaction) status.
parameters:
- description: Tracking Id
in: path
name: trackingId
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.OkResp'
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.ErrResp'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrResp'
summary: Track an OTX (Origin transaction) status.
tags:
- track
swagger: "2.0"

46
go.mod
View File

@ -3,36 +3,40 @@ module github.com/grassrootseconomics/cic-custodial
go 1.20
require (
github.com/VictoriaMetrics/metrics v1.23.1
github.com/bsm/redislock v0.9.1
github.com/VictoriaMetrics/metrics v1.24.0
github.com/bsm/redislock v0.9.3
github.com/celo-org/celo-blockchain v1.7.2
github.com/georgysavva/scany/v2 v2.0.0
github.com/go-playground/validator/v10 v10.11.2
github.com/go-playground/validator/v10 v10.14.1
github.com/google/uuid v1.3.0
github.com/grassrootseconomics/celoutils v1.1.1
github.com/grassrootseconomics/celoutils v1.4.0
github.com/grassrootseconomics/w3-celo-patch v0.2.0
github.com/hibiken/asynq v0.24.0
github.com/jackc/pgx/v5 v5.3.1
github.com/jackc/tern/v2 v2.0.1
github.com/jackc/pgx/v5 v5.4.0
github.com/jackc/tern/v2 v2.1.1
github.com/knadh/goyesql/v2 v2.2.0
github.com/knadh/koanf/parsers/toml v0.1.0
github.com/knadh/koanf/providers/env v0.1.0
github.com/knadh/koanf/providers/file v0.1.0
github.com/knadh/koanf/v2 v2.0.0
github.com/knadh/koanf/v2 v2.0.1
github.com/labstack/echo/v4 v4.10.2
github.com/nats-io/nats.go v1.24.0
github.com/redis/go-redis/v9 v9.0.2
github.com/nats-io/nats.go v1.27.1
github.com/redis/go-redis/v9 v9.0.5
github.com/swaggo/echo-swagger v1.4.0
github.com/swaggo/swag v1.16.1
github.com/zerodha/logf v0.5.5
golang.org/x/crypto v0.7.0
)
replace github.com/hibiken/asynq => github.com/grassrootseconomics/asynq v0.25.0
require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/VictoriaMetrics/fastcache v1.12.0 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/celo-org/celo-bls-go v0.6.4 // indirect
@ -46,7 +50,12 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-stack/stack v1.8.1 // indirect
@ -66,9 +75,12 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
@ -76,7 +88,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/nats-io/nats-server/v2 v2.9.14 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
@ -89,6 +101,7 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
@ -97,12 +110,15 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/histogram v1.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

109
go.sum
View File

@ -37,6 +37,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
@ -44,12 +46,16 @@ github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0=
github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc=
github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw=
github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -71,12 +77,12 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
github.com/bsm/redislock v0.9.1 h1:uTTZU82xg2PjI8X5T9PGcX/5k1FX3Id7bqkwy1As6c0=
github.com/bsm/redislock v0.9.1/go.mod h1:ToFoB1xQbOJYG7e2ZBiPXotlhImqWgEa4+u/lLQ1nSc=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/redislock v0.9.3 h1:osmvugkXGiLDEhzUPdM0EUtKpTEgLLuli4Ky2Z4vx38=
github.com/bsm/redislock v0.9.3/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
@ -128,6 +134,7 @@ github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB0
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -158,6 +165,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
@ -177,15 +186,23 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@ -260,8 +277,8 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/grassrootseconomics/asynq v0.25.0 h1:2zSz5YwNLu/oCTm/xfNixn86i9aw4zth9Dl0dc2kFEs=
github.com/grassrootseconomics/asynq v0.25.0/go.mod h1:pe2XOdK1eIbTgTmRFHIYl75lvVuTPJxZq2T9Ocz/+2s=
github.com/grassrootseconomics/celoutils v1.1.1 h1:REsndvfBkPN8UKOoQFNEGm/sCwKtTm+woYtgMl3bfZ0=
github.com/grassrootseconomics/celoutils v1.1.1/go.mod h1:Uo5YRy6AGLAHDZj9jaOI+AWoQ1H3L0v79728pPMkm9Q=
github.com/grassrootseconomics/celoutils v1.4.0 h1:AJNKiOpfnQqZ3kRxeUlhWH/zlDDjhtbs/OzAMb5zU4A=
github.com/grassrootseconomics/celoutils v1.4.0/go.mod h1:Uo5YRy6AGLAHDZj9jaOI+AWoQ1H3L0v79728pPMkm9Q=
github.com/grassrootseconomics/w3-celo-patch v0.2.0 h1:YqibbPzX0tQKmxU1nUGzThPKk/fiYeYZY6Aif3eyu8U=
github.com/grassrootseconomics/w3-celo-patch v0.2.0/go.mod h1:WhBXNzNIvHmS6B2hAeShs56oa9Azb4jQSrOMKuMdBWw=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
@ -305,12 +322,12 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.4.0 h1:BSr+GCm4N6QcgIwv0DyTFHK9ugfEFF9DzSbbzxOiXU0=
github.com/jackc/pgx/v5 v5.4.0/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/tern/v2 v2.0.1 h1:2J05jlmYFsNQe9rGsqoNs0+6eDm3iJns8RQb6DJl1V8=
github.com/jackc/tern/v2 v2.0.1/go.mod h1:4cpqN/grjWYeRWcKXah5YGoviJKJuoqNLoORKLumoG0=
github.com/jackc/tern/v2 v2.1.1 h1:qDo41wTtDHrTgkN7lhcoMQ6oiAWqiD8xKgslxyoKHNQ=
github.com/jackc/tern/v2 v2.1.1/go.mod h1:xnRalAguscgir18eW/wscn/QTEoWwFqrpW+5S+CREWM=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
@ -319,6 +336,8 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -333,7 +352,8 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -347,8 +367,8 @@ github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clw
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
github.com/knadh/koanf/v2 v2.0.0 h1:XPQ5ilNnwnNaHrfQ1YpTVhUAjcGHnEKA+lRpipQv02Y=
github.com/knadh/koanf/v2 v2.0.0/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -357,6 +377,7 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
@ -365,13 +386,16 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -418,12 +442,13 @@ github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
github.com/nats-io/nats-server/v2 v2.9.14 h1:n2GscWVgXpA14vQSRP/MM1SGi4wyazR9l19/gWxqgXQ=
github.com/nats-io/nats-server/v2 v2.9.14/go.mod h1:40ZwFm4npKdFBhOdY7rkh3YyI1oI91FzLvlYyB7HfzM=
github.com/nats-io/nats.go v1.24.0 h1:CRiD8L5GOQu/DcfkmgBcTTIQORMwizF+rPk6T0RaHVQ=
github.com/nats-io/nats.go v1.24.0/go.mod h1:dVQF+BK3SzUZpwyzHedXsvH3EO38aVKuOPkkHlv5hXA=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nats.go v1.27.1 h1:OuYnal9aKVSnOzLQIzf7554OXMCG7KbaTkCSBHRcSoo=
github.com/nats-io/nats.go v1.27.1/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -473,8 +498,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
github.com/redis/go-redis/v9 v9.0.1/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
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=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
@ -520,8 +545,15 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/swaggo/echo-swagger v1.4.0 h1:RCxLKySw1SceHLqnmc41pKyiIeE+OiD7NSI7FUOBlLo=
github.com/swaggo/echo-swagger v1.4.0/go.mod h1:Wh3VlwjZGZf/LH0s81tz916JokuPG7y/ZqaqnckYqoQ=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
@ -570,12 +602,11 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -605,6 +636,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -627,11 +659,12 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -685,6 +718,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -699,8 +733,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -714,8 +748,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -751,6 +785,8 @@ golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -806,6 +842,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -821,8 +858,10 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -12,52 +12,56 @@ import (
"github.com/labstack/echo/v4"
)
// CreateAccountHandler route.
// POST: /api/account/create
// Returns the public key.
func HandleAccountCreate(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
)
// HandleAccountCreate godoc
// @Summary Create a new custodial account.
// @Description Create a new custodial account.
// @Tags account
// @Accept */*
// @Produce json
// @Success 200 {object} OkResp
// @Failure 500 {object} ErrResp
// @Router /account/create [post]
func HandleAccountCreate(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
generatedKeyPair, err := keypair.Generate()
if err != nil {
return err
}
generatedKeyPair, err := keypair.Generate()
if err != nil {
return err
id, err := cu.Store.WriteKeyPair(c.Request().Context(), generatedKeyPair)
if err != nil {
return err
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.AccountPayload{
PublicKey: generatedKeyPair.Public,
TrackingId: trackingId,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.AccountRegisterTask,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"publicKey": generatedKeyPair.Public,
"custodialId": id,
"trackingId": trackingId,
},
})
}
id, err := cu.Keystore.WriteKeyPair(c.Request().Context(), generatedKeyPair)
if err != nil {
return err
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.AccountPayload{
PublicKey: generatedKeyPair.Public,
TrackingId: trackingId,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.AccountPrepareTask,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"publicKey": generatedKeyPair.Public,
"custodialId": id,
"trackingId": trackingId,
},
})
}

View File

@ -5,47 +5,60 @@ import (
"math/big"
"net/http"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/labstack/echo/v4"
)
func HandleNetworkAccountStatus(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
accountStatusRequest struct {
Address string `param:"address" validate:"required,eth_checksum"`
// HandleNetworkAccountStatus godoc
// @Summary Get an address's network balance and nonce.
// @Description Return network balance and nonce.
// @Tags network
// @Accept */*
// @Produce json
// @Param address path string true "Account Public Key"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /account/status/{address} [get]
func HandleNetworkAccountStatus(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
accountStatusRequest struct {
Address string `param:"address" validate:"required,eth_addr_checksum"`
}
networkBalance big.Int
networkNonce uint64
)
if err := c.Bind(&accountStatusRequest); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
networkBalance big.Int
networkNonce uint64
)
if err := c.Bind(&accountStatusRequest); err != nil {
return NewBadRequestError(ErrInvalidJSON)
if err := c.Validate(accountStatusRequest); err != nil {
return err
}
if err := cu.CeloProvider.Client.CallCtx(
c.Request().Context(),
eth.Nonce(celoutils.HexToAddress(accountStatusRequest.Address), nil).Returns(&networkNonce),
eth.Balance(celoutils.HexToAddress(accountStatusRequest.Address), nil).Returns(&networkBalance),
); err != nil {
return err
}
if networkNonce > 0 {
networkNonce--
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"balance": fmt.Sprintf("%s CELO", w3.FromWei(&networkBalance, 18)),
"nonce": networkNonce,
},
})
}
if err := c.Validate(accountStatusRequest); err != nil {
return err
}
if err := cu.CeloProvider.Client.CallCtx(
c.Request().Context(),
eth.Nonce(w3.A(accountStatusRequest.Address), nil).Returns(&networkNonce),
eth.Balance(w3.A(accountStatusRequest.Address), nil).Returns(&networkBalance),
); err != nil {
return err
}
if networkNonce > 0 {
networkNonce--
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"balance": fmt.Sprintf("%s CELO", w3.FromWei(&networkBalance, 18)),
"nonce": networkNonce,
},
})
}

View File

@ -1,98 +0,0 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// HandleSignTransfer route.
// POST: /api/sign/transfer
// JSON Body:
// from -> ETH address
// to -> ETH address
// voucherAddress -> ETH address
// amount -> int (6 d.p. precision)
// e.g. 1000000 = 1 VOUCHER
// Returns the task id.
func HandleSignTransfer(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
req struct {
From string `json:"from" validate:"required,eth_checksum"`
To string `json:"to" validate:"required,eth_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_checksum"`
Amount uint64 `json:"amount" validate:"required"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(req); err != nil {
return err
}
accountActive, gasQuota, err := cu.PgStore.GetAccountStatusByAddress(c.Request().Context(), req.From)
if err != nil {
return err
}
if !accountActive {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Account pending activation. Try again later.",
})
}
if gasQuota < 1 {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Out of gas, refill pending. Try again later.",
})
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferPayload{
TrackingId: trackingId,
From: req.From,
To: req.To,
VoucherAddress: req.VoucherAddress,
Amount: req.Amount,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTask,
tasker.HighPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
err = cu.PgStore.DecrGasQuota(c.Request().Context(), req.From)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}

View File

@ -0,0 +1,98 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// HandleSignTransfer godoc
//
// @Summary Sign and dispatch transfer request.
// @Description Sign and dispatch a transfer request.
// @Tags network
// @Accept json
// @Produce json
// @Param signTransferRequest body object{from=string,to=string,voucherAddress=string,amount=uint64} true "Sign Transfer Request"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /sign/transfer [post]
func HandleSignTransfer(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
req struct {
From string `json:"from" validate:"required,eth_addr_checksum"`
To string `json:"to" validate:"required,eth_addr_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_addr_checksum"`
Amount uint64 `json:"amount" validate:"gt=0"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(req); err != nil {
return err
}
accountActive, gasLock, err := cu.Store.GetAccountStatus(c.Request().Context(), req.From)
if err != nil {
return err
}
if !accountActive {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Account pending activation. Try again later.",
})
}
if gasLock {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Gas lock. Gas balance unavailable. Try again later.",
})
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferPayload{
TrackingId: trackingId,
From: req.From,
To: req.To,
VoucherAddress: req.VoucherAddress,
Amount: req.Amount,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTask,
tasker.HighPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}
}

View File

@ -0,0 +1,107 @@
package api
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/task"
"github.com/labstack/echo/v4"
)
// Max 10k vouchers per approval session
const approvalSafetyLimit = 10000 * 1000000
// HandleSignTransferAuthorization godoc
//
// @Summary Sign and dispatch a transfer authorization (approve) request.
// @Description Sign and dispatch a transfer authorization (approve) request.
// @Tags network
// @Accept json
// @Produce json
// @Param signTransferAuthorzationRequest body object{amount=uint64,authorizer=string,authorizedAddress=string,voucherAddress=string} true "Sign Transfer Authorization (approve) Request"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /sign/transferAuth [post]
func HandleSignTranserAuthorization(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
req struct {
Amount uint64 `json:"amount" validate:"gte=0"`
Authorizer string `json:"authorizer" validate:"required,eth_addr_checksum"`
AuthorizedAddress string `json:"authorizedAddress" validate:"required,eth_addr_checksum"`
VoucherAddress string `json:"voucherAddress" validate:"required,eth_addr_checksum"`
}
)
if err := c.Bind(&req); err != nil {
return NewBadRequestError(ErrInvalidJSON)
}
if err := c.Validate(req); err != nil {
return err
}
accountActive, gasLock, err := cu.Store.GetAccountStatus(c.Request().Context(), req.Authorizer)
if err != nil {
return err
}
if req.Amount > approvalSafetyLimit {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Approval amount per session exceeds 10k.",
})
}
if !accountActive {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Account pending activation. Try again later.",
})
}
if gasLock {
return c.JSON(http.StatusForbidden, ErrResp{
Ok: false,
Message: "Gas lock. Gas balance unavailable. Try again later.",
})
}
trackingId := uuid.NewString()
taskPayload, err := json.Marshal(task.TransferAuthPayload{
TrackingId: trackingId,
Amount: req.Amount,
Authorizer: req.Authorizer,
AuthorizedAddress: req.AuthorizedAddress,
VoucherAddress: req.VoucherAddress,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
c.Request().Context(),
tasker.SignTransferTaskAuth,
tasker.DefaultPriority,
&tasker.Task{
Id: trackingId,
Payload: taskPayload,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"trackingId": trackingId,
},
})
}
}

15
internal/api/swagger.go Normal file
View File

@ -0,0 +1,15 @@
package api
// @title CIC Custodial API
// @version 1.0
// @description Interact with CIC Custodial API
// @termsOfService https://grassecon.org/pages/terms-and-conditions.html
// @contact.name API Support
// @contact.url https://grassecon.org/pages/contact-us
// @contact.email devops@grassecon.org
// @license.name AGPL-3.0
// @license.url https://www.gnu.org/licenses/agpl-3.0.en.html
// @BasePath /api

View File

@ -7,36 +7,43 @@ import (
"github.com/labstack/echo/v4"
)
// HandleTxStatus route.
// GET: /api/track/:trackingId
// Route param:
// trackingId -> tracking UUID
// Returns array of tx status.
func HandleTrackTx(c echo.Context) error {
var (
cu = c.Get("cu").(*custodial.Custodial)
txStatusRequest struct {
TrackingId string `param:"trackingId" validate:"required,uuid"`
// HandleTrackTx godoc
// @Summary Track an OTX (Origin transaction) status.
// @Description Track an OTX (Origin transaction) status.
// @Tags track
// @Accept */*
// @Produce json
// @Param trackingId path string true "Tracking Id"
// @Success 200 {object} OkResp
// @Failure 400 {object} ErrResp
// @Failure 500 {object} ErrResp
// @Router /track/{trackingId} [get]
func HandleTrackTx(cu *custodial.Custodial) func(echo.Context) error {
return func(c echo.Context) error {
var (
txStatusRequest struct {
TrackingId string `param:"trackingId" validate:"required,uuid"`
}
)
if err := c.Bind(&txStatusRequest); err != nil {
return NewBadRequestError(err)
}
)
if err := c.Bind(&txStatusRequest); err != nil {
return NewBadRequestError(err)
if err := c.Validate(txStatusRequest); err != nil {
return err
}
txs, err := cu.Store.GetTxStatus(c.Request().Context(), txStatusRequest.TrackingId)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"transaction": txs,
},
})
}
if err := c.Validate(txStatusRequest); err != nil {
return err
}
txs, err := cu.PgStore.GetTxStatusByTrackingId(c.Request().Context(), txStatusRequest.TrackingId)
if err != nil {
return err
}
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"transactions": txs,
},
})
}

View File

@ -1,7 +1,6 @@
package api
import (
"github.com/celo-org/celo-blockchain/common"
"github.com/go-playground/validator/v10"
)
@ -17,12 +16,3 @@ func (v *Validator) Validate(i interface{}) error {
}
return nil
}
func EthChecksumValidator(fl validator.FieldLevel) bool {
addr, err := common.NewMixedcaseAddressFromString(fl.Field().String())
if err != nil {
return false
}
return addr.ValidChecksum()
}

View File

@ -0,0 +1,28 @@
package custodial
import "github.com/grassrootseconomics/w3-celo-patch"
const (
Approve = "approve"
Check = "check"
GiveTo = "giveTo"
MintTo = "mintTo"
NextTime = "nextTime"
Register = "register"
Transfer = "transfer"
TransferFrom = "transferFrom"
)
// Define common smart contrcat ABI's that can be injected into the system container.
// Any relevant function signature that will be used by the custodial system can be defined here.
func initAbis() map[string]*w3.Func {
return map[string]*w3.Func{
Approve: w3.MustNewFunc("approve(address, uint256)", "bool"),
Check: w3.MustNewFunc("check(address)", "bool"),
GiveTo: w3.MustNewFunc("giveTo(address)", "uint256"),
MintTo: w3.MustNewFunc("mintTo(address, uint256)", "bool"),
NextTime: w3.MustNewFunc("nextTime(address)", "uint256"),
Register: w3.MustNewFunc("register(address)", ""),
Transfer: w3.MustNewFunc("transfer(address,uint256)", "bool"),
}
}

View File

@ -1,47 +1,89 @@
package custodial
import (
"context"
"crypto/ecdsa"
"math/big"
"time"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/nonce"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/redis/go-redis/v9"
"github.com/zerodha/logf"
)
type (
SystemContainer struct {
Abis map[string]*w3.Func
AccountIndexContract common.Address
GasFaucetContract common.Address
GasRefillThreshold *big.Int
GasRefillValue *big.Int
GiftableGasValue *big.Int
GiftableToken common.Address
GiftableTokenValue *big.Int
LockTimeout time.Duration
PrivateKey *ecdsa.PrivateKey
PublicKey string
TokenDecimals int
TokenTransferGasLimit uint64
Opts struct {
ApprovalTimeout time.Duration
CeloProvider *celoutils.Provider
LockProvider *redislock.Client
Logg logf.Logger
Noncestore nonce.Noncestore
Store store.Store
RedisClient *redis.Client
RegistryAddress string
SystemPrivateKey string
SystemPublicKey string
TaskerClient *tasker.TaskerClient
}
Custodial struct {
CeloProvider *celoutils.Provider
Keystore keystore.Keystore
LockProvider *redislock.Client
Noncestore nonce.Noncestore
PgStore store.Store
Pub *pub.Pub
RedisClient *redis.Client
SystemContainer *SystemContainer
TaskerClient *tasker.TaskerClient
ApprovalTimeout time.Duration
Abis map[string]*w3.Func
CeloProvider *celoutils.Provider
LockProvider *redislock.Client
Logg logf.Logger
Noncestore nonce.Noncestore
Store store.Store
RedisClient *redis.Client
RegistryMap map[string]common.Address
SystemPrivateKey *ecdsa.PrivateKey
SystemPublicKey string
TaskerClient *tasker.TaskerClient
}
)
func NewCustodial(o Opts) (*Custodial, error) {
ctx, cancel := context.WithTimeout(context.Background(), util.SLATimeout)
defer cancel()
registryMap, err := o.CeloProvider.RegistryMap(ctx, celoutils.HexToAddress(o.RegistryAddress))
if err != nil {
o.Logg.Error("custodial: critical error loading contracts from registry: %v", err)
return nil, err
}
systemNonce, err := o.Noncestore.Peek(ctx, o.SystemPublicKey)
if err != nil {
return nil, err
}
o.Logg.Info("custodial: loaded_nonce", "system_nonce", systemNonce)
privateKey, err := eth_crypto.HexToECDSA(o.SystemPrivateKey)
if err != nil {
return nil, err
}
return &Custodial{
ApprovalTimeout: o.ApprovalTimeout,
Abis: initAbis(),
CeloProvider: o.CeloProvider,
LockProvider: o.LockProvider,
Logg: o.Logg,
Noncestore: o.Noncestore,
Store: o.Store,
RedisClient: o.RedisClient,
RegistryMap: registryMap,
SystemPrivateKey: privateKey,
SystemPublicKey: o.SystemPublicKey,
TaskerClient: o.TaskerClient,
}, nil
}

View File

@ -1,14 +0,0 @@
package keystore
import (
"context"
"crypto/ecdsa"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
)
// Keystore defines how keypairs should be stored and accessed from a storage backend.
type Keystore interface {
WriteKeyPair(context.Context, keypair.Key) (uint, error)
LoadPrivateKey(context.Context, string) (*ecdsa.PrivateKey, error)
}

View File

@ -1,61 +0,0 @@
package keystore
import (
"context"
"crypto/ecdsa"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
"github.com/jackc/pgx/v5/pgxpool"
)
type (
Opts struct {
PostgresPool *pgxpool.Pool
Queries *queries.Queries
}
PostgresKeystore struct {
db *pgxpool.Pool
queries *queries.Queries
}
)
func NewPostgresKeytore(o Opts) Keystore {
return &PostgresKeystore{
db: o.PostgresPool,
queries: o.Queries,
}
}
// WriteKeyPair inserts a keypair into the db and returns the linked id.
func (ks *PostgresKeystore) WriteKeyPair(ctx context.Context, keypair keypair.Key) (uint, error) {
var (
id uint
)
if err := ks.db.QueryRow(ctx, ks.queries.WriteKeyPair, keypair.Public, keypair.Private).Scan(&id); err != nil {
return id, err
}
return id, nil
}
// LoadPrivateKey loads a private key as a crypto primitive for direct use. An id is used to search for the private key.
func (ks *PostgresKeystore) LoadPrivateKey(ctx context.Context, publicKey string) (*ecdsa.PrivateKey, error) {
var (
privateKeyString string
)
if err := ks.db.QueryRow(ctx, ks.queries.LoadKeyPair, publicKey).Scan(&privateKeyString); err != nil {
return nil, err
}
privateKey, err := eth_crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}
return privateKey, nil
}

View File

@ -7,6 +7,5 @@ type Noncestore interface {
Peek(context.Context, string) (uint64, error)
Acquire(context.Context, string) (uint64, error)
Return(context.Context, string) error
SyncNetworkNonce(context.Context, string) (uint64, error)
SetNewAccountNonce(context.Context, string) error
SetAccountNonce(context.Context, string, uint64) error
}

View File

@ -2,35 +2,52 @@ package nonce
import (
"context"
"errors"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/store"
redispool "github.com/grassrootseconomics/cic-custodial/pkg/redis"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/jackc/pgx/v5"
"github.com/redis/go-redis/v9"
)
type Opts struct {
RedisPool *redispool.RedisPool
CeloProvider *celoutils.Provider
}
type (
Opts struct {
ChainProvider *celoutils.Provider
RedisPool *redispool.RedisPool
Store store.Store
}
// RedisNoncestore implements `Noncestore`
type RedisNoncestore struct {
chainProvider *celoutils.Provider
redis *redispool.RedisPool
}
// RedisNoncestore implements `Noncestore`
RedisNoncestore struct {
chainProvider *celoutils.Provider
redis *redispool.RedisPool
store store.Store
}
)
func NewRedisNoncestore(o Opts) Noncestore {
return &RedisNoncestore{
chainProvider: o.ChainProvider,
redis: o.RedisPool,
chainProvider: o.CeloProvider,
store: o.Store,
}
}
func (n *RedisNoncestore) Peek(ctx context.Context, publicKey string) (uint64, error) {
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
if err != nil {
return 0, err
if err == redis.Nil {
nonce, err = n.bootstrap(ctx, publicKey)
if err != nil {
return 0, err
}
return nonce, nil
} else {
return 0, err
}
}
return nonce, nil
@ -43,7 +60,14 @@ func (n *RedisNoncestore) Acquire(ctx context.Context, publicKey string) (uint64
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
if err != nil {
return 0, nil
if err == redis.Nil {
nonce, err = n.bootstrap(ctx, publicKey)
if err != nil {
return 0, err
}
} else {
return 0, err
}
}
err = n.redis.Client.Incr(ctx, publicKey).Err()
@ -70,32 +94,37 @@ func (n *RedisNoncestore) Return(ctx context.Context, publicKey string) error {
return nil
}
func (n *RedisNoncestore) SyncNetworkNonce(ctx context.Context, publicKey string) (uint64, error) {
var (
networkNonce uint64
)
err := n.chainProvider.Client.CallCtx(
ctx,
eth.Nonce(w3.A(publicKey), nil).Returns(&networkNonce),
)
if err != nil {
return 0, err
}
err = n.redis.Client.Set(ctx, publicKey, networkNonce, 0).Err()
if err != nil {
return 0, err
}
return networkNonce, nil
}
func (n *RedisNoncestore) SetNewAccountNonce(ctx context.Context, publicKey string) error {
err := n.redis.Client.Set(ctx, publicKey, 0, 0).Err()
if err != nil {
func (n *RedisNoncestore) SetAccountNonce(ctx context.Context, publicKey string, nonce uint64) error {
if err := n.redis.Client.Set(ctx, publicKey, nonce, 0).Err(); err != nil {
return err
}
return nil
}
// bootstrap can be used to restore a destroyed redis nonce cache automatically.
// It first uses the otx_sign table as a source of nonce values.
// If the otx_sign table is corrupted, it can fallback to the network nonce.
// Ideally, the redis nonce cache should never be lost.
func (n *RedisNoncestore) bootstrap(ctx context.Context, publicKey string) (uint64, error) {
lastDbNonce, err := n.store.GetNextNonce(ctx, publicKey)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
err := n.chainProvider.Client.CallCtx(
ctx,
eth.Nonce(celoutils.HexToAddress(publicKey), nil).Returns(&lastDbNonce),
)
if err != nil {
return 0, err
}
} else {
return 0, err
}
}
if err := n.SetAccountNonce(ctx, publicKey, lastDbNonce); err != nil {
return 0, err
}
return lastDbNonce, nil
}

View File

@ -1,73 +0,0 @@
package pub
import (
"encoding/json"
"time"
"github.com/nats-io/nats.go"
)
const (
streamName string = "CUSTODIAL"
streamSubjects string = "CUSTODIAL.*"
AccountNewNonce string = "CUSTODIAL.accountNewNonce"
AccountRegister string = "CUSTODIAL.accountRegister"
AccountGiftGas string = "CUSTODIAL.systemNewAccountGas"
AccountGiftVoucher string = "CUSTODIAL.systemNewAccountVoucher"
AccountRefillGas string = "CUSTODIAL.systemRefillAccountGas"
DispatchFail string = "CUSTODIAL.dispatchFail"
DispatchSuccess string = "CUSTODIAL.dispatchSuccess"
SignTransfer string = "CUSTODIAL.signTransfer"
)
type (
PubOpts struct {
DedupDuration time.Duration
JsCtx nats.JetStreamContext
PersistDuration time.Duration
}
Pub struct {
jsCtx nats.JetStreamContext
}
EventPayload struct {
OtxId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}
)
func NewPub(o PubOpts) (*Pub, error) {
stream, _ := o.JsCtx.StreamInfo(streamName)
if stream == nil {
_, err := o.JsCtx.AddStream(&nats.StreamConfig{
Name: streamName,
MaxAge: o.PersistDuration,
Storage: nats.FileStorage,
Subjects: []string{streamSubjects},
Duplicates: o.DedupDuration,
})
if err != nil {
return nil, err
}
}
return &Pub{
jsCtx: o.JsCtx,
}, nil
}
func (p *Pub) Publish(subject string, dedupId string, eventPayload interface{}) error {
jsonData, err := json.Marshal(eventPayload)
if err != nil {
return err
}
_, err = p.jsCtx.Publish(subject, jsonData, nats.MsgId(dedupId))
if err != nil {
return err
}
return nil
}

View File

@ -1,33 +0,0 @@
package queries
import (
"fmt"
"github.com/knadh/goyesql/v2"
)
type Queries struct {
// Keystore
WriteKeyPair string `query:"write-key-pair"`
LoadKeyPair string `query:"load-key-pair"`
// Store
CreateOTX string `query:"create-otx"`
CreateDispatchStatus string `query:"create-dispatch-status"`
ActivateAccount string `query:"activate-account"`
UpdateChainStatus string `query:"update-chain-status"`
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
GetAccountActivationQuorum string `query:"get-account-activation-quorum"`
GetAccountStatus string `query:"get-account-status-by-address"`
DecrGasQuota string `query:"decr-gas-quota"`
ResetGasQuota string `query:"reset-gas-quota"`
}
func LoadQueries(q goyesql.Queries) (*Queries, error) {
loadedQueries := &Queries{}
if err := goyesql.ScanToStruct(loadedQueries, q, nil); err != nil {
return nil, fmt.Errorf("failed to scan queries %v", err)
}
return loadedQueries, nil
}

View File

@ -4,56 +4,10 @@ import (
"context"
)
func (s *PostgresStore) GetAccountStatusByAddress(ctx context.Context, publicAddress string) (bool, int, error) {
var (
accountActive bool
gasQuota int
)
if err := s.db.QueryRow(ctx, s.queries.GetAccountStatus, publicAddress).Scan(&accountActive, &gasQuota); err != nil {
return false, 0, err
}
return accountActive, gasQuota, nil
}
func (s *PostgresStore) GetAccountActivationQuorum(ctx context.Context, trackingId string) (int, error) {
var (
quorum int
)
if err := s.db.QueryRow(ctx, s.queries.GetAccountActivationQuorum, trackingId).Scan(&quorum); err != nil {
return 0, err
}
return quorum, nil
}
func (s *PostgresStore) DecrGasQuota(ctx context.Context, publicAddress string) error {
if _, err := s.db.Exec(
ctx,
s.queries.DecrGasQuota,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PostgresStore) ResetGasQuota(ctx context.Context, publicAddress string) error {
if _, err := s.db.Exec(
ctx,
s.queries.ResetGasQuota,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PostgresStore) ActivateAccount(ctx context.Context, publicAddress string) error {
func (s *PgStore) ActivateAccount(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.ActivateAccount,
@ -64,3 +18,56 @@ func (s *PostgresStore) ActivateAccount(ctx context.Context, publicAddress strin
return nil
}
func (s *PgStore) GetAccountStatus(
ctx context.Context,
publicAddress string,
) (bool, bool, error) {
var (
accountActive bool
gasLock bool
)
if err := s.db.QueryRow(
ctx,
s.queries.GetAccountStatus,
publicAddress,
).Scan(
&accountActive,
&gasLock,
); err != nil {
return false, false, err
}
return accountActive, gasLock, nil
}
func (s *PgStore) GasLock(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.GasLock,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PgStore) GasUnlock(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.GasUnlock,
publicAddress,
); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,53 @@
package store
import (
"context"
"crypto/ecdsa"
eth_crypto "github.com/celo-org/celo-blockchain/crypto"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
)
func (s *PgStore) WriteKeyPair(
ctx context.Context,
keypair keypair.Key,
) (uint, error) {
var (
id uint
)
if err := s.db.QueryRow(
ctx,
s.queries.WriteKeyPair,
keypair.Public,
keypair.Private,
).Scan(&id); err != nil {
return id, err
}
return id, nil
}
func (s *PgStore) LoadPrivateKey(
ctx context.Context,
publicKey string,
) (*ecdsa.PrivateKey, error) {
var (
privateKeyString string
)
if err := s.db.QueryRow(
ctx,
s.queries.LoadKeyPair,
publicKey,
).Scan(&privateKeyString); err != nil {
return nil, err
}
privateKey, err := eth_crypto.HexToECDSA(privateKeyString)
if err != nil {
return nil, err
}
return privateKey, nil
}

View File

@ -2,21 +2,39 @@ package store
import (
"context"
"math/big"
"time"
"github.com/georgysavva/scany/v2/pgxscan"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
)
type TxStatus struct {
Type string `db:"type" json:"txType"`
TxHash string `db:"tx_hash" json:"txHash"`
TransferValue uint64 `db:"transfer_value" json:"transferValue"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
Status string `db:"status" json:"status"`
}
type (
Otx struct {
TrackingId string
Type enum.OtxType
RawTx string
TxHash string
From string
Data string
GasLimit uint64
TransferValue uint64
GasPrice *big.Int
Nonce uint64
}
TxStatus struct {
CreatedAt time.Time `db:"created_at" json:"createdAt"`
Status string `db:"status" json:"status"`
TransferValue uint64 `db:"transfer_value" json:"transferValue"`
TxHash string `db:"tx_hash" json:"txHash"`
Type string `db:"type" json:"txType"`
}
)
func (s *PostgresStore) CreateOtx(ctx context.Context, otx OTX) (uint, error) {
func (s *PgStore) CreateOtx(
ctx context.Context,
otx Otx,
) (uint, error) {
var (
id uint
)
@ -34,37 +52,73 @@ func (s *PostgresStore) CreateOtx(ctx context.Context, otx OTX) (uint, error) {
otx.GasLimit,
otx.TransferValue,
otx.Nonce,
).Scan(&id); err != nil {
).Scan(
&id,
); err != nil {
return id, err
}
return id, nil
}
func (s *PostgresStore) GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error) {
func (s *PgStore) GetNextNonce(
ctx context.Context,
publicAddress string,
) (uint64, error) {
var (
txs []*TxStatus
lastNonce uint64
)
if err := pgxscan.Select(
if err := s.db.QueryRow(
ctx,
s.db,
&txs,
s.queries.GetTxStatusByTrackingId,
trackingId,
s.queries.GetNextNonce,
publicAddress,
).Scan(
&lastNonce,
); err != nil {
return nil, err
return 0, err
}
return txs, nil
return lastNonce, nil
}
func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error {
func (s *PgStore) GetTxStatus(
ctx context.Context,
trackingId string,
) (TxStatus, error) {
var (
tx TxStatus
)
rows, err := s.db.Query(
ctx,
s.queries.GetTxStatusByTrackingId,
trackingId,
)
if err != nil {
return tx, err
}
if err := pgxscan.ScanOne(
&tx,
rows,
); err != nil {
return tx, err
}
return tx, nil
}
func (s *PgStore) CreateDispatchStatus(
ctx context.Context,
otxId uint,
otxStatus enum.OtxStatus,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.CreateDispatchStatus,
dispatch.OtxId,
dispatch.Status,
otxId,
otxStatus,
); err != nil {
return err
}
@ -72,21 +126,26 @@ func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch Dispa
return nil
}
func (s *PostgresStore) UpdateOtxStatusFromChainEvent(ctx context.Context, chainEvent MinimalTxInfo) error {
func (s *PgStore) UpdateDispatchStatus(
ctx context.Context,
txSuccess bool,
txHash string,
txBlock uint64,
) error {
var (
status = enum.SUCCESS
)
if !chainEvent.Success {
if !txSuccess {
status = enum.REVERTED
}
if _, err := s.db.Exec(
ctx,
s.queries.UpdateChainStatus,
chainEvent.TxHash,
s.queries.UpdateDispatchStatus,
txHash,
status,
chainEvent.Block,
txBlock,
); err != nil {
return err
}

View File

@ -1,25 +0,0 @@
package store
import (
"github.com/grassrootseconomics/cic-custodial/internal/queries"
"github.com/jackc/pgx/v5/pgxpool"
)
type (
Opts struct {
PostgresPool *pgxpool.Pool
Queries *queries.Queries
}
PostgresStore struct {
db *pgxpool.Pool
queries *queries.Queries
}
)
func NewPostgresStore(o Opts) Store {
return &PostgresStore{
db: o.PostgresPool,
queries: o.Queries,
}
}

View File

@ -2,48 +2,129 @@ package store
import (
"context"
"crypto/ecdsa"
"fmt"
"os"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/cic-custodial/pkg/keypair"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/tern/v2/migrate"
"github.com/knadh/goyesql/v2"
)
type (
MinimalTxInfo struct {
Block uint64 `json:"block"`
From string `json:"from"`
To string `json:"to"`
ContractAddress string `json:"contractAddress"`
Success bool `json:"success"`
TxHash string `json:"transactionHash"`
TxIndex uint `json:"transactionIndex"`
Value uint64 `json:"value"`
}
OTX struct {
TrackingId string
Type enum.OtxType
RawTx string
TxHash string
From string
Data string
GasLimit uint64
TransferValue uint64
GasPrice uint64
Nonce uint64
}
DispatchStatus struct {
OtxId uint
Status enum.OtxStatus
}
Store interface {
CreateOtx(ctx context.Context, otx OTX) (id uint, err error)
CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error
GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error)
UpdateOtxStatusFromChainEvent(ctx context.Context, chainEvent MinimalTxInfo) error
GetAccountStatusByAddress(ctx context.Context, publicAddress string) (bool, int, error)
GetAccountActivationQuorum(ctx context.Context, trackingId string) (int, error)
DecrGasQuota(ctx context.Context, publicAddress string) error
ResetGasQuota(ctx context.Context, publicAddress string) error
ActivateAccount(ctx context.Context, publicAddress string) error
// Keypair related actions.
LoadPrivateKey(context.Context, string) (*ecdsa.PrivateKey, error)
WriteKeyPair(context.Context, keypair.Key) (uint, error)
// Otx related actions.
CreateOtx(context.Context, Otx) (uint, error)
GetNextNonce(context.Context, string) (uint64, error)
GetTxStatus(context.Context, string) (TxStatus, error)
CreateDispatchStatus(context.Context, uint, enum.OtxStatus) error
UpdateDispatchStatus(context.Context, bool, string, uint64) error
// Account related actions.
ActivateAccount(context.Context, string) error
GetAccountStatus(context.Context, string) (bool, bool, error)
// Gas quota related actions.
GasLock(context.Context, string) error
GasUnlock(context.Context, string) error
}
Opts struct {
DSN string
MigrationsFolderPath string
QueriesFolderPath string
}
PgStore struct {
db *pgxpool.Pool
queries *queries
}
queries struct {
// Keystore related queries.
WriteKeyPair string `query:"write-key-pair"`
LoadKeyPair string `query:"load-key-pair"`
// Otx related queries.
CreateOTX string `query:"create-otx"`
GetNextNonce string `query:"get-next-nonce"`
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
CreateDispatchStatus string `query:"create-dispatch-status"`
UpdateDispatchStatus string `query:"update-dispatch-status"`
// Account related queries.
ActivateAccount string `query:"activate-account"`
GetAccountStatus string `query:"get-account-status-by-address"`
GasLock string `query:"acc-gas-lock"`
GasUnlock string `query:"acc-gas-unlock"`
}
)
func NewPgStore(o Opts) (Store, error) {
parsedConfig, err := pgxpool.ParseConfig(o.DSN)
if err != nil {
return nil, err
}
dbPool, err := pgxpool.NewWithConfig(context.Background(), parsedConfig)
if err != nil {
return nil, err
}
queries, err := loadQueries(o.QueriesFolderPath)
if err != nil {
return nil, err
}
if err := runMigrations(context.Background(), dbPool, o.MigrationsFolderPath); err != nil {
return nil, err
}
return &PgStore{
db: dbPool,
queries: queries,
}, nil
}
func loadQueries(queriesPath string) (*queries, error) {
parsedQueries, err := goyesql.ParseFile(queriesPath)
if err != nil {
return nil, err
}
loadedQueries := &queries{}
if err := goyesql.ScanToStruct(loadedQueries, parsedQueries, nil); err != nil {
return nil, fmt.Errorf("failed to scan queries %v", err)
}
return loadedQueries, nil
}
func runMigrations(ctx context.Context, dbPool *pgxpool.Pool, migrationsPath string) error {
ctx, cancel := context.WithTimeout(ctx, util.SLATimeout)
defer cancel()
conn, err := dbPool.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
migrator, err := migrate.NewMigrator(ctx, conn.Conn(), "schema_version")
if err != nil {
return err
}
if err := migrator.LoadMigrations(os.DirFS(migrationsPath)); err != nil {
return err
}
if err := migrator.Migrate(ctx); err != nil {
return err
}
return nil
}

View File

@ -4,27 +4,54 @@ import (
"context"
"encoding/json"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/nats-io/nats.go"
)
func (s *Sub) handler(ctx context.Context, msg *nats.Msg) error {
type (
ChainEvent struct {
Block uint64 `json:"block"`
From string `json:"from"`
To string `json:"to"`
ContractAddress string `json:"contractAddress"`
Success bool `json:"success"`
TxHash string `json:"transactionHash"`
TxIndex uint `json:"transactionIndex"`
Value uint64 `json:"value"`
}
)
func (s *Sub) processEventHandler(ctx context.Context, msg *nats.Msg) error {
var (
chainEvent store.MinimalTxInfo
chainEvent ChainEvent
)
if err := json.Unmarshal(msg.Data, &chainEvent); err != nil {
return err
}
if err := s.cu.PgStore.UpdateOtxStatusFromChainEvent(ctx, chainEvent); err != nil {
if err := s.cu.Store.UpdateDispatchStatus(
ctx,
chainEvent.Success,
chainEvent.TxHash,
chainEvent.Block,
); err != nil {
return err
}
switch msg.Subject {
case "CHAIN.gas":
if err := s.cu.PgStore.ResetGasQuota(ctx, checksum(chainEvent.To)); err != nil {
return err
if chainEvent.Success {
switch msg.Subject {
case "CHAIN.register":
if err := s.cu.Store.ActivateAccount(ctx, chainEvent.To); err != nil {
return err
}
if err := s.cu.Store.GasUnlock(ctx, chainEvent.To); err != nil {
return err
}
case "CHAIN.gas":
if err := s.cu.Store.GasUnlock(ctx, chainEvent.To); err != nil {
return err
}
}
}

View File

@ -3,19 +3,17 @@ package sub
import (
"context"
"errors"
"time"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/nats-io/nats.go"
"github.com/zerodha/logf"
)
const (
durableId = "cic-custodial"
pullStream = "CHAIN"
pullSubject = "CHAIN.*"
actionTimeout = 5 * time.Second
waitDelay = 1 * time.Second
durableId = "cic-custodial"
pullStream = "CHAIN"
pullSubject = "CHAIN.*"
)
type (
@ -67,7 +65,6 @@ func (s *Sub) Process() error {
events, err := natsSub.Fetch(1)
if err != nil {
if errors.Is(err, nats.ErrTimeout) {
s.logg.Debug("sub: no msg to pull")
continue
} else if errors.Is(err, nats.ErrConnectionClosed) {
return nil
@ -78,13 +75,12 @@ func (s *Sub) Process() error {
if len(events) > 0 {
msg := events[0]
ctx, cancel := context.WithTimeout(context.Background(), actionTimeout)
ctx, cancel := context.WithTimeout(context.Background(), util.SLATimeout)
if err := s.handler(ctx, msg); err != nil {
if err := s.processEventHandler(ctx, msg); err != nil {
s.logg.Error("sub: handler error", "error", err)
msg.Nak()
} else {
s.logg.Debug("sub: processed msg", "subject", msg.Subject)
msg.Ack()
}
cancel()

View File

@ -1,31 +0,0 @@
package sub
import (
"encoding/hex"
"strconv"
"strings"
"golang.org/x/crypto/sha3"
)
// TODO: This should probably be used project wide
func checksum(address string) string {
address = strings.ToLower(address)
address = strings.Replace(address, "0x", "", 1)
sha := sha3.NewLegacyKeccak256()
sha.Write([]byte(address))
hash := sha.Sum(nil)
hashstr := hex.EncodeToString(hash)
result := []string{"0x"}
for i, v := range address {
res, _ := strconv.ParseInt(string(hashstr[i]), 16, 64)
if res > 7 {
result = append(result, strings.ToUpper(string(v)))
continue
}
result = append(result, string(v))
}
return strings.Join(result, "")
}

View File

@ -11,7 +11,7 @@ import (
)
const (
retryRequeueInterval = 2 * time.Second
retryRequeueInterval = 1 * time.Second
)
type TaskerServerOpts struct {

View File

@ -1,45 +0,0 @@
package task
import (
"context"
"encoding/json"
"errors"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/hibiken/asynq"
)
const (
requiredQuorum = 3
)
var (
ErrQuorumNotReached = errors.New("Account activation quorum not reached.")
)
func AccountActivateProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
quorum, err := cu.PgStore.GetAccountActivationQuorum(ctx, payload.TrackingId)
if err != nil {
return err
}
if quorum < requiredQuorum {
return ErrQuorumNotReached
}
if err := cu.PgStore.ActivateAccount(ctx, payload.PublicKey); err != nil {
return err
}
return nil
}
}

View File

@ -1,145 +0,0 @@
package task
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
const (
accountActivationCheckDelay = 5 * time.Second
)
func AccountGiftGasProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
err = nErr
}
}
}()
builtTx, err := cu.CeloProvider.SignGasTransferTx(
cu.SystemContainer.PrivateKey,
celoutils.GasTransferTxOpts{
To: w3.A(payload.PublicKey),
Nonce: nonce,
Value: cu.SystemContainer.GiftableGasValue,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.GIFT_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableGasValue.Uint64(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountActivateTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
asynq.ProcessIn(accountActivationCheckDelay),
)
if err != nil {
return err
}
eventPayload := &pub.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.Pub.Publish(
pub.AccountGiftGas,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -1,137 +0,0 @@
package task
import (
"context"
"encoding/json"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
func GiftVoucherProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
payload AccountPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["mintTo"].EncodeArgs(
w3.A(payload.PublicKey),
cu.SystemContainer.GiftableTokenValue,
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemContainer.PrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.SystemContainer.GiftableToken,
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
Nonce: nonce,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.GIFT_VOUCHER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableTokenValue.Uint64(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
eventPayload := &pub.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.Pub.Publish(
pub.AccountGiftVoucher,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -1,81 +0,0 @@
package task
import (
"context"
"encoding/json"
"fmt"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/hibiken/asynq"
)
type AccountPayload struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}
func AccountPrepare(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var payload AccountPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.Noncestore.SetNewAccountNonce(ctx, payload.PublicKey); err != nil {
return err
}
_, err := cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRegisterTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountGiftGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountGiftVoucherTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
)
if err != nil {
return err
}
eventPayload := pub.EventPayload{
TrackingId: payload.TrackingId,
}
if err := cu.Pub.Publish(
pub.AccountNewNonce,
payload.PublicKey,
eventPayload,
); err != nil {
return err
}
return nil
}
}

View File

@ -3,26 +3,18 @@ package task
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"time"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
)
const (
gasLockPrefix = "gas_lock:"
gasLockExpiry = 1 * time.Hour
)
func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
@ -30,32 +22,65 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
var (
err error
payload AccountPayload
nextTime big.Int
checkStatus bool
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
}
// TODO: Check eth-faucet whether we can request for a topup before signing the tx.
_, gasQuota, err := cu.PgStore.GetAccountStatusByAddress(ctx, payload.PublicKey)
if err != nil {
return err
}
gasLock, err := cu.RedisClient.Get(ctx, gasLockPrefix+payload.PublicKey).Bool()
if !errors.Is(err, redis.Nil) {
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.CallFunc(
cu.Abis[custodial.NextTime],
cu.RegistryMap[celoutils.GasFaucet],
celoutils.HexToAddress(payload.PublicKey),
).Returns(&nextTime),
); err != nil {
return err
}
if gasQuota > 0 || gasLock {
// The user recently requested funds, there is a cooldown applied.
// We can schedule an attempt after the cooldown period has passed + 10 seconds.
if nextTime.Int64() > time.Now().Unix() {
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: t.Payload(),
},
asynq.ProcessAt(time.Unix(nextTime.Int64()+10, 0)),
)
if err != nil {
return err
}
return nil
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.CallFunc(
cu.Abis[custodial.Check],
cu.RegistryMap[celoutils.GasFaucet],
celoutils.HexToAddress(payload.PublicKey),
).Returns(&checkStatus),
); err != nil {
return err
}
// The gas faucet backend returns a false status, a poke will fail.
if !checkStatus {
return nil
}
// TODO: Use eth-faucet.
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
lockPrefix+cu.SystemPublicKey,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
@ -65,27 +90,34 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemPublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemPublicKey); nErr != nil {
err = nErr
}
}
}()
// TODO: Review gas params
builtTx, err := cu.CeloProvider.SignGasTransferTx(
cu.SystemContainer.PrivateKey,
celoutils.GasTransferTxOpts{
To: w3.A(payload.PublicKey),
Nonce: nonce,
Value: cu.SystemContainer.GiftableGasValue,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
input, err := cu.Abis[custodial.GiveTo].EncodeArgs(
celoutils.HexToAddress(payload.PublicKey),
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemPrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.RegistryMap[celoutils.GasFaucet],
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
if err != nil {
@ -97,17 +129,16 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.REFILL_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
TransferValue: cu.SystemContainer.GiftableGasValue.Uint64(),
Nonce: builtTx.Nonce(),
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.REFILL_GAS,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
@ -133,24 +164,6 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
eventPayload := &pub.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.Pub.Publish(
pub.AccountRefillGas,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
}
if _, err := cu.RedisClient.SetEx(ctx, gasLockPrefix+payload.PublicKey, true, gasLockExpiry).Result(); err != nil {
return err
}
return nil
}
}

View File

@ -3,20 +3,22 @@ package task
import (
"context"
"encoding/json"
"fmt"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/hibiken/asynq"
)
type AccountPayload struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}
func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
@ -25,13 +27,13 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+cu.SystemContainer.PublicKey,
cu.SystemContainer.LockTimeout,
lockPrefix+cu.SystemPublicKey,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
@ -41,34 +43,33 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
}
defer lock.Release(ctx)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemContainer.PublicKey)
nonce, err := cu.Noncestore.Acquire(ctx, cu.SystemPublicKey)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemPublicKey); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["add"].EncodeArgs(
w3.A(payload.PublicKey),
input, err := cu.Abis[custodial.Register].EncodeArgs(
celoutils.HexToAddress(payload.PublicKey),
)
if err != nil {
return err
}
// TODO: Review gas params.
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
cu.SystemContainer.PrivateKey,
cu.SystemPrivateKey,
celoutils.ContractExecutionTxOpts{
ContractAddress: cu.SystemContainer.AccountIndexContract,
ContractAddress: cu.RegistryMap[celoutils.CustodialProxy],
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
@ -81,14 +82,14 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.ACCOUNT_REGISTER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
Nonce: builtTx.Nonce(),
})
@ -116,17 +117,7 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
return err
}
eventPayload := &pub.EventPayload{
OtxId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.Pub.Publish(
pub.AccountRegister,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
if err := cu.Noncestore.SetAccountNonce(ctx, payload.PublicKey, 0); err != nil {
return err
}

View File

@ -9,8 +9,6 @@ import (
"github.com/celo-org/celo-blockchain/core/types"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
@ -25,53 +23,37 @@ func DispatchTx(cu *custodial.Custodial) func(context.Context, *asynq.Task) erro
return func(ctx context.Context, t *asynq.Task) error {
var (
payload TxPayload
dispatchStatus store.DispatchStatus
eventPayload pub.EventPayload
dispathchTx common.Hash
dispatchStatus enum.OtxStatus = enum.IN_NETWORK
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
return err
}
txHash := payload.Tx.Hash().Hex()
dispatchStatus.OtxId, eventPayload.OtxId = payload.OtxId, payload.OtxId
eventPayload.TxHash = txHash
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.SendTx(payload.Tx).Returns(&dispathchTx),
); err != nil {
switch err.Error() {
case celoutils.ErrGasPriceLow:
dispatchStatus.Status = enum.FAIL_LOW_GAS_PRICE
dispatchStatus = enum.FAIL_LOW_GAS_PRICE
case celoutils.ErrInsufficientGas:
dispatchStatus.Status = enum.FAIL_NO_GAS
dispatchStatus = enum.FAIL_NO_GAS
case celoutils.ErrNonceLow:
dispatchStatus.Status = enum.FAIL_LOW_NONCE
dispatchStatus = enum.FAIL_LOW_NONCE
default:
dispatchStatus.Status = enum.FAIL_UNKNOWN_RPC_ERROR
dispatchStatus = enum.FAIL_UNKNOWN_RPC_ERROR
}
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.Pub.Publish(pub.DispatchFail, txHash, eventPayload); err != nil {
if err := cu.Store.CreateDispatchStatus(ctx, payload.OtxId, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
dispatchStatus.Status = enum.IN_NETWORK
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
if err := cu.Pub.Publish(pub.DispatchSuccess, txHash, eventPayload); err != nil {
if err := cu.Store.CreateDispatchStatus(ctx, payload.OtxId, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}

View File

@ -3,53 +3,43 @@ package task
import (
"context"
"encoding/json"
"fmt"
"math/big"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/pub"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
type (
TransferPayload struct {
TrackingId string `json:"trackingId"`
From string `json:"from" `
To string `json:"to"`
VoucherAddress string `json:"voucherAddress"`
Amount uint64 `json:"amount"`
}
transferEventPayload struct {
DispatchTaskId string `json:"dispatchTaskId"`
OTXId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}
)
type TransferPayload struct {
TrackingId string `json:"trackingId"`
From string `json:"from" `
To string `json:"to"`
VoucherAddress string `json:"voucherAddress"`
Amount uint64 `json:"amount"`
}
func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
payload TransferPayload
err error
networkBalance big.Int
payload TransferPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("account: failed %v: %w", err, asynq.SkipRetry)
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+payload.From,
cu.SystemContainer.LockTimeout,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
@ -59,7 +49,7 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
}
defer lock.Release(ctx)
key, err := cu.Keystore.LoadPrivateKey(ctx, payload.From)
key, err := cu.Store.LoadPrivateKey(ctx, payload.From)
if err != nil {
return err
}
@ -70,26 +60,28 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, cu.SystemContainer.PublicKey); nErr != nil {
if nErr := cu.Noncestore.Return(ctx, payload.From); nErr != nil {
err = nErr
}
}
}()
input, err := cu.SystemContainer.Abis["transfer"].EncodeArgs(w3.A(payload.To), new(big.Int).SetUint64(payload.Amount))
input, err := cu.Abis[custodial.Transfer].EncodeArgs(
celoutils.HexToAddress(payload.To),
new(big.Int).SetUint64(payload.Amount),
)
if err != nil {
return err
}
// TODO: Review gas params.
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
key,
celoutils.ContractExecutionTxOpts{
ContractAddress: w3.A(payload.VoucherAddress),
ContractAddress: celoutils.HexToAddress(payload.VoucherAddress),
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: cu.SystemContainer.TokenTransferGasLimit,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
@ -102,14 +94,14 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.TRANSFER_VOUCHER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: payload.From,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
TransferValue: payload.Amount,
Nonce: builtTx.Nonce(),
@ -118,6 +110,13 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.Balance(celoutils.HexToAddress(payload.From), nil).Returns(&networkBalance),
); err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
@ -146,30 +145,22 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
if !balanceCheck(networkBalance) {
if err := cu.Store.GasLock(ctx, payload.From); err != nil {
return err
}
eventPayload := &transferEventPayload{
OTXId: id,
TrackingId: payload.TrackingId,
TxHash: builtTx.Hash().Hex(),
}
if err := cu.Pub.Publish(
pub.SignTransfer,
builtTx.Hash().Hex(),
eventPayload,
); err != nil {
return err
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
}
return nil

View File

@ -0,0 +1,196 @@
package task
import (
"context"
"encoding/json"
"math/big"
"github.com/bsm/redislock"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
type TransferAuthPayload struct {
Amount uint64 `json:"amount"`
Authorizer string `json:"authorizer"`
AuthorizedAddress string `json:"authorizedAddress"`
TrackingId string `json:"trackingId"`
VoucherAddress string `json:"voucherAddress"`
}
func SignTransferAuthorizationProcessor(cu *custodial.Custodial) func(context.Context, *asynq.Task) error {
return func(ctx context.Context, t *asynq.Task) error {
var (
err error
networkBalance big.Int
payload TransferAuthPayload
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
lock, err := cu.LockProvider.Obtain(
ctx,
lockPrefix+payload.Authorizer,
lockTimeout,
&redislock.Options{
RetryStrategy: lockRetry(),
},
)
if err != nil {
return err
}
defer lock.Release(ctx)
key, err := cu.Store.LoadPrivateKey(ctx, payload.Authorizer)
if err != nil {
return err
}
nonce, err := cu.Noncestore.Acquire(ctx, payload.Authorizer)
if err != nil {
return err
}
defer func() {
if err != nil {
if nErr := cu.Noncestore.Return(ctx, payload.Authorizer); nErr != nil {
err = nErr
}
}
}()
input, err := cu.Abis[custodial.Approve].EncodeArgs(
celoutils.HexToAddress(payload.AuthorizedAddress),
new(big.Int).SetUint64(payload.Amount),
)
if err != nil {
return err
}
builtTx, err := cu.CeloProvider.SignContractExecutionTx(
key,
celoutils.ContractExecutionTxOpts{
ContractAddress: celoutils.HexToAddress(payload.VoucherAddress),
InputData: input,
GasFeeCap: celoutils.SafeGasFeeCap,
GasTipCap: celoutils.SafeGasTipCap,
GasLimit: uint64(celoutils.SafeGasLimit),
Nonce: nonce,
},
)
if err != nil {
return err
}
rawTx, err := builtTx.MarshalBinary()
if err != nil {
return err
}
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.TRANSFER_AUTH,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemPublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice(),
GasLimit: builtTx.Gas(),
TransferValue: 0,
Nonce: builtTx.Nonce(),
})
if err != nil {
return err
}
if err := cu.CeloProvider.Client.CallCtx(
ctx,
eth.Balance(celoutils.HexToAddress(payload.Authorizer), nil).Returns(&networkBalance),
); err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.DispatchTxTask,
tasker.HighPriority,
&tasker.Task{
Payload: disptachJobPayload,
},
)
if err != nil {
return err
}
// Auto-revoke every session (15 min)
// Check if already a revoke request
if payload.Amount > 0 {
taskPayload, err := json.Marshal(TransferAuthPayload{
TrackingId: payload.TrackingId,
Amount: 0,
Authorizer: payload.Authorizer,
AuthorizedAddress: payload.AuthorizedAddress,
VoucherAddress: payload.VoucherAddress,
})
if err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.SignTransferTaskAuth,
tasker.DefaultPriority,
&tasker.Task{
Payload: taskPayload,
},
asynq.ProcessIn(cu.ApprovalTimeout),
)
if err != nil {
return err
}
}
gasRefillPayload, err := json.Marshal(AccountPayload{
PublicKey: payload.Authorizer,
TrackingId: payload.TrackingId,
})
if err != nil {
return err
}
if !balanceCheck(networkBalance) {
if err := cu.Store.GasLock(ctx, payload.Authorizer); err != nil {
return err
}
_, err = cu.TaskerClient.CreateTask(
ctx,
tasker.AccountRefillGasTask,
tasker.DefaultPriority,
&tasker.Task{
Payload: gasRefillPayload,
},
)
if err != nil {
return err
}
}
return nil
}
}

View File

@ -1,6 +1,7 @@
package task
import (
"math/big"
"time"
"github.com/bsm/redislock"
@ -9,6 +10,16 @@ import (
const (
lockPrefix = "lock:"
lockRetryDelay = 25 * time.Millisecond
lockTimeout = 1 * time.Second
)
var (
// 20 gwei = max gas price we are willing to pay
// 250k = max gas limit
// minGasBalanceRequired is optimistic that the immidiate next transfer request will be successful
// but the subsequent one could fail (though low probability), therefore we can trigger a gas lock.
// Therefore our system wide threshold is 0.01 CELO or 10000000000000000 gas units
minGasBalanceRequired = big.NewInt(20000000000 * 250000 * 2)
)
// lockRetry will at most try to obtain the lock 20 times within ~0.5s.
@ -19,3 +30,8 @@ func lockRetry() redislock.RetryStrategy {
20,
)
}
// balanceCheck compares the network balance with the system set min as threshold to execute a transfer.
func balanceCheck(networkBalance big.Int) bool {
return minGasBalanceRequired.Cmp(&networkBalance) < 0
}

View File

@ -15,14 +15,11 @@ type Task struct {
}
const (
AccountPrepareTask TaskName = "sys:prepare_account"
AccountRegisterTask TaskName = "sys:register_account"
AccountGiftGasTask TaskName = "sys:gift_gas"
AccountGiftVoucherTask TaskName = "sys:gift_token"
AccountRefillGasTask TaskName = "sys:refill_gas"
AccountActivateTask TaskName = "sys:quorum_check"
SignTransferTask TaskName = "usr:sign_transfer"
DispatchTxTask TaskName = "rpc:dispatch"
AccountRegisterTask TaskName = "sys:register_account"
AccountRefillGasTask TaskName = "sys:refill_gas"
SignTransferTask TaskName = "usr:sign_transfer"
SignTransferTaskAuth TaskName = "usr:sign_transfer_auth"
DispatchTxTask TaskName = "rpc:dispatch"
)
const (

View File

@ -0,0 +1,34 @@
-- Replace gas_quota with gas_lock which checks network balance threshold
DROP TRIGGER IF EXISTS update_gas_quota_timestamp ON gas_quota;
DROP TABLE IF EXISTS gas_quota_meta;
DROP TABLE IF EXISTS gas_quota;
DROP TRIGGER IF EXISTS insert_gas_quota ON keystore;
DROP FUNCTION IF EXISTS insert_gas_quota;
-- Gas lock table
-- A gas_locked account indicates gas balance is below threshold awaiting next available top up
CREATE TABLE IF NOT EXISTS gas_lock (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
key_id INT REFERENCES keystore(id) NOT NULL,
lock BOOLEAN DEFAULT true,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
create function insert_gas_lock()
returns trigger
as $$
begin
insert into gas_lock (key_id) values (new.id);
return new;
end;
$$ language plpgsql;
create trigger insert_gas_lock
after insert on keystore
for each row
execute procedure insert_gas_lock();
create trigger update_gas_lock_timestamp
before update on gas_lock
for each row
execute procedure update_timestamp();

View File

@ -0,0 +1 @@
INSERT INTO otx_tx_type (value) VALUES ('TRANSFER_AUTHORIZATION');

View File

@ -19,9 +19,8 @@ const (
FAIL_UNKNOWN_RPC_ERROR OtxStatus = "FAIL_UNKNOWN_RPC_ERROR"
REVERTED OtxStatus = "REVERTED"
GIFT_GAS OtxType = "GIFT_GAS"
ACCOUNT_REGISTER OtxType = "ACCOUNT_REGISTER"
GIFT_VOUCHER OtxType = "GIFT_VOUCHER"
REFILL_GAS OtxType = "REFILL_GAS"
TRANSFER_AUTH OtxType = "TRANSFER_AUTHORIZATION"
TRANSFER_VOUCHER OtxType = "TRANSFER_VOUCHER"
)

View File

@ -1,56 +0,0 @@
package postgres
import (
"context"
"os"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/tern/v2/migrate"
)
const (
schemaTable = "schema_version"
)
type PostgresPoolOpts struct {
DSN string
MigrationsFolderPath string
}
// NewPostgresPool creates a reusbale connection pool across the cic-custodial component.
func NewPostgresPool(ctx context.Context, o PostgresPoolOpts) (*pgxpool.Pool, error) {
parsedConfig, err := pgxpool.ParseConfig(o.DSN)
if err != nil {
return nil, err
}
dbPool, err := pgxpool.NewWithConfig(ctx, parsedConfig)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
conn, err := dbPool.Acquire(ctx)
if err != nil {
return nil, err
}
defer conn.Release()
migrator, err := migrate.NewMigrator(ctx, conn.Conn(), schemaTable)
if err != nil {
return nil, err
}
if err := migrator.LoadMigrations(os.DirFS(o.MigrationsFolderPath)); err != nil {
return nil, err
}
if err := migrator.Migrate(ctx); err != nil {
return nil, err
}
return dbPool, nil
}

View File

@ -2,8 +2,8 @@ package redis
import (
"context"
"time"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/redis/go-redis/v9"
)
@ -34,7 +34,7 @@ func NewRedisPool(ctx context.Context, o RedisPoolOpts) (*RedisPool, error) {
redisClient := redis.NewClient(redisOpts)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
ctx, cancel := context.WithTimeout(ctx, util.SLATimeout)
defer cancel()
if err := redisClient.SetNX(ctx, systemGlobalLockKey, false, 0).Err(); err != nil {

8
pkg/util/sla.go Normal file
View File

@ -0,0 +1,8 @@
package util
import "time"
const (
// SLATimeout is the max duration after which any network call/context should be aborted.
SLATimeout = 5 * time.Second
)

View File

@ -9,11 +9,6 @@ INSERT INTO keystore(public_key, private_key) VALUES($1, $2) RETURNING id
-- $1: public_key
SELECT private_key FROM keystore WHERE public_key=$1
--name: activate-account
-- Activate an account following successful quorum
-- $1: public_key
UPDATE keystore SET active = true WHERE public_key=$1
--name: create-otx
-- Create a new locally originating tx
-- $1: tracking_id
@ -39,6 +34,18 @@ INSERT INTO otx_sign(
nonce
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id
--name: get-next-nonce
-- Gets last nonce from the otx table for a particular address for bootstrapping purposes
-- $1: public_key
SELECT nonce + 1 AS nonce FROM otx_sign WHERE otx_sign.from = $1 ORDER BY created_at DESC LIMIT 1;
--name: get-tx-status-by-tracking-id
-- Gets tx status's from possible multiple txs with the same tracking_id
-- $1: tracking_id
SELECT otx_sign.type, otx_sign.tx_hash, otx_sign.transfer_value, otx_sign.created_at, otx_dispatch.status FROM otx_sign
INNER JOIN otx_dispatch ON otx_sign.id = otx_dispatch.otx_id
WHERE otx_sign.tracking_id=$1
--name: create-dispatch-status
-- Create a new dispatch status
-- $1: otx_id
@ -48,7 +55,7 @@ INSERT INTO otx_dispatch(
"status"
) VALUES($1, $2) RETURNING id
--name: update-chain-status
--name: update-dispatch-status
-- Updates the status of the dispatched tx with the chain mine status
-- $1: tx_hash
-- $2: status
@ -60,45 +67,30 @@ UPDATE otx_dispatch SET "status" = $2, "block" = $3 WHERE otx_dispatch.id = (
AND otx_dispatch.status = 'IN_NETWORK'
)
--name: get-tx-status-by-tracking-id
-- Gets tx status's from possible multiple txs with the same tracking_id
-- $1: tracking_id
SELECT otx_sign.type, otx_sign.tx_hash, otx_sign.transfer_value, otx_sign.created_at, otx_dispatch.status FROM otx_sign
INNER JOIN otx_dispatch ON otx_sign.id = otx_dispatch.otx_id
WHERE otx_sign.tracking_id=$1
-- TODO: Scroll by status type with cursor pagination
--name: get-account-activation-quorum
-- Gets quorum of required and confirmed system transactions for the account
-- $1: tracking_id
SELECT count(*) FROM otx_dispatch INNER JOIN
otx_sign ON otx_dispatch.otx_id = otx_sign.id
WHERE otx_sign.tracking_id=$1
AND otx_dispatch.status = 'SUCCESS'
--name: activate-account
-- Activate an account following successful quorum
-- $1: public_key
UPDATE keystore SET active = true WHERE public_key=$1
--name: get-account-status-by-address
-- Gets current gas quota for an individual account by address
-- Gets current gas lock and activation status for an individual account by address
-- $1: public_key
SELECT keystore.active, gas_quota.quota FROM keystore
INNER JOIN gas_quota ON keystore.id = gas_quota.key_id
SELECT keystore.active, gas_lock.lock FROM keystore
INNER JOIN gas_lock ON keystore.id = gas_lock.key_id
WHERE keystore.public_key=$1
--name: decr-gas-quota
-- Consumes a gas quota
--name: acc-gas-lock
-- Locks an account for gas reasons
-- $1: public_key
UPDATE gas_quota SET quota = quota - 1 WHERE key_id = (
UPDATE gas_lock SET lock = true WHERE key_id = (
SELECT id FROM keystore
WHERE public_key=$1
)
--name: reset-gas-quota
-- Resets the gas quota
-- 25 is the agreed upon quota
--name: acc-gas-unlock
-- Unlocks an account for gas reasons
-- $1: public_key
UPDATE gas_quota SET quota = gas_quota_meta.default_quota
FROM gas_quota_meta
WHERE key_id = (
UPDATE gas_lock SET lock = false WHERE key_id = (
SELECT id FROM keystore
WHERE public_key=$1
)