From 3bc5bbbb04b59b34487f530bf693f45b3d914985 Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Fri, 9 Aug 2024 17:43:44 +0200 Subject: [PATCH 1/5] Support parsing "pre-fork" p-chain blocks --- indexer/pchain/batch_indexer.go | 7 +------ utils/chain/p_chain_tx.go | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/indexer/pchain/batch_indexer.go b/indexer/pchain/batch_indexer.go index 880a332..56f094c 100644 --- a/indexer/pchain/batch_indexer.go +++ b/indexer/pchain/batch_indexer.go @@ -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" ) @@ -69,11 +68,7 @@ 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 } diff --git a/utils/chain/p_chain_tx.go b/utils/chain/p_chain_tx.go index eeb64aa..678210c 100644 --- a/utils/chain/p_chain_tx.go +++ b/utils/chain/p_chain_tx.go @@ -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 { From 890ae56c650302d970d978535ad61a48105539de Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Wed, 6 Nov 2024 19:06:24 +0100 Subject: [PATCH 2/5] Support Banff blocks (v1.9.0 upgrade) on P-chain. Added blockTime to PChainTx and ApiPChainTx. --- database/pchain_entities.go | 1 + indexer/pchain/batch_indexer.go | 35 +++++++++++++++++++++++++++------ services/api/pchain.go | 2 ++ services/main/services.go | 2 +- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/database/pchain_entities.go b/database/pchain_entities.go index 61e06ae..d2bb866 100644 --- a/database/pchain_entities.go +++ b/database/pchain_entities.go @@ -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 { diff --git a/indexer/pchain/batch_indexer.go b/indexer/pchain/batch_indexer.go index 56f094c..5d1ebc0 100644 --- a/indexer/pchain/batch_indexer.go +++ b/indexer/pchain/batch_indexer.go @@ -76,14 +76,29 @@ func (xi *txBatchIndexer) AddContainer(index uint64, container indexer.Container 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 } @@ -98,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 @@ -107,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) { @@ -134,7 +153,7 @@ func (xi *txBatchIndexer) addTx(container *indexer.Container, blockType database 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 @@ -142,6 +161,10 @@ func (xi *txBatchIndexer) addEmptyTx(container *indexer.Container, blockType dat 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) } diff --git a/services/api/pchain.go b/services/api/pchain.go index c87a80b..6383b50 100644 --- a/services/api/pchain.go +++ b/services/api/pchain.go @@ -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"` @@ -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), } diff --git a/services/main/services.go b/services/main/services.go index 8338b1b..5602a79 100644 --- a/services/main/services.go +++ b/services/main/services.go @@ -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) From d9d976ad0744d3e3b23659f7000fd85560a23bd3 Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Thu, 7 Nov 2024 14:58:57 +0100 Subject: [PATCH 3/5] Timeout for P-chain rpc client requests --- utils/chain/p_chain_rpc_client.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/utils/chain/p_chain_rpc_client.go b/utils/chain/p_chain_rpc_client.go index c6a2a51..5e2ae8a 100644 --- a/utils/chain/p_chain_rpc_client.go +++ b/utils/chain/p_chain_rpc_client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "time" "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/ids" @@ -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" @@ -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 @@ -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 From e73c64380746a4c0de8bddccd05427bf04736277 Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Thu, 7 Nov 2024 16:18:35 +0100 Subject: [PATCH 4/5] Add handling of P-chain transactions added in Avalanche 1.9.0 --- database/types.go | 24 ++++++++++++++---------- indexer/pchain/batch_indexer.go | 7 +++++++ indexer/pchain/migrations.go | 5 +++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/database/types.go b/database/types.go index b2603fa..501fc9e 100644 --- a/database/types.go +++ b/database/types.go @@ -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 diff --git a/indexer/pchain/batch_indexer.go b/indexer/pchain/batch_indexer.go index 5d1ebc0..cadb1b1 100644 --- a/indexer/pchain/batch_indexer.go +++ b/indexer/pchain/batch_indexer.go @@ -147,6 +147,13 @@ 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) } diff --git a/indexer/pchain/migrations.go b/indexer/pchain/migrations.go index 0f6fba3..f70f20d 100644 --- a/indexer/pchain/migrations.go +++ b/indexer/pchain/migrations.go @@ -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 { @@ -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 +} From fe0dd15c36131dda91297a2438474f4ec19bb93c Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Thu, 7 Nov 2024 17:24:23 +0100 Subject: [PATCH 5/5] Added version route and version command line argument --- indexer/context/context.go | 9 ++++++--- indexer/main/indexer.go | 9 ++++++++- indexer/shared/constants.go | 5 +++++ indexer/shared/metrics.go | 9 +++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 indexer/shared/constants.go diff --git a/indexer/context/context.go b/indexer/context/context.go index 52cccc1..21d5fea 100644 --- a/indexer/context/context.go +++ b/indexer/context/context.go @@ -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, @@ -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 @@ -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, diff --git a/indexer/main/indexer.go b/indexer/main/indexer.go index 3badb4c..14cafd1 100644 --- a/indexer/main/indexer.go +++ b/indexer/main/indexer.go @@ -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 diff --git a/indexer/shared/constants.go b/indexer/shared/constants.go new file mode 100644 index 0000000..477cf20 --- /dev/null +++ b/indexer/shared/constants.go @@ -0,0 +1,5 @@ +package shared + +const ( + ApplicationVersion = "2.0.0" +) diff --git a/indexer/shared/metrics.go b/indexer/shared/metrics.go index 3c5b1c1..aab3f0c 100644 --- a/indexer/shared/metrics.go +++ b/indexer/shared/metrics.go @@ -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, @@ -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 {