mirror of
https://github.com/grassrootseconomics/cic-custodial.git
synced 2024-11-21 13:56:47 +01:00
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
This commit is contained in:
parent
b137088d38
commit
0e5db7f06f
@ -110,9 +110,11 @@ func initCommonRedisPool() *redis.RedisPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load redis backed noncestore.
|
// Load redis backed noncestore.
|
||||||
func initRedisNoncestore(redisPool *redis.RedisPool) nonce.Noncestore {
|
func initRedisNoncestore(redisPool *redis.RedisPool, chainProvider *celoutils.Provider, store store.Store) nonce.Noncestore {
|
||||||
return nonce.NewRedisNoncestore(nonce.Opts{
|
return nonce.NewRedisNoncestore(nonce.Opts{
|
||||||
RedisPool: redisPool,
|
ChainProvider: chainProvider,
|
||||||
|
RedisPool: redisPool,
|
||||||
|
Store: store,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func main() {
|
|||||||
redisPool := initCommonRedisPool()
|
redisPool := initCommonRedisPool()
|
||||||
|
|
||||||
store := initPgStore()
|
store := initPgStore()
|
||||||
redisNoncestore := initRedisNoncestore(redisPool)
|
redisNoncestore := initRedisNoncestore(redisPool, celoProvider, store)
|
||||||
lockProvider := initLockProvider(redisPool.Client)
|
lockProvider := initLockProvider(redisPool.Client)
|
||||||
taskerClient := initTaskerClient(asynqRedisPool)
|
taskerClient := initTaskerClient(asynqRedisPool)
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
|
"github.com/grassrootseconomics/cic-custodial/internal/tasker"
|
||||||
"github.com/grassrootseconomics/cic-custodial/pkg/util"
|
"github.com/grassrootseconomics/cic-custodial/pkg/util"
|
||||||
"github.com/grassrootseconomics/w3-celo-patch"
|
"github.com/grassrootseconomics/w3-celo-patch"
|
||||||
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
|
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
@ -56,22 +55,7 @@ func NewCustodial(o Opts) (*Custodial, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = o.Noncestore.Peek(ctx, o.SystemPublicKey)
|
_, err = o.Noncestore.Peek(ctx, o.SystemPublicKey)
|
||||||
if err == redis.Nil {
|
if err != nil {
|
||||||
// TODO: Bootsrap from Postgres first
|
|
||||||
var networkNonce uint64
|
|
||||||
|
|
||||||
err := o.CeloProvider.Client.CallCtx(
|
|
||||||
ctx,
|
|
||||||
eth.Nonce(celoutils.HexToAddress(o.SystemPublicKey), nil).Returns(&networkNonce),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := o.Noncestore.SetAccountNonce(ctx, o.SystemPublicKey, networkNonce); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,30 +2,47 @@ package nonce
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/grassrootseconomics/celoutils"
|
||||||
|
"github.com/grassrootseconomics/cic-custodial/internal/store"
|
||||||
redispool "github.com/grassrootseconomics/cic-custodial/pkg/redis"
|
redispool "github.com/grassrootseconomics/cic-custodial/pkg/redis"
|
||||||
|
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Opts struct {
|
Opts struct {
|
||||||
RedisPool *redispool.RedisPool
|
ChainProvider *celoutils.Provider
|
||||||
|
RedisPool *redispool.RedisPool
|
||||||
|
Store store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedisNoncestore implements `Noncestore`
|
// RedisNoncestore implements `Noncestore`
|
||||||
RedisNoncestore struct {
|
RedisNoncestore struct {
|
||||||
redis *redispool.RedisPool
|
chainProvider *celoutils.Provider
|
||||||
|
redis *redispool.RedisPool
|
||||||
|
store store.Store
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRedisNoncestore(o Opts) Noncestore {
|
func NewRedisNoncestore(o Opts) Noncestore {
|
||||||
return &RedisNoncestore{
|
return &RedisNoncestore{
|
||||||
redis: o.RedisPool,
|
chainProvider: o.ChainProvider,
|
||||||
|
redis: o.RedisPool,
|
||||||
|
store: o.Store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *RedisNoncestore) Peek(ctx context.Context, publicKey string) (uint64, error) {
|
func (n *RedisNoncestore) Peek(ctx context.Context, publicKey string) (uint64, error) {
|
||||||
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
|
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
|
||||||
if err != nil {
|
if err == redis.Nil {
|
||||||
|
nonce, err = n.bootstrap(ctx, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,8 +55,13 @@ func (n *RedisNoncestore) Acquire(ctx context.Context, publicKey string) (uint64
|
|||||||
)
|
)
|
||||||
|
|
||||||
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
|
nonce, err := n.redis.Client.Get(ctx, publicKey).Uint64()
|
||||||
if err != nil {
|
if err == redis.Nil {
|
||||||
return 0, nil
|
nonce, err = n.bootstrap(ctx, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = n.redis.Client.Incr(ctx, publicKey).Err()
|
err = n.redis.Client.Incr(ctx, publicKey).Err()
|
||||||
@ -73,3 +95,30 @@ func (n *RedisNoncestore) SetAccountNonce(ctx context.Context, publicKey string,
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -61,6 +61,27 @@ func (s *PgStore) CreateOtx(
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PgStore) GetNextNonce(
|
||||||
|
ctx context.Context,
|
||||||
|
publicAddress string,
|
||||||
|
) (uint64, error) {
|
||||||
|
var (
|
||||||
|
lastNonce uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := s.db.QueryRow(
|
||||||
|
ctx,
|
||||||
|
s.queries.GetNextNonce,
|
||||||
|
publicAddress,
|
||||||
|
).Scan(
|
||||||
|
&lastNonce,
|
||||||
|
); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastNonce, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PgStore) GetTxStatus(
|
func (s *PgStore) GetTxStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
trackingId string,
|
trackingId string,
|
||||||
|
@ -21,6 +21,7 @@ type (
|
|||||||
WriteKeyPair(context.Context, keypair.Key) (uint, error)
|
WriteKeyPair(context.Context, keypair.Key) (uint, error)
|
||||||
// Otx related actions.
|
// Otx related actions.
|
||||||
CreateOtx(context.Context, Otx) (uint, error)
|
CreateOtx(context.Context, Otx) (uint, error)
|
||||||
|
GetNextNonce(context.Context, string) (uint64, error)
|
||||||
GetTxStatus(context.Context, string) (txStatus, error)
|
GetTxStatus(context.Context, string) (txStatus, error)
|
||||||
CreateDispatchStatus(context.Context, uint, enum.OtxStatus) error
|
CreateDispatchStatus(context.Context, uint, enum.OtxStatus) error
|
||||||
UpdateDispatchStatus(context.Context, bool, string, uint64) error
|
UpdateDispatchStatus(context.Context, bool, string, uint64) error
|
||||||
@ -49,6 +50,7 @@ type (
|
|||||||
LoadKeyPair string `query:"load-key-pair"`
|
LoadKeyPair string `query:"load-key-pair"`
|
||||||
// Otx related queries.
|
// Otx related queries.
|
||||||
CreateOTX string `query:"create-otx"`
|
CreateOTX string `query:"create-otx"`
|
||||||
|
GetNextNonce string `query:"get-next-nonce"`
|
||||||
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
|
GetTxStatusByTrackingId string `query:"get-tx-status-by-tracking-id"`
|
||||||
CreateDispatchStatus string `query:"create-dispatch-status"`
|
CreateDispatchStatus string `query:"create-dispatch-status"`
|
||||||
UpdateDispatchStatus string `query:"update-dispatch-status"`
|
UpdateDispatchStatus string `query:"update-dispatch-status"`
|
||||||
|
@ -34,6 +34,11 @@ INSERT INTO otx_sign(
|
|||||||
nonce
|
nonce
|
||||||
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id
|
) 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
|
--name: get-tx-status-by-tracking-id
|
||||||
-- Gets tx status's from possible multiple txs with the same tracking_id
|
-- Gets tx status's from possible multiple txs with the same tracking_id
|
||||||
-- $1: tracking_id
|
-- $1: tracking_id
|
||||||
|
Loading…
Reference in New Issue
Block a user