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]>
433 lines
12 KiB
Go
433 lines
12 KiB
Go
// (c) 2021, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package cvm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/ava-labs/avalanchego/codec"
|
|
"github.com/ava-labs/avalanchego/genesis"
|
|
"github.com/ava-labs/avalanchego/ids"
|
|
"github.com/ava-labs/avalanchego/utils/hashing"
|
|
"github.com/ava-labs/avalanchego/utils/math"
|
|
"github.com/ava-labs/avalanchego/version"
|
|
"github.com/ava-labs/avalanchego/vms/components/verify"
|
|
"github.com/ava-labs/coreth/core/types"
|
|
"github.com/ava-labs/coreth/plugin/evm"
|
|
"github.com/ava-labs/ortelius/cfg"
|
|
"github.com/ava-labs/ortelius/db"
|
|
"github.com/ava-labs/ortelius/models"
|
|
"github.com/ava-labs/ortelius/modelsc"
|
|
"github.com/ava-labs/ortelius/services"
|
|
avaxIndexer "github.com/ava-labs/ortelius/services/indexes/avax"
|
|
"github.com/ava-labs/ortelius/utils"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
var (
|
|
ErrUnknownBlockType = errors.New("unknown block type")
|
|
)
|
|
|
|
type Writer struct {
|
|
networkID uint32
|
|
avaxAssetID ids.ID
|
|
|
|
codec codec.Manager
|
|
avax *avaxIndexer.Writer
|
|
ap5Activation uint64
|
|
banffActivation uint64
|
|
}
|
|
|
|
func NewWriter(networkID uint32, chainID string) (*Writer, error) {
|
|
_, avaxAssetID, err := genesis.FromConfig(genesis.GetConfig(networkID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ap5Activation := version.GetApricotPhase5Time(networkID).Unix()
|
|
banffActivation := version.GetBanffTime(networkID).Unix()
|
|
|
|
return &Writer{
|
|
networkID: networkID,
|
|
avaxAssetID: avaxAssetID,
|
|
codec: evm.Codec,
|
|
avax: avaxIndexer.NewWriter(chainID, avaxAssetID),
|
|
ap5Activation: uint64(ap5Activation),
|
|
banffActivation: uint64(banffActivation),
|
|
}, nil
|
|
}
|
|
|
|
func (*Writer) Name() string { return "cvm-index" }
|
|
|
|
func (w *Writer) ParseJSON(txdata []byte) ([]byte, error) {
|
|
block, err := modelsc.Unmarshal(txdata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var atomicTxs []*evm.Tx
|
|
if block.BlockExtraData == nil || len(block.BlockExtraData) == 0 {
|
|
return []byte(""), nil
|
|
}
|
|
|
|
if block.Header.Time < w.ap5Activation {
|
|
atomicTxs, err = w.extractAtomicTxsPreApricotPhase5(block.BlockExtraData)
|
|
} else {
|
|
atomicTxs, err = w.extractAtomicTxsPostApricotPhase5(block.BlockExtraData)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return json.Marshal(atomicTxs)
|
|
}
|
|
|
|
func (w *Writer) extractAtomicTxsPreApricotPhase5(atomicTxBytes []byte) ([]*evm.Tx, error) {
|
|
atomicTx := new(evm.Tx)
|
|
if _, err := w.codec.Unmarshal(atomicTxBytes, atomicTx); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal atomic tx (pre-AP5): %w", err)
|
|
}
|
|
if err := atomicTx.Sign(w.codec, nil); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize singleton atomic tx due to: %w", err)
|
|
}
|
|
return []*evm.Tx{atomicTx}, nil
|
|
}
|
|
|
|
// [extractAtomicTxsPostApricotPhase5] extracts a slice of atomic transactions from [atomicTxBytes].
|
|
// Note: this function assumes [atomicTxBytes] is non-empty.
|
|
func (w *Writer) extractAtomicTxsPostApricotPhase5(atomicTxBytes []byte) ([]*evm.Tx, error) {
|
|
var atomicTxs []*evm.Tx
|
|
if _, err := w.codec.Unmarshal(atomicTxBytes, &atomicTxs); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal atomic tx (post-AP5): %w", err)
|
|
}
|
|
|
|
for index, atx := range atomicTxs {
|
|
if err := atx.Sign(w.codec, nil); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize atomic tx at index %d: %w", index, err)
|
|
}
|
|
}
|
|
return atomicTxs, nil
|
|
}
|
|
|
|
func (w *Writer) ConsumeLogs(ctx context.Context, conns *utils.Connections, c services.Consumable, txLogs *types.Log, persist db.Persist) error {
|
|
job := conns.Stream().NewJob("cvm-index")
|
|
sess := conns.DB().NewSessionForEventReceiver(job)
|
|
|
|
dbTx, err := sess.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dbTx.RollbackUnlessCommitted()
|
|
|
|
cCtx := services.NewConsumerContext(ctx, dbTx, c.Timestamp(), c.Nanosecond(), persist)
|
|
|
|
firstTopic := ""
|
|
if len(txLogs.Topics) > 0 {
|
|
firstTopic = txLogs.Topics[0].Hex()
|
|
}
|
|
cvmLogs := &db.CvmLogs{
|
|
BlockHash: txLogs.BlockHash.Hex(),
|
|
TxHash: txLogs.TxHash.Hex(),
|
|
LogIndex: uint64(txLogs.Index),
|
|
Block: fmt.Sprintf("%d", txLogs.BlockNumber),
|
|
FirstTopic: firstTopic,
|
|
Removed: txLogs.Removed,
|
|
CreatedAt: cCtx.Time(),
|
|
Serialization: c.Body(),
|
|
}
|
|
cvmLogs.ComputeID()
|
|
err = persist.InsertCvmLogs(ctx, dbTx, cvmLogs, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dbTx.Commit()
|
|
}
|
|
|
|
func (w *Writer) ConsumeTrace(ctx context.Context, conns *utils.Connections, c services.Consumable, transactionTrace *modelsc.TransactionTrace, persist db.Persist) error {
|
|
job := conns.Stream().NewJob("cvm-index")
|
|
sess := conns.DB().NewSessionForEventReceiver(job)
|
|
|
|
dbTx, err := sess.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dbTx.RollbackUnlessCommitted()
|
|
|
|
txTraceModel := &models.CvmTransactionsTxDataTrace{}
|
|
err = json.Unmarshal(transactionTrace.Trace, txTraceModel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cCtx := services.NewConsumerContext(ctx, dbTx, c.Timestamp(), c.Nanosecond(), persist)
|
|
|
|
txTraceService := &db.CvmTransactionsTxdataTrace{
|
|
Hash: transactionTrace.Hash,
|
|
Idx: transactionTrace.Idx,
|
|
ToAddr: txTraceModel.ToAddr,
|
|
FromAddr: txTraceModel.FromAddr,
|
|
CallType: txTraceModel.CallType,
|
|
Type: txTraceModel.Type,
|
|
Serialization: transactionTrace.Trace,
|
|
CreatedAt: cCtx.Time(),
|
|
}
|
|
|
|
err = persist.InsertCvmTransactionsTxdataTrace(ctx, dbTx, txTraceService, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dbTx.Commit()
|
|
}
|
|
|
|
func (w *Writer) Consume(ctx context.Context, conns *utils.Connections, c services.Consumable, block *modelsc.Block, persist db.Persist) error {
|
|
job := conns.Stream().NewJob("cvm-index")
|
|
sess := conns.DB().NewSessionForEventReceiver(job)
|
|
|
|
dbTx, err := sess.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dbTx.RollbackUnlessCommitted()
|
|
|
|
// Consume the tx and commit
|
|
err = w.indexBlock(services.NewConsumerContext(ctx, dbTx, c.Timestamp(), c.Nanosecond(), persist), c.Body(), block)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return dbTx.Commit()
|
|
}
|
|
|
|
func (w *Writer) indexBlock(ctx services.ConsumerCtx, blockBytes []byte, block *modelsc.Block) error {
|
|
var atomicTxs []*evm.Tx
|
|
if len(blockBytes) > 0 {
|
|
var err error
|
|
if block.Header.Time < w.ap5Activation {
|
|
atomicTxs, err = w.extractAtomicTxsPreApricotPhase5(block.BlockExtraData)
|
|
} else {
|
|
atomicTxs, err = w.extractAtomicTxsPostApricotPhase5(block.BlockExtraData)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.indexBlockInternal(ctx, atomicTxs, blockBytes, block)
|
|
}
|
|
|
|
func (w *Writer) indexBlockInternal(ctx services.ConsumerCtx, atomicTXs []*evm.Tx, blockBytes []byte, block *modelsc.Block) error {
|
|
var (
|
|
txIDs = make([]string, len(atomicTXs))
|
|
typ models.CChainType = 0
|
|
blockchainID string
|
|
err error
|
|
)
|
|
for i, atomicTX := range atomicTXs {
|
|
txID := atomicTX.ID()
|
|
txIDs[i] = txID.String()
|
|
switch atx := atomicTX.UnsignedAtomicTx.(type) {
|
|
case *evm.UnsignedExportTx:
|
|
typ = models.CChainExport
|
|
blockchainID = atx.BlockchainID.String()
|
|
err = w.indexExportTx(ctx, txID, atx, blockBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case *evm.UnsignedImportTx:
|
|
unsignedBytes, err := w.codec.Marshal(0, &atomicTX.UnsignedAtomicTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
typ = models.CChainImport
|
|
blockchainID = atx.BlockchainID.String()
|
|
err = w.indexImportTx(ctx, txID, atx, atomicTX.Creds, blockBytes, unsignedBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
|
|
for ipos, rawtx := range block.Txs {
|
|
rawtxCp := rawtx
|
|
txdata, err := json.Marshal(&rawtxCp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rawhash := rawtx.Hash()
|
|
rcptstr := utils.CommonAddressHexRepair(rawtx.To())
|
|
cvmTransactionTxdata := &db.CvmTransactionsTxdata{
|
|
Hash: rawhash.String(),
|
|
Block: block.Header.Number.String(),
|
|
Idx: uint64(ipos),
|
|
Rcpt: rcptstr,
|
|
Nonce: rawtx.Nonce(),
|
|
Serialization: txdata,
|
|
CreatedAt: ctx.Time(),
|
|
}
|
|
err = ctx.Persist().InsertCvmTransactionsTxdata(ctx.Ctx(), ctx.DB(), cvmTransactionTxdata, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
block.TxsBytes = nil
|
|
block.Txs = nil
|
|
|
|
blockjson, err := json.Marshal(block)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
htime := int64(block.Header.Time)
|
|
if htime == 0 {
|
|
htime = 1
|
|
}
|
|
tm := time.Unix(htime, 0)
|
|
|
|
id := ids.ID(hashing.ComputeHash256Array([]byte(block.Header.Number.String())))
|
|
idStr := id.String()
|
|
for _, txIDString := range txIDs {
|
|
cvmTransaction := &db.CvmTransactions{
|
|
ID: idStr,
|
|
TransactionID: txIDString,
|
|
Type: typ,
|
|
BlockchainID: blockchainID,
|
|
Block: block.Header.Number.String(),
|
|
CreatedAt: ctx.Time(),
|
|
Serialization: blockjson,
|
|
TxTime: tm,
|
|
Nonce: block.Header.Nonce.Uint64(),
|
|
Hash: block.Header.Hash().String(),
|
|
ParentHash: block.Header.ParentHash.String(),
|
|
}
|
|
err = ctx.Persist().InsertCvmTransactions(ctx.Ctx(), ctx.DB(), cvmTransaction, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *Writer) indexTransaction(
|
|
ctx services.ConsumerCtx,
|
|
id ids.ID,
|
|
typ models.CChainType,
|
|
blockChainID ids.ID,
|
|
txFee uint64,
|
|
unsignedBytes []byte,
|
|
) error {
|
|
avmTxtype := ""
|
|
switch typ {
|
|
case models.CChainImport:
|
|
avmTxtype = "atomic_import_tx"
|
|
case models.CChainExport:
|
|
avmTxtype = "atomic_export_tx"
|
|
}
|
|
|
|
return w.avax.InsertTransactionBase(
|
|
ctx,
|
|
id,
|
|
blockChainID.String(),
|
|
avmTxtype,
|
|
[]byte(""),
|
|
unsignedBytes,
|
|
txFee,
|
|
false,
|
|
w.networkID,
|
|
)
|
|
}
|
|
|
|
func (w *Writer) insertAddress(
|
|
typ models.CChainType,
|
|
ctx services.ConsumerCtx,
|
|
idx uint64,
|
|
id ids.ID,
|
|
address common.Address,
|
|
assetID ids.ID,
|
|
amount uint64,
|
|
nonce uint64,
|
|
) error {
|
|
idprefix := id.Prefix(idx)
|
|
|
|
cvmAddress := &db.CvmAddresses{
|
|
ID: idprefix.String(),
|
|
Type: typ,
|
|
Idx: idx,
|
|
TransactionID: id.String(),
|
|
Address: address.String(),
|
|
AssetID: assetID.String(),
|
|
Amount: amount,
|
|
Nonce: nonce,
|
|
CreatedAt: ctx.Time(),
|
|
}
|
|
return ctx.Persist().InsertCvmAddresses(ctx.Ctx(), ctx.DB(), cvmAddress, cfg.PerformUpdates)
|
|
}
|
|
|
|
func (w *Writer) indexExportTx(ctx services.ConsumerCtx, txID ids.ID, tx *evm.UnsignedExportTx, blockBytes []byte) error {
|
|
var err error
|
|
|
|
var totalin uint64
|
|
for icnt, in := range tx.Ins {
|
|
icntval := uint64(icnt)
|
|
err = w.insertAddress(models.CChainIn, ctx, icntval, txID, in.Address, in.AssetID, in.Amount, in.Nonce)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if in.AssetID == w.avaxAssetID {
|
|
totalin, err = math.Add64(totalin, in.Amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
var totalout uint64
|
|
var idx uint32
|
|
for _, out := range tx.ExportedOutputs {
|
|
totalout, err = w.avax.InsertTransactionOuts(idx, ctx, totalout, out, txID, tx.DestinationChain.String(), false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idx++
|
|
}
|
|
|
|
return w.indexTransaction(ctx, txID, models.CChainExport, tx.BlockchainID, totalin-totalout, blockBytes)
|
|
}
|
|
|
|
func (w *Writer) indexImportTx(ctx services.ConsumerCtx, txID ids.ID, tx *evm.UnsignedImportTx, creds []verify.Verifiable, blockBytes []byte, unsignedBytes []byte) error {
|
|
var err error
|
|
|
|
var totalout uint64
|
|
for icnt, out := range tx.Outs {
|
|
icntval := uint64(icnt)
|
|
err = w.insertAddress(models.CchainOut, ctx, icntval, txID, out.Address, out.AssetID, out.Amount, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if out.AssetID == w.avaxAssetID {
|
|
totalout, err = math.Add64(totalout, out.Amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
var totalin uint64
|
|
for inidx, in := range tx.ImportedInputs {
|
|
totalin, err = w.avax.InsertTransactionIns(inidx, ctx, totalin, in, txID, creds, unsignedBytes, tx.SourceChain.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return w.indexTransaction(ctx, txID, models.CChainImport, tx.BlockchainID, totalin-totalout, blockBytes)
|
|
}
|