Skip to content

Commit

Permalink
catchup: Provide more information to client when requested block not …
Browse files Browse the repository at this point in the history
…available (#5819)
  • Loading branch information
cce authored Nov 8, 2023
1 parent c1207a4 commit 7ebb9f4
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 8 deletions.
19 changes: 16 additions & 3 deletions catchup/universalFetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ package catchup
import (
"context"
"encoding/binary"
"errors"
"fmt"
"net/http"
"strconv"
"time"

"github.com/algorand/go-deadlock"
Expand Down Expand Up @@ -173,6 +173,9 @@ func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round)
}

if errMsg, found := resp.Topics.GetValue(network.ErrorKey); found {
if latest, lfound := resp.Topics.GetValue(rpcs.LatestRoundKey); lfound {
return nil, noBlockForRoundError{round: round, latest: basics.Round(binary.BigEndian.Uint64(latest))}
}
return nil, makeErrWsFetcherRequestFailed(round, w.target.GetAddress(), string(errMsg))
}

Expand All @@ -195,7 +198,11 @@ func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round)
// set max fetcher size to 10MB, this is enough to fit the block and certificate
const fetcherMaxBlockBytes = 10 << 20

var errNoBlockForRound = errors.New("No block available for given round")
type noBlockForRoundError struct {
latest, round basics.Round
}

func (noBlockForRoundError) Error() string { return "no block available for given round" }

// HTTPFetcher implements FetcherClient doing an HTTP GET of the block
type HTTPFetcher struct {
Expand Down Expand Up @@ -239,7 +246,13 @@ func (hf *HTTPFetcher) getBlockBytes(ctx context.Context, r basics.Round) (data
case http.StatusOK:
case http.StatusNotFound: // server could not find a block with that round numbers.
response.Body.Close()
return nil, errNoBlockForRound
noBlockErr := noBlockForRoundError{round: r}
if latestBytes := response.Header.Get(rpcs.BlockResponseLatestRoundHeader); latestBytes != "" {
if latest, pErr := strconv.ParseUint(latestBytes, 10, 64); pErr == nil {
noBlockErr.latest = basics.Round(latest)
}
}
return nil, noBlockErr
default:
bodyBytes, err := rpcs.ResponseBytes(response, hf.log, fetcherMaxBlockBytes)
hf.log.Warnf("HTTPFetcher.getBlockBytes: response status code %d from '%s'. Response body '%s' ", response.StatusCode, blockURL, string(bodyBytes))
Expand Down
10 changes: 7 additions & 3 deletions catchup/universalFetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ func TestUGetBlockWs(t *testing.T) {
block, cert, duration, err = fetcher.fetchBlock(context.Background(), next+1, up)

require.Error(t, err)
require.Contains(t, err.Error(), "requested block is not available")
require.Error(t, noBlockForRoundError{}, err)
require.Equal(t, next+1, err.(noBlockForRoundError).round)
require.Equal(t, next, err.(noBlockForRoundError).latest)
require.Nil(t, block)
require.Nil(t, cert)
require.Equal(t, int64(duration), int64(0))
Expand Down Expand Up @@ -118,8 +120,10 @@ func TestUGetBlockHTTP(t *testing.T) {

block, cert, duration, err = fetcher.fetchBlock(context.Background(), next+1, net.GetPeers()[0])

require.Error(t, errNoBlockForRound, err)
require.Contains(t, err.Error(), "No block available for given round")
require.Error(t, noBlockForRoundError{}, err)
require.Equal(t, next+1, err.(noBlockForRoundError).round)
require.Equal(t, next, err.(noBlockForRoundError).latest)
require.Contains(t, err.Error(), "no block available for given round")
require.Nil(t, block)
require.Nil(t, cert)
require.Equal(t, int64(duration), int64(0))
Expand Down
13 changes: 11 additions & 2 deletions rpcs/blockService.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ const blockResponseRetryAfter = "3"
const blockServerMaxBodyLength = 512 // we don't really pass meaningful content here, so 512 bytes should be a safe limit
const blockServerCatchupRequestBufferSize = 10

// BlockResponseLatestRoundHeader is returned in the response header when the requested block is not available
const BlockResponseLatestRoundHeader = "X-Latest-Round"

// BlockServiceBlockPath is the path to register BlockService as a handler for when using gorilla/mux
// e.g. .Handle(BlockServiceBlockPath, &ls)
const BlockServiceBlockPath = "/v{version:[0-9.]+}/{genesisID}/block/{round:[0-9a-z]+}"
Expand All @@ -65,6 +68,7 @@ const (
BlockDataKey = "blockData" // Block-data topic-key in the response
CertDataKey = "certData" // Cert-data topic-key in the response
BlockAndCertValue = "blockAndCert" // block+cert request data (as the value of requestDataTypeKey)
LatestRoundKey = "latest"
)

var errBlockServiceClosed = errors.New("block service is shutting down")
Expand Down Expand Up @@ -239,12 +243,13 @@ func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Re
}
encodedBlockCert, err := bs.rawBlockBytes(basics.Round(round))
if err != nil {
switch err.(type) {
switch lerr := err.(type) {
case ledgercore.ErrNoEntry:
// entry cound not be found.
ok := bs.redirectRequest(round, response, request)
if !ok {
response.Header().Set("Cache-Control", blockResponseMissingBlockCacheControl)
response.Header().Set(BlockResponseLatestRoundHeader, fmt.Sprintf("%d", lerr.Latest))
response.WriteHeader(http.StatusNotFound)
}
return
Expand Down Expand Up @@ -456,8 +461,12 @@ func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) {
func topicBlockBytes(log logging.Logger, dataLedger LedgerForBlockService, round basics.Round, requestType string) (network.Topics, uint64) {
blk, cert, err := dataLedger.EncodedBlockCert(round)
if err != nil {
switch err.(type) {
switch lerr := err.(type) {
case ledgercore.ErrNoEntry:
return network.Topics{
network.MakeTopic(network.ErrorKey, []byte(blockNotAvailableErrMsg)),
network.MakeTopic(LatestRoundKey, binary.BigEndian.AppendUint64([]byte{}, uint64(lerr.Latest))),
}, 0
default:
log.Infof("BlockService topicBlockBytes: %s", err)
}
Expand Down

0 comments on commit 7ebb9f4

Please sign in to comment.