diff --git a/api/spec/v1.yaml b/api/spec/v1.yaml index f77313d98..a0824d5cb 100644 --- a/api/spec/v1.yaml +++ b/api/spec/v1.yaml @@ -1981,7 +1981,7 @@ components: Validator: type: object - required: [entity_address, entity_id, escrow, voting_power, active, start_date, rank, in_validator_set, current_rate, current_commission_bound] + required: [entity_address, entity_id, escrow, voting_power, active, start_date, rank, in_validator_set, current_rate, current_commission_bound, signed_blocks] properties: entity_address: type: string @@ -2032,9 +2032,29 @@ components: description: Commission rate. current_commission_bound: allOf: [$ref: '#/components/schemas/ValidatorCommissionBound'] + signed_blocks: + type: array + description: An array containing details of the last 100 consensus blocks, indicating whether each block was signed by the validator. + items: + allOf: [$ref: '#/components/schemas/ValidatorSignedBlock'] description: | An validator registered at the consensus layer. + ValidatorSignedBlock: + type: object + required: [height, signed] + properties: + height: + type: integer + format: int64 + description: The block height at which this block was signed. + example: *block_height_1 + signed: + type: boolean + description: Whether the validator signed the block. + description: | + Information weather a block was signed by the validator. + Escrow: type: object properties: diff --git a/storage/client/client.go b/storage/client/client.go index 478f85ce5..cb70c36b4 100644 --- a/storage/client/client.go +++ b/storage/client/client.go @@ -1252,7 +1252,7 @@ func (c *StorageClient) Validators(ctx context.Context, p apiTypes.GetConsensusV } var schedule staking.CommissionSchedule var logoUrl *string - if err := res.rows.Scan( + if err = res.rows.Scan( &v.EntityID, &v.EntityAddress, &v.NodeID, @@ -1303,6 +1303,41 @@ func (c *StorageClient) Validators(ctx context.Context, p apiTypes.GetConsensusV vs.Validators = append(vs.Validators, v) } + // Load validators blocks signed data for last 100 blocks. + rows, err := c.db.Query(ctx, queries.ValidatorsBlocksSigned) + if err != nil { + return nil, wrapError(err) + } + defer rows.Close() + for rows.Next() { + var height int64 + var signersArr []string + if err = rows.Scan( + &height, + &signersArr, + ); err != nil { + return nil, wrapError(err) + } + // Lookup map of signers. + signers := make(map[string]struct{}) + for _, s := range signersArr { + signers[s] = struct{}{} + } + + // Go through all validators for each height and mark weather it has singed the block. + for i, v := range vs.Validators { + if v.SignedBlocks == nil { + v.SignedBlocks = []ValidatorSignedBlock{} + } + _, exists := signers[v.EntityID] + v.SignedBlocks = append(v.SignedBlocks, ValidatorSignedBlock{ + Height: height, + Signed: exists, + }) + vs.Validators[i] = v + } + } + return &vs, nil } diff --git a/storage/client/queries/queries.go b/storage/client/queries/queries.go index fa299683d..600adc4a8 100644 --- a/storage/client/queries/queries.go +++ b/storage/client/queries/queries.go @@ -320,6 +320,16 @@ const ( JOIN chain.accounts AS accts ON entities.address = accts.address) , 0) AS total_staked_balance` + ValidatorsBlocksSigned = ` + SELECT height, signer_entity_ids + FROM ( + SELECT height, signer_entity_ids + FROM chain.blocks + ORDER BY height DESC + LIMIT 100 + ) AS subquery + ORDER BY height ASC` + ValidatorsData = ` WITH -- Find all self-delegations for all accounts with active delegations. diff --git a/storage/client/types.go b/storage/client/types.go index 07d4712fd..c84628c64 100644 --- a/storage/client/types.go +++ b/storage/client/types.go @@ -99,6 +99,9 @@ type ValidatorMedia = api.ValidatorMedia // ValidatorCommissionBound is the commission bound for a validator. type ValidatorCommissionBound = api.ValidatorCommissionBound +// ValidatorSignedBlock is the information weather a validator has signed a specific block. +type ValidatorSignedBlock = api.ValidatorSignedBlock + // ValidatorHistory is the storage response for GetValidatorHistory. type ValidatorHistory = api.ValidatorHistory