Filecoin源码架构分析--框架分析

整体框架

所有的交易在节点间同步到每个节点的“Message Pool”中。经过“Expected Consensus”共识机制,当选为Leader的一个或者多个节点从“Message Pool”中挑选Message,并打包。被打包的区块,会同步给其他节点。打包的区块中的交易Message会被Filecoin虚拟机执行,更新各个Actor的状态。所有的区块数据,Actor的状态是通过IPFS/IPLD进行存储。

挖矿

// Mine implements the DefaultWorkers main mining function..
// The returned bool indicates if this miner created a new block or not.
func (w *DefaultWorker) Mine(ctx context.Context, base types.TipSet, nullBlkCount int, outCh chan<- Output) bool {
   log.Info("Worker.Mine")
   ctx = log.Start(ctx, "Worker.Mine")
   defer log.Finish(ctx)
   if len(base) == 0 {
      log.Warning("Worker.Mine returning because it can't mine on an empty tipset")
      outCh <- Output{Err: errors.New("bad input tipset with no blocks sent to Mine()")}
      return false
   }

   st, err := w.getStateTree(ctx, base)
   if err != nil {
      log.Errorf("Worker.Mine couldn't get state tree for tipset: %s", err.Error())
      outCh <- Output{Err: err}
      return false
   }

   log.Debugf("Mining on tipset: %s, with %d null blocks.", base.String(), nullBlkCount)
   if ctx.Err() != nil {
      log.Warningf("Worker.Mine returning with ctx error %s", ctx.Err().Error())
      return false
   }
    //生成随机挑战数,用上一个Tipset的最小tickets和空块数进行HASH
   challenge, err := consensus.CreateChallengeSeed(base, uint64(nullBlkCount))
   if err != nil {
      outCh <- Output{Err: err}
      return false
   }
   //根据随机挑战数生成时空证明
   prCh := createProof(challenge, w.createPoSTFunc)

   var proof proofs.PoStProof
   var ticket []byte
   select {
   case <-ctx.Done():
      log.Infof("Mining run on base %s with %d null blocks canceled.", base.String(), nullBlkCount)
      return false
   case prChRead, more := <-prCh:
      if !more {
         log.Errorf("Worker.Mine got zero value from channel prChRead")
         return false
      }
      copy(proof[:], prChRead[:])
      //时空证明成功,生成ticket
      ticket, err = consensus.CreateTicket(proof, w.minerPubKey, w.workerSigner)
      if err != nil {
         log.Errorf("failed to create ticket: %s", err)
         return false
      }
   }
   // TODO: Test the interplay of isWinningTicket() and createPoSTFunc()
   // https://github.com/filecoin-project/go-filecoin/issues/1791
   //判断是否是中奖区块
   weHaveAWinner, err := consensus.IsWinningTicket(ctx, w.blockstore, w.powerTable, st, ticket, w.minerAddr)
   if err != nil {
      log.Errorf("Worker.Mine couldn't compute ticket: %s", err.Error())
      outCh <- Output{Err: err}
      return false
   }

   if weHaveAWinner {
       //打包生成区块
      next, err := w.Generate(ctx, base, ticket, proof, uint64(nullBlkCount))
      if err == nil {
         log.SetTag(ctx, "block", next)
         log.Debugf("Worker.Mine generates new winning block! %s", next.Cid().String())
      }
      outCh <- NewOutput(next, err)
      return true
   }

   return false
}

生成时空证明

// TODO: Actually use the results of the PoST once it is implemented.
// Currently createProof just passes the challenge seed through.
func createProof(challengeSeed proofs.PoStChallengeSeed, createPoST DoSomeWorkFunc) <-chan proofs.PoStChallengeSeed {
   c := make(chan proofs.PoStChallengeSeed)
   go func() {
      // TODO send new PoST on channel once we can create it
      //  https://github.com/filecoin-project/go-filecoin/issues/1791
      createPoST()
      c <- challengeSeed
   }()
   return c
}

处理挖矿进程输出的block

func (node *Node) handleNewMiningOutput(miningOutCh <-chan mining.Output) {
   defer func() {
      node.miningDoneWg.Done()
   }()
   for {
      select {
      case <-node.miningCtx.Done():
         return
      case output, ok := <-miningOutCh:
         if !ok {
            return
         }
         if output.Err != nil {
            log.Errorf("stopping mining. error: %s", output.Err.Error())
            node.StopMining(context.Background())
         } else {
            node.miningDoneWg.Add(1)
            go func() {
               if node.IsMining() {
                  node.AddNewlyMinedBlock(node.miningCtx, output.NewBlock)
               }
               node.miningDoneWg.Done()
            }()
         }
      }
   }

}

