diff --git a/api/converter_utils.go b/api/converter_utils.go index 3f30a6678..ed53e5891 100644 --- a/api/converter_utils.go +++ b/api/converter_utils.go @@ -34,6 +34,15 @@ func decodeDigest(str *string, field string, errorArr []string) (string, []strin return "", errorArr } +// decodeSdkAddress returns the sdk.Address representation of the input string, or appends an error to errorArr +func decodeSdkAddress(str string, field string, errorArr []string) (sdk.Address, []string) { + addr, err := sdk.DecodeAddress(str) + if err != nil { + return sdk.ZeroAddress, append(errorArr, fmt.Sprintf("%s '%s': %v", errUnableToParseAddress, field, err)) + } + return addr, errorArr +} + // decodeAddress returns the byte representation of the input string, or appends an error to errorArr func decodeAddress(str *string, field string, errorArr []string) ([]byte, []string) { if str != nil { @@ -258,6 +267,95 @@ type rowData struct { AssetCloseAmount uint64 } +// rowToBlock parses the idb.BlockRow and generates the appropriate generated.Block object. +func rowToBlock(blockHeader *sdk.BlockHeader) generated.Block { + + rewards := generated.BlockRewards{ + FeeSink: blockHeader.FeeSink.String(), + RewardsCalculationRound: uint64(blockHeader.RewardsRecalculationRound), + RewardsLevel: blockHeader.RewardsLevel, + RewardsPool: blockHeader.RewardsPool.String(), + RewardsRate: blockHeader.RewardsRate, + RewardsResidue: blockHeader.RewardsResidue, + } + + upgradeState := generated.BlockUpgradeState{ + CurrentProtocol: string(blockHeader.CurrentProtocol), + NextProtocol: strPtr(string(blockHeader.NextProtocol)), + NextProtocolApprovals: uint64Ptr(blockHeader.NextProtocolApprovals), + NextProtocolSwitchOn: uint64Ptr(uint64(blockHeader.NextProtocolSwitchOn)), + NextProtocolVoteBefore: uint64Ptr(uint64(blockHeader.NextProtocolVoteBefore)), + } + + upgradeVote := generated.BlockUpgradeVote{ + UpgradeApprove: boolPtr(blockHeader.UpgradeApprove), + UpgradeDelay: uint64Ptr(uint64(blockHeader.UpgradeDelay)), + UpgradePropose: strPtr(string(blockHeader.UpgradePropose)), + } + + var partUpdates *generated.ParticipationUpdates = &generated.ParticipationUpdates{} + if len(blockHeader.ExpiredParticipationAccounts) > 0 { + addrs := make([]string, len(blockHeader.ExpiredParticipationAccounts)) + for i := 0; i < len(addrs); i++ { + addrs[i] = blockHeader.ExpiredParticipationAccounts[i].String() + } + partUpdates.ExpiredParticipationAccounts = strArrayPtr(addrs) + } + if len(blockHeader.AbsentParticipationAccounts) > 0 { + addrs := make([]string, len(blockHeader.AbsentParticipationAccounts)) + for i := 0; i < len(addrs); i++ { + addrs[i] = blockHeader.AbsentParticipationAccounts[i].String() + } + partUpdates.AbsentParticipationAccounts = strArrayPtr(addrs) + } + if *partUpdates == (generated.ParticipationUpdates{}) { + partUpdates = nil + } + + // order these so they're deterministic + orderedTrackingTypes := make([]sdk.StateProofType, len(blockHeader.StateProofTracking)) + trackingArray := make([]generated.StateProofTracking, len(blockHeader.StateProofTracking)) + elems := 0 + for key := range blockHeader.StateProofTracking { + orderedTrackingTypes[elems] = key + elems++ + } + sort.Slice(orderedTrackingTypes, func(i, j int) bool { return orderedTrackingTypes[i] < orderedTrackingTypes[j] }) + for i := 0; i < len(orderedTrackingTypes); i++ { + stpfTracking := blockHeader.StateProofTracking[orderedTrackingTypes[i]] + thing1 := generated.StateProofTracking{ + NextRound: uint64Ptr(uint64(stpfTracking.StateProofNextRound)), + Type: uint64Ptr(uint64(orderedTrackingTypes[i])), + VotersCommitment: byteSliceOmitZeroPtr(stpfTracking.StateProofVotersCommitment), + OnlineTotalWeight: uint64Ptr(uint64(stpfTracking.StateProofOnlineTotalWeight)), + } + trackingArray[orderedTrackingTypes[i]] = thing1 + } + + ret := generated.Block{ + Bonus: uint64PtrOrNil(uint64(blockHeader.Bonus)), + FeesCollected: uint64PtrOrNil(uint64(blockHeader.FeesCollected)), + GenesisHash: blockHeader.GenesisHash[:], + GenesisId: blockHeader.GenesisID, + ParticipationUpdates: partUpdates, + PreviousBlockHash: blockHeader.Branch[:], + Proposer: addrPtr(blockHeader.Proposer), + ProposerPayout: uint64PtrOrNil(uint64(blockHeader.ProposerPayout)), + Rewards: &rewards, + Round: uint64(blockHeader.Round), + Seed: blockHeader.Seed[:], + StateProofTracking: &trackingArray, + Timestamp: uint64(blockHeader.TimeStamp), + Transactions: nil, + TransactionsRoot: blockHeader.TxnCommitments.NativeSha512_256Commitment[:], + TransactionsRootSha256: blockHeader.TxnCommitments.Sha256Commitment[:], + TxnCounter: uint64Ptr(blockHeader.TxnCounter), + UpgradeState: &upgradeState, + UpgradeVote: &upgradeVote, + } + return ret +} + // txnRowToTransaction parses the idb.TxnRow and generates the appropriate generated.Transaction object. // If the TxnRow contains a RootTxn, the generated.Transaction object will be the root txn. func txnRowToTransaction(row idb.TxnRow) (generated.Transaction, error) { @@ -706,6 +804,142 @@ func (si *ServerImplementation) appParamsToApplicationQuery(params generated.Sea }, nil } +func (si *ServerImplementation) blockParamsToBlockFilter(params generated.SearchForBlocksParams) (filter idb.BlockFilter, err error) { + var errorArr []string + + // Integer + filter.Limit = min(uintOrDefaultValue(params.Limit, si.opts.DefaultBlocksLimit), si.opts.MaxBlocksLimit) + // If min/max are mixed up + // + // This check is performed here instead of in validateBlockFilter because + // when converting params into a filter, the next token is merged with params.MinRound. + if params.MinRound != nil && params.MaxRound != nil && *params.MinRound > *params.MaxRound { + errorArr = append(errorArr, errInvalidRoundMinMax) + } + filter.MaxRound = params.MaxRound + filter.MinRound = params.MinRound + + // String + if params.Next != nil { + n, err := idb.DecodeBlockRowNext(*params.Next) + if err != nil { + errorArr = append(errorArr, fmt.Sprintf("%s: %v", errUnableToParseNext, err)) + } + // Set the MinRound + if filter.MinRound == nil { + filter.MinRound = uint64Ptr(n + 1) + } else { + filter.MinRound = uint64Ptr(max(*filter.MinRound, n+1)) + } + } + + // Time + if params.AfterTime != nil { + filter.AfterTime = *params.AfterTime + } + if params.BeforeTime != nil { + filter.BeforeTime = *params.BeforeTime + } + + // Address list + { + // Make sure at most one of the participation parameters is set + numParticipationFilters := 0 + if params.Proposer != nil { + numParticipationFilters++ + } + if params.Expired != nil { + numParticipationFilters++ + } + if params.Absent != nil { + numParticipationFilters++ + } + if params.Updates != nil { + numParticipationFilters++ + } + if params.Participation != nil { + numParticipationFilters++ + } + if numParticipationFilters > 1 { + errorArr = append(errorArr, "only one of `proposer`, `expired`, `absent`, `updates`, or `participation` can be specified") + } + + // Validate the number of items in the participation account lists + if params.Proposer != nil && uint64(len(*params.Proposer)) > si.opts.MaxAccountListSize { + errorArr = append(errorArr, fmt.Sprintf("proposer list too long, max size is %d", si.opts.MaxAccountListSize)) + } + if params.Expired != nil && uint64(len(*params.Expired)) > si.opts.MaxAccountListSize { + errorArr = append(errorArr, fmt.Sprintf("expired list too long, max size is %d", si.opts.MaxAccountListSize)) + } + if params.Absent != nil && uint64(len(*params.Absent)) > si.opts.MaxAccountListSize { + errorArr = append(errorArr, fmt.Sprintf("absent list too long, max size is %d", si.opts.MaxAccountListSize)) + } + if params.Updates != nil && uint64(len(*params.Updates)) > si.opts.MaxAccountListSize { + errorArr = append(errorArr, fmt.Sprintf("updates list too long, max size is %d", si.opts.MaxAccountListSize)) + } + if params.Participation != nil && uint64(len(*params.Participation)) > si.opts.MaxAccountListSize { + errorArr = append(errorArr, fmt.Sprintf("participation list too long, max size is %d", si.opts.MaxAccountListSize)) + } + + filter.Proposers = make(map[sdk.Address]struct{}, 0) + if params.Proposer != nil { + for _, s := range *params.Proposer { + var addr sdk.Address + addr, errorArr = decodeSdkAddress(s, "proposer", errorArr) + filter.Proposers[addr] = struct{}{} + } + } + + filter.ExpiredParticipationAccounts = make(map[sdk.Address]struct{}, 0) + if params.Expired != nil { + for _, s := range *params.Expired { + var addr sdk.Address + addr, errorArr = decodeSdkAddress(s, "expired", errorArr) + filter.ExpiredParticipationAccounts[addr] = struct{}{} + } + } + + filter.AbsentParticipationAccounts = make(map[sdk.Address]struct{}, 0) + if params.Absent != nil { + for _, s := range *params.Absent { + var addr sdk.Address + addr, errorArr = decodeSdkAddress(s, "absent", errorArr) + filter.AbsentParticipationAccounts[addr] = struct{}{} + } + } + + // Updates = absent || expired + if params.Updates != nil { + for _, s := range *params.Updates { + var addr sdk.Address + addr, errorArr = decodeSdkAddress(s, "updates", errorArr) + filter.AbsentParticipationAccounts[addr] = struct{}{} + filter.ExpiredParticipationAccounts[addr] = struct{}{} + } + } + + // Participation = proposer || absent || expired + if params.Participation != nil { + for _, s := range *params.Participation { + var addr sdk.Address + addr, errorArr = decodeSdkAddress(s, "participation", errorArr) + filter.Proposers[addr] = struct{}{} + filter.AbsentParticipationAccounts[addr] = struct{}{} + filter.ExpiredParticipationAccounts[addr] = struct{}{} + } + } + } + + // If there were any errorArr while setting up the BlockFilter, return now. + if len(errorArr) > 0 { + err = errors.New("invalid input: " + strings.Join(errorArr, ", ")) + + // clear out the intermediates. + filter = idb.BlockFilter{} + } + return +} + func (si *ServerImplementation) transactionParamsToTransactionFilter(params generated.SearchForTransactionsParams) (filter idb.TransactionFilter, err error) { var errorArr = make([]string, 0) diff --git a/api/error_messages.go b/api/error_messages.go index 71401b0d4..0bc4fb348 100644 --- a/api/error_messages.go +++ b/api/error_messages.go @@ -11,6 +11,7 @@ import ( const ( errInvalidRoundAndMinMax = "cannot specify round and min-round/max-round" errInvalidRoundMinMax = "min-round must be less than max-round" + errInvalidTimeMinMax = "after-time must be less than before-time" errUnableToParseAddress = "unable to parse address" errInvalidCreatorAddress = "found an invalid creator address" errUnableToParseBase64 = "unable to parse base64 data" @@ -39,6 +40,7 @@ const ( ErrFailedLookingUpBoxes = "failed while looking up application boxes" errRewindingAccountNotSupported = "rewinding account is no longer supported, please remove the `round=` query parameter and try again" errLookingUpBlockForRound = "error while looking up block for round" + errBlockHeaderSearch = "error while searching for block headers" errTransactionSearch = "error while searching for transaction" errZeroAddressCloseRemainderToRole = "searching transactions by zero address with close address role is not supported" errZeroAddressAssetSenderRole = "searching transactions by zero address with asset sender role is not supported" diff --git a/api/generated/common/routes.go b/api/generated/common/routes.go index aa093f800..7fe53b159 100644 --- a/api/generated/common/routes.go +++ b/api/generated/common/routes.go @@ -73,189 +73,192 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", - "/y8AAP//tVra42nhAAA=", + "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", + "sSCa/mjpAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/generated/common/types.go b/api/generated/common/types.go index 92b65acb9..0ee1979d4 100644 --- a/api/generated/common/types.go +++ b/api/generated/common/types.go @@ -1144,6 +1144,9 @@ type TransactionStateProof struct { StateProofType *uint64 `json:"state-proof-type,omitempty"` } +// Absent defines model for absent. +type Absent = []string + // AccountId defines model for account-id. type AccountId = string @@ -1183,6 +1186,9 @@ type Exclude = []string // ExcludeCloseTo defines model for exclude-close-to. type ExcludeCloseTo = bool +// Expired defines model for expired. +type Expired = []string + // GroupId defines model for group-id. type GroupId = string @@ -1207,6 +1213,12 @@ type Next = string // NotePrefix defines model for note-prefix. type NotePrefix = string +// Participation defines model for participation. +type Participation = []string + +// Proposer defines model for proposer. +type Proposer = []string + // RekeyTo defines model for rekey-to. type RekeyTo = bool @@ -1228,6 +1240,9 @@ type TxType string // Txid defines model for txid. type Txid = string +// Updates defines model for updates. +type Updates = []string + // AccountResponse defines model for AccountResponse. type AccountResponse struct { // Account Account information at a given round. @@ -1343,6 +1358,17 @@ type AssetsResponse struct { // data/bookkeeping/block.go : Block type BlockResponse = Block +// BlocksResponse defines model for BlocksResponse. +type BlocksResponse struct { + Blocks []Block `json:"blocks"` + + // CurrentRound Round at which the results were computed. + CurrentRound uint64 `json:"current-round"` + + // NextToken Used for pagination, when making another request provide this token with the next parameter. + NextToken *string `json:"next-token,omitempty"` +} + // BoxResponse Box name and its content. type BoxResponse = Box diff --git a/api/generated/v2/routes.go b/api/generated/v2/routes.go index 67a7e76c7..54ac3db2d 100644 --- a/api/generated/v2/routes.go +++ b/api/generated/v2/routes.go @@ -69,6 +69,9 @@ type ServerInterface interface { // (GET /v2/assets/{asset-id}/transactions) LookupAssetTransactions(ctx echo.Context, assetId uint64, params LookupAssetTransactionsParams) error + // (GET /v2/blocks) + SearchForBlocks(ctx echo.Context, params SearchForBlocksParams) error + // (GET /v2/blocks/{round-number}) LookupBlock(ctx echo.Context, roundNumber uint64, params LookupBlockParams) error @@ -974,6 +977,94 @@ func (w *ServerInterfaceWrapper) LookupAssetTransactions(ctx echo.Context) error return err } +// SearchForBlocks converts echo context to params. +func (w *ServerInterfaceWrapper) SearchForBlocks(ctx echo.Context) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params SearchForBlocksParams + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "next" ------------- + + err = runtime.BindQueryParameter("form", true, false, "next", ctx.QueryParams(), ¶ms.Next) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter next: %s", err)) + } + + // ------------- Optional query parameter "min-round" ------------- + + err = runtime.BindQueryParameter("form", true, false, "min-round", ctx.QueryParams(), ¶ms.MinRound) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter min-round: %s", err)) + } + + // ------------- Optional query parameter "max-round" ------------- + + err = runtime.BindQueryParameter("form", true, false, "max-round", ctx.QueryParams(), ¶ms.MaxRound) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter max-round: %s", err)) + } + + // ------------- Optional query parameter "before-time" ------------- + + err = runtime.BindQueryParameter("form", true, false, "before-time", ctx.QueryParams(), ¶ms.BeforeTime) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter before-time: %s", err)) + } + + // ------------- Optional query parameter "after-time" ------------- + + err = runtime.BindQueryParameter("form", true, false, "after-time", ctx.QueryParams(), ¶ms.AfterTime) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter after-time: %s", err)) + } + + // ------------- Optional query parameter "proposer" ------------- + + err = runtime.BindQueryParameter("form", false, false, "proposer", ctx.QueryParams(), ¶ms.Proposer) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter proposer: %s", err)) + } + + // ------------- Optional query parameter "expired" ------------- + + err = runtime.BindQueryParameter("form", false, false, "expired", ctx.QueryParams(), ¶ms.Expired) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter expired: %s", err)) + } + + // ------------- Optional query parameter "absent" ------------- + + err = runtime.BindQueryParameter("form", false, false, "absent", ctx.QueryParams(), ¶ms.Absent) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter absent: %s", err)) + } + + // ------------- Optional query parameter "updates" ------------- + + err = runtime.BindQueryParameter("form", false, false, "updates", ctx.QueryParams(), ¶ms.Updates) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter updates: %s", err)) + } + + // ------------- Optional query parameter "participation" ------------- + + err = runtime.BindQueryParameter("form", false, false, "participation", ctx.QueryParams(), ¶ms.Participation) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter participation: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.SearchForBlocks(ctx, params) + return err +} + // LookupBlock converts echo context to params. func (w *ServerInterfaceWrapper) LookupBlock(ctx echo.Context) error { var err error @@ -1210,6 +1301,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/assets/:asset-id", wrapper.LookupAssetByID, m...) router.GET(baseURL+"/v2/assets/:asset-id/balances", wrapper.LookupAssetBalances, m...) router.GET(baseURL+"/v2/assets/:asset-id/transactions", wrapper.LookupAssetTransactions, m...) + router.GET(baseURL+"/v2/blocks", wrapper.SearchForBlocks, m...) router.GET(baseURL+"/v2/blocks/:round-number", wrapper.LookupBlock, m...) router.GET(baseURL+"/v2/transactions", wrapper.SearchForTransactions, m...) router.GET(baseURL+"/v2/transactions/:txid", wrapper.LookupTransaction, m...) @@ -1219,231 +1311,237 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/generated/v2/types.go b/api/generated/v2/types.go index b234ab79c..052287ec3 100644 --- a/api/generated/v2/types.go +++ b/api/generated/v2/types.go @@ -1232,6 +1232,9 @@ type TransactionStateProof struct { StateProofType *uint64 `json:"state-proof-type,omitempty"` } +// Absent defines model for absent. +type Absent = []string + // AccountId defines model for account-id. type AccountId = string @@ -1271,6 +1274,9 @@ type Exclude = []string // ExcludeCloseTo defines model for exclude-close-to. type ExcludeCloseTo = bool +// Expired defines model for expired. +type Expired = []string + // GroupId defines model for group-id. type GroupId = string @@ -1295,6 +1301,12 @@ type Next = string // NotePrefix defines model for note-prefix. type NotePrefix = string +// Participation defines model for participation. +type Participation = []string + +// Proposer defines model for proposer. +type Proposer = []string + // RekeyTo defines model for rekey-to. type RekeyTo = bool @@ -1316,6 +1328,9 @@ type TxType string // Txid defines model for txid. type Txid = string +// Updates defines model for updates. +type Updates = []string + // AccountResponse defines model for AccountResponse. type AccountResponse struct { // Account Account information at a given round. @@ -1431,6 +1446,17 @@ type AssetsResponse struct { // data/bookkeeping/block.go : Block type BlockResponse = Block +// BlocksResponse defines model for BlocksResponse. +type BlocksResponse struct { + Blocks []Block `json:"blocks"` + + // CurrentRound Round at which the results were computed. + CurrentRound uint64 `json:"current-round"` + + // NextToken Used for pagination, when making another request provide this token with the next parameter. + NextToken *string `json:"next-token,omitempty"` +} + // BoxResponse Box name and its content. type BoxResponse = Box @@ -1814,6 +1840,42 @@ type LookupAssetTransactionsParamsSigType string // LookupAssetTransactionsParamsAddressRole defines parameters for LookupAssetTransactions. type LookupAssetTransactionsParamsAddressRole string +// SearchForBlocksParams defines parameters for SearchForBlocks. +type SearchForBlocksParams struct { + // Limit Maximum number of results to return. There could be additional pages even if the limit is not reached. + Limit *uint64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Next The next page of results. Use the next token provided by the previous results. + Next *string `form:"next,omitempty" json:"next,omitempty"` + + // MinRound Include results at or after the specified min-round. + MinRound *uint64 `form:"min-round,omitempty" json:"min-round,omitempty"` + + // MaxRound Include results at or before the specified max-round. + MaxRound *uint64 `form:"max-round,omitempty" json:"max-round,omitempty"` + + // BeforeTime Include results before the given time. Must be an RFC 3339 formatted string. + BeforeTime *time.Time `form:"before-time,omitempty" json:"before-time,omitempty"` + + // AfterTime Include results after the given time. Must be an RFC 3339 formatted string. + AfterTime *time.Time `form:"after-time,omitempty" json:"after-time,omitempty"` + + // Proposer Account(s) marked as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses. + Proposer *[]string `form:"proposer,omitempty" json:"proposer,omitempty"` + + // Expired Account(s) marked as expired in the block header's participation updates. This parameter accepts a comma separated list of addresses. + Expired *[]string `form:"expired,omitempty" json:"expired,omitempty"` + + // Absent Account(s) marked as absent in the block header's participation updates. This parameter accepts a comma separated list of addresses. + Absent *[]string `form:"absent,omitempty" json:"absent,omitempty"` + + // Updates Account(s) marked as expired or absent in the block header's participation updates. This parameter accepts a comma separated list of addresses. + Updates *[]string `form:"updates,omitempty" json:"updates,omitempty"` + + // Participation Account(s) marked as expired, absent or as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses. + Participation *[]string `form:"participation,omitempty" json:"participation,omitempty"` +} + // LookupBlockParams defines parameters for LookupBlock. type LookupBlockParams struct { // HeaderOnly Header only flag. When this is set to true, returned block does not contain the transactions diff --git a/api/handlers.go b/api/handlers.go index 78f15ef7c..6e3f48665 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -45,6 +45,27 @@ type ServerImplementation struct { // Helper functions // ////////////////////// +func validateBlockFilter(filter *idb.BlockFilter) error { + var errorArr = make([]string, 0) + + // Int64 overflows + if (filter.MaxRound != nil && *filter.MaxRound > math.MaxInt64) || + (filter.MinRound != nil && *filter.MinRound > math.MaxInt64) { + errorArr = append(errorArr, errValueExceedingInt64) + } + + // Time + if !filter.AfterTime.IsZero() && !filter.BeforeTime.IsZero() && filter.AfterTime.After(filter.BeforeTime) { + errorArr = append(errorArr, errInvalidTimeMinMax) + } + + if len(errorArr) > 0 { + return errors.New("invalid input: " + strings.Join(errorArr, ", ")) + } + + return nil +} + func validateTransactionFilter(filter *idb.TransactionFilter) error { var errorArr = make([]string, 0) @@ -981,6 +1002,39 @@ func (si *ServerImplementation) LookupTransaction(ctx echo.Context, txid string) return ctx.JSON(http.StatusOK, response) } +// SearchForBlocks returns block headers matching the provided parameters +// (GET /v2/blocks) +func (si *ServerImplementation) SearchForBlocks(ctx echo.Context, params generated.SearchForBlocksParams) error { + // Validate query parameters + if err := si.verifyHandler("SearchForBlocks", ctx); err != nil { + return badRequest(ctx, err.Error()) + } + + // Convert query params into a filter + filter, err := si.blockParamsToBlockFilter(params) + if err != nil { + return badRequest(ctx, err.Error()) + } + err = validateBlockFilter(&filter) + if err != nil { + return badRequest(ctx, err.Error()) + } + + // Fetch the block headers + blockHeaders, next, round, err := si.fetchBlockHeaders(ctx.Request().Context(), filter) + if err != nil { + return indexerError(ctx, fmt.Errorf("%s: %w", errBlockHeaderSearch, err)) + } + + // Populate the response model and render it + response := generated.BlocksResponse{ + CurrentRound: round, + NextToken: strPtr(next), + Blocks: blockHeaders, + } + return ctx.JSON(http.StatusOK, response) +} + // SearchForTransactions returns transactions matching the provided parameters // (GET /v2/transactions) func (si *ServerImplementation) SearchForTransactions(ctx echo.Context, params generated.SearchForTransactionsParams) error { @@ -1416,6 +1470,46 @@ func (si *ServerImplementation) fetchAccounts(ctx context.Context, options idb.A return accounts, round, nil } +// fetchBlockHeaders is used to query the backend for block headers, and compute the next token +func (si *ServerImplementation) fetchBlockHeaders(ctx context.Context, bf idb.BlockFilter) ([]generated.Block, string, uint64 /*round*/, error) { + + var round uint64 + var nextToken string + results := make([]generated.Block, 0) + err := callWithTimeout(ctx, si.log, si.timeout, func(ctx context.Context) error { + + // Open a channel from which result rows will be received + var rows <-chan idb.BlockRow + rows, round = si.db.Blocks(ctx, bf) + + // Iterate receieved rows, converting each to a generated.Block + var lastRow idb.BlockRow + for row := range rows { + if row.Error != nil { + return row.Error + } + + results = append(results, rowToBlock(&row.BlockHeader)) + lastRow = row + } + + // No next token if there were no results. + if len(results) == 0 { + return nil + } + + // Generate the next token and return + var err error + nextToken, err = lastRow.Next() + return err + }) + if err != nil { + return nil, "", 0, err + } + + return results, nextToken, round, nil +} + // fetchTransactions is used to query the backend for transactions, and compute the next token // If returnInnerTxnOnly is false, then the root txn is returned for a inner txn match. func (si *ServerImplementation) fetchTransactions(ctx context.Context, filter idb.TransactionFilter) ([]generated.Transaction, string, uint64 /*round*/, error) { diff --git a/api/indexer.oas2.json b/api/indexer.oas2.json index 2deec1b49..164c8cf80 100644 --- a/api/indexer.oas2.json +++ b/api/indexer.oas2.json @@ -853,6 +853,67 @@ } } }, + "/v2/blocks": { + "get": { + "description": "Search for blocks. Blocks are returned in ascending round order. Transactions are not included in the output.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "operationId": "searchForBlocks", + "parameters": [ + { + "$ref": "#/parameters/limit" + }, + { + "$ref": "#/parameters/next" + }, + { + "$ref": "#/parameters/min-round" + }, + { + "$ref": "#/parameters/max-round" + }, + { + "$ref": "#/parameters/before-time" + }, + { + "$ref": "#/parameters/after-time" + }, + { + "$ref": "#/parameters/proposer" + }, + { + "$ref": "#/parameters/expired" + }, + { + "$ref": "#/parameters/absent" + }, + { + "$ref": "#/parameters/updates" + }, + { + "$ref": "#/parameters/participation" + } + ], + "responses": { + "200": { + "$ref": "#/responses/BlocksResponse" + }, + "404": { + "$ref": "#/responses/ErrorResponse" + }, + "500": { + "$ref": "#/responses/ErrorResponse" + } + } + } + }, "/v2/blocks/{round-number}": { "get": { "description": "Lookup block.", @@ -2680,6 +2741,61 @@ } }, "parameters": { + "proposer": { + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "description": "Account(s) marked as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "name": "proposer", + "in": "query", + "required": false + }, + "absent": { + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "description": "Account(s) marked as absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "name": "absent", + "in": "query", + "required": false + }, + "expired": { + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "description": "Account(s) marked as expired in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "name": "expired", + "in": "query", + "required": false + }, + "updates": { + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "description": "Account(s) marked as expired or absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "name": "updates", + "in": "query", + "required": false + }, + "participation": { + "type": "array", + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "description": "Account(s) marked as expired, absent or as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "name": "participation", + "in": "query", + "required": false + }, "account-id": { "type": "string", "description": "account string", @@ -3189,6 +3305,32 @@ "$ref": "#/definitions/Block" } }, + "BlocksResponse": { + "description": "(empty)", + "schema": { + "type": "object", + "required": [ + "current-round", + "blocks" + ], + "properties": { + "current-round": { + "description": "Round at which the results were computed.", + "type": "integer" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + }, + "blocks": { + "type": "array", + "items": { + "$ref": "#/definitions/Block" + } + } + } + } + }, "HealthCheckResponse": { "description": "(empty)", "schema": { diff --git a/api/indexer.oas3.yml b/api/indexer.oas3.yml index 63382e00a..07aadbbe8 100644 --- a/api/indexer.oas3.yml +++ b/api/indexer.oas3.yml @@ -1,6 +1,20 @@ { "components": { "parameters": { + "absent": { + "description": "Account(s) marked as absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "absent", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, "account-id": { "description": "account string", "in": "path", @@ -135,6 +149,20 @@ "type": "boolean" } }, + "expired": { + "description": "Account(s) marked as expired in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "expired", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, "group-id": { "description": "Lookup transactions by group ID. This field must be base64-encoded, and afterwards, base64 characters like +, / and = must be URL-encoded", "in": "query", @@ -203,6 +231,34 @@ }, "x-algorand-format": "base64" }, + "participation": { + "description": "Account(s) marked as expired, absent or as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "participation", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, + "proposer": { + "description": "Account(s) marked as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "proposer", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, "rekey-to": { "description": "Include results which include the rekey-to field.", "in": "query", @@ -274,6 +330,20 @@ "schema": { "type": "string" } + }, + "updates": { + "description": "Account(s) marked as expired or absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "updates", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" } }, "responses": { @@ -569,6 +639,36 @@ }, "description": "(empty)" }, + "BlocksResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "blocks": { + "items": { + "$ref": "#/components/schemas/Block" + }, + "type": "array" + }, + "current-round": { + "description": "Round at which the results were computed.", + "type": "integer" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + } + }, + "required": [ + "blocks", + "current-round" + ], + "type": "object" + } + } + }, + "description": "(empty)" + }, "BoxResponse": { "content": { "application/json": { @@ -4773,6 +4873,217 @@ ] } }, + "/v2/blocks": { + "get": { + "description": "Search for blocks. Blocks are returned in ascending round order. Transactions are not included in the output.", + "operationId": "searchForBlocks", + "parameters": [ + { + "description": "Maximum number of results to return. There could be additional pages even if the limit is not reached.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "The next page of results. Use the next token provided by the previous results.", + "in": "query", + "name": "next", + "schema": { + "type": "string" + } + }, + { + "description": "Include results at or after the specified min-round.", + "in": "query", + "name": "min-round", + "schema": { + "type": "integer" + } + }, + { + "description": "Include results at or before the specified max-round.", + "in": "query", + "name": "max-round", + "schema": { + "type": "integer" + } + }, + { + "description": "Include results before the given time. Must be an RFC 3339 formatted string.", + "in": "query", + "name": "before-time", + "schema": { + "format": "date-time", + "type": "string", + "x-algorand-format": "RFC3339 String" + }, + "x-algorand-format": "RFC3339 String" + }, + { + "description": "Include results after the given time. Must be an RFC 3339 formatted string.", + "in": "query", + "name": "after-time", + "schema": { + "format": "date-time", + "type": "string", + "x-algorand-format": "RFC3339 String" + }, + "x-algorand-format": "RFC3339 String" + }, + { + "description": "Account(s) marked as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "proposer", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "Account(s) marked as expired in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "expired", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "Account(s) marked as absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "absent", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "Account(s) marked as expired or absent in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "updates", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "Account(s) marked as expired, absent or as proposer in the block header's participation updates. This parameter accepts a comma separated list of addresses.", + "explode": false, + "in": "query", + "name": "participation", + "schema": { + "items": { + "type": "string", + "x-algorand-format": "Address" + }, + "type": "array" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "blocks": { + "items": { + "$ref": "#/components/schemas/Block" + }, + "type": "array" + }, + "current-round": { + "description": "Round at which the results were computed.", + "type": "integer" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + } + }, + "required": [ + "blocks", + "current-round" + ], + "type": "object" + } + } + }, + "description": "(empty)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "properties": {}, + "type": "object" + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + }, + "description": "Response for errors" + }, + "500": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "properties": {}, + "type": "object" + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + }, + "description": "Response for errors" + } + }, + "tags": [ + "search" + ] + } + }, "/v2/blocks/{round-number}": { "get": { "description": "Lookup block.", diff --git a/api/server.go b/api/server.go index 0f4d402d5..6a703d24a 100644 --- a/api/server.go +++ b/api/server.go @@ -44,10 +44,20 @@ type ExtraOptions struct { // If an address exceeds this number, a 400 error is returned. Zero means unlimited. MaxAPIResourcesPerAccount uint64 + // MaxAccountListSize is the maximum number of items that can be passed in query parameter account lists. + // (e.g.: GET /v2/blocks?proposer=A,B,C) + // + // Zero means unlimited. + MaxAccountListSize uint64 + ///////////////////// // Limit Constants // ///////////////////// + // Blocks + MaxBlocksLimit uint64 + DefaultBlocksLimit uint64 + // Transactions MaxTransactionsLimit uint64 DefaultTransactionsLimit uint64 diff --git a/cmd/algorand-indexer/daemon.go b/cmd/algorand-indexer/daemon.go index ce73ea764..39fc4888d 100644 --- a/cmd/algorand-indexer/daemon.go +++ b/cmd/algorand-indexer/daemon.go @@ -32,6 +32,9 @@ type daemonConfig struct { readTimeout time.Duration maxConn uint32 maxAPIResourcesPerAccount uint32 + maxAccountListSize uint32 + maxBlocksLimit uint32 + defaultBlocksLimit uint32 maxTransactionsLimit uint32 defaultTransactionsLimit uint32 maxAccountsLimit uint32 @@ -79,6 +82,9 @@ func DaemonCmd() *cobra.Command { cfg.flags.StringVar(&cfg.suppliedAPIConfigFile, "api-config-file", "", "supply an API config file to enable/disable parameters") cfg.flags.BoolVar(&cfg.enableAllParameters, "enable-all-parameters", false, "override default configuration and enable all parameters. Can't be used with --api-config-file") cfg.flags.Uint32VarP(&cfg.maxAPIResourcesPerAccount, "max-api-resources-per-account", "", 1000, "set the maximum total number of resources (created assets, created apps, asset holdings, and application local state) per account that will be allowed in REST API lookupAccountByID and searchForAccounts responses before returning a 400 Bad Request. Set zero for no limit") + cfg.flags.Uint32VarP(&cfg.maxAccountListSize, "max-account-list-size", "", 50, "set the maximum number of items for query parameters that accept account lists. Set zero for no limit") + cfg.flags.Uint32VarP(&cfg.maxBlocksLimit, "max-blocks-limit", "", 1000, "set the maximum allowed Limit parameter for querying blocks") + cfg.flags.Uint32VarP(&cfg.defaultBlocksLimit, "default-blocks-limit", "", 100, "set the default Limit parameter for querying blocks, if none is provided") cfg.flags.Uint32VarP(&cfg.maxTransactionsLimit, "max-transactions-limit", "", 10000, "set the maximum allowed Limit parameter for querying transactions") cfg.flags.Uint32VarP(&cfg.defaultTransactionsLimit, "default-transactions-limit", "", 1000, "set the default Limit parameter for querying transactions, if none is provided") cfg.flags.Uint32VarP(&cfg.maxAccountsLimit, "max-accounts-limit", "", 1000, "set the maximum allowed Limit parameter for querying accounts") @@ -319,6 +325,9 @@ func makeOptions(daemonConfig *daemonConfig) (options api.ExtraOptions) { options.ReadTimeout = daemonConfig.readTimeout options.MaxAPIResourcesPerAccount = uint64(daemonConfig.maxAPIResourcesPerAccount) + options.MaxAccountListSize = uint64(daemonConfig.maxAccountListSize) + options.MaxBlocksLimit = uint64(daemonConfig.maxBlocksLimit) + options.DefaultBlocksLimit = uint64(daemonConfig.defaultBlocksLimit) options.MaxTransactionsLimit = uint64(daemonConfig.maxTransactionsLimit) options.DefaultTransactionsLimit = uint64(daemonConfig.defaultTransactionsLimit) options.MaxAccountsLimit = uint64(daemonConfig.maxAccountsLimit) diff --git a/idb/dummy/dummy.go b/idb/dummy/dummy.go index 19e276e8b..29552c4c3 100644 --- a/idb/dummy/dummy.go +++ b/idb/dummy/dummy.go @@ -53,6 +53,11 @@ func (db *dummyIndexerDb) GetBlock(ctx context.Context, round uint64, options id return sdk.BlockHeader{}, nil, nil } +// Blocks is part of idb.IndexerDB +func (db *dummyIndexerDb) Blocks(ctx context.Context, bf idb.BlockFilter) (<-chan idb.BlockRow, uint64) { + return nil, 0 +} + // Transactions is part of idb.IndexerDB func (db *dummyIndexerDb) Transactions(ctx context.Context, tf idb.TransactionFilter) (<-chan idb.TxnRow, uint64) { return nil, 0 diff --git a/idb/idb.go b/idb/idb.go index 5e423747f..61c4430e2 100644 --- a/idb/idb.go +++ b/idb/idb.go @@ -15,6 +15,23 @@ import ( sdk "github.com/algorand/go-algorand-sdk/v2/types" ) +// BlockRow is metadata relating to one block in a block query. +type BlockRow struct { + BlockHeader sdk.BlockHeader + + // Error indicates that there was an internal problem processing the expected block. + Error error +} + +// Next returns what should be an opaque string to be used with the next query to resume where a previous limit left off. +func (br BlockRow) Next() (string, error) { + + var b [8]byte + binary.LittleEndian.PutUint64(b[:8], uint64(br.BlockHeader.Round)) + + return base64.URLEncoding.EncodeToString(b[:]), nil +} + // TxnRow is metadata relating to one transaction in a transaction query. type TxnRow struct { // Round is the round where the transaction was committed. @@ -83,6 +100,21 @@ func (tr TxnRow) Next(ascending bool) (string, error) { return base64.URLEncoding.EncodeToString(b[:]), nil } +// DecodeBlockRowNext unpacks opaque string returned from BlockRow.Next() +func DecodeBlockRowNext(s string) (uint64 /*round*/, error) { + b, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return 0, fmt.Errorf("DecodeBlockRowNext() decode err: %w", err) + } + + if len(b) != 8 { + return 0, fmt.Errorf("DecodeBlockRowNext() bad next token b: %x", b) + } + + round := binary.LittleEndian.Uint64(b[:8]) + return round, nil +} + // DecodeTxnRowNext unpacks opaque string returned from TxnRow.Next() func DecodeTxnRowNext(s string) (uint64 /*round*/, uint32 /*intra*/, error) { b, err := base64.URLEncoding.DecodeString(s) @@ -173,6 +205,7 @@ type IndexerDb interface { // The next multiple functions return a channel with results as well as the latest round // accounted. + Blocks(ctx context.Context, bf BlockFilter) (<-chan BlockRow, uint64) Transactions(ctx context.Context, tf TransactionFilter) (<-chan TxnRow, uint64) GetAccounts(ctx context.Context, opts AccountQueryOptions) (<-chan AccountRow, uint64) Assets(ctx context.Context, filter AssetsQuery) (<-chan AssetRow, uint64) @@ -195,6 +228,18 @@ type GetBlockOptions struct { MaxTransactionsLimit uint64 } +// BlockFilter is a parameter object with all the block filter options. +type BlockFilter struct { + Limit uint64 + MaxRound *uint64 + MinRound *uint64 + AfterTime time.Time + BeforeTime time.Time + Proposers map[sdk.Address]struct{} + ExpiredParticipationAccounts map[sdk.Address]struct{} + AbsentParticipationAccounts map[sdk.Address]struct{} +} + // TransactionFilter is a parameter object with all the transaction filter options. type TransactionFilter struct { // SkipOptimization is used for testing to ensure the parameters are not modified. diff --git a/idb/postgres/internal/schema/setup_postgres.sql b/idb/postgres/internal/schema/setup_postgres.sql index fa74d463b..458f6ead9 100644 --- a/idb/postgres/internal/schema/setup_postgres.sql +++ b/idb/postgres/internal/schema/setup_postgres.sql @@ -5,7 +5,10 @@ CREATE TABLE public.block_header ( rewardslevel INT8 NOT NULL, header JSONB NOT NULL, CONSTRAINT block_header_pkey PRIMARY KEY (round ASC), - INDEX block_header_time (realtime ASC) + INDEX block_header_time (realtime ASC), + INDEX block_header_idx_proposer (((header->'prp')::TEXT), round) WHERE (header->'prp') IS NOT NULL, + INVERTED INDEX block_header_expired ((header->'partupdrmv')) WHERE (header->'partupdrmv' IS NOT NULL), + INVERTED INDEX block_header_absent ((header->'partupdabs')) WHERE (header->'partupdabs' IS NOT NULL) ); CREATE TABLE public.txn ( diff --git a/idb/postgres/internal/schema/setup_postgres_sql.go b/idb/postgres/internal/schema/setup_postgres_sql.go index 5721e2c01..328ec13fa 100644 --- a/idb/postgres/internal/schema/setup_postgres_sql.go +++ b/idb/postgres/internal/schema/setup_postgres_sql.go @@ -9,7 +9,10 @@ CREATE TABLE public.block_header ( rewardslevel INT8 NOT NULL, header JSONB NOT NULL, CONSTRAINT block_header_pkey PRIMARY KEY (round ASC), - INDEX block_header_time (realtime ASC) + INDEX block_header_time (realtime ASC), + INDEX block_header_idx_proposer (((header->'prp')::TEXT), round) WHERE (header->'prp') IS NOT NULL, + INVERTED INDEX block_header_expired ((header->'partupdrmv')) WHERE (header->'partupdrmv' IS NOT NULL), + INVERTED INDEX block_header_absent ((header->'partupdabs')) WHERE (header->'partupdabs' IS NOT NULL) ); CREATE TABLE public.txn ( diff --git a/idb/postgres/postgres.go b/idb/postgres/postgres.go index 579259fda..ce3723b4e 100644 --- a/idb/postgres/postgres.go +++ b/idb/postgres/postgres.go @@ -723,6 +723,214 @@ func buildTransactionQuery(tf idb.TransactionFilter) (query string, whereArgs [] return } +// buildBlockFilters generates filters based on a block's round and/or timestamp. +// +// Filters related to participation are generated elsewhere. +// +// Some of the filters are meant to be used as boolean conditions in the WHERE clause. +// Others, for performance reasons, have to be used as INNER JOIN terms in the FROM clause. +func buildBlockFilters(bf idb.BlockFilter) (whereTerms []string, joinTerms []string) { + + // Round-based filters + if bf.MaxRound != nil { + whereTerms = append( + whereTerms, + fmt.Sprintf("bh.round <= %d", *bf.MaxRound), + ) + } + if bf.MinRound != nil { + whereTerms = append( + whereTerms, + fmt.Sprintf("bh.round >= %d", *bf.MinRound), + ) + } + + // Timestamp-based filters + // + // Converting the timestamp into a round usually results in faster execution plans + // (compared to the execution plans that would result from using the `block_header.realtime` column directly) + // + // Unfortunately, writing this condition in the WHERE clause results in poor execution plans. + // Expressing the filter as an INNER JOIN results in an efficient query execution plan. + if !bf.AfterTime.IsZero() { + tmpl := ` + INNER JOIN ( + SELECT COALESCE(tmp.round, 0) AS round + FROM block_header tmp + WHERE tmp.realtime > (to_timestamp(%d) AT TIME ZONE 'UTC') + ORDER BY tmp.realtime ASC, tmp.round ASC + LIMIT 1 + ) bh_at ON bh.round >= bh_at.round + ` + joinTerms = append( + joinTerms, + fmt.Sprintf(tmpl, bf.AfterTime.UTC().Unix()), + ) + } + if !bf.BeforeTime.IsZero() { + tmpl := ` + INNER JOIN ( + SELECT COALESCE(tmp.round, 0) AS round + FROM block_header tmp + WHERE tmp.realtime < (to_timestamp(%d) AT TIME ZONE 'UTC') + ORDER BY tmp.realtime DESC, tmp.round DESC + LIMIT 1 + ) bh_bt ON bh.round <= bh_bt.round + ` + joinTerms = append( + joinTerms, + fmt.Sprintf(tmpl, bf.BeforeTime.UTC().Unix()), + ) + } + + return whereTerms, joinTerms +} + +func buildBlockQuery(bf idb.BlockFilter) (query string, err error) { + + // helper function to build CTEs + buildCte := func(cteName string, whereTerms []string, joinTerms []string) string { + tmpl := `%s AS ( + SELECT bh.round, bh.header + FROM block_header bh + %s + WHERE %s + ORDER BY bh.round ASC + LIMIT %d + )` + return fmt.Sprintf(tmpl, cteName, strings.Join(joinTerms, "\n"), strings.Join(whereTerms, " AND "), bf.Limit) + } + + // Build auxiliary CTEs for participation-related parameters. + // + // Using CTEs in this way turned out to be necessary to lead CockroachDB's query optimizer + // into using the execution plan we want. + // + // If we were to put the CTE filters in the main query's WHERE clause, that would result + // in a sub-optimal execution plan. At least this was the case at the time of writing. + var CTEs []string + var CteNames []string + { + if len(bf.Proposers) > 0 { + whereTerms, joinTerms := buildBlockFilters(bf) + var proposersStr []string + for addr := range bf.Proposers { + proposersStr = append(proposersStr, `'"`+addr.String()+`"'`) + } + whereTerms = append( + whereTerms, + fmt.Sprintf("( (bh.header->'prp') IS NOT NULL AND ((bh.header->'prp')::TEXT IN (%s)) )", strings.Join(proposersStr, ",")), + ) + + cteName := "prp" + cte := buildCte(cteName, whereTerms, joinTerms) + CTEs = append(CTEs, cte) + CteNames = append(CteNames, cteName) + + } + if len(bf.ExpiredParticipationAccounts) > 0 { + whereTerms, joinTerms := buildBlockFilters(bf) + var expiredStr []string + for addr := range bf.ExpiredParticipationAccounts { + expiredStr = append(expiredStr, `'`+addr.String()+`'`) + } + whereTerms = append( + whereTerms, + fmt.Sprintf("( (bh.header->'partupdrmv') IS NOT NULL AND (bh.header->'partupdrmv') ?| array[%s] )", strings.Join(expiredStr, ",")), + ) + + cteName := "expired" + CTE := buildCte(cteName, whereTerms, joinTerms) + CTEs = append(CTEs, CTE) + CteNames = append(CteNames, "expired") + + } + if len(bf.AbsentParticipationAccounts) > 0 { + whereTerms, joinTerms := buildBlockFilters(bf) + var absentStr []string + for addr := range bf.AbsentParticipationAccounts { + absentStr = append(absentStr, `'`+addr.String()+`'`) + } + whereTerms = append( + whereTerms, + fmt.Sprintf("( (bh.header->'partupdabs') IS NOT NULL AND (bh.header->'partupdabs') ?| array[%s] )", strings.Join(absentStr, ",")), + ) + + cteName := "absent" + CTE := buildCte(cteName, whereTerms, joinTerms) + CTEs = append(CTEs, CTE) + CteNames = append(CteNames, cteName) + } + if len(CteNames) > 0 { + var selects []string + for _, cteName := range CteNames { + selects = append(selects, fmt.Sprintf("SELECT * FROM %s", cteName)) + } + CTE := "tmp AS (" + strings.Join(selects, " UNION ") + ")" + CTEs = append(CTEs, CTE) + } + } + + // Build the main query. It uses the CTEs, if any. + { + var withClause string + if len(CTEs) > 0 { + withClause = "WITH " + strings.Join(CTEs, ",\n") + } + + var fromTable string + if len(CTEs) > 0 { + fromTable = "tmp bh" + } else { + fromTable = "block_header bh" + } + + var whereClause string + var joinClause string + if len(CTEs) == 0 { + whereTerms, joinTerms := buildBlockFilters(bf) + if len(whereTerms) > 0 { + whereClause = "WHERE " + strings.Join(whereTerms, " AND ") + "\n" + } + if len(joinTerms) > 0 { + joinClause = strings.Join(joinTerms, "\n") + } + } + + tmpl := ` + %s + SELECT bh.header + FROM %s + %s + %s + ORDER BY bh.round ASC + LIMIT %d` + + query = fmt.Sprintf(tmpl, withClause, fromTable, joinClause, whereClause, bf.Limit) + } + + return query, nil +} + +// This function blocks. `tx` must be non-nil. +func (db *IndexerDb) yieldBlocks(ctx context.Context, tx pgx.Tx, bf idb.BlockFilter, out chan<- idb.BlockRow) { + + query, err := buildBlockQuery(bf) + if err != nil { + err = fmt.Errorf("block query err %v", err) + out <- idb.BlockRow{Error: err} + return + } + + rows, err := tx.Query(ctx, query) + if err != nil { + err = fmt.Errorf("block query %#v err %v", query, err) + out <- idb.BlockRow{Error: err} + return + } + db.yieldBlocksThreadSimple(rows, out) +} + // This function blocks. `tx` must be non-nil. func (db *IndexerDb) yieldTxns(ctx context.Context, tx pgx.Tx, tf idb.TransactionFilter, out chan<- idb.TxnRow) { if len(tf.NextToken) > 0 { @@ -769,6 +977,42 @@ func txnFilterOptimization(tf idb.TransactionFilter) idb.TransactionFilter { return tf } +// Blocks is part of idb.IndexerDB +func (db *IndexerDb) Blocks(ctx context.Context, bf idb.BlockFilter) (<-chan idb.BlockRow, uint64) { + out := make(chan idb.BlockRow, 1) + + tx, err := db.db.BeginTx(ctx, readonlyRepeatableRead) + if err != nil { + out <- idb.BlockRow{Error: err} + close(out) + return out, 0 + } + + round, err := db.getMaxRoundAccounted(ctx, tx) + if err != nil { + out <- idb.BlockRow{Error: err} + close(out) + if rerr := tx.Rollback(ctx); rerr != nil { + db.log.Printf("rollback error: %s", rerr) + } + return out, round + } + + go func() { + db.yieldBlocks(ctx, tx, bf, out) + // Because we return a channel into a "callWithTimeout" function, + // We need to make sure that rollback is called before close() + // otherwise we can end up with a situation where "callWithTimeout" + // will cancel our context, resulting in connection pool churn + if rerr := tx.Rollback(ctx); rerr != nil { + db.log.Printf("rollback error: %s", rerr) + } + close(out) + }() + + return out, round +} + // Transactions is part of idb.IndexerDB func (db *IndexerDb) Transactions(ctx context.Context, tf idb.TransactionFilter) (<-chan idb.TxnRow, uint64) { out := make(chan idb.TxnRow, 1) @@ -893,6 +1137,30 @@ func (db *IndexerDb) txnsWithNext(ctx context.Context, tx pgx.Tx, tf idb.Transac db.yieldTxnsThreadSimple(rows, out, nil, nil) } +func (db *IndexerDb) yieldBlocksThreadSimple(rows pgx.Rows, results chan<- idb.BlockRow) { + defer rows.Close() + + for rows.Next() { + var row idb.BlockRow + + var blockheaderjson []byte + err := rows.Scan(&blockheaderjson) + if err != nil { + row.Error = err + } else { + row.BlockHeader, err = encoding.DecodeBlockHeader(blockheaderjson) + if err != nil { + row.Error = fmt.Errorf("failed to decode block header: %w", err) + } + } + + results <- row + } + if err := rows.Err(); err != nil { + results <- idb.BlockRow{Error: err} + } +} + func (db *IndexerDb) yieldTxnsThreadSimple(rows pgx.Rows, results chan<- idb.TxnRow, countp *int, errp *error) { defer rows.Close()