Skip to content

Commit

Permalink
Merge pull request #36 from flare-foundation/Banff_block_support
Browse files Browse the repository at this point in the history
Banff block support
  • Loading branch information
mboben authored Nov 22, 2024
2 parents 65ebe38 + f0e4e4b commit 5877de8
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 39 deletions.
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 {
versionFlag := flag.Bool("version", false, "Print version information and exit")
cfgFlag := flag.String("config", globalConfig.CONFIG_FILE, "Configuration file (toml format)")
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
12 changes: 11 additions & 1 deletion indexer/main/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@ 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
}

logger.Info("Starting Flare indexer application version %s", shared.ApplicationVersion)

err = migrations.Container.ExecuteAll(ctx.DB())
if err != nil {
fmt.Printf("%v\n", err)
Expand Down
52 changes: 38 additions & 14 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 @@ -127,26 +141,36 @@ func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database
err = xi.updateExportTx(dbTx, unsignedTx)
case *txs.AdvanceTimeTx:
xi.updateAdvanceTimeTx(dbTx, unsignedTx)
case *txs.AddSubnetValidatorTx:
err = xi.updateGeneralBaseTx(dbTx, database.PChainAddSubnetValidatorTx, &unsignedTx.BaseTx)
case *txs.CreateChainTx:
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
// - AddSubnetValidatorTx
// - 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.GenesisCodec, 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

0 comments on commit 5877de8

Please sign in to comment.