共识机制

Filecoin的共识算法叫Expected Consensus,简称EC共识机制。Expected Consensus实现的相关代码在consensus目录。

// Expected implements expected consensus.
type Expected struct {
   // PwrTableView provides miner and total power for the EC chain weight
   // computation.
   PwrTableView PowerTableView

   // cstore is used for loading state trees during message running.
   cstore *hamt.CborIpldStore

   // bstore contains data referenced by actors within the state
   // during message running.  Additionally bstore is used for
   // accessing the power table.
   bstore blockstore.Blockstore

   // processor is what we use to process messages and pay rewards
   processor Processor

   genesisCid cid.Cid

   verifier proofs.Verifier
}

除了区块链数据外,Expected Consensus每一轮会生成一个Ticket,每个节点通过一定的计算,确定是否是该轮的Leader。如果选为Leader,节点可以打包区块。也就是说,每一轮可能没有Leader(所有节点都不符合Leader的条件),或者多个Leader(有多个节点符合Leader)。Filecoin使用TipSet的概念,表明一轮中多个Leader产生的指向同一个父亲区块的区块集合。每个矿工在一轮round 中只能提交一个block。

// CreateTicket computes a valid ticket.
//     params:  proof  []byte, the proof to sign
//            signerPubKey []byte, the public key for the signer. Must exist in the signer
//          signer, implements TicketSigner interface. Must have signerPubKey in its keyinfo.
//  returns:  types.Signature ( []byte ), error
func CreateTicket(proof proofs.PoStProof, signerPubKey []byte, signer TicketSigner) (types.Signature, error) {

   var ticket types.Signature

   signerAddr, err := signer.GetAddressForPubKey(signerPubKey)
   if err != nil {
      return ticket, errors.Wrap(err, "could not get address for signerPubKey")
   }
   buf := append(proof[:], signerAddr.Bytes()...)
   // Don't hash it here; it gets hashed in walletutil.Sign
   return signer.SignBytes(buf[:], signerAddr)
}

Leader的选择:

在每个Ticket生成的基础上,进行Leader的选择,具体查看consensus/expected.go中的IsWinningTicket函数。也就是说,如果Ticket的数值小于当前节点的有效存储的比例,该节点在该轮就是Leader。

// IsWinningTicket fetches miner power & total power, returns true if it's a winning ticket, false if not,
//    errors out if minerPower or totalPower can't be found.
//    See https://github.com/filecoin-project/aq/issues/70 for an explanation of the math here.
func IsWinningTicket(ctx context.Context, bs blockstore.Blockstore, ptv PowerTableView, st state.Tree,
   ticket types.Signature, miner address.Address) (bool, error) {

   totalPower, err := ptv.Total(ctx, st, bs)
   if err != nil {
      return false, errors.Wrap(err, "Couldn't get totalPower")
   }

   minerPower, err := ptv.Miner(ctx, st, bs, miner)
   if err != nil {
      return false, errors.Wrap(err, "Couldn't get minerPower")
   }

   return CompareTicketPower(ticket, minerPower, totalPower), nil
}

时空证明

矿工向链上CommitSector,然后会为这些数据提供存储证明。

// CommitSector adds a commitment to the specified sector. The sector must not
// already be committed.
func (ma *Actor) CommitSector(ctx exec.VMContext, sectorID uint64, commD, commR, commRStar, proof []byte) (uint8, error) {
   if err := ctx.Charge(actor.DefaultGasCost); err != nil {
      return exec.ErrInsufficientGas, errors.RevertErrorWrap(err, "Insufficient gas")
   }
   …………
      req := proofs.VerifySealRequest{}
      copy(req.CommD[:], commD)
      copy(req.CommR[:], commR)
      copy(req.CommRStar[:], commRStar)
      copy(req.Proof[:], proof)
      req.ProverID = sectorbuilder.AddressToProverID(ctx.Message().To)
      req.SectorID = sectorbuilder.SectorIDToBytes(sectorID)
      req.StoreType = sectorStoreType
   }
   // TODO: use uint64 instead of this abomination, once refmt is fixed
   // https://github.com/polydawn/refmt/issues/35
   sectorIDstr := strconv.FormatUint(sectorID, 10)
   return 0, nil
}

