lotus扇区生成规则-干货

2,064 阅读3分钟

本文作者:星际联盟 原创作品,转载请注明出处

扇区任务生成命令:

lotus-storage-miner pledge-sector

命令作用:

进程创建远程RPC API和lotus-storage-miner run进程即miner通信,上代码:

var pledgeSectorCmd = &cli.Command{
	Name:  "pledge-sector",
	Usage: "store random data in a sector",
	Action: func(cctx *cli.Context) error {
		nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
		if err != nil {
			return err
		}
		defer closer()
		ctx := lcli.ReqContext(cctx)

		return nodeApi.PledgeSector(ctx)
	},
}

func GetStorageMinerAPI(ctx *cli.Context) (api.StorageMiner, jsonrpc.ClientCloser, error) {
	addr, headers, err := GetRawAPI(ctx, repo.StorageMiner)
	if err != nil {
		return nil, nil, err
	}

	return client.NewStorageMinerRPC(addr, headers)
}

lotus-storage-miner run进程通信通过变量代码中nodeApi接口进行。生成扇区即通过代码中的nodeApi.PledgeSector(ctx)调用miner接口函数进行。

lotus-storage-miner run进程被调用PledgeSector方法

PledgeSector函数如下:

func (m *Sealing) PledgeSector() error {
	go func() {
		ctx := context.TODO() // we can't use the context from command which invokes
		// this, as we run everything here async, and it's cancelled when the
		// command exits
		size := sectorbuilder.UserBytesForSectorSize(m.sb.SectorSize())
		sid, err := m.sb.AcquireSectorId()
		if err != nil {
			log.Errorf("%+v", err)
			return
		}
		pieces, err := m.pledgeSector(ctx, sid, []uint64{}, size)
		if err != nil {
			log.Errorf("%+v", err)
			return
		}
		if err := m.newSector(context.TODO(), sid, pieces[0].DealID, pieces[0].ppi()); err != nil {
			log.Errorf("%+v", err)
			return
		}
	}()
	return nil
}

第一步操作sectorbuilder.UserBytesForSectorSize:获取扇区大小,当前只支持32GB。

第二步操作m.sb.AcquireSectorId:获取扇区ID,扇区ID生成规则:先上代码,再解释。

var lastUsedID uint64
b, err := ds.Get(lastSectorIdKey)
switch err {
case nil:
	i, err := strconv.ParseInt(string(b), 10, 64)
	if err != nil {
		return nil, err
	}
	lastUsedID = uint64(i)
case datastore.ErrNotFound:
	lastUsedID = cfg.FallbackLastID
default:
	return nil, err
}
func (sb *SectorBuilder) AcquireSectorId() (uint64, error) {
	sb.idLk.Lock()
	defer sb.idLk.Unlock()

	sb.lastID++
	id := sb.lastID

	err := sb.ds.Put(lastSectorIdKey, []byte(fmt.Sprint(id)))
	if err != nil {
		return 0, err
	}
	return id, nil
}

-->初次启动初始化为1
-->此后收到扇区生成任务,逐次加1
-->并将ID本地存储
-->重启后读取本地存储作为初始化。

第三步操作pledgeSector:生成扇区pieces。这一步操作比较涉及任务比较多。容小官一步步说来。

commP, err := m.fastPledgeCommitment(size, uint64(1))
if err != nil {
	return nil, err
}

首先通过32GB扇区大小的随机数据生成commP结果。

sdp := actors.StorageDealProposal{
	PieceRef:             commP[:],
	PieceSize:            size,
	Client:               m.worker,
	Provider:             m.maddr,
	ProposalExpiration:   math.MaxUint64,
	Duration:             math.MaxUint64 / 2, // /2 because overflows
	StoragePricePerEpoch: types.NewInt(0),
	StorageCollateral:    types.NewInt(0),
	ProposerSignature:    nil, // nil because self dealing
}
deals[i] = sdp

由commP生成StorageDealProposal结构。

params, aerr := actors.SerializeParams(&actors.PublishStorageDealsParams{
	Deals: deals,
})

将StorageDealProposal结构通过CBOR序列化生成参数结果。

smsg, err := m.api.MpoolPushMessage(ctx, &types.Message{
	To:       actors.StorageMarketAddress,
	From:     m.worker,
	Value:    types.NewInt(0),
	GasPrice: types.NewInt(0),
	GasLimit: types.NewInt(1000000),
	Method:   actors.SMAMethods.PublishStorageDeals,
	Params:   params,
})

将结果作为Message的参数,推送到链上进行校验。

r, err := m.api.StateWaitMsg(ctx, smsg.Cid()) // TODO: more finality
if err != nil {
	return nil, err
}
if r.Receipt.ExitCode != 0 {
	log.Error(xerrors.Errorf("publishing deal failed: exit %d", r.Receipt.ExitCode))
}
var resp actors.PublishStorageDealResponse
if err := resp.UnmarshalCBOR(bytes.NewReader(r.Receipt.Return)); err != nil {
	return nil, err
}

当等到链上校验通过时,将返回结果CBOR序列化生成resp结构。

ppi, err := m.sb.AddPiece(ctx, size, sectorID, m.pledgeReader(size, uint64(1)), existingPieceSizes)
if err != nil {
	return nil, xerrors.Errorf("add piece: %w", err)
}
existingPieceSizes = append(existingPieceSizes, size)
out[i] = Piece{
	DealID: resp.DealIDs[i],
	Size:   ppi.Size,
	CommP:  ppi.CommP[:],
}

写入32G文件至~/.lotusstorage/staging/目录,并生成commP,由此commP以及DealID生成扇区pieces信息。

第四步操作newSector:产生新扇区生成任务。

func (m *Sealing) newSector(ctx context.Context, sid uint64, dealID uint64, ppi sectorbuilder.PublicPieceInfo) error {
	log.Infof("Start sealing %d", sid)
	return m.sectors.Send(sid, SectorStart{
		id: sid,
		pieces: []Piece{
			{
			    DealID: dealID,
				Size:  ppi.Size,
				CommP: ppi.CommP[:],
			},
		},
	})
}

根据sector ID以及上一步的扇区pieces信息,产生新扇区生成任务。发送事件至StateMachine扇区状态机。

func (fsm *StateMachine) send(evt Event) error {
	fsm.eventsIn <- evt // TODO: ctx, at least
	return nil
}

转发事件消息至状态机的eventsIn channel处。

// NOTE: This requires at least one event to be sent to trigger a stage
//  This means that after restarting the state machine users of this
//  code must send a 'restart' event
select {
	case evt := <-fsm.eventsIn:
		pendingEvents = append(pendingEvents, evt)
	case <-fsm.stageDone:
    	if len(pendingEvents) == 0 {
			continue
		}
	case <-fsm.closing:
		return
}

当其收到事件,将进行扇区密封任务操作。

由于篇幅问题,扇区密封操作过程以及扇区状态变化,请继续关注野牛哥的下期分解。