mirror of
https://github.com/luxfi/indexer.git
synced 2026-09-24 00:25:37 +00:00
Co-authored-by: dhrubabasu <[email protected]> Co-authored-by: Patrick O'Grady <[email protected]> Co-authored-by: Stephen <[email protected]>
354 lines
8.3 KiB
Go
354 lines
8.3 KiB
Go
// (c) 2021, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package consumers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
avlancheGoUtils "github.com/ava-labs/avalanchego/utils"
|
|
"github.com/ava-labs/ortelius/cfg"
|
|
"github.com/ava-labs/ortelius/db"
|
|
"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/servicesctrl"
|
|
"github.com/ava-labs/ortelius/stream"
|
|
"github.com/ava-labs/ortelius/utils"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
IndexerAVMName = "avm"
|
|
IndexerPVMName = "pvm"
|
|
|
|
MaximumRecordsRead = 10000
|
|
MaxTheads = 10
|
|
|
|
IteratorTimeout = 3 * time.Minute
|
|
)
|
|
|
|
type ConsumerFactory func(uint32, string, string) (services.Consumer, error)
|
|
|
|
var IndexerConsumer = func(networkID uint32, chainVM string, chainID string) (indexer services.Consumer, err error) {
|
|
switch chainVM {
|
|
case IndexerAVMName:
|
|
indexer, err = avm.NewWriter(networkID, chainID)
|
|
case IndexerPVMName:
|
|
indexer, err = pvm.NewWriter(networkID, chainID)
|
|
default:
|
|
return nil, stream.ErrUnknownVM
|
|
}
|
|
return indexer, err
|
|
}
|
|
|
|
var IndexerConsumerCChain = func(networkID uint32, chainID string) (indexer services.ConsumerCChain, err error) {
|
|
return cvm.NewWriter(networkID, chainID)
|
|
}
|
|
|
|
type ConsumerDBFactory func(uint32, string, string) (stream.ProcessorFactoryChainDB, error)
|
|
|
|
var IndexerDB = stream.NewConsumerDBFactory(IndexerConsumer, stream.EventTypeDecisions)
|
|
var IndexerConsensusDB = stream.NewConsumerDBFactory(IndexerConsumer, stream.EventTypeConsensus)
|
|
var IndexerCChainDB = stream.NewConsumerCChainDB
|
|
|
|
func Bootstrap(sc *servicesctrl.Control, networkID uint32, chains cfg.Chains, factories []ConsumerFactory) error {
|
|
if sc.IsDisableBootstrap {
|
|
return nil
|
|
}
|
|
|
|
conns, err := sc.Database()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
_ = conns.Close()
|
|
}()
|
|
|
|
persist := db.NewPersist()
|
|
ctx := context.Background()
|
|
job := conns.Stream().NewJob("bootstrap-key-value")
|
|
sess := conns.DB().NewSessionForEventReceiver(job)
|
|
|
|
bootstrapValue := "true"
|
|
|
|
// check if we have bootstrapped..
|
|
keyValueStore := &db.KeyValueStore{
|
|
K: utils.KeyValueBootstrap,
|
|
}
|
|
keyValueStore, _ = persist.QueryKeyValueStore(ctx, sess, keyValueStore)
|
|
if keyValueStore.V == bootstrapValue {
|
|
sc.Log.Info("skipping bootstrap")
|
|
return nil
|
|
}
|
|
|
|
errs := avlancheGoUtils.AtomicInterface{}
|
|
|
|
wg := sync.WaitGroup{}
|
|
for _, chain := range chains {
|
|
for _, factory := range factories {
|
|
bootstrapfactory, err := factory(networkID, chain.VMType, chain.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sc.Log.Info("starting bootstrap",
|
|
zap.Uint32("networkID", networkID),
|
|
zap.String("vmType", chain.VMType),
|
|
zap.String("chainID", chain.ID),
|
|
)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
err = bootstrapfactory.Bootstrap(ctx, conns, sc.Persist)
|
|
if err != nil {
|
|
errs.SetValue(err)
|
|
}
|
|
sc.Log.Info("finished bootstrap",
|
|
zap.Uint32("networkID", networkID),
|
|
zap.String("vmType", chain.VMType),
|
|
zap.String("chainID", chain.ID),
|
|
)
|
|
}()
|
|
}
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
if errs.GetValue() != nil {
|
|
return errs.GetValue().(error)
|
|
}
|
|
|
|
// write a complete row.
|
|
keyValueStore = &db.KeyValueStore{
|
|
K: utils.KeyValueBootstrap,
|
|
V: bootstrapValue,
|
|
}
|
|
return persist.InsertKeyValueStore(ctx, sess, keyValueStore)
|
|
}
|
|
|
|
type IndexerFactoryControl struct {
|
|
sc *servicesctrl.Control
|
|
fsm map[string]stream.ProcessorDB
|
|
doneCh chan struct{}
|
|
}
|
|
|
|
func (c *IndexerFactoryControl) updateTxPollStatus(conns *utils.Connections, txPoll *db.TxPool) error {
|
|
sess := conns.DB().NewSessionForEventReceiver(conns.Stream().NewJob("update-txpoll-status"))
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), cfg.DefaultConsumeProcessWriteTimeout)
|
|
defer cancelFn()
|
|
return c.sc.Persist.UpdateTxPoolStatus(ctx, sess, txPoll)
|
|
}
|
|
|
|
func (c *IndexerFactoryControl) handleTxPool(_ int, conns *utils.Connections) {
|
|
defer func() {
|
|
_ = conns.Close()
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case txd := <-c.sc.LocalTxPool:
|
|
if c.sc.IndexedList.Exists(txd.TxPool.ID) {
|
|
continue
|
|
}
|
|
if txd.Errs != nil && txd.Errs.GetValue() != nil {
|
|
continue
|
|
}
|
|
if p, ok := c.fsm[txd.TxPool.Topic]; ok {
|
|
err := p.Process(conns, txd.TxPool)
|
|
if err != nil {
|
|
if txd.Errs != nil {
|
|
txd.Errs.SetValue(err)
|
|
}
|
|
continue
|
|
}
|
|
txd.TxPool.Processed = 1
|
|
err = c.updateTxPollStatus(conns, txd.TxPool)
|
|
if err != nil {
|
|
if txd.Errs != nil {
|
|
txd.Errs.SetValue(err)
|
|
}
|
|
continue
|
|
}
|
|
c.sc.IndexedList.PushFront(txd.TxPool.ID, txd.TxPool.ID)
|
|
}
|
|
case <-c.doneCh:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func IndexerFactories(
|
|
sc *servicesctrl.Control,
|
|
config *cfg.Config,
|
|
factoriesChainDB []stream.ProcessorFactoryChainDB,
|
|
factoriesInstDB []stream.ProcessorFactoryInstDB,
|
|
wg *sync.WaitGroup,
|
|
runningControl utils.Running,
|
|
) error {
|
|
ctrl := &IndexerFactoryControl{
|
|
sc: sc,
|
|
fsm: make(map[string]stream.ProcessorDB),
|
|
doneCh: make(chan struct{}),
|
|
}
|
|
|
|
var topicNames []string
|
|
|
|
for _, factory := range factoriesChainDB {
|
|
for _, chainConfig := range config.Chains {
|
|
f, err := factory(sc, *config, chainConfig.VMType, chainConfig.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, topic := range f.Topic() {
|
|
if _, ok := ctrl.fsm[topic]; ok {
|
|
return fmt.Errorf("duplicate topic %v", topic)
|
|
}
|
|
ctrl.fsm[topic] = f
|
|
topicNames = append(topicNames, topic)
|
|
}
|
|
}
|
|
}
|
|
for _, factory := range factoriesInstDB {
|
|
f, err := factory(sc, *config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, topic := range f.Topic() {
|
|
if _, ok := ctrl.fsm[topic]; ok {
|
|
return fmt.Errorf("duplicate topic %v", topic)
|
|
}
|
|
ctrl.fsm[topic] = f
|
|
topicNames = append(topicNames, topic)
|
|
}
|
|
}
|
|
|
|
conns, err := sc.Database()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for ipos := 0; ipos < MaxTheads; ipos++ {
|
|
conns1, err := sc.Database()
|
|
if err != nil {
|
|
_ = conns.Close()
|
|
close(ctrl.doneCh)
|
|
return err
|
|
}
|
|
go ctrl.handleTxPool(ipos, conns1)
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer func() {
|
|
wg.Done()
|
|
close(ctrl.doneCh)
|
|
_ = conns.Close()
|
|
}()
|
|
for !runningControl.IsStopped() {
|
|
iterateTxPool := func() {
|
|
ctx, cancelCTX := context.WithTimeout(context.Background(), IteratorTimeout)
|
|
defer cancelCTX()
|
|
|
|
name := "tx-pool"
|
|
sess, err := conns.DB().NewSession(name, IteratorTimeout)
|
|
if err != nil {
|
|
sc.Log.Error("failed creating session",
|
|
zap.String("name", name),
|
|
zap.Error(err),
|
|
)
|
|
time.Sleep(250 * time.Millisecond)
|
|
return
|
|
}
|
|
iterator, err := sess.Select(
|
|
"id",
|
|
"network_id",
|
|
"chain_id",
|
|
"msg_key",
|
|
"serialization",
|
|
"processed",
|
|
"topic",
|
|
"created_at",
|
|
).From(db.TableTxPool).
|
|
Where("processed=? and topic in ?", 0, topicNames).
|
|
OrderAsc("processed").OrderAsc("created_at").
|
|
IterateContext(ctx)
|
|
if err != nil {
|
|
sc.Log.Warn("failed creating iterator",
|
|
zap.String("name", name),
|
|
zap.Error(err),
|
|
)
|
|
time.Sleep(250 * time.Millisecond)
|
|
return
|
|
}
|
|
|
|
errs := &avlancheGoUtils.AtomicInterface{}
|
|
|
|
var readMessages uint64
|
|
|
|
for iterator.Next() {
|
|
if errs.GetValue() != nil {
|
|
break
|
|
}
|
|
|
|
if readMessages > MaximumRecordsRead {
|
|
break
|
|
}
|
|
|
|
err = iterator.Err()
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
sc.Log.Error("failed iterating",
|
|
zap.String("name", name),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
break
|
|
}
|
|
|
|
txp := &db.TxPool{}
|
|
err = iterator.Scan(txp)
|
|
if err != nil {
|
|
sc.Log.Error("failed scanning iterator",
|
|
zap.String("name", name),
|
|
zap.Error(err),
|
|
)
|
|
break
|
|
}
|
|
// skip previously processed
|
|
if sc.IndexedList.Exists(txp.ID) {
|
|
continue
|
|
}
|
|
readMessages++
|
|
sc.LocalTxPool <- &servicesctrl.LocalTxPoolJob{TxPool: txp, Errs: errs}
|
|
}
|
|
|
|
for ipos := 0; ipos < (5*1000) && len(sc.LocalTxPool) > 0; ipos++ {
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
if errIntf := errs.GetValue(); errIntf != nil {
|
|
err := errIntf.(error)
|
|
sc.Log.Error("failed processing",
|
|
zap.String("name", name),
|
|
zap.Error(err),
|
|
)
|
|
time.Sleep(250 * time.Millisecond)
|
|
return
|
|
}
|
|
|
|
if readMessages == 0 {
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
iterateTxPool()
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|