Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement GET /v2/blocks #10

Merged
merged 14 commits into from
Oct 24, 2024
234 changes: 234 additions & 0 deletions api/converter_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions api/error_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
Loading
Loading