生成时空证明

每个proving period,矿工调用generatepost,产生一个紧凑的时空证明,然后通过submitpost提交给链上。

// generatePoSt creates the required PoSt, given a list of sector ids and
// matching seeds. It returns the Snark Proof for the PoSt, and a list of
// sectors that faulted, if there were any faults.
func (sm *Miner) generatePoSt(commRs []proofs.CommR, seed proofs.PoStChallengeSeed) ([]proofs.PoStProof, []uint64, error) {
   req := sectorbuilder.GeneratePoStRequest{
      CommRs:        commRs,
      ChallengeSeed: seed,
   }
   res, err := sm.node.SectorBuilder().GeneratePoSt(req)
   if err != nil {
      return nil, nil, errors.Wrap(err, "failed to generate PoSt")
   }

   return res.Proofs, res.Faults, nil
}

提交时空证明

证明期是一个时间窗口,在该窗口中,矿工必须生成时空证明。如果矿工在验证期间未收到区块中包含的“submitPost”消息,则会受到处罚。有两种不同的时间提交。标准提交:标准提交是指在验证期结束前将其链接起来的提交。合理设置所需的时间长度,以便在验证期的实际结束之间有一个宽限期,从而最小化网络拥塞对典型矿工操作的影响。惩罚性提交:惩罚性提交是指在证明期结束后,但在生成攻击阈值之前的链上提交,视为有效的后提交,但矿工必须因为迟提交而支付罚款。

func (sm *Miner) submitPoSt(start, end *types.BlockHeight, seed proofs.PoStChallengeSeed, inputs []generatePostInput) {
   commRs := make([]proofs.CommR, len(inputs))
   for i, input := range inputs {
      commRs[i] = input.commR
   }

   proofs, faults, err := sm.generatePoSt(commRs, seed)
   if err != nil {
      log.Errorf("failed to generate PoSts: %s", err)
      return
   }
   if len(faults) != 0 {
      log.Warningf("some faults when generating PoSt: %v", faults)
      // TODO: proper fault handling
   }

   height, err := sm.node.BlockHeight()
   if err != nil {
      log.Errorf("failed to submit PoSt, as the current block height can not be determined: %s", err)
      // TODO: what should happen in this case?
      return
   }
   if height.LessThan(start) {
      // TODO: what to do here? not sure this can happen, maybe through reordering?
      log.Errorf("PoSt generation time took negative block time: %s < %s", height, start)
      return
   }

   if height.GreaterEqual(end) {
      // TODO: we are too late, figure out faults and decide if we want to still submit
      log.Errorf("PoSt generation was too slow height=%s end=%s", height, end)
      return
   }

   // TODO: figure out a more sensible timeout
   ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
   defer cancel()

   // TODO: algorithmically determine appropriate values for these
   gasPrice := types.NewGasPrice(submitPostGasPrice)
   gasLimit := types.NewGasUnits(submitPostGasLimit)

   _, err = sm.porcelainAPI.MessageSend(ctx, sm.minerOwnerAddr, sm.minerAddr, types.ZeroAttoFIL, gasPrice, gasLimit, "submitPoSt", proofs)
   if err != nil {
      log.Errorf("failed to submit PoSt: %s", err)
      return
   }

   log.Debug("submitted PoSt")
}

区块同步

接收区块处理进程

func (node *Node) processBlock(ctx context.Context, pubSubMsg pubsub.Message) (err error) {
   // ignore messages from ourself
   if pubSubMsg.GetFrom() == node.Host().ID() {
      return nil
   }

   blk, err := types.DecodeBlock(pubSubMsg.GetData())
   if err != nil {
      return errors.Wrap(err, "got bad block data")
   }

   log.Infof("Received new block from network cid: %s", blk.Cid().String())
   log.Debugf("Received new block from network: %s", blk)

   err = node.Syncer.HandleNewBlocks(ctx, []cid.Cid{blk.Cid()})
   if err != nil {
      return errors.Wrap(err, "processing block from network")
   }

   return nil
}

CollectChain是代码的入口点与网络的交互,它不会向主链添加tipsets。解析head tipset 的cids,直到它解析存储中包含parent tipset。返回新的未完全验证的tipset链和parent tipset的id,同步到存储区中。

