Skip to content

Commit

Permalink
Implement GET /v2/blocks
Browse files Browse the repository at this point in the history
Currently using the exact same implementation as AlgoNode#10
  • Loading branch information
agodnic committed Nov 17, 2024
1 parent e6a823d commit abfe125
Show file tree
Hide file tree
Showing 14 changed files with 1,722 additions and 414 deletions.
233 changes: 233 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 @@ -298,6 +307,94 @@ func txnRowToTransaction(row idb.TxnRow) (generated.Transaction, error) {
return txn, nil
}

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
}

func signedTxnWithAdToTransaction(stxn *sdk.SignedTxnWithAD, extra rowData) (generated.Transaction, error) {
var payment *generated.TransactionPayment
var keyreg *generated.TransactionKeyreg
Expand Down Expand Up @@ -749,6 +846,142 @@ func (si *ServerImplementation) transactionParamsToTransactionFilter(params gene
return
}

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) maxAccountsErrorToAccountsErrorResponse(maxErr idb.MaxAPIResourcesPerAccountError) generated.ErrorResponse {
addr := maxErr.Address.String()
max := uint64(si.opts.MaxAPIResourcesPerAccount)
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 @@ -38,6 +39,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

0 comments on commit abfe125

Please sign in to comment.