feat: add otx tracking API, add enums, minor fixes

* otx can nw be tracked at /api/track/:trackingId
* moved queries to queries folder
* fixed validation error check in ErrorHandler
* added enum package with enum types
* updated migrations: added enum tables
This commit is contained in:
Mohamed Sohail 2023-02-21 17:34:22 +00:00
parent 140c887341
commit 40cb86f522
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
22 changed files with 314 additions and 147 deletions

View File

@ -30,7 +30,7 @@ func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
return
}
if err.(validator.ValidationErrors) != nil {
if _, ok := err.(validator.ValidationErrors); ok {
c.JSON(http.StatusForbidden, api.ErrResp{
Ok: false,
Code: api.VALIDATION_ERROR,
@ -66,6 +66,7 @@ func initApiServer(custodialContainer *custodial.Custodial) *echo.Echo {
apiRoute := server.Group("/api")
apiRoute.POST("/account/create", api.CreateAccountHandler(custodialContainer))
apiRoute.POST("/sign/transfer", api.SignTransferHandler(custodialContainer))
apiRoute.GET("/track/:trackingId", api.TxStatus(custodialContainer.PgStore))
return server
}

View File

@ -28,7 +28,7 @@ var (
func init() {
flag.StringVar(&confFlag, "config", "config.toml", "Config file location")
flag.BoolVar(&debugFlag, "log", false, "Enable debug logging")
flag.StringVar(&queriesFlag, "queries", "queries.sql", "Queries file location")
flag.StringVar(&queriesFlag, "queries", "queries/queries.sql", "Queries file location")
flag.Parse()
lo = initLogger(debugFlag)

2
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/VictoriaMetrics/metrics v1.23.1
github.com/bsm/redislock v0.7.2
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-redis/redis/v8 v8.11.5
github.com/google/uuid v1.3.0
@ -70,7 +71,6 @@ require (
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect

7
go.sum
View File

@ -136,6 +136,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
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/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -175,6 +176,8 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
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=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -218,6 +221,7 @@ github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@ -424,6 +428,7 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2
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/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=
@ -572,7 +577,6 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -602,6 +606,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@ -29,7 +29,7 @@ func SignTransferHandler(cu *custodial.Custodial) func(echo.Context) error {
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 int64 `json:"amount" validate:"required,numeric"`
Amount uint64 `json:"amount" validate:"required,numeric"`
}
if err := c.Bind(&transferRequest); err != nil {

37
internal/api/track.go Normal file
View File

@ -0,0 +1,37 @@
package api
import (
"net/http"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/labstack/echo/v4"
)
func TxStatus(store store.Store) 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 err
}
if err := c.Validate(txStatusRequest); err != nil {
return err
}
// TODO: handle potential timeouts
txs, err := store.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

@ -5,10 +5,8 @@ type EventEmitter interface {
Publish(subject string, dedupId string, eventPayload interface{}) error
}
type (
EventPayload struct {
OtxId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}
)
type EventPayload struct {
OtxId uint `json:"otxId"`
TrackingId string `json:"trackingId"`
TxHash string `json:"txHash"`
}

View File

@ -10,10 +10,11 @@ type Queries struct {
// Keystore
WriteKeyPair string `query:"write-key-pair"`
LoadKeyPair string `query:"load-key-pair"`
// OTX
CreateOTX string `query:"create-otx"`
// Dispatch
CreateDispatchStatus string `query:"create-dispatch-status"`
// Store
CreateOTX string `query:"create-otx"`
CreateDispatchStatus string `query:"create-dispatch-status"`
UpdateChainStatus string `query:"update-chain-status"`
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
}
func LoadQueries(q goyesql.Queries) (*Queries, error) {

View File

@ -2,9 +2,9 @@ package store
import (
"context"
)
type Status string
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
)
func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error {
if _, err := s.db.Exec(
@ -18,3 +18,17 @@ func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch Dispa
return nil
}
func (s *PostgresStore) UpdateChainStatus(ctx context.Context, txHash string, status enum.OtxStatus, block uint64) error {
if _, err := s.db.Exec(
ctx,
s.queries.UpdateChainStatus,
txHash,
status,
block,
); err != nil {
return err
}
return nil
}

View File

@ -1,8 +1,21 @@
package store
import "context"
import (
"context"
"time"
func (s *PostgresStore) CreateOTX(ctx context.Context, otx OTX) (uint, error) {
"github.com/georgysavva/scany/v2/pgxscan"
)
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"`
}
func (s *PostgresStore) CreateOtx(ctx context.Context, otx OTX) (uint, error) {
var (
id uint
)
@ -17,6 +30,8 @@ func (s *PostgresStore) CreateOTX(ctx context.Context, otx OTX) (uint, error) {
otx.From,
otx.Data,
otx.GasPrice,
otx.GasLimit,
otx.TransferValue,
otx.Nonce,
).Scan(&id); err != nil {
return id, err
@ -24,3 +39,21 @@ func (s *PostgresStore) CreateOTX(ctx context.Context, otx OTX) (uint, error) {
return id, nil
}
func (s *PostgresStore) GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error) {
var (
txs []*TxStatus
)
if err := pgxscan.Select(
ctx,
s.db,
&txs,
s.queries.GetTxStatusByTrackingId,
trackingId,
); err != nil {
return nil, err
}
return txs, nil
}

View File

@ -3,30 +3,32 @@ package store
import (
"context"
"github.com/grassrootseconomics/cic-custodial/pkg/status"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
)
type (
OTX struct {
TrackingId string
Type string
RawTx string
TxHash string
From string
Data string
GasPrice uint64
Nonce uint64
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 status.Status
Status enum.OtxStatus
}
Store interface {
// OTX (Custodial originating transactions).
CreateOTX(ctx context.Context, otx OTX) (id uint, err error)
// Dispatch status.
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)
UpdateChainStatus(ctx context.Context, txHash string, status enum.OtxStatus, block uint64) error
}
)

View File

@ -11,6 +11,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/events"
"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"
)
@ -67,15 +68,17 @@ func AccountGiftGasProcessor(cu *custodial.Custodial) func(context.Context, *asy
return err
}
id, err := cu.PgStore.CreateOTX(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: "GIFT_GAS",
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
Nonce: builtTx.Nonce(),
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

View File

@ -10,6 +10,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/events"
"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"
)
@ -75,15 +76,17 @@ func GiftVoucherProcessor(cu *custodial.Custodial) func(context.Context, *asynq.
return err
}
id, err := cu.PgStore.CreateOTX(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: "GIFT_VOUCHER",
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
Nonce: builtTx.Nonce(),
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: enum.ACCOUNT_REGISTER,
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 {

View File

@ -12,6 +12,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/events"
"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"
@ -82,15 +83,17 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
id, err := cu.PgStore.CreateOTX(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: "REFILL_GAS",
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
Nonce: builtTx.Nonce(),
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(),
})
if err != nil {
return err

View File

@ -11,6 +11,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/events"
"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"
)
@ -76,14 +77,15 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
return err
}
id, err := cu.PgStore.CreateOTX(ctx, store.OTX{
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: "ACCOUNT_REGISTER",
Type: enum.ACCOUNT_REGISTER,
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: cu.SystemContainer.PublicKey,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
GasLimit: builtTx.Gas(),
Nonce: builtTx.Nonce(),
})
if err != nil {

View File

@ -11,7 +11,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/custodial"
"github.com/grassrootseconomics/cic-custodial/internal/events"
"github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/grassrootseconomics/cic-custodial/pkg/status"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
)
@ -43,15 +43,15 @@ func DispatchTx(cu *custodial.Custodial) func(context.Context, *asynq.Task) erro
ctx,
eth.SendTx(payload.Tx).Returns(&dispathchTx),
); err != nil {
dispatchStatus.Status = status.Unknown
switch err.Error() {
case celoutils.ErrGasPriceLow:
dispatchStatus.Status = status.FailGasPrice
dispatchStatus.Status = enum.FAIL_LOW_GAS_PRICE
case celoutils.ErrInsufficientGas:
dispatchStatus.Status = status.FailInsufficientGas
dispatchStatus.Status = enum.FAIL_NO_GAS
case celoutils.ErrNonceLow:
dispatchStatus.Status = status.FailNonce
dispatchStatus.Status = enum.FAIL_LOW_NONCE
default:
dispatchStatus.Status = enum.FAIL_UNKNOWN_RPC_ERROR
}
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
@ -65,7 +65,7 @@ func DispatchTx(cu *custodial.Custodial) func(context.Context, *asynq.Task) erro
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}
dispatchStatus.Status = status.Successful
dispatchStatus.Status = enum.IN_NETWORK
if err := cu.PgStore.CreateDispatchStatus(ctx, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)

View File

@ -12,6 +12,7 @@ import (
"github.com/grassrootseconomics/cic-custodial/internal/events"
"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"
)
@ -22,7 +23,7 @@ type (
From string `json:"from" `
To string `json:"to"`
VoucherAddress string `json:"voucherAddress"`
Amount int64 `json:"amount"`
Amount uint64 `json:"amount"`
}
transferEventPayload struct {
@ -71,7 +72,7 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
}
}()
input, err := cu.SystemContainer.Abis["transfer"].EncodeArgs(w3.A(payload.To), big.NewInt(payload.Amount))
input, err := cu.SystemContainer.Abis["transfer"].EncodeArgs(w3.A(payload.To), new(big.Int).SetUint64(payload.Amount))
if err != nil {
return err
}
@ -97,15 +98,17 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
id, err := cu.PgStore.CreateOTX(ctx, store.OTX{
TrackingId: payload.TrackingId,
Type: "TRANSFER",
RawTx: hexutil.Encode(rawTx),
TxHash: builtTx.Hash().Hex(),
From: payload.From,
Data: hexutil.Encode(builtTx.Data()),
GasPrice: builtTx.GasPrice().Uint64(),
Nonce: builtTx.Nonce(),
id, err := cu.PgStore.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(),
GasLimit: builtTx.Gas(),
TransferValue: payload.Amount,
Nonce: builtTx.Nonce(),
})
if err != nil {
return err

View File

@ -1,23 +1,53 @@
-- Otx tx type enum table
CREATE TABLE IF NOT EXISTS otx_tx_type (
value TEXT PRIMARY KEY
);
INSERT INTO otx_tx_type (value) VALUES
('GIFT_GAS'),
('ACCOUNT_REGISTER'),
('GIFT_VOUCHER'),
('REFILL_GAS'),
('TRANSFER_VOUCHER');
-- Origin tx table
CREATE TABLE IF NOT EXISTS otx (
CREATE TABLE IF NOT EXISTS otx_sign (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tracking_id TEXT NOT NULL,
"type" TEXT NOT NULL,
"type" TEXT REFERENCES otx_tx_type(value) NOT NULL,
raw_tx TEXT NOT NULL,
tx_hash TEXT NOT NULL,
"from" TEXT NOT NULL,
"data" TEXT NOT NULL,
gas_price bigint NOT NULL,
gas_limit bigint NOT NULL,
transfer_value bigint NOT NULL,
nonce int NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS tx_hash_idx ON otx USING hash(tx_hash);
CREATE INDEX IF NOT EXISTS from_idx ON otx USING hash("from");
CREATE INDEX IF NOT EXISTS tx_hash_idx ON otx_sign USING hash(tx_hash);
CREATE INDEX IF NOT EXISTS from_idx ON otx_sign USING hash("from");
-- Otx dispatch status enum table
-- Enforces referential integrity on the dispatch table
CREATE TABLE IF NOT EXISTS otx_dispatch_status_type (
value TEXT PRIMARY KEY
);
INSERT INTO otx_dispatch_status_type (value) VALUES
('IN_NETWORK'),
('OBSOLETE'),
('SUCCESS'),
('FAIL_NO_GAS'),
('FAIL_LOW_NONCE'),
('FAIL_LOW_GAS_PRICE'),
('FAIL_UNKNOWN_RPC_ERROR'),
('REVERTED');
-- Dispatch status table
CREATE TABLE IF NOT EXISTS dispatch (
CREATE TABLE IF NOT EXISTS otx_dispatch (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
otx_id INT REFERENCES otx(id),
"status" TEXT NOT NULL,
otx_id INT REFERENCES otx_sign(id),
"status" TEXT REFERENCES otx_dispatch_status_type(value) NOT NULL,
"block" bigint,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS status_idx ON otx_dispatch("status");

27
pkg/enum/enum.go Normal file
View File

@ -0,0 +1,27 @@
package enum
type (
// OtxStatus represents enum-like values received in the dispatcher from the RPC node or Network callback.
// It includes a subset of well-known and likely failures the dispatcher may encounter.
OtxStatus string
// OtxType reprsents the specific type of signed transaction.
OtxType string
)
// NOTE: These values must also be inserted/updated into db to enforce referential integrity.
const (
IN_NETWORK OtxStatus = "IN_NETWORK"
OBSOLETE OtxStatus = "OBSOLETE"
SUCCESS OtxStatus = "SUCCESS"
FAIL_NO_GAS OtxStatus = "FAIL_NO_GAS"
FAIL_LOW_NONCE OtxStatus = "FAIL_LOW_NONCE"
FAIL_LOW_GAS_PRICE OtxStatus = "FAIL_LOW_GAS_PRICE"
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_VOUCHER OtxType = "TRANSFER_VOUCHER"
)

View File

@ -1,13 +0,0 @@
package status
// Status represents enum-like values received in the dispatcher from the RPC node.
// It includes a subset of well-known and likely failures the dispatcher may encounter.
type Status string
const (
FailGasPrice = "FAIL_LOW_GAS_PRICE"
FailInsufficientGas = "FAIL_NO_GAS"
FailNonce = "FAIL_LOW_NONCE"
Successful = "SUCCESSFUL"
Unknown = "UNKNOWN"
)

View File

@ -1,47 +0,0 @@
-- Keystore queries
--name: write-key-pair
-- Save hex encoded private key
-- $1: public_key
-- $2: private_key
INSERT INTO keystore(public_key, private_key) VALUES($1, $2) RETURNING id
--name: load-key-pair
-- Load saved key pair
-- $1: public_key
SELECT private_key FROM keystore WHERE public_key=$1
-- OTX queries
--name: create-otx
-- Create a new locally originating tx
-- $1: tracking_id
-- $2: type
-- $3: raw_tx
-- $4: tx_hash
-- $5: from
-- $6: data
-- $7: gas_price
-- $8: nonce
INSERT INTO otx(
tracking_id,
"type",
raw_tx,
tx_hash,
"from",
"data",
gas_price,
nonce
) VALUES($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id
-- Dispatch status queries
--name: create-dispatch-status
-- Create a new dispatch status
-- $1: otx_id
-- $2: status
INSERT INTO dispatch(
otx_id,
"status"
) VALUES($1, $2) RETURNING id

65
queries/queries.sql Normal file
View File

@ -0,0 +1,65 @@
--name: write-key-pair
-- Save hex encoded private key
-- $1: public_key
-- $2: private_key
INSERT INTO keystore(public_key, private_key) VALUES($1, $2) RETURNING id
--name: load-key-pair
-- Load saved key pair
-- $1: public_key
SELECT private_key FROM keystore WHERE public_key=$1
--name: create-otx
-- Create a new locally originating tx
-- $1: tracking_id
-- $2: type
-- $3: raw_tx
-- $4: tx_hash
-- $5: from
-- $6: data
-- $7: gas_price
-- $8: gas_limit
-- $9: transfer_value
-- $10: nonce
INSERT INTO otx_sign(
tracking_id,
"type",
raw_tx,
tx_hash,
"from",
"data",
gas_price,
gas_limit,
transfer_value,
nonce
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id
--name: create-dispatch-status
-- Create a new dispatch status
-- $1: otx_id
-- $2: status
INSERT INTO otx_dispatch(
otx_id,
"status"
) VALUES($1, $2) RETURNING id
--name: update-chain-status
-- Updates the status of the dispatched tx with the chain mine status
-- $1: tx_hash
-- $2: status
-- $3: block
UPDATE otx_dispatch SET "status" = $2, "block" = $3 WHERE otx_dispatch.id = (
SELECT otx_dispatch.id FROM otx_dispatch
INNER JOIN otx_sign ON otx_dispatch.otx_id = otx_sign.id
WHERE otx_sign.tx_hash = $1
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