func (syncer *DefaultSyncer) collectChain(ctx context.Context, blkCids []cid.Cid) ([]types.TipSet, error) {
   var chain []types.TipSet
   defer logSyncer.Info("chain synced")
   for {
      var blks []*types.Block
      // check the cache for bad tipsets before doing anything
      tsKey := types.NewSortedCidSet(blkCids...).String()

      // Finish traversal if the tipset made is tracked in the store.
      if syncer.chainStore.HasTipSetAndState(ctx, tsKey) {
         return chain, nil
      }

      logSyncer.Debugf("CollectChain next link: %s", tsKey)

      if syncer.badTipSets.Has(tsKey) {
         return nil, ErrChainHasBadTipSet
      }

      blks, err := syncer.getBlksMaybeFromNet(ctx, blkCids)
      if err != nil {
         return nil, err
      }

      ts, err := syncer.consensus.NewValidTipSet(ctx, blks)
      if err != nil {
         syncer.badTipSets.Add(tsKey)
         syncer.badTipSets.AddChain(chain)
         return nil, err
      }

      height, _ := ts.Height()
      if len(chain)%500 == 0 {
         logSyncer.Infof("syncing the chain, currently at block height %d", height)
      }

      // Update values to traverse next tipset
      chain = append([]types.TipSet{ts}, chain...)
      parentCidSet, err := ts.Parents()
      if err != nil {
         return nil, err
      }
      blkCids = parentCidSet.ToSlice()
   }
}
func (c *Expected) validateBlockStructure(ctx context.Context, b *types.Block) error {
   // TODO: validate signature on block
   ctx = log.Start(ctx, "Expected.validateBlockStructure")
   log.LogKV(ctx, "ValidateBlockStructure", b.Cid().String())
   if !b.StateRoot.Defined() {
      return fmt.Errorf("block has nil StateRoot")
   }

   return nil
}

SyncOne计算tipset的父状态,调用EC共识以运行状态转换以验证tipset。如果输入tipset有效,调用共识来检查其权重,如果这个tipset的权重是最重的,就更新区块头。Filecoin采用EC共识机制,通过TipSet的Weight确定主链。

// syncOne syncs a single tipset with the chain store. syncOne calculates the
// parent state of the tipset and calls into consensus to run a state transition
// in order to validate the tipset.  In the case the input tipset is valid,
// syncOne calls into consensus to check its weight, and then updates the head
// of the store if this tipset is the heaviest.
//
// Precondition: the caller of syncOne must hold the syncer's lock (syncer.mu) to
// ensure head is not modified by another goroutine during run.
func (syncer *DefaultSyncer) syncOne(ctx context.Context, parent, next types.TipSet) error {
   // Lookup parent state. It is guaranteed by the syncer that it is in
   // the chainStore.
   ……
   //验证tipset状态,给store添加新的状态
   err = syncer.chainStore.PutTipSetAndState(ctx, &TipSetAndState{
      TipSet:          next,
      TipSetStateRoot: root,
   })
   if err != nil {
      return err
   }
   logSyncer.Debugf("Successfully updated store with %s", next.String())

   // TipSet is validated and added to store, now check if it is the heaviest.
   // If it is the heaviest update the chainStore.
   nextParentSt, err := syncer.tipSetState(ctx, parent.String()) // call again to get a copy
   if err != nil {
      return err
   }
   headParentCids, err := syncer.chainStore.Head().Parents()
   if err != nil {
      return err
   }
   var headParentSt state.Tree
   if headParentCids.Len() != 0 { // head is not genesis
      headParentSt, err = syncer.tipSetState(ctx, headParentCids.String())
      if err != nil {
         return err
      }
   }

   heavier, err := syncer.consensus.IsHeavier(ctx, next, syncer.chainStore.Head(), nextParentSt, headParentSt)
   if err != nil {
      return err
   }

   if heavier {
      // Gather the entire new chain for reorg comparison.
      // See Issue #2151 for making this scalable.
      newChain, err := CollectTipSetsOfHeightAtLeast(ctx, syncer.chainStore.BlockHistory(ctx, parent), types.NewBlockHeight(uint64(0)))
      if err != nil {
         return err
      }
      newChain = append(newChain, next)
      if IsReorg(syncer.chainStore.Head(), newChain) {
         logSyncer.Infof("reorg occurring while switching from %s to %s", syncer.chainStore.Head().String(), next.String())
      }
      if err = syncer.chainStore.SetHead(ctx, next); err != nil {
         return err
      }
   }

   return nil
}

区块验证

