add: wip jetstream durable consumer

This commit is contained in:
Mohamed Sohail 2023-03-02 09:12:39 +00:00
parent 1ddff06502
commit a1b6cb08d8
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
8 changed files with 80 additions and 34 deletions

View File

@ -48,13 +48,15 @@ func initConfig(configFilePath string) *koanf.Koanf {
lo.Fatal("Could not load config file", "error", err) lo.Fatal("Could not load config file", "error", err)
} }
if err := ko.Load(env.Provider("", ".", func(s string) string { if err := ko.Load(env.Provider("CUSTODIAL_", ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower( return strings.ReplaceAll(strings.ToLower(
strings.TrimPrefix(s, "")), "_", ".") strings.TrimPrefix(s, "CUSTODIAL_")), "__", ".")
}), nil); err != nil { }), nil); err != nil {
lo.Fatal("Could not override config from env vars", "error", err) lo.Fatal("Could not override config from env vars", "error", err)
} }
ko.Print()
return ko return ko
} }
@ -180,6 +182,7 @@ func initPostgresStore(postgresPool *pgxpool.Pool, queries *queries.Queries) sto
// Init JetStream context for tasker events. // Init JetStream context for tasker events.
func initJetStream() (*events.JetStream, error) { func initJetStream() (*events.JetStream, error) {
jsEmitter, err := events.NewJetStreamEventEmitter(events.JetStreamOpts{ jsEmitter, err := events.NewJetStreamEventEmitter(events.JetStreamOpts{
Logg: lo,
ServerUrl: ko.MustString("jetstream.endpoint"), ServerUrl: ko.MustString("jetstream.endpoint"),
PersistDuration: time.Duration(ko.MustInt("jetstream.persist_duration_hrs")) * time.Hour, PersistDuration: time.Duration(ko.MustInt("jetstream.persist_duration_hrs")) * time.Hour,
DedupDuration: time.Duration(ko.MustInt("jetstream.dedup_duration_hrs")) * time.Hour, DedupDuration: time.Duration(ko.MustInt("jetstream.dedup_duration_hrs")) * time.Hour,

View File

@ -128,6 +128,15 @@ func main() {
} }
}() }()
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("Starting jetstream subscriber")
if err := jsEventEmitter.ChainSubscription(ctx, pgStore); err != nil {
lo.Fatal("main: jetstream subscriber", "err", err)
}
}()
<-ctx.Done() <-ctx.Done()
lo.Info("main: stopping tasker") lo.Info("main: stopping tasker")

View File

@ -7,6 +7,7 @@ metrics = true
[chain] [chain]
rpc_endpoint = "" rpc_endpoint = ""
testnet = true testnet = true
devnet = false
[system] [system]
# System default values # System default values

View File

@ -62,6 +62,7 @@ func NewJetStreamEventEmitter(o JetStreamOpts) (*JetStream, error) {
} }
return &JetStream{ return &JetStream{
logg: o.Logg,
jsCtx: js, jsCtx: js,
natsConn: natsConn, natsConn: natsConn,
}, nil }, nil

View File

@ -2,21 +2,29 @@ package events
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"time"
"github.com/grassrootseconomics/cic-custodial/internal/store" "github.com/grassrootseconomics/cic-custodial/internal/store"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
) )
const ( const (
backOffTimer = 2 * time.Second
durableId = "cic-custodial" durableId = "cic-custodial"
pullStream = "CHAIN" pullStream = "CHAIN"
pullSubject = "CHAIN.*" pullSubject = "CHAIN.*"
) )
func (js *JetStream) ChainSubscription(ctx context.Context, store store.Store) error { func (js *JetStream) ChainSubscription(ctx context.Context, pgStore store.Store) error {
_, err := js.jsCtx.AddConsumer(pullStream, &nats.ConsumerConfig{
Durable: durableId,
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: pullSubject,
})
if err != nil {
return err
}
subOpts := []nats.SubOpt{ subOpts := []nats.SubOpt{
nats.ManualAck(), nats.ManualAck(),
nats.Bind(pullStream, durableId), nats.Bind(pullStream, durableId),
@ -31,23 +39,34 @@ func (js *JetStream) ChainSubscription(ctx context.Context, store store.Store) e
select { select {
case <-ctx.Done(): case <-ctx.Done():
js.logg.Info("jetstream chain sub: shutdown signal received") js.logg.Info("jetstream chain sub: shutdown signal received")
js.Close()
return nil return nil
default: default:
events, err := natsSub.Fetch(1) events, err := natsSub.Fetch(1)
if err != nil { if err != nil {
if errors.Is(err, nats.ErrTimeout) { if errors.Is(err, nats.ErrTimeout) {
// Supressed retry
js.logg.Error("jetstream chain sub: fetch NATS timeout", "error", err)
time.Sleep(backOffTimer)
continue continue
} else { } else {
js.logg.Error("jetstream chain sub: fetch other error", "error", err) js.logg.Error("jetstream chain sub: fetch other error", "error", err)
} }
} }
if len(events) > 0 { if len(events) == 0 {
// TODO: Unmarshal continue
// TODO: UpdateOtxStatus
} }
var (
chainEvent store.MinimalTxInfo
)
if err := json.Unmarshal(events[0].Data, &chainEvent); err != nil {
js.logg.Error("jetstream chain sub: json unmarshal fail", "error", err)
}
if err := pgStore.UpdateOtxStatusFromChainEvent(context.Background(), chainEvent); err != nil {
events[0].Nak()
js.logg.Error("jetstream chain sub: otx marker failed to update state", "error", err)
}
events[0].Ack()
js.logg.Debug("jetstream chain sub: successfully updated status", "tx", chainEvent.TxHash)
} }
} }

View File

@ -2,8 +2,6 @@ package store
import ( import (
"context" "context"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
) )
func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error { func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error {
@ -18,17 +16,3 @@ func (s *PostgresStore) CreateDispatchStatus(ctx context.Context, dispatch Dispa
return nil 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

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/georgysavva/scany/v2/pgxscan" "github.com/georgysavva/scany/v2/pgxscan"
"github.com/grassrootseconomics/cic-custodial/pkg/enum"
) )
type TxStatus struct { type TxStatus struct {
@ -58,6 +59,24 @@ func (s *PostgresStore) GetTxStatusByTrackingId(ctx context.Context, trackingId
return txs, nil return txs, nil
} }
func (s *PostgresStore) UpdateOtxStatus(ctx context.Context, status string) error { func (s *PostgresStore) UpdateOtxStatusFromChainEvent(ctx context.Context, chainEvent MinimalTxInfo) error {
var (
status = enum.SUCCESS
)
if !chainEvent.Success {
status = enum.REVERTED
}
if _, err := s.db.Exec(
ctx,
s.queries.UpdateChainStatus,
chainEvent.TxHash,
status,
chainEvent.Block,
); err != nil {
return err
}
return nil return nil
} }

View File

@ -7,6 +7,16 @@ import (
) )
type ( 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 { OTX struct {
TrackingId string TrackingId string
Type enum.OtxType Type enum.OtxType
@ -29,6 +39,6 @@ type (
CreateOtx(ctx context.Context, otx OTX) (id uint, err error) CreateOtx(ctx context.Context, otx OTX) (id uint, err error)
CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error CreateDispatchStatus(ctx context.Context, dispatch DispatchStatus) error
GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error) GetTxStatusByTrackingId(ctx context.Context, trackingId string) ([]*TxStatus, error)
UpdateChainStatus(ctx context.Context, txHash string, status enum.OtxStatus, block uint64) error UpdateOtxStatusFromChainEvent(ctx context.Context, chainEvent MinimalTxInfo) error
} }
) )