diff --git a/cmd/service/api.go b/cmd/service/api.go index 711e257..57f4f87 100644 --- a/cmd/service/api.go +++ b/cmd/service/api.go @@ -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 } diff --git a/cmd/service/main.go b/cmd/service/main.go index 09f5849..47100d4 100644 --- a/cmd/service/main.go +++ b/cmd/service/main.go @@ -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) diff --git a/go.mod b/go.mod index d122af5..fa09467 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8adc9e8..568e28d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/api/sign.go b/internal/api/sign.go index 92260e8..9b128bb 100644 --- a/internal/api/sign.go +++ b/internal/api/sign.go @@ -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 { diff --git a/internal/api/track.go b/internal/api/track.go new file mode 100644 index 0000000..398f938 --- /dev/null +++ b/internal/api/track.go @@ -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, + }, + }) + } +} diff --git a/internal/events/events.go b/internal/events/events.go index 10ff251..83a16ea 100644 --- a/internal/events/events.go +++ b/internal/events/events.go @@ -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"` +} diff --git a/internal/queries/queries.go b/internal/queries/queries.go index fb3d173..2a426c5 100644 --- a/internal/queries/queries.go +++ b/internal/queries/queries.go @@ -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) { diff --git a/internal/store/dispatch.go b/internal/store/dispatch.go index a584e48..6d36bea 100644 --- a/internal/store/dispatch.go +++ b/internal/store/dispatch.go @@ -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 +} diff --git a/internal/store/otx.go b/internal/store/otx.go index 9f5aec7..7d0ac7a 100644 --- a/internal/store/otx.go +++ b/internal/store/otx.go @@ -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 +} diff --git a/internal/store/store.go b/internal/store/store.go index 12f6db7..ee369e0 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -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 } ) diff --git a/internal/tasker/task/account_gift_gas.go b/internal/tasker/task/account_gift_gas.go index c9a8e55..6a1350a 100644 --- a/internal/tasker/task/account_gift_gas.go +++ b/internal/tasker/task/account_gift_gas.go @@ -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 diff --git a/internal/tasker/task/account_gift_voucher.go b/internal/tasker/task/account_gift_voucher.go index 6f1dbe0..e718f95 100644 --- a/internal/tasker/task/account_gift_voucher.go +++ b/internal/tasker/task/account_gift_voucher.go @@ -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 { diff --git a/internal/tasker/task/account_refill_gas.go b/internal/tasker/task/account_refill_gas.go index 07623e6..9bd6caf 100644 --- a/internal/tasker/task/account_refill_gas.go +++ b/internal/tasker/task/account_refill_gas.go @@ -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 diff --git a/internal/tasker/task/account_register_onchain.go b/internal/tasker/task/account_register_onchain.go index bc35de6..206d34f 100644 --- a/internal/tasker/task/account_register_onchain.go +++ b/internal/tasker/task/account_register_onchain.go @@ -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 { diff --git a/internal/tasker/task/dispatch_tx.go b/internal/tasker/task/dispatch_tx.go index d0b362a..46cb873 100644 --- a/internal/tasker/task/dispatch_tx.go +++ b/internal/tasker/task/dispatch_tx.go @@ -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) diff --git a/internal/tasker/task/sign_transfer.go b/internal/tasker/task/sign_transfer.go index caf92c5..efe104e 100644 --- a/internal/tasker/task/sign_transfer.go +++ b/internal/tasker/task/sign_transfer.go @@ -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 diff --git a/migrations/002_custodial_db.sql b/migrations/002_custodial_db.sql index ae6c9fb..d6a68de 100644 --- a/migrations/002_custodial_db.sql +++ b/migrations/002_custodial_db.sql @@ -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"); diff --git a/pkg/enum/enum.go b/pkg/enum/enum.go new file mode 100644 index 0000000..5b4e159 --- /dev/null +++ b/pkg/enum/enum.go @@ -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" +) diff --git a/pkg/status/status.go b/pkg/status/status.go deleted file mode 100644 index cc01d90..0000000 --- a/pkg/status/status.go +++ /dev/null @@ -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" -) diff --git a/queries.sql b/queries.sql deleted file mode 100644 index 20c9467..0000000 --- a/queries.sql +++ /dev/null @@ -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 \ No newline at end of file diff --git a/queries/queries.sql b/queries/queries.sql new file mode 100644 index 0000000..3f66cb4 --- /dev/null +++ b/queries/queries.sql @@ -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