Files
3a9331169b Update avalanchego to v1.9.0-fuji (#472)
Co-authored-by: dhrubabasu <[email protected]>
Co-authored-by: Patrick O'Grady <[email protected]>
Co-authored-by: Stephen <[email protected]>
2022-10-02 20:20:49 -04:00

728 lines
17 KiB
Go

package replay
import (
"context"
"encoding/json"
"fmt"
"log"
"sort"
"sync/atomic"
"time"
"github.com/ava-labs/ortelius/servicesctrl"
"go.uber.org/zap"
"github.com/ava-labs/ortelius/db"
"github.com/ava-labs/avalanchego/ids"
avlancheGoUtils "github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/ortelius/cfg"
"github.com/ava-labs/ortelius/modelsc"
"github.com/ava-labs/ortelius/services"
"github.com/ava-labs/ortelius/services/indexes/avm"
"github.com/ava-labs/ortelius/services/indexes/cvm"
"github.com/ava-labs/ortelius/services/indexes/pvm"
"github.com/ava-labs/ortelius/stream"
"github.com/ava-labs/ortelius/stream/consumers"
"github.com/ava-labs/ortelius/utils"
)
type Replay interface {
Start() error
}
type ConsumeType uint32
var (
CONSUME ConsumeType = 1
CONSUMECONSENSUS ConsumeType = 2
CONSUMEC ConsumeType = 3
CONSUMECTRC ConsumeType = 4
CONSUMECLOG ConsumeType = 5
)
type WorkerPacket struct {
writer services.Consumer
cwriter *cvm.Writer
message services.Consumable
consumeType ConsumeType
block *modelsc.Block
}
type TxPoolID struct {
ID string
}
func NewDB(sc *servicesctrl.Control, config *cfg.Config, replayqueuesize int, replayqueuethreads int) Replay {
return &dbReplay{
sc: sc,
config: config,
counterAdded: utils.NewCounterID(),
counterWaits: utils.NewCounterID(),
queueSize: replayqueuesize,
queueTheads: replayqueuethreads,
}
}
type dbReplay struct {
errs *avlancheGoUtils.AtomicInterface
sc *servicesctrl.Control
config *cfg.Config
conns *utils.Connections
counterAdded *utils.CounterID
counterWaits *utils.CounterID
queueSize int
queueTheads int
persist db.Persist
}
func (replay *dbReplay) Start() error {
cfg.PerformUpdates = true
replay.persist = db.NewPersist()
replay.errs = &avlancheGoUtils.AtomicInterface{}
worker := utils.NewWorker(replay.queueSize, replay.queueTheads, replay.workerProcessor())
waitGroup := new(int64)
conns, err := replay.sc.Database()
if err != nil {
return err
}
replay.conns = conns
for _, chainID := range replay.config.Chains {
err := replay.handleReader(chainID, waitGroup, worker, conns)
if err != nil {
log.Fatalln("reader failed", chainID, ":", err.Error())
return err
}
}
err = replay.handleCReader(replay.config.CchainID, waitGroup, worker)
if err != nil {
log.Fatalln("reader failed", replay.config.CchainID, ":", err.Error())
return err
}
timeLog := time.Now()
logemit := func(waitGroupCnt int64) {
type CounterValues struct {
Added int64
Waits int64
}
ctot := make(map[string]*CounterValues)
countersValues := replay.counterAdded.Clone()
for cnter := range countersValues {
if _, ok := ctot[cnter]; !ok {
ctot[cnter] = &CounterValues{}
}
ctot[cnter].Added = countersValues[cnter]
}
countersValues = replay.counterWaits.Clone()
for cnter := range countersValues {
if _, ok := ctot[cnter]; !ok {
ctot[cnter] = &CounterValues{}
}
ctot[cnter].Waits = countersValues[cnter]
}
replay.sc.Log.Info("replaying db",
zap.Int64("wgc", waitGroupCnt),
zap.Int64("jobs", worker.JobCnt()),
)
var sortedcnters []string
for cnter := range ctot {
sortedcnters = append(sortedcnters, cnter)
}
sort.Strings(sortedcnters)
for _, cnter := range sortedcnters {
if ctot[cnter].Waits != 0 {
newlogline := fmt.Sprintf("key:%s add:%d wait:%d", cnter, ctot[cnter].Added, ctot[cnter].Waits)
replay.sc.Log.Info(newlogline)
}
}
}
var waitGroupCnt int64
for {
waitGroupCnt = atomic.LoadInt64(waitGroup)
if waitGroupCnt == 0 && worker.JobCnt() == 0 {
break
}
if time.Since(timeLog).Seconds() > 30 {
timeLog = time.Now()
logemit(waitGroupCnt)
}
time.Sleep(time.Second)
}
logemit(waitGroupCnt)
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Error("replaying db failed",
zap.Error(err),
)
return err
}
return nil
}
func (replay *dbReplay) handleCReader(chain string, waitGroup *int64, worker utils.Worker) error {
writer, err := cvm.NewWriter(replay.config.NetworkID, chain)
if err != nil {
return err
}
err = replay.startCchain(chain, waitGroup, worker, writer)
if err != nil {
return err
}
err = replay.startCchainTrc(chain, waitGroup, worker, writer)
if err != nil {
return err
}
err = replay.startCchainLog(chain, waitGroup, worker, writer)
if err != nil {
return err
}
return nil
}
func (replay *dbReplay) handleReader(chain cfg.Chain, waitGroup *int64, worker utils.Worker, conns *utils.Connections) error {
var err error
var writer services.Consumer
switch chain.VMType {
case consumers.IndexerAVMName:
writer, err = avm.NewWriter(replay.config.NetworkID, chain.ID)
if err != nil {
return err
}
case consumers.IndexerPVMName:
writer, err = pvm.NewWriter(replay.config.NetworkID, chain.ID)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown vmtype")
}
{
tn := fmt.Sprintf("%d-%s", replay.config.NetworkID, chain.ID)
replay.sc.Log.Info("starting bootstrap replay",
zap.String("topic", tn),
)
ctx := context.Background()
err := writer.Bootstrap(ctx, conns, replay.persist)
replay.sc.Log.Info("finished bootstrap replay",
zap.String("topic", tn),
zap.Error(err),
)
if err != nil {
replay.errs.SetValue(err)
return err
}
}
err = replay.startDecision(chain, waitGroup, worker, writer)
if err != nil {
return err
}
err = replay.startConsensus(chain, waitGroup, worker, writer)
if err != nil {
return err
}
return nil
}
func (replay *dbReplay) workerProcessor() func(int, interface{}) {
return func(_ int, valuei interface{}) {
ctx := context.Background()
switch value := valuei.(type) {
case *WorkerPacket:
var consumererr error
switch value.consumeType {
case CONSUME:
rsleep := utils.NewRetrySleeper(1, 100*time.Millisecond, time.Second)
for {
consumererr = value.writer.Consume(ctx, replay.conns, value.message, replay.persist)
if !utils.ErrIsLockError(consumererr) {
break
}
rsleep.Inc()
}
if consumererr != nil {
replay.errs.SetValue(consumererr)
return
}
case CONSUMECONSENSUS:
rsleep := utils.NewRetrySleeper(1, 100*time.Millisecond, time.Second)
for {
consumererr = value.writer.ConsumeConsensus(ctx, replay.conns, value.message, replay.persist)
if !utils.ErrIsLockError(consumererr) {
break
}
rsleep.Inc()
}
if consumererr != nil {
replay.errs.SetValue(consumererr)
return
}
case CONSUMEC:
rsleep := utils.NewRetrySleeper(1, 100*time.Millisecond, time.Second)
for {
consumererr = value.cwriter.Consume(ctx, replay.conns, value.message, value.block, replay.persist)
if !utils.ErrIsLockError(consumererr) {
break
}
rsleep.Inc()
}
if consumererr != nil {
replay.errs.SetValue(consumererr)
return
}
case CONSUMECTRC:
transactionTrace := &modelsc.TransactionTrace{}
err := json.Unmarshal(value.message.Body(), transactionTrace)
if err != nil {
replay.errs.SetValue(consumererr)
return
}
rsleep := utils.NewRetrySleeper(1, 100*time.Millisecond, time.Second)
for {
consumererr = value.cwriter.ConsumeTrace(ctx, replay.conns, value.message, transactionTrace, replay.persist)
if !utils.ErrIsLockError(consumererr) {
break
}
rsleep.Inc()
}
if consumererr != nil {
replay.errs.SetValue(consumererr)
return
}
case CONSUMECLOG:
txLogs := &types.Log{}
err := json.Unmarshal(value.message.Body(), txLogs)
if err != nil {
replay.errs.SetValue(consumererr)
return
}
rsleep := utils.NewRetrySleeper(1, 100*time.Millisecond, time.Second)
for {
consumererr = value.cwriter.ConsumeLogs(ctx, replay.conns, value.message, txLogs, replay.persist)
if !utils.ErrIsLockError(consumererr) {
break
}
rsleep.Inc()
}
if consumererr != nil {
replay.errs.SetValue(consumererr)
return
}
}
default:
}
}
}
func (replay *dbReplay) startCchain(chain string, waitGroup *int64, worker utils.Worker, writer *cvm.Writer) error {
tn := fmt.Sprintf("%d-%s-cchain", replay.config.NetworkID, chain)
replay.counterWaits.Inc(tn)
replay.counterAdded.Add(tn, 0)
atomic.AddInt64(waitGroup, 1)
go func() {
defer atomic.AddInt64(waitGroup, -1)
defer replay.counterWaits.Add(tn, -1)
job := replay.conns.Stream().NewJob("query-replay-txpoll")
sess := replay.conns.DB().NewSessionForEventReceiver(job)
ctx := context.Background()
var txPools []TxPoolID
_, err := sess.Select("id").
From(db.TableTxPool).
Where("topic=?", tn).
OrderAsc("created_at").
LoadContext(ctx, &txPools)
if err != nil {
replay.errs.SetValue(err)
return
}
for _, txPoolID := range txPools {
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Info("topic replay stopped due to error",
zap.String("topic", tn),
zap.Error(err),
)
return
}
txPoolQ := db.TxPool{
ID: txPoolID.ID,
}
var txPool *db.TxPool
for {
txPool, err = replay.persist.QueryTxPool(ctx, sess, &txPoolQ)
if err == nil {
break
}
replay.sc.Log.Warn("topic replay erred",
zap.String("topic", tn),
zap.Error(err),
)
time.Sleep(500 * time.Millisecond)
}
id, err := ids.FromString(txPool.MsgKey)
if err != nil {
replay.errs.SetValue(err)
return
}
replay.counterAdded.Inc(tn)
block, err := modelsc.Unmarshal(txPool.Serialization)
if err != nil {
replay.errs.SetValue(err)
return
}
if block.BlockExtraData == nil {
block.BlockExtraData = []byte("")
}
msgc := stream.NewMessage(
id.String(),
chain,
block.BlockExtraData,
txPool.CreatedAt.UTC().Unix(),
int64(txPool.CreatedAt.UTC().Nanosecond()),
)
worker.Enque(&WorkerPacket{cwriter: writer, message: msgc, block: block, consumeType: CONSUMEC})
}
}()
return nil
}
func (replay *dbReplay) startCchainTrc(chain string, waitGroup *int64, worker utils.Worker, writer *cvm.Writer) error {
tn := fmt.Sprintf("%d-%s-cchain-trc", replay.config.NetworkID, chain)
replay.counterWaits.Inc(tn)
replay.counterAdded.Add(tn, 0)
atomic.AddInt64(waitGroup, 1)
go func() {
defer atomic.AddInt64(waitGroup, -1)
defer replay.counterWaits.Add(tn, -1)
job := replay.conns.Stream().NewJob("query-replay-txpoll")
sess := replay.conns.DB().NewSessionForEventReceiver(job)
ctx := context.Background()
var txPools []TxPoolID
_, err := sess.Select("id").
From(db.TableTxPool).
Where("topic=?", tn).
OrderAsc("created_at").
LoadContext(ctx, &txPools)
if err != nil {
replay.errs.SetValue(err)
return
}
for _, txPoolID := range txPools {
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Info("topic replay stopped due to error",
zap.String("topic", tn),
zap.Error(err),
)
return
}
txPoolQ := db.TxPool{
ID: txPoolID.ID,
}
var txPool *db.TxPool
for {
txPool, err = replay.persist.QueryTxPool(ctx, sess, &txPoolQ)
if err == nil {
break
}
replay.sc.Log.Warn("topic replay erred",
zap.String("topic", tn),
zap.Error(err),
)
time.Sleep(500 * time.Millisecond)
}
id, err := ids.FromString(txPool.MsgKey)
if err != nil {
replay.errs.SetValue(err)
return
}
replay.counterAdded.Inc(tn)
msgc := stream.NewMessage(
id.String(),
chain,
txPool.Serialization,
txPool.CreatedAt.UTC().Unix(),
int64(txPool.CreatedAt.UTC().Nanosecond()),
)
worker.Enque(&WorkerPacket{cwriter: writer, message: msgc, consumeType: CONSUMECTRC})
}
}()
return nil
}
func (replay *dbReplay) startCchainLog(chain string, waitGroup *int64, worker utils.Worker, writer *cvm.Writer) error {
tn := fmt.Sprintf("%d-%s-cchain-logs", replay.config.NetworkID, chain)
replay.counterWaits.Inc(tn)
replay.counterAdded.Add(tn, 0)
atomic.AddInt64(waitGroup, 1)
go func() {
defer atomic.AddInt64(waitGroup, -1)
defer replay.counterWaits.Add(tn, -1)
job := replay.conns.Stream().NewJob("query-replay-txpoll")
sess := replay.conns.DB().NewSessionForEventReceiver(job)
ctx := context.Background()
var txPools []TxPoolID
_, err := sess.Select("id").
From(db.TableTxPool).
Where("topic=?", tn).
OrderAsc("created_at").
LoadContext(ctx, &txPools)
if err != nil {
replay.errs.SetValue(err)
return
}
for _, txPoolID := range txPools {
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Info("topic replay stopped due to error",
zap.String("topic", tn),
zap.Error(err),
)
return
}
txPoolQ := db.TxPool{
ID: txPoolID.ID,
}
var txPool *db.TxPool
for {
txPool, err = replay.persist.QueryTxPool(ctx, sess, &txPoolQ)
if err == nil {
break
}
replay.sc.Log.Warn("topic replay erred",
zap.String("topic", tn),
zap.Error(err),
)
time.Sleep(500 * time.Millisecond)
}
id, err := ids.FromString(txPool.MsgKey)
if err != nil {
replay.errs.SetValue(err)
return
}
replay.counterAdded.Inc(tn)
msgc := stream.NewMessage(
id.String(),
chain,
txPool.Serialization,
txPool.CreatedAt.UTC().Unix(),
int64(txPool.CreatedAt.UTC().Nanosecond()),
)
worker.Enque(&WorkerPacket{cwriter: writer, message: msgc, consumeType: CONSUMECLOG})
}
}()
return nil
}
func (replay *dbReplay) startConsensus(chain cfg.Chain, waitGroup *int64, worker utils.Worker, writer services.Consumer) error {
tn := stream.GetTopicName(replay.config.NetworkID, chain.ID, stream.EventTypeConsensus)
replay.counterWaits.Inc(tn)
replay.counterAdded.Add(tn, 0)
atomic.AddInt64(waitGroup, 1)
go func() {
defer atomic.AddInt64(waitGroup, -1)
defer replay.counterWaits.Add(tn, -1)
job := replay.conns.Stream().NewJob("query-replay-txpoll")
sess := replay.conns.DB().NewSessionForEventReceiver(job)
ctx := context.Background()
var txPools []TxPoolID
_, err := sess.Select("id").
From(db.TableTxPool).
Where("topic=?", tn).
OrderAsc("created_at").
LoadContext(ctx, &txPools)
if err != nil {
replay.errs.SetValue(err)
return
}
for _, txPoolID := range txPools {
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Info("topic replay stopped due to error",
zap.String("topic", tn),
zap.Error(err),
)
return
}
txPoolQ := db.TxPool{
ID: txPoolID.ID,
}
var txPool *db.TxPool
for {
txPool, err = replay.persist.QueryTxPool(ctx, sess, &txPoolQ)
if err == nil {
break
}
replay.sc.Log.Warn("topic replay erred",
zap.String("topic", tn),
zap.Error(err),
)
time.Sleep(500 * time.Millisecond)
}
id, err := ids.FromString(txPool.MsgKey)
if err != nil {
replay.errs.SetValue(err)
return
}
replay.counterAdded.Inc(tn)
msgc := stream.NewMessage(
id.String(),
chain.ID,
txPool.Serialization,
txPool.CreatedAt.UTC().Unix(),
int64(txPool.CreatedAt.UTC().Nanosecond()),
)
worker.Enque(&WorkerPacket{writer: writer, message: msgc, consumeType: CONSUMECONSENSUS})
}
}()
return nil
}
func (replay *dbReplay) startDecision(chain cfg.Chain, waitGroup *int64, worker utils.Worker, writer services.Consumer) error {
tn := stream.GetTopicName(replay.config.NetworkID, chain.ID, stream.EventTypeDecisions)
replay.counterWaits.Inc(tn)
replay.counterAdded.Add(tn, 0)
atomic.AddInt64(waitGroup, 1)
go func() {
defer atomic.AddInt64(waitGroup, -1)
defer replay.counterWaits.Add(tn, -1)
job := replay.conns.Stream().NewJob("query-replay-txpoll")
sess := replay.conns.DB().NewSessionForEventReceiver(job)
ctx := context.Background()
var txPools []TxPoolID
_, err := sess.Select("id").
From(db.TableTxPool).
Where("topic=?", tn).
OrderAsc("created_at").
LoadContext(ctx, &txPools)
if err != nil {
replay.errs.SetValue(err)
return
}
for _, txPoolID := range txPools {
if errIntf := replay.errs.GetValue(); errIntf != nil {
err := errIntf.(error)
replay.sc.Log.Info("topic replay stopped due to error",
zap.String("topic", tn),
zap.Error(err),
)
return
}
txPoolQ := db.TxPool{
ID: txPoolID.ID,
}
var txPool *db.TxPool
for {
txPool, err = replay.persist.QueryTxPool(ctx, sess, &txPoolQ)
if err == nil {
break
}
replay.sc.Log.Warn("topic replay erred",
zap.String("topic", tn),
zap.Error(err),
)
time.Sleep(500 * time.Millisecond)
}
id, err := ids.FromString(txPool.MsgKey)
if err != nil {
replay.errs.SetValue(err)
return
}
replay.counterAdded.Inc(tn)
msgc := stream.NewMessage(
id.String(),
chain.ID,
txPool.Serialization,
txPool.CreatedAt.UTC().Unix(),
int64(txPool.CreatedAt.UTC().Nanosecond()),
)
worker.Enque(&WorkerPacket{writer: writer, message: msgc, consumeType: CONSUME})
}
}()
return nil
}