ca8f5861fe
Co-authored-by: Łukasz Magiera <magik6k@users.noreply.github.com>
273 lines
6.8 KiB
Go
273 lines
6.8 KiB
Go
package blocksync
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.opencensus.io/trace"
|
|
"golang.org/x/xerrors"
|
|
|
|
cborutil "github.com/filecoin-project/go-cbor-util"
|
|
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
inet "github.com/libp2p/go-libp2p-core/network"
|
|
)
|
|
|
|
// BlockSyncService is the component that services BlockSync requests from
|
|
// peers.
|
|
//
|
|
// BlockSync is the basic chain synchronization protocol of Filecoin. BlockSync
|
|
// is an RPC-oriented protocol, with a single operation to request blocks.
|
|
//
|
|
// A request contains a start anchor block (referred to with a CID), and a
|
|
// amount of blocks requested beyond the anchor (including the anchor itself).
|
|
//
|
|
// A client can also pass options, encoded as a 64-bit bitfield. Lotus supports
|
|
// two options at the moment:
|
|
//
|
|
// - include block contents
|
|
// - include block messages
|
|
//
|
|
// The response will include a status code, an optional message, and the
|
|
// response payload in case of success. The payload is a slice of serialized
|
|
// tipsets.
|
|
// FIXME: Rename to just `Server` (will be done later, see note on `BlockSync`).
|
|
type BlockSyncService struct {
|
|
cs *store.ChainStore
|
|
}
|
|
|
|
func NewBlockSyncService(cs *store.ChainStore) *BlockSyncService {
|
|
return &BlockSyncService{
|
|
cs: cs,
|
|
}
|
|
}
|
|
|
|
// Entry point of the service, handles `Request`s.
|
|
func (server *BlockSyncService) HandleStream(stream inet.Stream) {
|
|
ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream")
|
|
defer span.End()
|
|
|
|
defer stream.Close() //nolint:errcheck
|
|
|
|
var req Request
|
|
if err := cborutil.ReadCborRPC(bufio.NewReader(stream), &req); err != nil {
|
|
log.Warnf("failed to read block sync request: %s", err)
|
|
return
|
|
}
|
|
log.Infow("block sync request",
|
|
"start", req.Head, "len", req.Length)
|
|
|
|
resp, err := server.processRequest(ctx, &req)
|
|
if err != nil {
|
|
log.Warn("failed to process request: ", err)
|
|
return
|
|
}
|
|
|
|
_ = stream.SetDeadline(time.Now().Add(WRITE_RES_DEADLINE))
|
|
if err := cborutil.WriteCborRPC(stream, resp); err != nil {
|
|
_ = stream.SetDeadline(time.Time{})
|
|
log.Warnw("failed to write back response for handle stream",
|
|
"err", err, "peer", stream.Conn().RemotePeer())
|
|
return
|
|
}
|
|
_ = stream.SetDeadline(time.Time{})
|
|
}
|
|
|
|
// Validate and service the request. We return either a protocol
|
|
// response or an internal error.
|
|
func (server *BlockSyncService) processRequest(
|
|
ctx context.Context,
|
|
req *Request,
|
|
) (*Response, error) {
|
|
validReq, errResponse := validateRequest(ctx, req)
|
|
if errResponse != nil {
|
|
// The request did not pass validation, return the response
|
|
// indicating it.
|
|
return errResponse, nil
|
|
}
|
|
|
|
return server.serviceRequest(ctx, validReq)
|
|
}
|
|
|
|
// Validate request. We either return a `validatedRequest`, or an error
|
|
// `Response` indicating why we can't process it. We do not return any
|
|
// internal errors here, we just signal protocol ones.
|
|
func validateRequest(
|
|
ctx context.Context,
|
|
req *Request,
|
|
) (*validatedRequest, *Response) {
|
|
_, span := trace.StartSpan(ctx, "blocksync.ValidateRequest")
|
|
defer span.End()
|
|
|
|
validReq := validatedRequest{}
|
|
|
|
validReq.options = parseOptions(req.Options)
|
|
if validReq.options.noOptionsSet() {
|
|
return nil, &Response{
|
|
Status: BadRequest,
|
|
ErrorMessage: "no options set",
|
|
}
|
|
}
|
|
|
|
validReq.length = req.Length
|
|
if validReq.length > MaxRequestLength {
|
|
return nil, &Response{
|
|
Status: BadRequest,
|
|
ErrorMessage: fmt.Sprintf("request length over maximum allowed (%d)",
|
|
MaxRequestLength),
|
|
}
|
|
}
|
|
if validReq.length == 0 {
|
|
return nil, &Response{
|
|
Status: BadRequest,
|
|
ErrorMessage: "invalid request length of zero",
|
|
}
|
|
}
|
|
|
|
if len(req.Head) == 0 {
|
|
return nil, &Response{
|
|
Status: BadRequest,
|
|
ErrorMessage: "no cids in request",
|
|
}
|
|
}
|
|
validReq.head = types.NewTipSetKey(req.Head...)
|
|
|
|
// FIXME: Add as a defer at the start.
|
|
span.AddAttributes(
|
|
trace.BoolAttribute("blocks", validReq.options.IncludeHeaders),
|
|
trace.BoolAttribute("messages", validReq.options.IncludeMessages),
|
|
trace.Int64Attribute("reqlen", int64(validReq.length)),
|
|
)
|
|
|
|
return &validReq, nil
|
|
}
|
|
|
|
func (server *BlockSyncService) serviceRequest(
|
|
ctx context.Context,
|
|
req *validatedRequest,
|
|
) (*Response, error) {
|
|
_, span := trace.StartSpan(ctx, "blocksync.ServiceRequest")
|
|
defer span.End()
|
|
|
|
chain, err := collectChainSegment(server.cs, req)
|
|
if err != nil {
|
|
log.Warn("block sync request: collectChainSegment failed: ", err)
|
|
return &Response{
|
|
Status: InternalError,
|
|
ErrorMessage: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
status := Ok
|
|
if len(chain) < int(req.length) {
|
|
status = Partial
|
|
}
|
|
|
|
return &Response{
|
|
Chain: chain,
|
|
Status: status,
|
|
}, nil
|
|
}
|
|
|
|
func collectChainSegment(
|
|
cs *store.ChainStore,
|
|
req *validatedRequest,
|
|
) ([]*BSTipSet, error) {
|
|
var bstips []*BSTipSet
|
|
|
|
cur := req.head
|
|
for {
|
|
var bst BSTipSet
|
|
ts, err := cs.LoadTipSet(cur)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err)
|
|
}
|
|
|
|
if req.options.IncludeHeaders {
|
|
bst.Blocks = ts.Blocks()
|
|
}
|
|
|
|
if req.options.IncludeMessages {
|
|
bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("gather messages failed: %w", err)
|
|
}
|
|
|
|
// FIXME: Pass the response to `gatherMessages()` and set all this there.
|
|
bst.Messages = &CompactedMessages{}
|
|
bst.Messages.Bls = bmsgs
|
|
bst.Messages.BlsIncludes = bmincl
|
|
bst.Messages.Secpk = smsgs
|
|
bst.Messages.SecpkIncludes = smincl
|
|
}
|
|
|
|
bstips = append(bstips, &bst)
|
|
|
|
// If we collected the length requested or if we reached the
|
|
// start (genesis), then stop.
|
|
if uint64(len(bstips)) >= req.length || ts.Height() == 0 {
|
|
return bstips, nil
|
|
}
|
|
|
|
cur = ts.Parents()
|
|
}
|
|
}
|
|
|
|
func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) {
|
|
blsmsgmap := make(map[cid.Cid]uint64)
|
|
secpkmsgmap := make(map[cid.Cid]uint64)
|
|
var secpkincl, blsincl [][]uint64
|
|
|
|
var blscids, secpkcids []cid.Cid
|
|
for _, block := range ts.Blocks() {
|
|
bc, sc, err := cs.ReadMsgMetaCids(block.Messages)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
// FIXME: DRY. Use `chain.Message` interface.
|
|
bmi := make([]uint64, 0, len(bc))
|
|
for _, m := range bc {
|
|
i, ok := blsmsgmap[m]
|
|
if !ok {
|
|
i = uint64(len(blscids))
|
|
blscids = append(blscids, m)
|
|
blsmsgmap[m] = i
|
|
}
|
|
|
|
bmi = append(bmi, i)
|
|
}
|
|
blsincl = append(blsincl, bmi)
|
|
|
|
smi := make([]uint64, 0, len(sc))
|
|
for _, m := range sc {
|
|
i, ok := secpkmsgmap[m]
|
|
if !ok {
|
|
i = uint64(len(secpkcids))
|
|
secpkcids = append(secpkcids, m)
|
|
secpkmsgmap[m] = i
|
|
}
|
|
|
|
smi = append(smi, i)
|
|
}
|
|
secpkincl = append(secpkincl, smi)
|
|
}
|
|
|
|
blsmsgs, err := cs.LoadMessagesFromCids(blscids)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
return blsmsgs, blsincl, secpkmsgs, secpkincl, nil
|
|
}
|