diff --git a/chain/exchange/client.go b/chain/exchange/client.go index 769c375ca..9cbb44955 100644 --- a/chain/exchange/client.go +++ b/chain/exchange/client.go @@ -284,16 +284,18 @@ func (c *client) validateCompressedIndices(chain []*BSTipSet) error { len(msgs.SecpkIncludes), blocksNum) } + blsLen := uint64(len(msgs.Bls)) + secpLen := uint64(len(msgs.Secpk)) for blockIdx := 0; blockIdx < blocksNum; blockIdx++ { for _, mi := range msgs.BlsIncludes[blockIdx] { - if int(mi) >= len(msgs.Bls) { + if mi >= blsLen { return xerrors.Errorf("index in BlsIncludes (%d) exceeds number of messages (%d)", mi, len(msgs.Bls)) } } for _, mi := range msgs.SecpkIncludes[blockIdx] { - if int(mi) >= len(msgs.Secpk) { + if mi >= secpLen { return xerrors.Errorf("index in SecpkIncludes (%d) exceeds number of messages (%d)", mi, len(msgs.Secpk)) } @@ -315,18 +317,36 @@ func (c *client) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ) } - req := &Request{ - Head: tsk.Cids(), - Length: uint64(count), - Options: Headers, + var ret []*types.TipSet + start := tsk.Cids() + for len(ret) < count { + req := &Request{ + Head: start, + Length: uint64(count - len(ret)), + Options: Headers, + } + + validRes, err := c.doRequest(ctx, req, nil, nil) + if err != nil { + return nil, xerrors.Errorf("failed to doRequest: %w", err) + } + + if len(validRes.tipsets) == 0 { + return nil, xerrors.Errorf("doRequest fetched zero tipsets: %w", err) + } + + ret = append(ret, validRes.tipsets...) + + last := validRes.tipsets[len(validRes.tipsets)-1] + if last.Height() <= 1 { + // we've walked all the way up to genesis, return + break + } + + start = last.Parents().Cids() } - validRes, err := c.doRequest(ctx, req, nil, nil) - if err != nil { - return nil, err - } - - return validRes.tipsets, nil + return ret, nil } // GetFullTipSet implements Client.GetFullTipSet(). Refer to the godocs there. @@ -341,12 +361,16 @@ func (c *client) GetFullTipSet(ctx context.Context, peer peer.ID, tsk types.TipS validRes, err := c.doRequest(ctx, req, &peer, nil) if err != nil { - return nil, err + return nil, xerrors.Errorf("failed to doRequest: %w", err) } - return validRes.toFullTipSets()[0], nil - // If `doRequest` didn't fail we are guaranteed to have at least - // *one* tipset here, so it's safe to index directly. + fullTipsets := validRes.toFullTipSets() + + if len(fullTipsets) == 0 { + return nil, xerrors.New("unexpectedly got no tipsets in exchange") + } + + return fullTipsets[0], nil } // GetChainMessages implements Client.GetChainMessages(). Refer to the godocs there. diff --git a/chain/exchange/interfaces.go b/chain/exchange/interfaces.go index c95127929..ff11b63eb 100644 --- a/chain/exchange/interfaces.go +++ b/chain/exchange/interfaces.go @@ -28,8 +28,8 @@ type Server interface { // used by the Syncer. type Client interface { // GetBlocks fetches block headers from the network, from the provided - // tipset *backwards*, returning as many tipsets as the count parameter, - // or less. + // tipset *backwards*, returning as many tipsets as the count parameter. + // The ONLY case in which we return fewer than `count` tipsets is if we hit genesis. GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) // GetChainMessages fetches messages from the network, starting from the first provided tipset