refactor: remove action provider

* allows better control over accounting  locking
This commit is contained in:
Mohamed Sohail 2022-10-26 09:11:15 +00:00
parent fe40c0604d
commit 1bc8d65016
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
12 changed files with 109 additions and 230 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"log" "log"
"strings" "strings"
@ -90,6 +91,8 @@ func initKeystore() keystore.Keystore {
} }
func initNoncestore() noncestore.Noncestore { func initNoncestore() noncestore.Noncestore {
var loadedNoncestore noncestore.Noncestore
switch provider := ko.MustString("noncestore.provider"); provider { switch provider := ko.MustString("noncestore.provider"); provider {
case "redis": case "redis":
redisNoncestore, err := redis_noncestore.NewRedisNoncestore(redis_noncestore.Opts{ redisNoncestore, err := redis_noncestore.NewRedisNoncestore(redis_noncestore.Opts{
@ -103,11 +106,22 @@ func initNoncestore() noncestore.Noncestore {
lo.Fatal("initNoncestore", "error", err) lo.Fatal("initNoncestore", "error", err)
} }
return redisNoncestore loadedNoncestore = redisNoncestore
case "postgres": case "postgres":
lo.Fatal("initNoncestore", "error", "not implemented") lo.Fatal("initNoncestore", "error", "not implemented")
default: default:
lo.Fatal("initNoncestore", "error", "no noncestore provider selected") lo.Fatal("initNoncestore", "error", "no noncestore provider selected")
} }
return nil
currentSystemNonce, err := loadedNoncestore.Peek(context.Background(), ko.MustString("admin.public"))
lo.Debug("initNoncestore: loaded (noncestore) system nonce", "nonce", currentSystemNonce)
if err != nil {
nonce, err := loadedNoncestore.SyncNetworkNonce(context.Background(), ko.MustString("admin.public"))
lo.Debug("initNoncestore: syncing system nonce", "nonce", nonce)
if err != nil {
lo.Fatal("initNonceStore", "error", "system account nonce sync failed")
}
}
return loadedNoncestore
} }

View File

@ -8,7 +8,6 @@ import (
"sync" "sync"
"syscall" "syscall"
"github.com/grassrootseconomics/cic-custodial/internal/actions"
"github.com/grassrootseconomics/cic-custodial/internal/api" "github.com/grassrootseconomics/cic-custodial/internal/api"
tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client" tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
tasker_server "github.com/grassrootseconomics/cic-custodial/internal/tasker/server" tasker_server "github.com/grassrootseconomics/cic-custodial/internal/tasker/server"
@ -40,35 +39,32 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop() defer stop()
taskerClient = initTaskerClient() taskerClient := initTaskerClient()
keystore := initKeystore()
noncestore := initNoncestore()
actionsProvider, err := actions.NewActionsProvider(actions.Opts{ httpServer = api.BootstrapHTTPServer(api.Opts{
Keystore: keystore,
TaskerClient: taskerClient,
})
taskerServer, err := tasker_server.NewTaskerServer(tasker_server.Opts{
SystemPublicKey: ko.MustString("admin.public"), SystemPublicKey: ko.MustString("admin.public"),
SystemPrivateKey: ko.MustString("admin.key"), SystemPrivateKey: ko.MustString("admin.key"),
ChainProvider: chainProvider, ChainProvider: chainProvider,
Keystore: initKeystore(), Keystore: keystore,
Noncestore: initNoncestore(), Noncestore: noncestore,
Logger: lo,
})
if err != nil {
lo.Fatal("initActionsProvider", "err", err)
}
httpServer = api.BootstrapHTTPServer(api.Opts{
ActionsProvider: actionsProvider,
TaskerClient: taskerClient,
})
taskerServer = tasker_server.NewTaskerServer(tasker_server.Opts{
ActionsProvider: actionsProvider,
TaskerClient: taskerClient, TaskerClient: taskerClient,
RedisDSN: ko.MustString("redis.dsn"), RedisDSN: ko.MustString("redis.dsn"),
Concurrency: 20,
Logger: lo,
RedisLockDB: 1, RedisLockDB: 1,
RedisLockMinIdleConns: 3, RedisLockMinIdleConns: 3,
RedisLockPoolSize: 6, RedisLockPoolSize: 6,
Concurrency: 20,
Logger: lo,
}) })
if err != nil {
lo.Fatal("initTaskerServer", "err", err)
}
var wg sync.WaitGroup var wg sync.WaitGroup

View File

@ -1,71 +0,0 @@
package actions
import (
"context"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/core/types"
eth_crypto "github.com/ethereum/go-ethereum/crypto"
"github.com/grassrootseconomics/cic-custodial/internal/ethereum"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/noncestore"
"github.com/grassrootseconomics/cic-go-sdk/chain"
"github.com/zerodha/logf"
)
type Actions interface {
CreateNewKeyPair(context.Context) (ethereum.Key, error)
ActivateCustodialAccount(context.Context, string) error
SetNewAccountNonce(context.Context, string) error
SignGiftGasTx(context.Context, string) (*types.Transaction, error)
SignTopUpGasTx(context.Context, string) (*types.Transaction, error)
SignGiftVouchertx(context.Context, string) (*types.Transaction, error)
DispatchSignedTx(context.Context, *types.Transaction) (string, error)
}
type Opts struct {
SystemPublicKey string
SystemPrivateKey string
ChainProvider *chain.Provider
Keystore keystore.Keystore
Noncestore noncestore.Noncestore
Logger logf.Logger
}
type ActionsProvider struct {
SystemPublicKey string
SystemPrivateKey *ecdsa.PrivateKey
ChainProvider *chain.Provider
Keystore keystore.Keystore
Noncestore noncestore.Noncestore
Lo logf.Logger
}
func NewActionsProvider(o Opts) (*ActionsProvider, error) {
var _ Actions = (*ActionsProvider)(nil)
loadedPrivateKey, err := eth_crypto.HexToECDSA(o.SystemPrivateKey)
if err != nil {
return nil, err
}
_, err = o.Noncestore.Peek(context.Background(), o.SystemPublicKey)
if err != nil {
nonce, err := o.Noncestore.SyncNetworkNonce(context.Background(), o.SystemPublicKey)
o.Logger.Debug("actionsProvider: syncing system nonce", "nonce", nonce)
if err != nil {
return nil, err
}
}
return &ActionsProvider{
SystemPublicKey: o.SystemPublicKey,
SystemPrivateKey: loadedPrivateKey,
ChainProvider: o.ChainProvider,
Keystore: o.Keystore,
Noncestore: o.Noncestore,
Lo: o.Logger,
}, nil
}

View File

@ -1,22 +0,0 @@
package actions
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/lmittmann/w3/module/eth"
)
func (ap *ActionsProvider) DispatchSignedTx(ctx context.Context, builtTx *types.Transaction) (string, error) {
var txHash common.Hash
if err := ap.ChainProvider.EthClient.CallCtx(
ctx,
eth.SendTx(builtTx).Returns(&txHash),
); err != nil {
return "", err
}
return txHash.String(), nil
}

View File

@ -1,36 +0,0 @@
package actions
import (
"context"
"github.com/grassrootseconomics/cic-custodial/internal/ethereum"
)
func (ap *ActionsProvider) CreateNewKeyPair(ctx context.Context) (ethereum.Key, error) {
generatedKeyPair, err := ethereum.GenerateKeyPair()
if err != nil {
return ethereum.Key{}, err
}
if err := ap.Keystore.WriteKeyPair(ctx, generatedKeyPair); err != nil {
return ethereum.Key{}, err
}
return generatedKeyPair, nil
}
func (ap *ActionsProvider) ActivateCustodialAccount(ctx context.Context, publicKey string) error {
if err := ap.Keystore.ActivateAccount(ctx, publicKey); err != nil {
return err
}
return nil
}
func (ap *ActionsProvider) SetNewAccountNonce(ctx context.Context, publicKey string) error {
if err := ap.Noncestore.SetNewAccountNonce(ctx, publicKey); err != nil {
return err
}
return nil
}

View File

@ -1,53 +0,0 @@
package actions
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/grassrootseconomics/cic-go-sdk/chain"
"github.com/lmittmann/w3"
)
const (
initialGiftGasValue = 1000000
topupGiftGasValue = 500000
)
func (ap *ActionsProvider) SignGiftGasTx(ctx context.Context, giftTo string) (*types.Transaction, error) {
nonce, err := ap.Noncestore.Acquire(ctx, ap.SystemPublicKey)
if err != nil {
return nil, err
}
builtTx, err := ap.ChainProvider.BuildGasTransferTx(ap.SystemPrivateKey, chain.TransactionData{
To: w3.A(giftTo),
Nonce: nonce,
}, big.NewInt(initialGiftGasValue))
if err != nil {
return &types.Transaction{}, err
}
return builtTx, nil
}
func (ap *ActionsProvider) SignTopUpGasTx(ctx context.Context, giftTo string) (*types.Transaction, error) {
nonce, err := ap.Noncestore.Acquire(ctx, ap.SystemPublicKey)
if err != nil {
return nil, err
}
builtTx, err := ap.ChainProvider.BuildGasTransferTx(ap.SystemPrivateKey, chain.TransactionData{
To: w3.A(giftTo),
Nonce: nonce,
}, big.NewInt(topupGiftGasValue))
if err != nil {
return &types.Transaction{}, err
}
return builtTx, nil
}
func (ap *ActionsProvider) SignGiftVouchertx(ctx context.Context, giftTo string) (*types.Transaction, error) {
return &types.Transaction{}, nil
}

View File

@ -3,7 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/grassrootseconomics/cic-custodial/internal/actions" "github.com/grassrootseconomics/cic-custodial/internal/ethereum"
"github.com/grassrootseconomics/cic-custodial/internal/keystore"
tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client" tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -15,15 +16,19 @@ type registrationResponse struct {
func handleRegistration(c echo.Context) error { func handleRegistration(c echo.Context) error {
var ( var (
ap = c.Get("actions").(*actions.ActionsProvider)
tc = c.Get("tasker_client").(*tasker_client.TaskerClient) tc = c.Get("tasker_client").(*tasker_client.TaskerClient)
ks = c.Get("keystore").(keystore.Keystore)
) )
generatedKeyPair, err := ap.CreateNewKeyPair(c.Request().Context()) generatedKeyPair, err := ethereum.GenerateKeyPair()
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "ERR_GEN_KEYPAIR") return echo.NewHTTPError(http.StatusInternalServerError, "ERR_GEN_KEYPAIR")
} }
if err := ks.WriteKeyPair(c.Request().Context(), generatedKeyPair); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "ERR_SAVE_KEYPAIR")
}
job, err := tc.CreateRegistrationTask(tasker_client.RegistrationPayload{ job, err := tc.CreateRegistrationTask(tasker_client.RegistrationPayload{
PublicKey: generatedKeyPair.Public, PublicKey: generatedKeyPair.Public,
}, tasker_client.SetNewAccountNonceTask) }, tasker_client.SetNewAccountNonceTask)