// validateMining checks validity of the block ticket, proof, and miner address.
//    Returns an error if:
//     * any tipset's block was mined by an invalid miner address.
//      * the block proof is invalid for the challenge
//      * the block ticket fails the power check, i.e. is not a winning ticket
//    Returns nil if all the above checks pass.
// See https://github.com/filecoin-project/specs/blob/master/mining.md#chain-validation
func (c *Expected) validateMining(ctx context.Context, st state.Tree, ts types.TipSet, parentTs types.TipSet) error {
   for _, blk := range ts.ToSlice() {
      // TODO: Also need to validate BlockSig

      // TODO: Once we've picked a delay function (see #2119), we need to
      // verify its proof here. The proof will likely be written to a field on
      // the mined block.

      // See https://github.com/filecoin-project/specs/blob/master/mining.md#ticket-checking
      result, err := IsWinningTicket(ctx, c.bstore, c.PwrTableView, st, blk.Ticket, blk.Miner)
      if err != nil {
         return errors.Wrap(err, "can't check for winning ticket")
      }

      if !result {
         return errors.New("not a winning ticket")
      }
   }
   return nil
}

每个区块的Weight的计算公式:

Weight = ParentWeight + ECV + ECPrM * ratio

目前,ECV设置为10, ECPrM设置为100,Ratio是当前节点的存储有效率(节点存储的容量/所有节点的存储容量)。在目前的算法下节点的ratio高,Weight就高。

一个TipSet的Weight等于TipSet中所有区块的Weight的总和。Weight大的TipSet认为是主链。当两个TipSet的Weight一样大的时候,取Ticket较小者。

// Weight returns the EC weight of this TipSet in uint64 encoded fixed point
// representation.
func (c *Expected) Weight(ctx context.Context, ts types.TipSet, pSt state.Tree) (uint64, error) {
   ctx = log.Start(ctx, "Expected.Weight")
   log.LogKV(ctx, "Weight", ts.String())
   if len(ts) == 1 && ts.ToSlice()[0].Cid().Equals(c.genesisCid) {
      return uint64(0), nil
   }
   // Compute parent weight.
   parentW, err := ts.ParentWeight()
   if err != nil {
      return uint64(0), err
   }

   w, err := types.FixedToBig(parentW)
   if err != nil {
      return uint64(0), err
   }
   // Each block in the tipset adds ECV + ECPrm * miner_power to parent weight.
   totalBytes, err := c.PwrTableView.Total(ctx, pSt, c.bstore)
   if err != nil {
      return uint64(0), err
   }
   floatTotalBytes := new(big.Float).SetInt64(int64(totalBytes))
   floatECV := new(big.Float).SetInt64(int64(ECV))
   floatECPrM := new(big.Float).SetInt64(int64(ECPrM))
   for _, blk := range ts.ToSlice() {
      minerBytes, err := c.PwrTableView.Miner(ctx, pSt, c.bstore, blk.Miner)
      if err != nil {
         return uint64(0), err
      }
      floatOwnBytes := new(big.Float).SetInt64(int64(minerBytes))
      wBlk := new(big.Float)
      wBlk.Quo(floatOwnBytes, floatTotalBytes)
      wBlk.Mul(wBlk, floatECPrM) // Power addition
      wBlk.Add(wBlk, floatECV)   // Constant addition
      w.Add(w, wBlk)
   }
   return types.BigToFixed(w)
}

存储协议流程

Filecoin实现三个上层协议:Hello,Storage(存储协议)以及Retrieval(检索协议)。

Miner是存储矿工,通过createMiner创建Miner Actor。

