Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Banff block support #35

Merged
merged 5 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions database/pchain_entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type PChainTx struct {
Memo string `gorm:"type:varchar(256)"`
Bytes []byte `gorm:"type:mediumblob"`
FeePercentage uint32 // Fee percentage (in case of add validator transaction)
BlockTime *time.Time `gorm:"index"` // Block time, non-null from Banff block activation on (Avalanche 1.9.0)
}

type PChainTxInput struct {
Expand Down
24 changes: 14 additions & 10 deletions database/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ const (
type PChainTxType string

const (
PChainRewardValidatorTx PChainTxType = "REWARD_TX"
PChainAddDelegatorTx PChainTxType = "ADD_DELEGATOR_TX"
PChainAddValidatorTx PChainTxType = "ADD_VALIDATOR_TX"
PChainImportTx PChainTxType = "IMPORT_TX"
PChainExportTx PChainTxType = "EXPORT_TX"
PChainAdvanceTimeTx PChainTxType = "ADVANCE_TIME_TX"
PChainCreateChainTx PChainTxType = "CREATE_CHAIN_TX"
PChainCreateSubnetTx PChainTxType = "CREATE_SUBNET_TX"
PChainAddSubnetValidatorTx PChainTxType = "ADD_SUBNET_VALIDATOR_TX"
PChainUnknownTx PChainTxType = "UNKNOWN_TX"
PChainRewardValidatorTx PChainTxType = "REWARD_TX"
PChainAddDelegatorTx PChainTxType = "ADD_DELEGATOR_TX"
PChainAddValidatorTx PChainTxType = "ADD_VALIDATOR_TX"
PChainImportTx PChainTxType = "IMPORT_TX"
PChainExportTx PChainTxType = "EXPORT_TX"
PChainAdvanceTimeTx PChainTxType = "ADVANCE_TIME_TX"
PChainCreateChainTx PChainTxType = "CREATE_CHAIN_TX"
PChainCreateSubnetTx PChainTxType = "CREATE_SUBNET_TX"
PChainAddSubnetValidatorTx PChainTxType = "ADD_SUBNET_VALIDATOR_TX"
PChainRemoveSubnetValidatorTx PChainTxType = "REMOVE_SUBNET_VALIDATOR_TX"
PChainTransformSubnetTx PChainTxType = "TRANSFORM_SUBNET_TX"
PChainAddPermissionlessValidatorTx PChainTxType = "ADD_PERMISSIONLESS_VALIDATOR_TX"
PChainAddPermissionlessDelegatorTx PChainTxType = "ADD_PERMISSIONLESS_DELEGATOR_TX"
PChainUnknownTx PChainTxType = "UNKNOWN_TX"
)

type PChainBlockType string
Expand Down
9 changes: 6 additions & 3 deletions indexer/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type IndexerContext interface {
}

type IndexerFlags struct {
Version bool

ConfigFileName string

// Set start epoch for voting cronjob to this value, overrides config and database value,
Expand All @@ -33,8 +35,7 @@ type indexerContext struct {
flags *IndexerFlags
}

func BuildContext() (IndexerContext, error) {
flags := parseIndexerFlags()
func BuildContext(flags *IndexerFlags) (IndexerContext, error) {
cfg, err := config.BuildConfig(flags.ConfigFileName)
if err != nil {
return nil, err
Expand All @@ -59,13 +60,15 @@ func (c *indexerContext) DB() *gorm.DB { return c.db }

func (c *indexerContext) Flags() *IndexerFlags { return c.flags }

func parseIndexerFlags() *IndexerFlags {
func ParseIndexerFlags() *IndexerFlags {
cfgFlag := flag.String("config", globalConfig.CONFIG_FILE, "Configuration file (toml format)")
versionFlag := flag.Bool("version", false, "Print version information and exit")
resetVotingFlag := flag.Int64("reset-voting", 0, "Set start epoch for voting cronjob to this value, overrides config and database value, valid values are > 0")
resetMirrorFlag := flag.Int64("reset-mirroring", 0, "Set start epoch for mirroring cronjob to this value, overrides config and database value, valid values are > 0")
flag.Parse()

return &IndexerFlags{
Version: *versionFlag,
ConfigFileName: *cfgFlag,
ResetVotingCronjob: *resetVotingFlag,
ResetMirrorCronjob: *resetMirrorFlag,
Expand Down
9 changes: 8 additions & 1 deletion indexer/main/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ import (
)

func main() {
ctx, err := context.BuildContext()
flags := context.ParseIndexerFlags()

if flags.Version {
fmt.Printf("Flare P-chain indexer version %s\n", shared.ApplicationVersion)
return
}

ctx, err := context.BuildContext(flags)
if err != nil {
fmt.Printf("%v\n", err)
return
Expand Down
49 changes: 37 additions & 12 deletions indexer/pchain/batch_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/blocks"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/proposervm/block"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -69,26 +68,37 @@ func (xi *txBatchIndexer) Reset(containerLen int) {
}

func (xi *txBatchIndexer) AddContainer(index uint64, container indexer.Container) error {
blk, err := block.Parse(container.Bytes)
if err != nil {
return err
}
innerBlk, err := blocks.Parse(blocks.GenesisCodec, blk.Block())
innerBlk, err := chain.ParsePChainBlock(container.Bytes)
if err != nil {
return err
}

switch innerBlkType := innerBlk.(type) {
case *blocks.ApricotProposalBlock:
tx := innerBlkType.Tx
err = xi.addTx(&container, database.PChainProposalBlock, innerBlk.Height(), tx)
err = xi.addTx(&container, database.PChainProposalBlock, innerBlk.Height(), 0, tx)
case *blocks.ApricotCommitBlock:
xi.addEmptyTx(&container, database.PChainCommitBlock, innerBlk.Height())
xi.addEmptyTx(&container, database.PChainCommitBlock, innerBlk.Height(), 0)
case *blocks.ApricotAbortBlock:
xi.addEmptyTx(&container, database.PChainAbortBlock, innerBlk.Height())
xi.addEmptyTx(&container, database.PChainAbortBlock, innerBlk.Height(), 0)
case *blocks.ApricotStandardBlock:
for _, tx := range innerBlkType.Txs() {
err = xi.addTx(&container, database.PChainStandardBlock, innerBlk.Height(), tx)
err = xi.addTx(&container, database.PChainStandardBlock, innerBlk.Height(), 0, tx)
if err != nil {
break
}
}
// Banff blocks were introduced in Avalanche 1.9.0
case *blocks.BanffProposalBlock:
tx := innerBlkType.Tx
err = xi.addTx(&container, database.PChainProposalBlock, innerBlk.Height(), innerBlkType.Time, tx)
case *blocks.BanffCommitBlock:
xi.addEmptyTx(&container, database.PChainCommitBlock, innerBlk.Height(), innerBlkType.Time)
case *blocks.BanffAbortBlock:
xi.addEmptyTx(&container, database.PChainAbortBlock, innerBlk.Height(), innerBlkType.Time)
case *blocks.BanffStandardBlock:
for _, tx := range innerBlkType.Txs() {
err = xi.addTx(&container, database.PChainStandardBlock, innerBlk.Height(), innerBlkType.Time, tx)
if err != nil {
break
}
Expand All @@ -103,7 +113,7 @@ func (xi *txBatchIndexer) ProcessBatch() error {
return xi.inOutIndexer.ProcessBatch()
}

func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database.PChainBlockType, height uint64, tx *txs.Tx) error {
func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database.PChainBlockType, height uint64, blockTime uint64, tx *txs.Tx) error {
txID := tx.ID().String()
dbTx := &database.PChainTx{}
dbTx.TxID = &txID
Expand All @@ -112,6 +122,10 @@ func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database
dbTx.BlockHeight = height
dbTx.Timestamp = chain.TimestampToTime(container.Timestamp)
dbTx.Bytes = container.Bytes
if blockTime != 0 {
time := time.Unix(int64(blockTime), 0)
dbTx.BlockTime = &time
}

var err error = nil
switch unsignedTx := tx.Unsigned.(type) {
Expand All @@ -133,20 +147,31 @@ func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database
err = xi.updateGeneralBaseTx(dbTx, database.PChainCreateChainTx, &unsignedTx.BaseTx)
case *txs.CreateSubnetTx:
err = xi.updateGeneralBaseTx(dbTx, database.PChainCreateSubnetTx, &unsignedTx.BaseTx)
case *txs.RemoveSubnetValidatorTx:
err = xi.updateGeneralBaseTx(dbTx, database.PChainRemoveSubnetValidatorTx, &unsignedTx.BaseTx)
case *txs.TransformSubnetTx:
err = xi.updateGeneralBaseTx(dbTx, database.PChainTransformSubnetTx, &unsignedTx.BaseTx)
// We leave out the following transaction types as they are rejected by Flare nodes
// - AddPermissionlessValidatorTx
// - AddPermissionlessDelegatorTx
default:
err = fmt.Errorf("p-chain transaction %v with type %T in block %d is not indexed", dbTx.TxID, unsignedTx, height)
}
return err
}

func (xi *txBatchIndexer) addEmptyTx(container *indexer.Container, blockType database.PChainBlockType, height uint64) {
func (xi *txBatchIndexer) addEmptyTx(container *indexer.Container, blockType database.PChainBlockType, height uint64, blockTime uint64) {
dbTx := &database.PChainTx{}
dbTx.BlockID = container.ID.String()
dbTx.BlockType = blockType
dbTx.BlockHeight = height
dbTx.Timestamp = chain.TimestampToTime(container.Timestamp)
dbTx.Bytes = container.Bytes
dbTx.TxID = nil
if blockTime != 0 {
time := time.Unix(int64(blockTime), 0)
dbTx.BlockTime = &time
}

xi.newTxs = append(xi.newTxs, dbTx)
}
Expand Down
5 changes: 5 additions & 0 deletions indexer/pchain/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

func init() {
migrations.Container.Add("2023-02-10-00-00", "Create initial state for P-Chain transactions", createPChainTxState)
migrations.Container.Add("2024-11-07-00-00", "Alter type column size in p_chain_txes table", alterPChainTxType)
}

func createPChainTxState(db *gorm.DB) error {
Expand All @@ -20,3 +21,7 @@ func createPChainTxState(db *gorm.DB) error {
Updated: time.Now(),
})
}

func alterPChainTxType(db *gorm.DB) error {
return db.Exec("ALTER TABLE p_chain_txes CHANGE COLUMN type type VARCHAR(40)").Error
}
5 changes: 5 additions & 0 deletions indexer/shared/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package shared

const (
ApplicationVersion = "2.0.0"
)
9 changes: 9 additions & 0 deletions indexer/shared/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func InitMetricsServer(cfg *config.MetricsConfig) {

r.Path("/metrics").Handler(promhttp.Handler())
r.Path("/health").HandlerFunc(healthHandler)
r.Path("/version").HandlerFunc(versionHandler)

srv := &http.Server{
Addr: cfg.PrometheusAddress,
Expand All @@ -70,6 +71,14 @@ func healthHandler(w http.ResponseWriter, r *http.Request) {
}
}

func versionHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, err := w.Write([]byte(ApplicationVersion))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}

func writeHealthResponse(w http.ResponseWriter) (err error) {
ok, err := getHealthStatus()
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions services/api/pchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type ApiPChainTx struct {
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
Weight uint64 `json:"weight"`
BlockTime *time.Time `json:"blockTime"`

Inputs []ApiPChainTxInput `json:"inputs"`
Outputs []ApiPChainTxOutput `json:"outputs"`
Expand All @@ -40,6 +41,7 @@ func NewApiPChainTx(tx *database.PChainTx, inputs []database.PChainTxInput, outp
StartTime: tx.StartTime,
EndTime: tx.EndTime,
Weight: tx.Weight,
BlockTime: tx.BlockTime,
Inputs: newApiPChainInputs(inputs),
Outputs: newApiPChainOutputs(outputs),
}
Expand Down
2 changes: 1 addition & 1 deletion services/main/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func main() {
}

muxRouter := mux.NewRouter()
router := utils.NewSwaggerRouter(muxRouter, "Flare P-Chain Indexer", "0.1.0")
router := utils.NewSwaggerRouter(muxRouter, "Flare P-Chain Indexer", "0.1.1")
routes.AddTransferRoutes(router, ctx)
routes.AddStakerRoutes(router, ctx)
routes.AddTransactionRoutes(router, ctx, epochs)
Expand Down
19 changes: 15 additions & 4 deletions utils/chain/p_chain_rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"os"
"time"

"github.com/ava-labs/avalanchego/api"
"github.com/ava-labs/avalanchego/ids"
Expand All @@ -13,6 +14,10 @@ import (
"github.com/ybbus/jsonrpc/v3"
)

const (
ClientRequestTimeout = 10 * time.Second
)

// Copy-paste from
//
// "github.com/ava-labs/avalanchego/vms/platformvm/service"
Expand Down Expand Up @@ -44,12 +49,15 @@ func (c *AvalancheRPCClient) GetRewardUTXOs(id ids.ID) (*GetRewardUTXOsReply, er
TxID: id,
Encoding: formatting.Hex,
}
reply := &GetRewardUTXOsReply{}
ctx := context.Background()
ctx, cancelCtx := context.WithTimeout(context.Background(), ClientRequestTimeout)
defer cancelCtx()

response, err := c.client.Call(ctx, "platform.getRewardUTXOs", params)
if err != nil {
return nil, err
}

reply := &GetRewardUTXOsReply{}
err = response.GetObject(reply)
if err != nil {
return nil, err
Expand All @@ -62,12 +70,15 @@ func (c *AvalancheRPCClient) GetTx(id ids.ID) (*api.GetTxReply, error) {
TxID: id,
Encoding: formatting.Hex,
}
reply := &api.GetTxReply{}
ctx := context.Background()
ctx, cancelCtx := context.WithTimeout(context.Background(), ClientRequestTimeout)
defer cancelCtx()

response, err := c.client.Call(ctx, "platform.getTx", params)
if err != nil {
return nil, err
}

reply := &api.GetTxReply{}
err = response.GetObject(reply)
if err != nil {
return nil, err
Expand Down
28 changes: 22 additions & 6 deletions utils/chain/p_chain_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,34 @@ var (
ErrInvalidCredentialType = errors.New("invalid credential type")
)

// If block.Parse fails, try to parse as a "pre-fork" block
func ParsePChainBlock(blockBytes []byte) (blocks.Block, error) {
blk, err := block.Parse(blockBytes)
var innerBlk blocks.Block
if err == nil {
innerBlk, err = blocks.Parse(blocks.GenesisCodec, blk.Block())
if err != nil {
return nil, errors.Wrap(err, "failed to parse inner block")
}
} else {
// try to parse as as a "pre-fork" block
innerBlk, err = blocks.Parse(blocks.Codec, blockBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse block")
}
}
return innerBlk, nil
}

// For a given block (byte array) return a list of public keys for
// signatures of inputs of the transaction in this block
// Block must be of type "ApricotProposalBlock"
func PublicKeysFromPChainBlock(blockBytes []byte) ([][]crypto.PublicKey, error) {
blk, err := block.Parse(blockBytes)
innerBlk, err := ParsePChainBlock(blockBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse block")
}
innerBlk, err := blocks.Parse(blocks.GenesisCodec, blk.Block())
if err != nil {
return nil, errors.Wrap(err, "failed to parse inner block")
return nil, err
}

if propBlk, ok := innerBlk.(*blocks.ApricotProposalBlock); ok {
return PublicKeysFromPChainTx(propBlk.Tx)
} else {
Expand Down
Loading