View File

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"github.com/arl/statsviz" "github.com/arl/statsviz"
"github.com/grassrootseconomics/cic-custodial/internal/actions" "github.com/grassrootseconomics/cic-custodial/internal/keystore"
tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client" tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -14,7 +14,7 @@ type okResp struct {
} }
type Opts struct { type Opts struct {
ActionsProvider *actions.ActionsProvider Keystore keystore.Keystore
TaskerClient *tasker_client.TaskerClient TaskerClient *tasker_client.TaskerClient
} }
@ -31,7 +31,7 @@ func BootstrapHTTPServer(o Opts) *echo.Echo {
server.Use(func(next echo.HandlerFunc) echo.HandlerFunc { server.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
c.Set("actions", o.ActionsProvider) c.Set("keystore", o.Keystore)
c.Set("tasker_client", o.TaskerClient) c.Set("tasker_client", o.TaskerClient)
return next(c) return next(c)
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/grassrootseconomics/cic-go-sdk/chain" "github.com/grassrootseconomics/cic-go-sdk/chain"
"github.com/lmittmann/w3" "github.com/lmittmann/w3"
"github.com/lmittmann/w3/module/eth" "github.com/lmittmann/w3/module/eth"
"github.com/zerodha/logf"
) )
// Opts represents the Redis nonce store specific params // Opts represents the Redis nonce store specific params
@ -17,12 +18,14 @@ type Opts struct {
MinIdleConns int MinIdleConns int
PoolSize int PoolSize int
ChainProvider *chain.Provider ChainProvider *chain.Provider
Lo logf.Logger
} }
// RedisNoncestore implements `noncestore.Noncestore` // RedisNoncestore implements `noncestore.Noncestore`
type RedisNoncestore struct { type RedisNoncestore struct {
chainProvider *chain.Provider chainProvider *chain.Provider
redis *redis.Client redis *redis.Client
lo logf.Logger
} }
func NewRedisNoncestore(o Opts) (noncestore.Noncestore, error) { func NewRedisNoncestore(o Opts) (noncestore.Noncestore, error) {
@ -40,6 +43,7 @@ func NewRedisNoncestore(o Opts) (noncestore.Noncestore, error) {
return &RedisNoncestore{ return &RedisNoncestore{
redis: redisClient, redis: redisClient,
chainProvider: o.ChainProvider, chainProvider: o.ChainProvider,
lo: o.Lo,
}, nil }, nil
} }
@ -117,6 +121,7 @@ func (ns *RedisNoncestore) SyncNetworkNonce(ctx context.Context, publicKey strin
func (ns *RedisNoncestore) SetNewAccountNonce(ctx context.Context, publicKey string) error { func (ns *RedisNoncestore) SetNewAccountNonce(ctx context.Context, publicKey string) error {
err := ns.redis.Set(ctx, publicKey, 0, 0).Err() err := ns.redis.Set(ctx, publicKey, 0, 0).Err()
if err != nil { if err != nil {
ns.lo.Error("noncestore", "err", err)
return err return err
} }

View File

@ -5,21 +5,26 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/client" "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/lmittmann/w3/module/eth"
) )
func (tp *TaskerProcessor) txDispatcher(ctx context.Context, t *asynq.Task) error { func (tp *TaskerProcessor) txDispatcher(ctx context.Context, t *asynq.Task) error {
var ( var (
p client.TxPayload p client.TxPayload
txHash common.Hash
) )
if err := json.Unmarshal(t.Payload(), &p); err != nil { if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
} }
_, err := tp.ActionsProvider.DispatchSignedTx(ctx, p.Tx) if err := tp.ChainProvider.EthClient.CallCtx(
if err != nil { ctx,
eth.SendTx(p.Tx).Returns(&txHash),
); err != nil {
return err return err
} }

View File

@ -4,9 +4,16 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big"
"github.com/grassrootseconomics/cic-custodial/internal/tasker/client" "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
"github.com/grassrootseconomics/cic-go-sdk/chain"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/lmittmann/w3"
)
const (
initialGiftGasValue = 1000000
) )
func (tp *TaskerProcessor) setNewAccountNonce(ctx context.Context, t *asynq.Task) error { func (tp *TaskerProcessor) setNewAccountNonce(ctx context.Context, t *asynq.Task) error {
@ -18,7 +25,7 @@ func (tp *TaskerProcessor) setNewAccountNonce(ctx context.Context, t *asynq.Task
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
} }
if err := tp.ActionsProvider.SetNewAccountNonce(ctx, p.PublicKey); err != nil { if err := tp.Noncestore.SetNewAccountNonce(ctx, p.PublicKey); err != nil {
return err return err
} }
@ -39,19 +46,27 @@ func (tp *TaskerProcessor) giftGasProcessor(ctx context.Context, t *asynq.Task)
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
} }
lock, err := tp.LockProvider.Obtain(ctx, tp.ActionsProvider.SystemPublicKey, LockTTL, nil) lock, err := tp.LockProvider.Obtain(ctx, tp.SystemPublicKey, LockTTL, nil)
if err != nil { if err != nil {
return err return err
} }
defer lock.Release(ctx) defer lock.Release(ctx)
signedTx, err := tp.ActionsProvider.SignGiftGasTx(ctx, p.PublicKey) nonce, err := tp.Noncestore.Acquire(ctx, tp.SystemPublicKey)
if err != nil {
return err
}
builtTx, err := tp.ChainProvider.BuildGasTransferTx(tp.SystemPrivateKey, chain.TransactionData{
To: w3.A(p.PublicKey),
Nonce: nonce,
}, big.NewInt(initialGiftGasValue))
if err != nil { if err != nil {
return err return err
} }
_, err = tp.TaskerClient.CreateTxDispatchTask(client.TxPayload{ _, err = tp.TaskerClient.CreateTxDispatchTask(client.TxPayload{
Tx: signedTx, Tx: builtTx,
}, client.TxDispatchTask) }, client.TxDispatchTask)
if err != nil { if err != nil {
return err return err
@ -74,7 +89,7 @@ func (tp *TaskerProcessor) activateAccountProcessor(ctx context.Context, t *asyn
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry) return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
} }
if err := tp.ActionsProvider.ActivateCustodialAccount(ctx, p.PublicKey); err != nil { if err := tp.Keystore.ActivateAccount(ctx, p.PublicKey); err != nil {
return err return err
} }