// CreateMiner creates a new miner with the a pledge of the given amount of sectors. The
// miners collateral is set by the value in the message.
func (sma *Actor) CreateMiner(vmctx exec.VMContext, pledge *big.Int, publicKey []byte, pid peer.ID) (address.Address, uint8, error) {
   if err := vmctx.Charge(actor.DefaultGasCost); err != nil {
      return address.Undef, exec.ErrInsufficientGas, errors.RevertErrorWrap(err, "Insufficient gas")
   }

   var state State
   ret, err := actor.WithState(vmctx, &state, func() (interface{}, error) {
      if pledge.Cmp(MinimumPledge) < 0 {
         // TODO This should probably return a non-zero exit code instead of an error.
         return nil, Errors[ErrPledgeTooLow]
      }
      addr, err := vmctx.AddressForNewActor()
      minerInitializationParams := miner.NewState(vmctx.Message().From, publicKey, pledge, pid, vmctx.Message().Value)
      actorCodeCid := types.MinerActorCodeCid
      if vmctx.BlockHeight().Equal(types.NewBlockHeight(0)) {
         actorCodeCid = types.BootstrapMinerActorCodeCid
      }

      if err := vmctx.CreateNewActor(addr, actorCodeCid, minerInitializationParams); err != nil {
         return nil, err
      }

      _, _, err = vmctx.Send(addr, "", vmctx.Message().Value, nil)
      if err != nil {
         return nil, err
      }
}

使用Miner Actor的addAsk提供存储服务。存储需求方,也就是Client。

// AddAsk adds an ask to this miners ask list
func (ma *Actor) AddAsk(ctx exec.VMContext, price *types.AttoFIL, expiry *big.Int) (*big.Int, uint8,
   error) {
   if err := ctx.Charge(actor.DefaultGasCost); err != nil {
      return nil, exec.ErrInsufficientGas, errors.RevertErrorWrap(err, "Insufficient gas")
   }

   var state State
   out, err := actor.WithState(ctx, &state, func() (interface{}, error) {
      if ctx.Message().From != state.Owner {
         return nil, Errors[ErrCallerUnauthorized]
      }

      id := big.NewInt(0).Set(state.NextAskID)
      state.NextAskID = state.NextAskID.Add(state.NextAskID, big.NewInt(1))

      // filter out expired asks
      asks := state.Asks
      state.Asks = state.Asks[:0]
      for _, a := range asks {
         if ctx.BlockHeight().LessThan(a.Expiry) {
            state.Asks = append(state.Asks, a)
         }
      }

      if !expiry.IsUint64() {
         return nil, errors.NewRevertError("expiry was invalid")
      }
      expiryBH := types.NewBlockHeight(expiry.Uint64())

      state.Asks = append(state.Asks, &Ask{
         Price:  price,
         Expiry: ctx.BlockHeight().Add(expiryBH),
         ID:     id,
      })

      return id, nil
   })
   if err != nil {
      return nil, errors.CodeError(err), err
   }

   askID, ok := out.(*big.Int)
   if !ok {
      return nil, 1, errors.NewRevertErrorf("expected an Integer return value from call, but got %T instead", out)
   }

   return askID, 0, nil
}

通过getAsks获取所有Miner Actor的存储服务,并在这些服务中确定相应的存储矿工。

// GetAsks returns all the asks for this miner. (TODO: this isnt a great function signature, it returns the asks in a
// serialized array. Consider doing this some other way)
func (ma *Actor) GetAsks(ctx exec.VMContext) ([]uint64, uint8, error) {
   if err := ctx.Charge(actor.DefaultGasCost); err != nil {
      return nil, exec.ErrInsufficientGas, errors.RevertErrorWrap(err, "Insufficient gas")
   }
   var state State
   out, err := actor.WithState(ctx, &state, func() (interface{}, error) {
      var askids []uint64
      for _, ask := range state.Asks {
         if !ask.ID.IsUint64() {
            return nil, errors.NewFaultErrorf("miner ask has invalid ID (bad invariant)")
         }
         askids = append(askids, ask.ID.Uint64())
      }

      return askids, nil
   })
   if err != nil {
      return nil, errors.CodeError(err), err
   }

   askids, ok := out.([]uint64)
   if !ok {
      return nil, 1, errors.NewRevertErrorf("expected a []uint64 return value from call, but got %T instead", out)
   }

   return askids, 0, nil
}

在确定存储矿工的基础上,使用createChannel创建支付通道,并和存储矿工进行数据的传输。存储矿工在存储数据后,定期向Miner Actor报告存储证明(submitPoSt)。

// CreateChannel creates a new payment channel from the caller to the target.
// The value attached to the invocation is used as the deposit, and the channel
// will expire and return all of its money to the owner after the given block height.
func (pb *Actor) CreateChannel(vmctx exec.VMContext, target address.Address, eol *types.BlockHeight) (*types.ChannelID, uint8, error) {
   if err := vmctx.Charge(actor.DefaultGasCost); err != nil {
      return nil, exec.ErrInsufficientGas, errors.RevertErrorWrap(err, "Insufficient gas")
   }
    …………
   return channelID, 0, nil
}

存储矿工通过FPS(Filecon Proving Subsystem)实现数据的存储以及存储证明:SectorBase提供存储接口,Storage Proof提供PoRep以及PoSt的存储证明。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268