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
This commit is contained in:
2023-04-11 10:14:49 +00:00
parent 82294b96f8
commit eba329eefa
22 changed files with 318 additions and 394 deletions

View File

@@ -22,7 +22,7 @@ func HandleAccountCreate(cu *custodial.Custodial) func(echo.Context) error {
return err
}
id, err := cu.Keystore.WriteKeyPair(c.Request().Context(), generatedKeyPair)
id, err := cu.Store.WriteKeyPair(c.Request().Context(), generatedKeyPair)
if err != nil {
return err
}

View File

@@ -40,7 +40,7 @@ func HandleSignTransfer(cu *custodial.Custodial) func(echo.Context) error {
return err
}
accountActive, gasQuota, err := cu.PgStore.GetAccountStatusByAddress(c.Request().Context(), req.From)
accountActive, gasQuota, err := cu.Store.GetAccountStatus(c.Request().Context(), req.From)
if err != nil {
return err
}

View File

@@ -28,7 +28,7 @@ func HandleTrackTx(cu *custodial.Custodial) func(echo.Context) error {
return err
}
txs, err := cu.PgStore.GetTxStatusByTrackingId(c.Request().Context(), txStatusRequest.TrackingId)
txs, err := cu.Store.GetTxStatus(c.Request().Context(), txStatusRequest.TrackingId)
if err != nil {
return err
}
@@ -36,7 +36,7 @@ func HandleTrackTx(cu *custodial.Custodial) func(echo.Context) error {
return c.JSON(http.StatusOK, OkResp{
Ok: true,
Result: H{
"transactions": txs,
"transaction": txs,
},
})
}

View File

@@ -3,16 +3,15 @@ package custodial
import (
"context"
"crypto/ecdsa"
"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/store"
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
"github.com/grassrootseconomics/cic-custodial/pkg/util"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/labstack/gommon/log"
@@ -22,10 +21,9 @@ import (
type (
Opts struct {
CeloProvider *celoutils.Provider
Keystore keystore.Keystore
LockProvider *redislock.Client
Noncestore nonce.Noncestore
PgStore store.Store
Store store.Store
RedisClient *redis.Client
RegistryAddress string
SystemPrivateKey string
@@ -36,10 +34,9 @@ type (
Custodial struct {
Abis map[string]*w3.Func
CeloProvider *celoutils.Provider
Keystore keystore.Keystore
LockProvider *redislock.Client
Noncestore nonce.Noncestore
PgStore store.Store
Store store.Store
RedisClient *redis.Client
RegistryMap map[string]common.Address
SystemPrivateKey *ecdsa.PrivateKey
@@ -49,7 +46,7 @@ type (
)
func NewCustodial(o Opts) (*Custodial, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), util.SLATimeout)
defer cancel()
registryMap, err := o.CeloProvider.RegistryMap(ctx, celoutils.HexToAddress(o.RegistryAddress))
@@ -86,10 +83,9 @@ func NewCustodial(o Opts) (*Custodial, error) {
return &Custodial{
Abis: initAbis(),
CeloProvider: o.CeloProvider,
Keystore: o.Keystore,
LockProvider: o.LockProvider,
Noncestore: o.Noncestore,
PgStore: o.PgStore,
Store: o.Store,
RedisClient: o.RedisClient,
RegistryMap: registryMap,
SystemPrivateKey: privateKey,

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

@@ -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,32 +4,48 @@ import (
"context"
)
func (s *PostgresStore) GetAccountStatusByAddress(ctx context.Context, publicAddress string) (bool, int, error) {
func (s *PgStore) ActivateAccount(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.ActivateAccount,
publicAddress,
); err != nil {
return err
}
return nil
}
func (s *PgStore) GetAccountStatus(
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 {
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 {
func (s *PgStore) DecrGasQuota(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.DecrGasQuota,
@@ -41,7 +57,10 @@ func (s *PostgresStore) DecrGasQuota(ctx context.Context, publicAddress string)
return nil
}
func (s *PostgresStore) ResetGasQuota(ctx context.Context, publicAddress string) error {
func (s *PgStore) ResetGasQuota(
ctx context.Context,
publicAddress string,
) error {
if _, err := s.db.Exec(
ctx,
s.queries.ResetGasQuota,
@@ -52,15 +71,3 @@ func (s *PostgresStore) ResetGasQuota(ctx context.Context, publicAddress string)
return nil
}
func (s *PostgresStore) ActivateAccount(ctx context.Context, publicAddress string) error {
if _, err := s.db.Exec(
ctx,
s.queries.ActivateAccount,
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,52 @@ 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) GetTxStatus(
ctx context.Context,
trackingId string,
) (txStatus, error) {
var (
txs []*TxStatus
tx txStatus
)
if err := pgxscan.Select(
rows, err := s.db.Query(
ctx,
s.db,
&txs,
s.queries.GetTxStatusByTrackingId,
trackingId,
); err != nil {
return nil, err
)
if err != nil {
return tx, err
}
return txs, nil
if err := pgxscan.ScanOne(
&tx,
rows,
); err != nil {
return tx, err
}
return tx, nil
}
func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error {
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 +105,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,49 +2,127 @@ package store
import (
"context"
"math/big"
"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 *big.Int
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)
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, int, error)
// Gas quota related actions.
DecrGasQuota(context.Context, string) error
ResetGasQuota(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"`
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"`
DecrGasQuota string `query:"decr-gas-quota"`
ResetGasQuota string `query:"reset-gas-quota"`
}
)
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

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

View File

@@ -35,7 +35,7 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
_, gasQuota, err := cu.PgStore.GetAccountStatusByAddress(ctx, payload.PublicKey)
_, gasQuota, err := cu.Store.GetAccountStatus(ctx, payload.PublicKey)
if err != nil {
return err
}
@@ -142,7 +142,7 @@ func AccountRefillGasProcessor(cu *custodial.Custodial) func(context.Context, *a
return err
}
id, err := cu.PgStore.CreateOtx(ctx, store.OTX{
id, err := cu.Store.CreateOtx(ctx, store.Otx{
TrackingId: payload.TrackingId,
Type: enum.REFILL_GAS,
RawTx: hexutil.Encode(rawTx),

View File

@@ -82,7 +82,7 @@ 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),
@@ -96,7 +96,7 @@ func AccountRegisterOnChainProcessor(cu *custodial.Custodial) func(context.Conte
if err != nil {
return err
}
disptachJobPayload, err := json.Marshal(TxPayload{
OtxId: id,
Tx: builtTx,

View File

@@ -9,7 +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/store"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/hibiken/asynq"
@@ -24,41 +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
dispathchTx common.Hash
dispatchStatus enum.OtxStatus = enum.IN_NETWORK
)
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return err
}
dispatchStatus.OtxId = payload.OtxId
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 {
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 {
if err := cu.Store.CreateDispatchStatus(ctx, payload.OtxId, dispatchStatus); err != nil {
return fmt.Errorf("dispatch: failed %v: %w", err, asynq.SkipRetry)
}

View File

@@ -47,7 +47,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
}
@@ -89,7 +89,7 @@ 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),
@@ -105,7 +105,7 @@ func SignTransfer(cu *custodial.Custodial) func(context.Context, *asynq.Task) er
return err
}
if err := cu.PgStore.DecrGasQuota(ctx, payload.From); err != nil {
if err := cu.Store.DecrGasQuota(ctx, payload.From); err != nil {
return err
}