View File

@ -1,12 +1,16 @@
package server package server
import ( import (
"crypto/ecdsa"
"time" "time"
"github.com/bsm/redislock" "github.com/bsm/redislock"
eth_crypto "github.com/ethereum/go-ethereum/crypto"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/grassrootseconomics/cic-custodial/internal/actions" "github.com/grassrootseconomics/cic-custodial/internal/keystore"
"github.com/grassrootseconomics/cic-custodial/internal/noncestore"
tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client" tasker_client "github.com/grassrootseconomics/cic-custodial/internal/tasker/client"
"github.com/grassrootseconomics/cic-go-sdk/chain"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"github.com/zerodha/logf" "github.com/zerodha/logf"
) )
@ -16,7 +20,11 @@ const (
) )
type Opts struct { type Opts struct {
ActionsProvider *actions.ActionsProvider SystemPublicKey string
SystemPrivateKey string
ChainProvider *chain.Provider
Keystore keystore.Keystore
Noncestore noncestore.Noncestore
TaskerClient *tasker_client.TaskerClient TaskerClient *tasker_client.TaskerClient
RedisDSN string RedisDSN string
RedisLockDB int RedisLockDB int
@ -27,8 +35,12 @@ type Opts struct {
} }
type TaskerProcessor struct { type TaskerProcessor struct {
SystemPublicKey string
SystemPrivateKey *ecdsa.PrivateKey
ChainProvider *chain.Provider
Noncestore noncestore.Noncestore
Keystore keystore.Keystore
LockProvider *redislock.Client LockProvider *redislock.Client
ActionsProvider *actions.ActionsProvider
TaskerClient *tasker_client.TaskerClient TaskerClient *tasker_client.TaskerClient
} }
@ -37,7 +49,12 @@ type TaskerServer struct {
Mux *asynq.ServeMux Mux *asynq.ServeMux
} }
func NewTaskerServer(o Opts) *TaskerServer { func NewTaskerServer(o Opts) (*TaskerServer, error) {
loadedPrivateKey, err := eth_crypto.HexToECDSA(o.SystemPrivateKey)
if err != nil {
return nil, err
}
redisLockClient := redis.NewClient(&redis.Options{ redisLockClient := redis.NewClient(&redis.Options{
Addr: o.RedisDSN, Addr: o.RedisDSN,
DB: o.RedisLockDB, DB: o.RedisLockDB,
@ -46,7 +63,11 @@ func NewTaskerServer(o Opts) *TaskerServer {
}) })
taskerProcessor := &TaskerProcessor{ taskerProcessor := &TaskerProcessor{
ActionsProvider: o.ActionsProvider, SystemPublicKey: o.SystemPublicKey,
SystemPrivateKey: loadedPrivateKey,
ChainProvider: o.ChainProvider,
Noncestore: o.Noncestore,
Keystore: o.Keystore,
TaskerClient: o.TaskerClient, TaskerClient: o.TaskerClient,
LockProvider: redislock.New(redisLockClient), LockProvider: redislock.New(redisLockClient),
} }
@ -85,5 +106,5 @@ func NewTaskerServer(o Opts) *TaskerServer {
return &TaskerServer{ return &TaskerServer{
Server: asynqServer, Server: asynqServer,
Mux: mux, Mux: mux,
} }, nil
} }