mirror of
https://github.com/luxfi/indexer.git
synced 2026-10-30 14:45:22 +00:00
Co-authored-by: dhrubabasu <[email protected]> Co-authored-by: Patrick O'Grady <[email protected]> Co-authored-by: Stephen <[email protected]>
546 lines
12 KiB
Go
546 lines
12 KiB
Go
// (c) 2021, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package avax
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/palantir/stacktrace"
|
|
|
|
"github.com/ava-labs/avalanchego/ids"
|
|
"github.com/ava-labs/avalanchego/utils/crypto"
|
|
"github.com/ava-labs/avalanchego/utils/formatting/address"
|
|
"github.com/ava-labs/avalanchego/utils/math"
|
|
"github.com/ava-labs/avalanchego/vms/components/avax"
|
|
"github.com/ava-labs/avalanchego/vms/components/verify"
|
|
"github.com/ava-labs/avalanchego/vms/nftfx"
|
|
"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
|
|
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
|
|
"github.com/ava-labs/ortelius/cfg"
|
|
"github.com/ava-labs/ortelius/db"
|
|
"github.com/ava-labs/ortelius/models"
|
|
"github.com/ava-labs/ortelius/services"
|
|
)
|
|
|
|
var (
|
|
MaxSerializationLen = (16 * 1024 * 1024) - 1
|
|
|
|
// MaxMemoLen is the maximum number of bytes a memo can be in the database
|
|
MaxMemoLen = 1024
|
|
)
|
|
|
|
var ecdsaRecoveryFactory = crypto.FactorySECP256K1R{}
|
|
|
|
type Writer struct {
|
|
chainID string
|
|
avaxAssetID ids.ID
|
|
}
|
|
|
|
func NewWriter(chainID string, avaxAssetID ids.ID) *Writer {
|
|
return &Writer{chainID: chainID, avaxAssetID: avaxAssetID}
|
|
}
|
|
|
|
type AddInsContainer struct {
|
|
Ins []*avax.TransferableInput
|
|
ChainID string
|
|
}
|
|
|
|
type AddOutsContainer struct {
|
|
Outs []*avax.TransferableOutput
|
|
Stake bool
|
|
ChainID string
|
|
}
|
|
|
|
func (w *Writer) InsertTransaction(
|
|
ctx services.ConsumerCtx,
|
|
txBytes []byte,
|
|
txID ids.ID,
|
|
unsignedBytes []byte,
|
|
baseTx *avax.BaseTx,
|
|
creds []verify.Verifiable,
|
|
txType models.TransactionType,
|
|
addIns *AddInsContainer,
|
|
addOuts *AddOutsContainer,
|
|
addlOutTxfee uint64,
|
|
genesis bool,
|
|
) error {
|
|
var (
|
|
err error
|
|
totalin uint64 = 0
|
|
totalout uint64 = 0
|
|
)
|
|
|
|
inidx := 0
|
|
for _, in := range baseTx.Ins {
|
|
totalin, err = w.InsertTransactionIns(inidx, ctx, totalin, in, txID, creds, unsignedBytes, w.chainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inidx++
|
|
}
|
|
|
|
if addIns != nil {
|
|
for _, in := range addIns.Ins {
|
|
totalin, err = w.InsertTransactionIns(inidx, ctx, totalin, in, txID, creds, unsignedBytes, addIns.ChainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inidx++
|
|
}
|
|
}
|
|
|
|
var idx uint32
|
|
for _, out := range baseTx.Outs {
|
|
totalout, err = w.InsertTransactionOuts(idx, ctx, totalout, out, txID, w.chainID, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idx++
|
|
}
|
|
|
|
if addOuts != nil {
|
|
for _, out := range addOuts.Outs {
|
|
totalout, err = w.InsertTransactionOuts(idx, ctx, totalout, out, txID, addOuts.ChainID, addOuts.Stake, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idx++
|
|
}
|
|
}
|
|
|
|
txfee := totalin - (totalout + addlOutTxfee)
|
|
if genesis {
|
|
txfee = 0
|
|
} else if totalin < (totalout + addlOutTxfee) {
|
|
txfee = 0
|
|
}
|
|
|
|
// Add baseTx to the table
|
|
return w.InsertTransactionBase(
|
|
ctx,
|
|
txID,
|
|
w.chainID,
|
|
txType.String(),
|
|
baseTx.Memo,
|
|
txBytes,
|
|
txfee,
|
|
genesis,
|
|
baseTx.NetworkID,
|
|
)
|
|
}
|
|
|
|
func (w *Writer) InsertTransactionBase(
|
|
ctx services.ConsumerCtx,
|
|
txID ids.ID,
|
|
chainID string,
|
|
txType string,
|
|
memo []byte,
|
|
txBytes []byte,
|
|
txfee uint64,
|
|
genesis bool,
|
|
networkID uint32,
|
|
) error {
|
|
if len(txBytes) > MaxSerializationLen {
|
|
txBytes = []byte("")
|
|
}
|
|
if len(memo) > MaxMemoLen {
|
|
memo = nil
|
|
}
|
|
|
|
t := &db.Transactions{
|
|
ID: txID.String(),
|
|
ChainID: chainID,
|
|
Type: txType,
|
|
Memo: memo,
|
|
CanonicalSerialization: txBytes,
|
|
Txfee: txfee,
|
|
Genesis: genesis,
|
|
CreatedAt: ctx.Time(),
|
|
NetworkID: networkID,
|
|
}
|
|
|
|
return ctx.Persist().InsertTransactions(ctx.Ctx(), ctx.DB(), t, cfg.PerformUpdates)
|
|
}
|
|
|
|
func (w *Writer) InsertTransactionIns(
|
|
idx int,
|
|
ctx services.ConsumerCtx,
|
|
totalin uint64,
|
|
in *avax.TransferableInput,
|
|
txID ids.ID,
|
|
creds []verify.Verifiable,
|
|
unsignedBytes []byte,
|
|
chainID string,
|
|
) (uint64, error) {
|
|
var err error
|
|
if in.AssetID() == w.avaxAssetID {
|
|
totalin, err = math.Add64(totalin, in.Input().Amount())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
inputID := in.TxID.Prefix(uint64(in.OutputIndex))
|
|
|
|
if idx < len(creds) {
|
|
// For each signature we recover the public key and the data to the db
|
|
cred, ok := creds[idx].(*secp256k1fx.Credential)
|
|
if ok {
|
|
for _, sig := range cred.Sigs {
|
|
publicKey, err := ecdsaRecoveryFactory.RecoverPublicKey(unsignedBytes, sig[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = w.InsertAddressFromPublicKey(ctx, publicKey)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
err = w.InsertOutputAddress(ctx, inputID, publicKey.Address(), sig[:], in.TxID, in.OutputIndex, chainID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
outputsRedeeming := &db.OutputsRedeeming{
|
|
ID: inputID.String(),
|
|
RedeemedAt: ctx.Time(),
|
|
RedeemingTransactionID: txID.String(),
|
|
Amount: in.Input().Amount(),
|
|
OutputIndex: in.OutputIndex,
|
|
Intx: in.TxID.String(),
|
|
AssetID: in.AssetID().String(),
|
|
ChainID: chainID,
|
|
CreatedAt: ctx.Time(),
|
|
}
|
|
|
|
err = ctx.Persist().UpdateOutputAddressAccumulateInOutputsProcessed(ctx.Ctx(), ctx.DB(), inputID.String())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return totalin, ctx.Persist().InsertOutputsRedeeming(ctx.Ctx(), ctx.DB(), outputsRedeeming, cfg.PerformUpdates)
|
|
}
|
|
|
|
func (w *Writer) InsertTransactionOuts(
|
|
idx uint32,
|
|
ctx services.ConsumerCtx,
|
|
totalout uint64,
|
|
out *avax.TransferableOutput,
|
|
txID ids.ID,
|
|
chainID string,
|
|
stake bool,
|
|
genesisutxo bool,
|
|
) (uint64, error) {
|
|
var err error
|
|
_, totalout, err = w.ProcessStateOut(ctx, out.Out, txID, idx, out.AssetID(), 0, totalout, chainID, stake, genesisutxo)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return totalout, nil
|
|
}
|
|
|
|
func (w *Writer) InsertOutput(
|
|
ctx services.ConsumerCtx,
|
|
txID ids.ID,
|
|
idx uint32,
|
|
assetID ids.ID,
|
|
out *secp256k1fx.TransferOutput,
|
|
outputType models.OutputType,
|
|
groupID uint32,
|
|
payload []byte,
|
|
stakeLocktime uint64,
|
|
chainID string,
|
|
stake bool,
|
|
frozen bool,
|
|
stakeableout bool,
|
|
genesisutxo bool,
|
|
) error {
|
|
outputID := txID.Prefix(uint64(idx))
|
|
|
|
var err error
|
|
|
|
// Ingest each Output Address
|
|
for _, addr := range out.Addresses() {
|
|
addrBytes := [20]byte{}
|
|
copy(addrBytes[:], addr)
|
|
addrid := ids.ShortID(addrBytes)
|
|
err = w.InsertOutputAddress(ctx, outputID, addrid, nil, txID, idx, chainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outputTxsAccumulate := &db.OutputTxsAccumulate{
|
|
ChainID: chainID,
|
|
AssetID: assetID.String(),
|
|
Address: addrid.String(),
|
|
TransactionID: txID.String(),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
outputTxsAccumulate.ComputeID()
|
|
err = ctx.Persist().InsertOutputTxsAccumulate(ctx.Ctx(), ctx.DB(), outputTxsAccumulate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
output := &db.Outputs{
|
|
ID: outputID.String(),
|
|
ChainID: chainID,
|
|
TransactionID: txID.String(),
|
|
OutputIndex: idx,
|
|
AssetID: assetID.String(),
|
|
OutputType: outputType,
|
|
Amount: out.Amount(),
|
|
Locktime: out.Locktime,
|
|
Threshold: out.Threshold,
|
|
GroupID: groupID,
|
|
Payload: payload,
|
|
StakeLocktime: stakeLocktime,
|
|
Stake: stake,
|
|
Frozen: frozen,
|
|
Stakeableout: stakeableout,
|
|
Genesisutxo: genesisutxo,
|
|
CreatedAt: ctx.Time(),
|
|
}
|
|
|
|
// ensure that addresses are created before the outputs
|
|
return ctx.Persist().InsertOutputs(ctx.Ctx(), ctx.DB(), output, cfg.PerformUpdates)
|
|
}
|
|
|
|
func (w *Writer) InsertAddressFromPublicKey(
|
|
ctx services.ConsumerCtx,
|
|
publicKey crypto.PublicKey,
|
|
) error {
|
|
addresses := &db.Addresses{
|
|
Address: publicKey.Address().String(),
|
|
PublicKey: publicKey.Bytes(),
|
|
CreatedAt: ctx.Time(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
return ctx.Persist().InsertAddresses(ctx.Ctx(), ctx.DB(), addresses, cfg.PerformUpdates)
|
|
}
|
|
|
|
func (w *Writer) InsertOutputAddress(
|
|
ctx services.ConsumerCtx,
|
|
outputID ids.ID,
|
|
addressID ids.ShortID,
|
|
sig []byte,
|
|
txID ids.ID,
|
|
idx uint32,
|
|
chainID string,
|
|
) error {
|
|
addressChain := &db.AddressChain{
|
|
Address: addressID.String(),
|
|
ChainID: chainID,
|
|
CreatedAt: ctx.Time(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
err := ctx.Persist().InsertAddressChain(ctx.Ctx(), ctx.DB(), addressChain, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bech32Addr, err := address.FormatBech32(models.Bech32HRP, addressID.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addressBech32 := &db.AddressBech32{
|
|
Address: addressID.String(),
|
|
Bech32Address: bech32Addr,
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
err = ctx.Persist().InsertAddressBech32(ctx.Ctx(), ctx.DB(), addressBech32, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outputAddressAccumulate := &db.OutputAddressAccumulate{
|
|
OutputID: outputID.String(),
|
|
Address: addressID.String(),
|
|
TransactionID: txID.String(),
|
|
OutputIndex: idx,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
outputAddressAccumulate.ComputeID()
|
|
err = ctx.Persist().InsertOutputAddressAccumulateOut(ctx.Ctx(), ctx.DB(), outputAddressAccumulate, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ctx.Persist().InsertOutputAddressAccumulateIn(ctx.Ctx(), ctx.DB(), outputAddressAccumulate, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outputAddresses := &db.OutputAddresses{
|
|
OutputID: outputID.String(),
|
|
Address: addressID.String(),
|
|
RedeemingSignature: sig,
|
|
CreatedAt: ctx.Time(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
err = ctx.Persist().InsertOutputAddresses(ctx.Ctx(), ctx.DB(), outputAddresses, cfg.PerformUpdates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if sig == nil {
|
|
return nil
|
|
}
|
|
|
|
return ctx.Persist().UpdateOutputAddresses(ctx.Ctx(), ctx.DB(), outputAddresses)
|
|
}
|
|
|
|
func (w *Writer) ProcessStateOut(
|
|
ctx services.ConsumerCtx,
|
|
out verify.State,
|
|
txID ids.ID,
|
|
outputCount uint32,
|
|
assetID ids.ID,
|
|
amount uint64,
|
|
totalout uint64,
|
|
chainID string,
|
|
stake bool,
|
|
genesisutxo bool,
|
|
) (uint64, uint64, error) {
|
|
xOut := func(oo secp256k1fx.OutputOwners) *secp256k1fx.TransferOutput {
|
|
return &secp256k1fx.TransferOutput{OutputOwners: oo}
|
|
}
|
|
|
|
var err error
|
|
|
|
switch typedOut := out.(type) {
|
|
case *stakeable.LockOut:
|
|
xOut, ok := typedOut.TransferableOut.(*secp256k1fx.TransferOutput)
|
|
if !ok {
|
|
return 0, 0, fmt.Errorf("invalid type *secp256k1fx.TransferOutput")
|
|
}
|
|
if assetID == w.avaxAssetID {
|
|
totalout, err = math.Add64(totalout, xOut.Amt)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
|
|
err = w.InsertOutput(
|
|
ctx,
|
|
txID,
|
|
outputCount,
|
|
assetID,
|
|
xOut,
|
|
models.OutputTypesSECP2556K1Transfer,
|
|
0,
|
|
nil,
|
|
typedOut.Locktime,
|
|
chainID,
|
|
stake,
|
|
false,
|
|
true,
|
|
genesisutxo,
|
|
)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
case *nftfx.TransferOutput:
|
|
err = w.InsertOutput(
|
|
ctx,
|
|
txID,
|
|
outputCount,
|
|
assetID,
|
|
xOut(typedOut.OutputOwners),
|
|
models.OutputTypesNFTTransfer,
|
|
typedOut.GroupID,
|
|
typedOut.Payload,
|
|
0,
|
|
chainID,
|
|
stake,
|
|
false,
|
|
false,
|
|
genesisutxo,
|
|
)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
case *nftfx.MintOutput:
|
|
err = w.InsertOutput(
|
|
ctx,
|
|
txID,
|
|
outputCount,
|
|
assetID,
|
|
xOut(typedOut.OutputOwners),
|
|
models.OutputTypesNFTMint,
|
|
typedOut.GroupID,
|
|
nil,
|
|
0,
|
|
chainID,
|
|
stake,
|
|
false,
|
|
false,
|
|
genesisutxo,
|
|
)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
case *secp256k1fx.MintOutput:
|
|
err = w.InsertOutput(
|
|
ctx,
|
|
txID,
|
|
outputCount,
|
|
assetID,
|
|
xOut(typedOut.OutputOwners),
|
|
models.OutputTypesSECP2556K1Mint,
|
|
0,
|
|
nil,
|
|
0,
|
|
chainID,
|
|
stake,
|
|
false,
|
|
false,
|
|
genesisutxo,
|
|
)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
case *secp256k1fx.TransferOutput:
|
|
if assetID == w.avaxAssetID {
|
|
totalout, err = math.Add64(totalout, typedOut.Amount())
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
err = w.InsertOutput(
|
|
ctx,
|
|
txID,
|
|
outputCount,
|
|
assetID,
|
|
typedOut,
|
|
models.OutputTypesSECP2556K1Transfer,
|
|
0,
|
|
nil,
|
|
0,
|
|
chainID,
|
|
stake,
|
|
false,
|
|
false,
|
|
genesisutxo,
|
|
)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
amount, err = math.Add64(amount, typedOut.Amount())
|
|
if err != nil {
|
|
return 0, 0, stacktrace.Propagate(err, "add %v to %v", typedOut.Amount(), amount)
|
|
}
|
|
default:
|
|
return 0, 0, fmt.Errorf("unknown type %s", reflect.TypeOf(out))
|
|
}
|
|
|
|
return amount, totalout, nil
|
|
}
|