277 lines
6.7 KiB
Go
277 lines
6.7 KiB
Go
package blocksync
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/libp2p/go-libp2p-core/protocol"
|
|
"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"
|
|
logging "github.com/ipfs/go-log/v2"
|
|
inet "github.com/libp2p/go-libp2p-core/network"
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
|
)
|
|
|
|
var log = logging.Logger("blocksync")
|
|
|
|
type NewStreamFunc func(context.Context, peer.ID, ...protocol.ID) (inet.Stream, error)
|
|
|
|
const BlockSyncProtocolID = "/fil/sync/blk/0.0.1"
|
|
|
|
const BlockSyncMaxRequestLength = 800
|
|
|
|
// 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.
|
|
type BlockSyncService struct {
|
|
cs *store.ChainStore
|
|
}
|
|
|
|
type BlockSyncRequest struct {
|
|
Start []cid.Cid
|
|
RequestLength uint64
|
|
|
|
Options uint64
|
|
}
|
|
|
|
type BSOptions struct {
|
|
IncludeBlocks bool
|
|
IncludeMessages bool
|
|
}
|
|
|
|
func ParseBSOptions(optfield uint64) *BSOptions {
|
|
return &BSOptions{
|
|
IncludeBlocks: optfield&(BSOptBlocks) != 0,
|
|
IncludeMessages: optfield&(BSOptMessages) != 0,
|
|
}
|
|
}
|
|
|
|
const (
|
|
BSOptBlocks = 1 << iota
|
|
BSOptMessages
|
|
)
|
|
|
|
const (
|
|
StatusOK = uint64(0)
|
|
StatusPartial = uint64(101)
|
|
StatusNotFound = uint64(201)
|
|
StatusGoAway = uint64(202)
|
|
StatusInternalError = uint64(203)
|
|
StatusBadRequest = uint64(204)
|
|
)
|
|
|
|
type BlockSyncResponse struct {
|
|
Chain []*BSTipSet
|
|
|
|
Status uint64
|
|
Message string
|
|
}
|
|
|
|
type BSTipSet struct {
|
|
Blocks []*types.BlockHeader
|
|
|
|
BlsMessages []*types.Message
|
|
BlsMsgIncludes [][]uint64
|
|
|
|
SecpkMessages []*types.SignedMessage
|
|
SecpkMsgIncludes [][]uint64
|
|
}
|
|
|
|
func NewBlockSyncService(cs *store.ChainStore) *BlockSyncService {
|
|
return &BlockSyncService{
|
|
cs: cs,
|
|
}
|
|
}
|
|
|
|
func (bss *BlockSyncService) HandleStream(s inet.Stream) {
|
|
ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream")
|
|
defer span.End()
|
|
|
|
defer s.Close() //nolint:errcheck
|
|
|
|
var req BlockSyncRequest
|
|
if err := cborutil.ReadCborRPC(bufio.NewReader(s), &req); err != nil {
|
|
log.Warnf("failed to read block sync request: %s", err)
|
|
return
|
|
}
|
|
log.Infow("block sync request", "start", req.Start, "len", req.RequestLength)
|
|
|
|
resp, err := bss.processRequest(ctx, s.Conn().RemotePeer(), &req)
|
|
if err != nil {
|
|
log.Warn("failed to process block sync request: ", err)
|
|
return
|
|
}
|
|
|
|
writeDeadline := 60 * time.Second
|
|
_ = s.SetDeadline(time.Now().Add(writeDeadline))
|
|
if err := cborutil.WriteCborRPC(s, resp); err != nil {
|
|
log.Warnw("failed to write back response for handle stream", "err", err, "peer", s.Conn().RemotePeer())
|
|
return
|
|
}
|
|
}
|
|
|
|
func (bss *BlockSyncService) processRequest(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
|
|
_, span := trace.StartSpan(ctx, "blocksync.ProcessRequest")
|
|
defer span.End()
|
|
|
|
opts := ParseBSOptions(req.Options)
|
|
if len(req.Start) == 0 {
|
|
return &BlockSyncResponse{
|
|
Status: StatusBadRequest,
|
|
Message: "no cids given in blocksync request",
|
|
}, nil
|
|
}
|
|
|
|
span.AddAttributes(
|
|
trace.BoolAttribute("blocks", opts.IncludeBlocks),
|
|
trace.BoolAttribute("messages", opts.IncludeMessages),
|
|
trace.Int64Attribute("reqlen", int64(req.RequestLength)),
|
|
)
|
|
|
|
reqlen := req.RequestLength
|
|
if reqlen > BlockSyncMaxRequestLength {
|
|
log.Warnw("limiting blocksync request length", "orig", req.RequestLength, "peer", p)
|
|
reqlen = BlockSyncMaxRequestLength
|
|
}
|
|
|
|
chain, err := collectChainSegment(bss.cs, types.NewTipSetKey(req.Start...), reqlen, opts)
|
|
if err != nil {
|
|
log.Warn("encountered error while responding to block sync request: ", err)
|
|
return &BlockSyncResponse{
|
|
Status: StatusInternalError,
|
|
Message: err.Error(),
|
|
}, nil
|
|
}
|
|
|
|
status := StatusOK
|
|
if reqlen < req.RequestLength {
|
|
status = StatusPartial
|
|
}
|
|
|
|
return &BlockSyncResponse{
|
|
Chain: chain,
|
|
Status: status,
|
|
}, nil
|
|
}
|
|
|
|
func collectChainSegment(cs *store.ChainStore, start types.TipSetKey, length uint64, opts *BSOptions) ([]*BSTipSet, error) {
|
|
var bstips []*BSTipSet
|
|
cur := start
|
|
for {
|
|
var bst BSTipSet
|
|
ts, err := cs.LoadTipSet(cur)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err)
|
|
}
|
|
|
|
if opts.IncludeMessages {
|
|
bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("gather messages failed: %w", err)
|
|
}
|
|
|
|
bst.BlsMessages = bmsgs
|
|
bst.BlsMsgIncludes = bmincl
|
|
bst.SecpkMessages = smsgs
|
|
bst.SecpkMsgIncludes = smincl
|
|
}
|
|
|
|
if opts.IncludeBlocks {
|
|
bst.Blocks = ts.Blocks()
|
|
}
|
|
|
|
bstips = append(bstips, &bst)
|
|
|
|
if uint64(len(bstips)) >= 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 secpkmsgs []*types.SignedMessage
|
|
var blsmsgs []*types.Message
|
|
var secpkincl, blsincl [][]uint64
|
|
|
|
for _, b := range ts.Blocks() {
|
|
bmsgs, smsgs, err := cs.MessagesForBlock(b)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
bmi := make([]uint64, 0, len(bmsgs))
|
|
for _, m := range bmsgs {
|
|
i, ok := blsmsgmap[m.Cid()]
|
|
if !ok {
|
|
i = uint64(len(blsmsgs))
|
|
blsmsgs = append(blsmsgs, m)
|
|
blsmsgmap[m.Cid()] = i
|
|
}
|
|
|
|
bmi = append(bmi, i)
|
|
}
|
|
blsincl = append(blsincl, bmi)
|
|
|
|
smi := make([]uint64, 0, len(smsgs))
|
|
for _, m := range smsgs {
|
|
i, ok := secpkmsgmap[m.Cid()]
|
|
if !ok {
|
|
i = uint64(len(secpkmsgs))
|
|
secpkmsgs = append(secpkmsgs, m)
|
|
secpkmsgmap[m.Cid()] = i
|
|
}
|
|
|
|
smi = append(smi, i)
|
|
}
|
|
secpkincl = append(secpkincl, smi)
|
|
}
|
|
|
|
return blsmsgs, blsincl, secpkmsgs, secpkincl, nil
|
|
}
|
|
|
|
func bstsToFullTipSet(bts *BSTipSet) (*store.FullTipSet, error) {
|
|
fts := &store.FullTipSet{}
|
|
for i, b := range bts.Blocks {
|
|
fb := &types.FullBlock{
|
|
Header: b,
|
|
}
|
|
for _, mi := range bts.BlsMsgIncludes[i] {
|
|
fb.BlsMessages = append(fb.BlsMessages, bts.BlsMessages[mi])
|
|
}
|
|
for _, mi := range bts.SecpkMsgIncludes[i] {
|
|
fb.SecpkMessages = append(fb.SecpkMessages, bts.SecpkMessages[mi])
|
|
}
|
|
|
|
fts.Blocks = append(fts.Blocks, fb)
|
|
}
|
|
|
|
return fts, nil
|
|
}
|